pdfdancer-client-typescript 1.0.12 → 1.0.13

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 (65) hide show
  1. package/.github/workflows/ci.yml +1 -1
  2. package/README.md +1 -1
  3. package/dist/__tests__/e2e/pdf-assertions.d.ts +1 -0
  4. package/dist/__tests__/e2e/pdf-assertions.d.ts.map +1 -1
  5. package/dist/__tests__/e2e/pdf-assertions.js +9 -3
  6. package/dist/__tests__/e2e/pdf-assertions.js.map +1 -1
  7. package/dist/fingerprint.d.ts +12 -0
  8. package/dist/fingerprint.d.ts.map +1 -0
  9. package/dist/fingerprint.js +196 -0
  10. package/dist/fingerprint.js.map +1 -0
  11. package/dist/image-builder.d.ts +4 -2
  12. package/dist/image-builder.d.ts.map +1 -1
  13. package/dist/image-builder.js +12 -3
  14. package/dist/image-builder.js.map +1 -1
  15. package/dist/index.d.ts +2 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +7 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/models.d.ts +75 -8
  20. package/dist/models.d.ts.map +1 -1
  21. package/dist/models.js +179 -21
  22. package/dist/models.js.map +1 -1
  23. package/dist/page-builder.d.ts +24 -0
  24. package/dist/page-builder.d.ts.map +1 -0
  25. package/dist/page-builder.js +107 -0
  26. package/dist/page-builder.js.map +1 -0
  27. package/dist/paragraph-builder.d.ts +48 -54
  28. package/dist/paragraph-builder.d.ts.map +1 -1
  29. package/dist/paragraph-builder.js +408 -135
  30. package/dist/paragraph-builder.js.map +1 -1
  31. package/dist/pdfdancer_v1.d.ts +90 -9
  32. package/dist/pdfdancer_v1.d.ts.map +1 -1
  33. package/dist/pdfdancer_v1.js +535 -50
  34. package/dist/pdfdancer_v1.js.map +1 -1
  35. package/dist/types.d.ts +24 -3
  36. package/dist/types.d.ts.map +1 -1
  37. package/dist/types.js +117 -2
  38. package/dist/types.js.map +1 -1
  39. package/docs/openapi.yml +2076 -0
  40. package/fixtures/Showcase.pdf +0 -0
  41. package/package.json +1 -1
  42. package/src/__tests__/e2e/acroform.test.ts +5 -5
  43. package/src/__tests__/e2e/context-manager-showcase.test.ts +267 -0
  44. package/src/__tests__/e2e/form_x_object.test.ts +1 -1
  45. package/src/__tests__/e2e/image-showcase.test.ts +133 -0
  46. package/src/__tests__/e2e/image.test.ts +1 -1
  47. package/src/__tests__/e2e/line-showcase.test.ts +118 -0
  48. package/src/__tests__/e2e/line.test.ts +1 -16
  49. package/src/__tests__/e2e/page-showcase.test.ts +154 -0
  50. package/src/__tests__/e2e/paragraph-showcase.test.ts +523 -0
  51. package/src/__tests__/e2e/paragraph.test.ts +8 -8
  52. package/src/__tests__/e2e/pdf-assertions.ts +10 -3
  53. package/src/__tests__/e2e/pdfdancer-showcase.test.ts +40 -0
  54. package/src/__tests__/e2e/snapshot-showcase.test.ts +158 -0
  55. package/src/__tests__/e2e/snapshot.test.ts +296 -0
  56. package/src/__tests__/fingerprint.test.ts +36 -0
  57. package/src/fingerprint.ts +169 -0
  58. package/src/image-builder.ts +13 -6
  59. package/src/index.ts +6 -1
  60. package/src/models.ts +208 -24
  61. package/src/page-builder.ts +130 -0
  62. package/src/paragraph-builder.ts +517 -159
  63. package/src/pdfdancer_v1.ts +630 -51
  64. package/src/types.ts +145 -2
  65. package/update-api-spec.sh +3 -0
@@ -6,36 +6,111 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ParagraphBuilder = void 0;
7
7
  const exceptions_1 = require("./exceptions");
8
8
  const models_1 = require("./models");
9
+ const DEFAULT_LINE_SPACING_FACTOR = 1.2;
10
+ const DEFAULT_BASE_FONT_SIZE = 12;
11
+ const cloneColor = (color) => {
12
+ if (!color) {
13
+ return undefined;
14
+ }
15
+ return new models_1.Color(color.r, color.g, color.b, color.a);
16
+ };
17
+ const clonePosition = (position) => {
18
+ return position ? position.copy() : undefined;
19
+ };
20
+ const defaultTextColor = () => new models_1.Color(0, 0, 0);
9
21
  /**
10
22
  * Builder class for constructing Paragraph objects with fluent interface.
23
+ * Aligns with the Python client's ParagraphBuilder behaviour.
11
24
  */
12
25
  class ParagraphBuilder {
13
26
  constructor(_client, objectRefOrPageIndex) {
14
27
  this._client = _client;
15
- this.objectRefOrPageIndex = objectRefOrPageIndex;
28
+ this._fontExplicitlyChanged = false;
29
+ this._positionChanged = false;
16
30
  this._pending = [];
17
31
  this._registeringFont = false;
18
32
  if (!_client) {
19
33
  throw new exceptions_1.ValidationException("Client cannot be null");
20
34
  }
21
- this._pageIndex = objectRefOrPageIndex instanceof models_1.ObjectRef ? objectRefOrPageIndex.position.pageIndex : objectRefOrPageIndex;
22
35
  this._paragraph = new models_1.Paragraph();
23
- // Cast to the internal interface to get access
24
36
  this._internals = this._client;
37
+ if (objectRefOrPageIndex instanceof models_1.TextObjectRef) {
38
+ this.target(objectRefOrPageIndex);
39
+ }
40
+ else if (typeof objectRefOrPageIndex === 'number') {
41
+ this._pageIndex = objectRefOrPageIndex;
42
+ }
43
+ }
44
+ static fromObjectRef(client, objectRef) {
45
+ if (!objectRef) {
46
+ throw new exceptions_1.ValidationException("Object reference cannot be null");
47
+ }
48
+ const builder = new ParagraphBuilder(client, objectRef);
49
+ builder.target(objectRef);
50
+ builder.setOriginalParagraphPosition(objectRef.position);
51
+ if (objectRef.lineSpacings) {
52
+ builder._paragraph.setLineSpacings(objectRef.lineSpacings);
53
+ const [firstSpacing] = objectRef.lineSpacings;
54
+ if (firstSpacing !== undefined) {
55
+ builder._paragraph.lineSpacing = firstSpacing;
56
+ }
57
+ }
58
+ if (objectRef.fontName && objectRef.fontSize) {
59
+ builder._originalFont = new models_1.Font(objectRef.fontName, objectRef.fontSize);
60
+ }
61
+ if (objectRef.color) {
62
+ builder._originalColor = cloneColor(objectRef.color);
63
+ }
64
+ if (objectRef.children && objectRef.children.length > 0) {
65
+ objectRef.children.forEach(child => builder.addTextLine(child));
66
+ }
67
+ else if (objectRef.text) {
68
+ builder._splitText(objectRef.text).forEach(segment => builder.addTextLine(segment));
69
+ }
70
+ return builder;
71
+ }
72
+ setFontExplicitlyChanged(changed) {
73
+ this._fontExplicitlyChanged = !!changed;
74
+ }
75
+ setOriginalParagraphPosition(position) {
76
+ this._originalParagraphPosition = clonePosition(position);
77
+ if (position && !this._paragraph.getPosition()) {
78
+ this._paragraph.setPosition(clonePosition(position));
79
+ }
80
+ if (position?.pageIndex !== undefined) {
81
+ this._pageIndex = position.pageIndex;
82
+ }
83
+ }
84
+ target(objectRef) {
85
+ if (!objectRef) {
86
+ throw new exceptions_1.ValidationException("Object reference cannot be null");
87
+ }
88
+ this._targetObjectRef = objectRef;
89
+ if (objectRef.position) {
90
+ this.setOriginalParagraphPosition(objectRef.position);
91
+ }
92
+ if (objectRef.position?.pageIndex !== undefined) {
93
+ this._pageIndex = objectRef.position.pageIndex;
94
+ }
95
+ return this;
96
+ }
97
+ onlyTextChanged() {
98
+ return (this._text !== undefined &&
99
+ this._textColor === undefined &&
100
+ this._ttfSource === undefined &&
101
+ (this._font === undefined || !this._fontExplicitlyChanged) &&
102
+ this._lineSpacingFactor === undefined);
25
103
  }
26
- /**
27
- * Set the text content for the paragraph.
28
- */
29
104
  replace(text, color) {
105
+ return this.text(text, color);
106
+ }
107
+ text(text, color) {
30
108
  if (text === null || text === undefined) {
31
109
  throw new exceptions_1.ValidationException("Text cannot be null");
32
110
  }
33
- if (!text.trim()) {
34
- throw new exceptions_1.ValidationException("Text cannot be empty");
35
- }
36
111
  this._text = text;
37
112
  if (color) {
38
- this._textColor = color;
113
+ this.color(color);
39
114
  }
40
115
  return this;
41
116
  }
@@ -52,37 +127,36 @@ class ParagraphBuilder {
52
127
  }
53
128
  this._font = new models_1.Font(fontOrName, fontSize);
54
129
  }
130
+ this._fontExplicitlyChanged = true;
55
131
  return this;
56
132
  }
57
- /**
58
- * Set the font for the paragraph using a TTF file.
59
- */
60
133
  fontFile(ttfFile, fontSize) {
61
- if (!ttfFile)
134
+ if (!ttfFile) {
62
135
  throw new exceptions_1.ValidationException("TTF file cannot be null");
136
+ }
63
137
  if (fontSize <= 0) {
64
138
  throw new exceptions_1.ValidationException(`Font size must be positive, got ${fontSize}`);
65
139
  }
140
+ this._ttfSource = ttfFile;
66
141
  this._registeringFont = true;
67
- const job = this._registerTtf(ttfFile, fontSize).then(font => {
142
+ const job = this._registerTtf(ttfFile, fontSize)
143
+ .then(font => {
68
144
  this._font = font;
145
+ this._fontExplicitlyChanged = true;
146
+ })
147
+ .finally(() => {
148
+ this._registeringFont = false;
69
149
  });
70
150
  this._pending.push(job);
71
151
  return this;
72
152
  }
73
- /**
74
- * Set the line spacing for the paragraph.
75
- */
76
153
  lineSpacing(spacing) {
77
154
  if (spacing <= 0) {
78
155
  throw new exceptions_1.ValidationException(`Line spacing must be positive, got ${spacing}`);
79
156
  }
80
- this._lineSpacing = spacing;
157
+ this._lineSpacingFactor = spacing;
81
158
  return this;
82
159
  }
83
- /**
84
- * Set the text color for the paragraph.
85
- */
86
160
  color(color) {
87
161
  if (!color) {
88
162
  throw new exceptions_1.ValidationException("Color cannot be null");
@@ -90,154 +164,353 @@ class ParagraphBuilder {
90
164
  this._textColor = color;
91
165
  return this;
92
166
  }
93
- /**
94
- * Set the position for the paragraph.
95
- */
96
167
  moveTo(x, y) {
97
168
  if (x === null || x === undefined || y === null || y === undefined) {
98
169
  throw new exceptions_1.ValidationException("Coordinates cannot be null or undefined");
99
170
  }
100
- this._position = models_1.Position.atPageCoordinates(this._pageIndex, x, y);
101
- this._paragraph.setPosition(this._position);
171
+ let position = this._paragraph.getPosition();
172
+ if (!position && this._targetObjectRef?.position) {
173
+ position = clonePosition(this._targetObjectRef.position);
174
+ }
175
+ const pageIndex = position?.pageIndex ?? this._pageIndex;
176
+ if (pageIndex === undefined) {
177
+ throw new exceptions_1.ValidationException("Paragraph position must include a page index to move");
178
+ }
179
+ this._paragraph.setPosition(models_1.Position.atPageCoordinates(pageIndex, x, y));
180
+ this._positionChanged = true;
102
181
  return this;
103
182
  }
104
- /**
105
- * Build and return the final Paragraph object.
106
- */
107
- build() {
108
- // Validate required fields
109
- if (!this._text) {
110
- throw new exceptions_1.ValidationException("Text must be set before building paragraph");
111
- }
112
- // Set paragraph properties
113
- this._paragraph.font = this._font;
114
- this._paragraph.color = this._textColor ?? new models_1.Color(0, 0, 0);
115
- this._paragraph.lineSpacing = this._lineSpacing ?? 1.2; // Default 1.2 like Python
116
- // Process text into lines
117
- this._paragraph.textLines = this._processTextLines(this._text);
118
- return this._paragraph;
183
+ atPosition(position) {
184
+ if (!position) {
185
+ throw new exceptions_1.ValidationException("Position cannot be null");
186
+ }
187
+ this._paragraph.setPosition(clonePosition(position));
188
+ this._positionChanged = true;
189
+ if (position.pageIndex !== undefined) {
190
+ this._pageIndex = position.pageIndex;
191
+ }
192
+ return this;
193
+ }
194
+ at(pageIndexOrX, xOrY, maybeY) {
195
+ if (maybeY === undefined) {
196
+ const pageIndex = this._pageIndex ?? this._paragraph.getPosition()?.pageIndex;
197
+ if (pageIndex === undefined) {
198
+ throw new exceptions_1.ValidationException("Page index must be provided before calling at(x, y)");
199
+ }
200
+ return this._setPosition(pageIndex, pageIndexOrX, xOrY);
201
+ }
202
+ return this._setPosition(pageIndexOrX, xOrY, maybeY);
203
+ }
204
+ _setPosition(pageIndex, x, y) {
205
+ this._pageIndex = pageIndex;
206
+ return this.atPosition(models_1.Position.atPageCoordinates(pageIndex, x, y));
207
+ }
208
+ addTextLine(textLine) {
209
+ this._paragraph.addLine(this._coerceTextLine(textLine));
210
+ return this;
211
+ }
212
+ getText() {
213
+ return this._text;
119
214
  }
120
- // Python-style getter methods that preserve original values if not explicitly set
121
- _getLineSpacing(originalRef) {
122
- if (this._lineSpacing !== undefined) {
123
- return this._lineSpacing;
215
+ async add() {
216
+ await this._prepareAsync();
217
+ if (this._targetObjectRef) {
218
+ throw new exceptions_1.ValidationException("Target object reference provided; use modify() for updates");
124
219
  }
125
- else if (originalRef.lineSpacings && originalRef.lineSpacings.length > 0) {
126
- // Calculate average like Python does
127
- const sum = originalRef.lineSpacings.reduce((a, b) => a + b, 0);
128
- return sum / originalRef.lineSpacings.length;
220
+ const paragraph = this._finalizeParagraph();
221
+ return this._internals.addParagraph(paragraph);
222
+ }
223
+ async modify(objectRef) {
224
+ await this._prepareAsync();
225
+ const target = objectRef ?? this._targetObjectRef;
226
+ if (!target) {
227
+ throw new exceptions_1.ValidationException("Object reference must be provided to modify a paragraph");
129
228
  }
130
- else {
131
- return 1.2; // DEFAULT_LINE_SPACING
229
+ if (this.onlyTextChanged()) {
230
+ const result = await this._internals.modifyParagraph(target, this._text ?? '');
231
+ return this._withWarning(result);
132
232
  }
233
+ const paragraph = this._finalizeParagraph();
234
+ const result = await this._internals.modifyParagraph(target, paragraph);
235
+ return this._withWarning(result);
133
236
  }
134
- _getFont(originalRef) {
135
- if (this._font) {
136
- return this._font;
237
+ async apply() {
238
+ await this._prepareAsync();
239
+ if (this._targetObjectRef) {
240
+ return this.modify(this._targetObjectRef);
241
+ }
242
+ return this.add();
243
+ }
244
+ async _prepareAsync() {
245
+ if (this._pending.length) {
246
+ await Promise.all(this._pending);
247
+ this._pending = [];
137
248
  }
138
- else if (originalRef.fontName && originalRef.fontSize) {
139
- return new models_1.Font(originalRef.fontName, originalRef.fontSize);
249
+ if (this._registeringFont) {
250
+ throw new exceptions_1.ValidationException("Font registration is not complete");
140
251
  }
141
- else {
142
- throw new exceptions_1.ValidationException("Font is required");
252
+ }
253
+ _withWarning(result) {
254
+ if (result && result.warning) {
255
+ process.stderr.write(`WARNING: ${result.warning}\n`);
143
256
  }
257
+ return result;
144
258
  }
145
- _getColor(originalRef) {
259
+ _finalizeParagraph() {
260
+ const position = this._paragraph.getPosition();
261
+ if (!position) {
262
+ throw new exceptions_1.ValidationException("Paragraph position is null, you need to specify a position for the new paragraph, using .at(x,y)");
263
+ }
264
+ if (!this._targetObjectRef && !this._font && !this._paragraph.font) {
265
+ throw new exceptions_1.ValidationException("Font must be set before building paragraph");
266
+ }
267
+ if (this._text !== undefined) {
268
+ this._finalizeLinesFromText();
269
+ }
270
+ else if (!this._paragraph.textLines || this._paragraph.textLines.length === 0) {
271
+ throw new exceptions_1.ValidationException("Either text must be provided or existing lines supplied");
272
+ }
273
+ else {
274
+ this._finalizeExistingLines();
275
+ }
276
+ this._repositionLines();
277
+ const shouldSkipLines = (this._positionChanged &&
278
+ this._text === undefined &&
279
+ this._textColor === undefined &&
280
+ (this._font === undefined || !this._fontExplicitlyChanged) &&
281
+ this._lineSpacingFactor === undefined);
282
+ if (shouldSkipLines) {
283
+ this._paragraph.textLines = undefined;
284
+ this._paragraph.setLineSpacings(null);
285
+ }
286
+ let finalFont = this._font ?? this._paragraph.font ?? this._originalFont;
287
+ if (!finalFont) {
288
+ finalFont = new models_1.Font(models_1.StandardFonts.HELVETICA, DEFAULT_BASE_FONT_SIZE);
289
+ }
290
+ this._paragraph.font = finalFont;
291
+ let finalColor;
146
292
  if (this._textColor) {
147
- return this._textColor;
293
+ finalColor = cloneColor(this._textColor);
148
294
  }
149
- else if (originalRef.color) {
150
- return originalRef.color;
295
+ else if (this._text !== undefined) {
296
+ finalColor = cloneColor(this._originalColor) ?? defaultTextColor();
151
297
  }
152
298
  else {
153
- return new models_1.Color(0, 0, 0); // DEFAULT_COLOR
299
+ finalColor = cloneColor(this._originalColor);
154
300
  }
301
+ this._paragraph.color = finalColor;
302
+ return this._paragraph;
155
303
  }
156
- /**
157
- * Register a TTF font with the client and return a Font object.
158
- */
159
- async _registerTtf(ttfFile, fontSize) {
160
- try {
161
- const fontName = await this._client.registerFont(ttfFile);
162
- return new models_1.Font(fontName, fontSize);
304
+ _finalizeLinesFromText() {
305
+ const baseFont = this._font ?? this._originalFont;
306
+ const baseColor = this._textColor ?? cloneColor(this._originalColor) ?? defaultTextColor();
307
+ let spacing;
308
+ if (this._lineSpacingFactor !== undefined) {
309
+ spacing = this._lineSpacingFactor;
163
310
  }
164
- catch (error) {
165
- throw new exceptions_1.ValidationException(`Failed to register font file: ${error}`);
311
+ else {
312
+ const existingSpacings = this._paragraph.getLineSpacings();
313
+ if (existingSpacings && existingSpacings.length > 0) {
314
+ spacing = existingSpacings[0];
315
+ }
316
+ else if (this._paragraph.lineSpacing !== undefined && this._paragraph.lineSpacing !== null) {
317
+ spacing = this._paragraph.lineSpacing;
318
+ }
319
+ else {
320
+ spacing = DEFAULT_LINE_SPACING_FACTOR;
321
+ }
166
322
  }
323
+ this._paragraph.clearLines();
324
+ const lines = [];
325
+ this._splitText(this._text ?? '').forEach((lineText, index) => {
326
+ const linePosition = this._calculateLinePosition(index, spacing);
327
+ lines.push(new models_1.TextLine(linePosition, baseFont, cloneColor(baseColor), spacing, lineText));
328
+ });
329
+ this._paragraph.setLines(lines);
330
+ if (lines.length > 1) {
331
+ this._paragraph.setLineSpacings(Array(lines.length - 1).fill(spacing));
332
+ }
333
+ else {
334
+ this._paragraph.setLineSpacings(null);
335
+ }
336
+ this._paragraph.lineSpacing = spacing;
167
337
  }
168
- /**
169
- * Process text into lines for the paragraph.
170
- * This is a simplified version - the full implementation would handle
171
- * word wrapping, line breaks, and other text formatting based on the font
172
- * and paragraph width.
173
- */
174
- _processTextLines(text) {
175
- // Handle escaped newlines (\\n) as actual newlines
176
- const processedText = text.replace(/\\\\n/g, '\n');
177
- // Simple implementation - split on newlines
178
- // In the full version, this would implement proper text layout
179
- let lines = processedText.split('\n');
180
- // Remove empty lines at the end but preserve intentional line breaks
181
- while (lines.length > 0 && !lines[lines.length - 1].trim()) {
182
- lines.pop();
183
- }
184
- // Ensure at least one line
185
- if (lines.length === 0) {
186
- lines = [''];
187
- }
188
- return lines;
189
- }
190
- async apply() {
191
- // Wait for all deferred operations (e.g., fontFile, images, etc.)
192
- if (this._pending.length) {
193
- await Promise.all(this._pending);
194
- this._pending = []; // reset if builder is reusable
338
+ _finalizeExistingLines() {
339
+ const lines = this._paragraph.getLines();
340
+ const spacingOverride = this._lineSpacingFactor;
341
+ let spacingForCalc = spacingOverride;
342
+ if (spacingForCalc === undefined) {
343
+ const existingSpacings = this._paragraph.getLineSpacings();
344
+ if (existingSpacings && existingSpacings.length > 0) {
345
+ spacingForCalc = existingSpacings[0];
346
+ }
195
347
  }
196
- if (this._registeringFont && !this._font) {
197
- throw new exceptions_1.ValidationException("Font registration is not complete");
348
+ if (spacingForCalc === undefined) {
349
+ spacingForCalc = this._paragraph.lineSpacing ?? DEFAULT_LINE_SPACING_FACTOR;
198
350
  }
199
- if (this.objectRefOrPageIndex instanceof models_1.TextObjectRef) {
200
- // Modifying existing paragraph - match Python's ParagraphEdit.apply() logic
201
- const originalRef = this.objectRefOrPageIndex;
202
- // Python logic: if ONLY text is being changed (all other properties are None), use simple text modification
203
- if (this._position === undefined &&
204
- this._lineSpacing === undefined &&
205
- this._font === undefined &&
206
- this._textColor === undefined) {
207
- // Simple text-only modification
208
- const result = await this._internals.modifyParagraph(originalRef, this._text);
209
- if (result.warning) {
210
- process.stderr.write(`WARNING: ${result.warning}\n`);
351
+ const updatedLines = [];
352
+ lines.forEach((line, index) => {
353
+ if (line instanceof models_1.TextLine) {
354
+ if (spacingOverride !== undefined) {
355
+ line.lineSpacing = spacingOverride;
356
+ }
357
+ if (this._textColor) {
358
+ line.color = cloneColor(this._textColor);
211
359
  }
212
- return result;
360
+ if (this._font && this._fontExplicitlyChanged) {
361
+ line.font = this._font;
362
+ }
363
+ updatedLines.push(line);
364
+ }
365
+ else {
366
+ const linePosition = this._calculateLinePosition(index, spacingForCalc);
367
+ updatedLines.push(new models_1.TextLine(linePosition, this._font ?? this._originalFont, this._textColor ?? cloneColor(this._originalColor) ?? defaultTextColor(), spacingOverride ?? spacingForCalc, String(line)));
368
+ }
369
+ });
370
+ this._paragraph.setLines(updatedLines);
371
+ if (spacingOverride !== undefined) {
372
+ if (updatedLines.length > 1) {
373
+ this._paragraph.setLineSpacings(Array(updatedLines.length - 1).fill(spacingOverride));
213
374
  }
214
375
  else {
215
- // Full paragraph modification - build new paragraph using getter methods to preserve original values
216
- const newParagraph = new models_1.Paragraph();
217
- newParagraph.position = this._position ?? originalRef.position;
218
- newParagraph.lineSpacing = this._getLineSpacing(originalRef);
219
- newParagraph.font = this._getFont(originalRef);
220
- newParagraph.textLines = this._text ? this._processTextLines(this._text) : this._processTextLines(originalRef.text);
221
- newParagraph.color = this._getColor(originalRef);
222
- const result = await this._internals.modifyParagraph(originalRef, newParagraph);
223
- if (result.warning) {
224
- process.stderr.write(`WARNING: ${result.warning}\n`);
376
+ this._paragraph.setLineSpacings(null);
377
+ }
378
+ this._paragraph.lineSpacing = spacingOverride;
379
+ }
380
+ }
381
+ _repositionLines() {
382
+ if (this._text !== undefined) {
383
+ return;
384
+ }
385
+ const paragraphPos = this._paragraph.getPosition();
386
+ const lines = this._paragraph.textLines;
387
+ if (!paragraphPos || !lines || lines.length === 0) {
388
+ return;
389
+ }
390
+ let basePosition = this._originalParagraphPosition;
391
+ if (!basePosition) {
392
+ for (const line of lines) {
393
+ if (line instanceof models_1.TextLine && line.position) {
394
+ basePosition = line.position;
395
+ break;
225
396
  }
226
- return result;
227
397
  }
228
398
  }
229
- else {
230
- // Adding new paragraph
231
- let paragraph = this.build();
232
- return await this._internals.addParagraph(paragraph);
399
+ if (!basePosition) {
400
+ return;
401
+ }
402
+ const targetX = paragraphPos.getX();
403
+ const targetY = paragraphPos.getY();
404
+ const baseX = basePosition.getX();
405
+ const baseY = basePosition.getY();
406
+ if (targetX === undefined || targetY === undefined || baseX === undefined || baseY === undefined) {
407
+ return;
408
+ }
409
+ const dx = targetX - baseX;
410
+ const dy = targetY - baseY;
411
+ if (dx === 0 && dy === 0) {
412
+ return;
413
+ }
414
+ lines.forEach(line => {
415
+ if (line instanceof models_1.TextLine && line.position) {
416
+ const currentX = line.position.getX();
417
+ const currentY = line.position.getY();
418
+ if (currentX === undefined || currentY === undefined) {
419
+ return;
420
+ }
421
+ const updatedPosition = line.position.copy();
422
+ updatedPosition.atCoordinates({ x: currentX + dx, y: currentY + dy });
423
+ line.setPosition(updatedPosition);
424
+ }
425
+ });
426
+ }
427
+ _coerceTextLine(source) {
428
+ if (source instanceof models_1.TextLine) {
429
+ return source;
430
+ }
431
+ if (source instanceof models_1.TextObjectRef) {
432
+ let font;
433
+ if (source.fontName && source.fontSize) {
434
+ font = new models_1.Font(source.fontName, source.fontSize);
435
+ }
436
+ else if (source.children) {
437
+ for (const child of source.children) {
438
+ if (child.fontName && child.fontSize) {
439
+ font = new models_1.Font(child.fontName, child.fontSize);
440
+ break;
441
+ }
442
+ }
443
+ }
444
+ if (!font) {
445
+ font = this._originalFont;
446
+ }
447
+ let spacing = this._lineSpacingFactor;
448
+ if (spacing === undefined && source.lineSpacings && source.lineSpacings.length > 0) {
449
+ spacing = source.lineSpacings[0];
450
+ }
451
+ if (spacing === undefined) {
452
+ spacing = this._paragraph.lineSpacing ?? DEFAULT_LINE_SPACING_FACTOR;
453
+ }
454
+ const color = source.color ?? this._originalColor;
455
+ if (!this._originalFont && font) {
456
+ this._originalFont = font;
457
+ }
458
+ if (!this._originalColor && color) {
459
+ this._originalColor = color;
460
+ }
461
+ return new models_1.TextLine(clonePosition(source.position), font, cloneColor(color), spacing, source.text ?? '');
233
462
  }
463
+ const currentIndex = this._paragraph.getLines().length;
464
+ const spacing = this._lineSpacingFactor ?? this._paragraph.lineSpacing ?? DEFAULT_LINE_SPACING_FACTOR;
465
+ const linePosition = this._calculateLinePosition(currentIndex, spacing);
466
+ return new models_1.TextLine(linePosition, this._font ?? this._originalFont, this._textColor ?? cloneColor(this._originalColor) ?? defaultTextColor(), spacing, source);
234
467
  }
235
- text(text) {
236
- this._text = text;
237
- return this;
468
+ _splitText(text) {
469
+ const processed = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').replace(/\\n/g, '\n');
470
+ const parts = processed.split('\n');
471
+ while (parts.length > 0 && parts[parts.length - 1] === '') {
472
+ parts.pop();
473
+ }
474
+ if (parts.length === 0) {
475
+ parts.push('');
476
+ }
477
+ return parts;
238
478
  }
239
- at(x, y) {
240
- return this.moveTo(x, y);
479
+ _calculateLinePosition(lineIndex, spacingFactor) {
480
+ const paragraphPosition = this._paragraph.getPosition();
481
+ if (!paragraphPosition) {
482
+ return undefined;
483
+ }
484
+ const pageIndex = paragraphPosition.pageIndex;
485
+ const baseX = paragraphPosition.getX();
486
+ const baseY = paragraphPosition.getY();
487
+ if (pageIndex === undefined || baseX === undefined || baseY === undefined) {
488
+ return undefined;
489
+ }
490
+ const offset = lineIndex * this._calculateBaselineDistance(spacingFactor);
491
+ return models_1.Position.atPageCoordinates(pageIndex, baseX, baseY + offset);
492
+ }
493
+ _calculateBaselineDistance(spacingFactor) {
494
+ const factor = spacingFactor > 0 ? spacingFactor : DEFAULT_LINE_SPACING_FACTOR;
495
+ return this._baselineFontSize() * factor;
496
+ }
497
+ _baselineFontSize() {
498
+ if (this._font?.size) {
499
+ return this._font.size;
500
+ }
501
+ if (this._originalFont?.size) {
502
+ return this._originalFont.size;
503
+ }
504
+ return DEFAULT_BASE_FONT_SIZE;
505
+ }
506
+ async _registerTtf(ttfFile, fontSize) {
507
+ try {
508
+ const fontName = await this._client.registerFont(ttfFile);
509
+ return new models_1.Font(fontName, fontSize);
510
+ }
511
+ catch (error) {
512
+ throw new exceptions_1.ValidationException(`Failed to register font file: ${error}`);
513
+ }
241
514
  }
242
515
  }
243
516
  exports.ParagraphBuilder = ParagraphBuilder;