pdfdancer-client-typescript 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +5 -5
  2. package/dist/__tests__/e2e/test-helpers.d.ts +2 -1
  3. package/dist/__tests__/e2e/test-helpers.d.ts.map +1 -1
  4. package/dist/__tests__/e2e/test-helpers.js +7 -3
  5. package/dist/__tests__/e2e/test-helpers.js.map +1 -1
  6. package/dist/client-v1.js +1 -1
  7. package/dist/client-v1.js.map +1 -1
  8. package/dist/client-v2.d.ts +129 -0
  9. package/dist/client-v2.d.ts.map +1 -0
  10. package/dist/client-v2.js +696 -0
  11. package/dist/client-v2.js.map +1 -0
  12. package/dist/image-builder.d.ts +13 -0
  13. package/dist/image-builder.d.ts.map +1 -0
  14. package/dist/image-builder.js +44 -0
  15. package/dist/image-builder.js.map +1 -0
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +3 -3
  19. package/dist/index.js.map +1 -1
  20. package/dist/models.d.ts +5 -10
  21. package/dist/models.d.ts.map +1 -1
  22. package/dist/models.js +3 -13
  23. package/dist/models.js.map +1 -1
  24. package/dist/paragraph-builder.d.ts +19 -10
  25. package/dist/paragraph-builder.d.ts.map +1 -1
  26. package/dist/paragraph-builder.js +65 -26
  27. package/dist/paragraph-builder.js.map +1 -1
  28. package/dist/pdfdancer_v1.d.ts +202 -0
  29. package/dist/pdfdancer_v1.d.ts.map +1 -0
  30. package/dist/pdfdancer_v1.js +702 -0
  31. package/dist/pdfdancer_v1.js.map +1 -0
  32. package/dist/types.d.ts +56 -0
  33. package/dist/types.d.ts.map +1 -0
  34. package/dist/types.js +92 -0
  35. package/dist/types.js.map +1 -0
  36. package/package.json +1 -1
  37. package/scripts/release.js +1 -1
  38. package/src/__tests__/client-v1.test.ts +46 -87
  39. package/src/__tests__/e2e/acroform.test.ts +60 -57
  40. package/src/__tests__/e2e/form_x_object.test.ts +17 -16
  41. package/src/__tests__/e2e/image.test.ts +53 -56
  42. package/src/__tests__/e2e/line.test.ts +47 -48
  43. package/src/__tests__/e2e/page.test.ts +37 -36
  44. package/src/__tests__/e2e/paragraph.test.ts +107 -101
  45. package/src/__tests__/e2e/path.test.ts +67 -64
  46. package/src/__tests__/e2e/test-helpers.ts +71 -67
  47. package/src/__tests__/e2e/token_from_env.test.ts +35 -0
  48. package/src/image-builder.ts +52 -0
  49. package/src/index.ts +1 -1
  50. package/src/models.ts +5 -21
  51. package/src/paragraph-builder.ts +217 -162
  52. package/src/{client-v1.ts → pdfdancer_v1.ts} +248 -53
  53. package/src/types.ts +133 -0
  54. package/example.ts +0 -99
@@ -0,0 +1,35 @@
1
+ import {requireEnvAndFixture} from "./test-helpers";
2
+ import {PDFDancer} from "../../pdfdancer_v1";
3
+ import {ObjectType} from "../../models";
4
+
5
+ describe('Env Token E2E Tests', () => {
6
+
7
+ test('get pages with token from env', async () => {
8
+ process.env.PDFDANCER_TOKEN = "42";
9
+ process.env.PDFDANCER_BASE_URL = "http://localhost:8080";
10
+ const [, , pdfData] = await requireEnvAndFixture('ObviouslyAwesome.pdf');
11
+ const client = await PDFDancer.open(pdfData);
12
+ const pages = await client.pages();
13
+ expect(pages).toBeDefined();
14
+ expect(pages[0].type).toBe(ObjectType.PAGE);
15
+ expect(pages).toHaveLength(12);
16
+ });
17
+
18
+ test('fail without token', async () => {
19
+ delete process.env.PDFDANCER_TOKEN;
20
+ delete process.env.PDFDANCER_BASE_URL;
21
+ const [, , pdfData] = await requireEnvAndFixture('ObviouslyAwesome.pdf');
22
+ await expect(PDFDancer.open(pdfData))
23
+ .rejects
24
+ .toThrow("Missing PDFDancer token");
25
+ });
26
+
27
+ test('fail with wrong token', async () => {
28
+ process.env.PDFDANCER_TOKEN = "43";
29
+ process.env.PDFDANCER_BASE_URL = "http://localhost:8080";
30
+ const [, , pdfData] = await requireEnvAndFixture('ObviouslyAwesome.pdf');
31
+ await expect(PDFDancer.open(pdfData))
32
+ .rejects
33
+ .toThrow("Failed to create session: Unauthorized");
34
+ });
35
+ });
@@ -0,0 +1,52 @@
1
+ import {PDFDancer} from "./pdfdancer_v1";
2
+ import fs from "fs";
3
+ import {Image, Position} from "./models";
4
+
5
+ // 👇 Internal view of PDFDancer methods, not exported
6
+ interface PDFDancerInternals {
7
+ addImage(image: Image, position: Position): Promise<boolean>;
8
+ }
9
+
10
+ export class ImageBuilder {
11
+ private _client: PDFDancer;
12
+ private _imageData: Uint8Array<ArrayBuffer> | undefined;
13
+ private _position: Position | undefined;
14
+ private _internals: PDFDancerInternals;
15
+
16
+ constructor(_client: PDFDancer) {
17
+ this._client = _client;
18
+ // Cast to the internal interface to get access
19
+ this._internals = this._client as unknown as PDFDancerInternals;
20
+ }
21
+
22
+ fromFile(imagePath: string) {
23
+ if (!fs.existsSync(imagePath)) {
24
+ throw new Error(`Image not found: ${imagePath}`);
25
+ }
26
+ this._imageData = new Uint8Array(fs.readFileSync(imagePath));
27
+ return this
28
+ }
29
+
30
+ // noinspection JSUnusedGlobalSymbols
31
+ fromBytes(imageData: Uint8Array<ArrayBuffer>) {
32
+ this._imageData = imageData;
33
+ return this;
34
+ }
35
+
36
+ at(pageIndex: number, x: number, y: number) {
37
+ this._position = Position.atPageCoordinates(pageIndex, x, y);
38
+ return this;
39
+ }
40
+
41
+ async add() {
42
+ if (!this._imageData) {
43
+ throw new Error("Image data is not set");
44
+ }
45
+ if (!this._position) {
46
+ throw new Error("Position is not set");
47
+ }
48
+ let image = new Image();
49
+ image.data = this._imageData;
50
+ return await this._internals.addImage(image, this._position);
51
+ }
52
+ }
package/src/index.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * A TypeScript client library for the PDFDancer PDF manipulation API.
5
5
  */
6
6
 
7
- export { ClientV1 } from './client-v1';
7
+ export { PDFDancer } from './pdfdancer_v1';
8
8
  export { ParagraphBuilder } from './paragraph-builder';
9
9
 
10
10
  export {
package/src/models.ts CHANGED
@@ -88,7 +88,8 @@ export class Position {
88
88
  public shape?: ShapeType,
89
89
  public mode?: PositionMode,
90
90
  public boundingRect?: BoundingRect,
91
- public textStartsWith?: string
91
+ public textStartsWith?: string,
92
+ public textPattern?: string
92
93
  ) {
93
94
  }
94
95
 
@@ -200,8 +201,6 @@ export class Position {
200
201
  * Lightweight reference to a PDF object providing identity and type information.
201
202
  */
202
203
  export class ObjectRef {
203
- public name?: string;
204
- public value?: string | null;
205
204
 
206
205
  constructor(
207
206
  public internalId: string,
@@ -210,22 +209,6 @@ export class ObjectRef {
210
209
  ) {
211
210
  }
212
211
 
213
- getInternalId(): string {
214
- return this.internalId;
215
- }
216
-
217
- getPosition(): Position {
218
- return this.position;
219
- }
220
-
221
- setPosition(position: Position): void {
222
- this.position = position;
223
- }
224
-
225
- getType(): ObjectType {
226
- return this.type;
227
- }
228
-
229
212
  toDict(): Record<string, any> {
230
213
  return {
231
214
  internalId: this.internalId,
@@ -244,8 +227,8 @@ export class FormFieldRef extends ObjectRef {
244
227
  internalId: string,
245
228
  position: Position,
246
229
  type: ObjectType,
247
- public name?: string,
248
- public value?: string | null
230
+ public name: string,
231
+ public value: string | null
249
232
  ) {
250
233
  super(internalId, position, type);
251
234
  }
@@ -525,6 +508,7 @@ function positionToDict(position: Position): Record<string, any> {
525
508
  const result: Record<string, any> = {
526
509
  pageIndex: position.pageIndex,
527
510
  textStartsWith: position.textStartsWith,
511
+ textPattern: position.textPattern,
528
512
  name: position.name
529
513
  };
530
514
 
@@ -2,173 +2,228 @@
2
2
  * ParagraphBuilder for the PDFDancer TypeScript client.
3
3
  */
4
4
 
5
- import { ValidationException } from './exceptions';
6
- import { Paragraph, Font, Color, Position } from './models';
7
- import type { ClientV1 } from './client-v1';
5
+ import {ValidationException} from './exceptions';
6
+ import {Color, Font, ObjectRef, Paragraph, Position} from './models';
7
+ import {PDFDancer} from "./pdfdancer_v1";
8
+
9
+ // 👇 Internal view of PDFDancer methods, not exported
10
+ interface PDFDancerInternals {
11
+ modifyParagraph(objectRefOrPageIndex: ObjectRef, text: string | Paragraph): Promise<boolean>;
12
+
13
+ addParagraph(paragraph: Paragraph): Promise<boolean>;
14
+ }
8
15
 
9
16
  /**
10
17
  * Builder class for constructing Paragraph objects with fluent interface.
11
18
  */
12
19
  export class ParagraphBuilder {
13
- private _paragraph: Paragraph;
14
- private _lineSpacing: number = 1.2;
15
- private _textColor: Color = new Color(0, 0, 0); // Black by default
16
- private _text?: string;
17
- private _font?: Font;
18
-
19
- constructor(private _client: ClientV1) {
20
- if (!_client) {
21
- throw new ValidationException("Client cannot be null");
22
- }
23
-
24
- this._paragraph = new Paragraph();
25
- }
26
-
27
- /**
28
- * Set the text content for the paragraph.
29
- */
30
- fromString(text: string, color?: Color): ParagraphBuilder {
31
- if (text === null || text === undefined) {
32
- throw new ValidationException("Text cannot be null");
33
- }
34
- if (!text.trim()) {
35
- throw new ValidationException("Text cannot be empty");
36
- }
37
-
38
- this._text = text;
39
- if (color) {
40
- this._textColor = color;
41
- }
42
-
43
- return this;
44
- }
45
-
46
- /**
47
- * Set the font for the paragraph using an existing Font object.
48
- */
49
- withFont(font: Font): ParagraphBuilder {
50
- if (!font) {
51
- throw new ValidationException("Font cannot be null");
52
- }
53
-
54
- this._font = font;
55
- return this;
56
- }
57
-
58
- /**
59
- * Set the font for the paragraph using a TTF file.
60
- */
61
- async withFontFile(ttfFile: Uint8Array | File, fontSize: number): Promise<ParagraphBuilder> {
62
- if (!ttfFile) {
63
- throw new ValidationException("TTF file cannot be null");
64
- }
65
- if (fontSize <= 0) {
66
- throw new ValidationException(`Font size must be positive, got ${fontSize}`);
67
- }
68
-
69
- // Register font and create Font object
70
- this._font = await this._registerTtf(ttfFile, fontSize);
71
- return this;
72
- }
73
-
74
- /**
75
- * Set the line spacing for the paragraph.
76
- */
77
- withLineSpacing(spacing: number): ParagraphBuilder {
78
- if (spacing <= 0) {
79
- throw new ValidationException(`Line spacing must be positive, got ${spacing}`);
80
- }
81
-
82
- this._lineSpacing = spacing;
83
- return this;
84
- }
85
-
86
- /**
87
- * Set the text color for the paragraph.
88
- */
89
- withColor(color: Color): ParagraphBuilder {
90
- if (!color) {
91
- throw new ValidationException("Color cannot be null");
92
- }
93
-
94
- this._textColor = color;
95
- return this;
96
- }
97
-
98
- /**
99
- * Set the position for the paragraph.
100
- */
101
- withPosition(position: Position): ParagraphBuilder {
102
- if (!position) {
103
- throw new ValidationException("Position cannot be null");
104
- }
105
-
106
- this._paragraph.setPosition(position);
107
- return this;
108
- }
109
-
110
- /**
111
- * Build and return the final Paragraph object.
112
- */
113
- build(): Paragraph {
114
- // Validate required fields
115
- if (!this._text) {
116
- throw new ValidationException("Text must be set before building paragraph");
117
- }
118
- if (!this._font) {
119
- throw new ValidationException("Font must be set before building paragraph");
120
- }
121
- if (!this._paragraph.getPosition()) {
122
- throw new ValidationException("Position must be set before building paragraph");
123
- }
124
-
125
- // Set paragraph properties
126
- this._paragraph.font = this._font;
127
- this._paragraph.color = this._textColor;
128
- this._paragraph.lineSpacing = this._lineSpacing;
129
-
130
- // Process text into lines
131
- this._paragraph.textLines = this._processTextLines(this._text);
132
-
133
- return this._paragraph;
134
- }
135
-
136
- /**
137
- * Register a TTF font with the client and return a Font object.
138
- */
139
- private async _registerTtf(ttfFile: Uint8Array | File, fontSize: number): Promise<Font> {
140
- try {
141
- const fontName = await this._client.registerFont(ttfFile);
142
- return new Font(fontName, fontSize);
143
- } catch (error) {
144
- throw new ValidationException(`Failed to register font file: ${error}`);
145
- }
146
- }
147
-
148
- /**
149
- * Process text into lines for the paragraph.
150
- * This is a simplified version - the full implementation would handle
151
- * word wrapping, line breaks, and other text formatting based on the font
152
- * and paragraph width.
153
- */
154
- private _processTextLines(text: string): string[] {
155
- // Handle escaped newlines (\\n) as actual newlines
156
- const processedText = text.replace(/\\\\n/g, '\n');
157
-
158
- // Simple implementation - split on newlines
159
- // In the full version, this would implement proper text layout
160
- let lines = processedText.split('\n');
161
-
162
- // Remove empty lines at the end but preserve intentional line breaks
163
- while (lines.length > 0 && !lines[lines.length - 1].trim()) {
164
- lines.pop();
165
- }
166
-
167
- // Ensure at least one line
168
- if (lines.length === 0) {
169
- lines = [''];
20
+ private _paragraph: Paragraph;
21
+ private _lineSpacing: number = 1.2;
22
+ private _textColor: Color = new Color(0, 0, 0); // Black by default
23
+ private _text?: string;
24
+ private _font?: Font;
25
+ private _pending: Promise<unknown>[] = [];
26
+ private _registeringFont: boolean = false;
27
+ private _pageIndex: number;
28
+ private _internals: PDFDancerInternals;
29
+
30
+ constructor(private _client: PDFDancer, private objectRefOrPageIndex?: ObjectRef | number) {
31
+ if (!_client) {
32
+ throw new ValidationException("Client cannot be null");
33
+ }
34
+
35
+ this._pageIndex = objectRefOrPageIndex instanceof ObjectRef ? objectRefOrPageIndex.position.pageIndex! : objectRefOrPageIndex!;
36
+ this._paragraph = new Paragraph();
37
+
38
+ // Cast to the internal interface to get access
39
+ this._internals = this._client as unknown as PDFDancerInternals;
40
+ }
41
+
42
+ /**
43
+ * Set the text content for the paragraph.
44
+ */
45
+ replace(text: string, color?: Color): ParagraphBuilder {
46
+ if (text === null || text === undefined) {
47
+ throw new ValidationException("Text cannot be null");
48
+ }
49
+ if (!text.trim()) {
50
+ throw new ValidationException("Text cannot be empty");
51
+ }
52
+
53
+ this._text = text;
54
+ if (color) {
55
+ this._textColor = color;
56
+ }
57
+
58
+ return this;
59
+ }
60
+
61
+ /**
62
+ * Set the font for the paragraph using an existing Font object.
63
+ */
64
+ font(font: Font): ParagraphBuilder;
65
+ font(fontName: string, fontSize: number): ParagraphBuilder;
66
+ font(fontOrName: Font | string, fontSize?: number): ParagraphBuilder {
67
+ if (fontOrName instanceof Font) {
68
+ this._font = fontOrName;
69
+ } else {
70
+ if (!fontOrName) {
71
+ throw new ValidationException("Font name cannot be null");
72
+ }
73
+ if (fontSize == null) {
74
+ throw new ValidationException("Font size cannot be null");
75
+ }
76
+ this._font = new Font(fontOrName, fontSize);
77
+ }
78
+
79
+ return this;
80
+ }
81
+
82
+ /**
83
+ * Set the font for the paragraph using a TTF file.
84
+ */
85
+ fontFile(ttfFile: Uint8Array | File, fontSize: number): this {
86
+ if (!ttfFile) throw new ValidationException("TTF file cannot be null");
87
+ if (fontSize <= 0) {
88
+ throw new ValidationException(`Font size must be positive, got ${fontSize}`);
89
+ }
90
+
91
+ this._registeringFont = true;
92
+ const job = this._registerTtf(ttfFile, fontSize).then(font => {
93
+ this._font = font;
94
+ });
95
+
96
+ this._pending.push(job);
97
+ return this;
98
+ }
99
+
100
+ /**
101
+ * Set the line spacing for the paragraph.
102
+ */
103
+ lineSpacing(spacing: number): ParagraphBuilder {
104
+ if (spacing <= 0) {
105
+ throw new ValidationException(`Line spacing must be positive, got ${spacing}`);
106
+ }
107
+
108
+ this._lineSpacing = spacing;
109
+ return this;
110
+ }
111
+
112
+ /**
113
+ * Set the text color for the paragraph.
114
+ */
115
+ color(color: Color): ParagraphBuilder {
116
+ if (!color) {
117
+ throw new ValidationException("Color cannot be null");
118
+ }
119
+
120
+ this._textColor = color;
121
+ return this;
122
+ }
123
+
124
+ /**
125
+ * Set the position for the paragraph.
126
+ */
127
+ moveTo(x: number, y: number): ParagraphBuilder {
128
+ if (x === null || x === undefined || y === null || y === undefined) {
129
+ throw new ValidationException("Coordinates cannot be null or undefined");
130
+ }
131
+
132
+ this._paragraph.setPosition(Position.atPageCoordinates(this._pageIndex, x, y));
133
+ return this;
134
+ }
135
+
136
+ /**
137
+ * Build and return the final Paragraph object.
138
+ */
139
+ private build(): Paragraph {
140
+ // Validate required fields
141
+ if (!this._text) {
142
+ throw new ValidationException("Text must be set before building paragraph");
143
+ }
144
+ // Set paragraph properties
145
+ this._paragraph.font = this._font;
146
+ this._paragraph.color = this._textColor;
147
+ this._paragraph.lineSpacing = this._lineSpacing;
148
+
149
+ // Process text into lines
150
+ this._paragraph.textLines = this._processTextLines(this._text);
151
+
152
+ return this._paragraph;
153
+ }
154
+
155
+ /**
156
+ * Register a TTF font with the client and return a Font object.
157
+ */
158
+ private async _registerTtf(ttfFile: Uint8Array | File, fontSize: number): Promise<Font> {
159
+ try {
160
+ const fontName = await this._client.registerFont(ttfFile);
161
+ return new Font(fontName, fontSize);
162
+ } catch (error) {
163
+ throw new ValidationException(`Failed to register font file: ${error}`);
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Process text into lines for the paragraph.
169
+ * This is a simplified version - the full implementation would handle
170
+ * word wrapping, line breaks, and other text formatting based on the font
171
+ * and paragraph width.
172
+ */
173
+ private _processTextLines(text: string): string[] {
174
+ // Handle escaped newlines (\\n) as actual newlines
175
+ const processedText = text.replace(/\\\\n/g, '\n');
176
+
177
+ // Simple implementation - split on newlines
178
+ // In the full version, this would implement proper text layout
179
+ let lines = processedText.split('\n');
180
+
181
+ // Remove empty lines at the end but preserve intentional line breaks
182
+ while (lines.length > 0 && !lines[lines.length - 1].trim()) {
183
+ lines.pop();
184
+ }
185
+
186
+ // Ensure at least one line
187
+ if (lines.length === 0) {
188
+ lines = [''];
189
+ }
190
+
191
+ return lines;
192
+ }
193
+
194
+ async apply() {
195
+ if (!this._text) {
196
+ throw new ValidationException("Text must be set before building paragraph");
197
+ }
198
+ // Wait for all deferred operations (e.g., fontFile, images, etc.)
199
+ if (this._pending.length) {
200
+ await Promise.all(this._pending);
201
+ this._pending = []; // reset if builder is reusable
202
+ }
203
+
204
+ if (this._registeringFont && !this._font) {
205
+ throw new ValidationException("Font registration is not complete");
206
+ }
207
+
208
+ let paragraph = this.build();
209
+ if (this.objectRefOrPageIndex instanceof ObjectRef) {
210
+ if (!this._font || !this._textColor) {
211
+ return await this._internals.modifyParagraph(this.objectRefOrPageIndex, this._text);
212
+ } else {
213
+ return await this._internals.modifyParagraph(this.objectRefOrPageIndex, paragraph);
214
+ }
215
+ } else {
216
+ return await this._internals.addParagraph(paragraph);
217
+ }
218
+ }
219
+
220
+ text(text: string) {
221
+ this._text = text;
222
+ return this;
223
+ }
224
+
225
+ at(x: number, y: number) {
226
+ return this.moveTo(x, y);
170
227
  }
171
228
 
172
- return lines;
173
- }
174
229
  }