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.
- package/.github/workflows/ci.yml +1 -1
- package/README.md +1 -1
- package/dist/__tests__/e2e/pdf-assertions.d.ts +1 -0
- package/dist/__tests__/e2e/pdf-assertions.d.ts.map +1 -1
- package/dist/__tests__/e2e/pdf-assertions.js +9 -3
- package/dist/__tests__/e2e/pdf-assertions.js.map +1 -1
- package/dist/fingerprint.d.ts +12 -0
- package/dist/fingerprint.d.ts.map +1 -0
- package/dist/fingerprint.js +196 -0
- package/dist/fingerprint.js.map +1 -0
- package/dist/image-builder.d.ts +4 -2
- package/dist/image-builder.d.ts.map +1 -1
- package/dist/image-builder.js +12 -3
- package/dist/image-builder.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +75 -8
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +179 -21
- package/dist/models.js.map +1 -1
- package/dist/page-builder.d.ts +24 -0
- package/dist/page-builder.d.ts.map +1 -0
- package/dist/page-builder.js +107 -0
- package/dist/page-builder.js.map +1 -0
- package/dist/paragraph-builder.d.ts +48 -54
- package/dist/paragraph-builder.d.ts.map +1 -1
- package/dist/paragraph-builder.js +408 -135
- package/dist/paragraph-builder.js.map +1 -1
- package/dist/pdfdancer_v1.d.ts +90 -9
- package/dist/pdfdancer_v1.d.ts.map +1 -1
- package/dist/pdfdancer_v1.js +535 -50
- package/dist/pdfdancer_v1.js.map +1 -1
- package/dist/types.d.ts +24 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +117 -2
- package/dist/types.js.map +1 -1
- package/docs/openapi.yml +2076 -0
- package/fixtures/Showcase.pdf +0 -0
- package/package.json +1 -1
- package/src/__tests__/e2e/acroform.test.ts +5 -5
- package/src/__tests__/e2e/context-manager-showcase.test.ts +267 -0
- package/src/__tests__/e2e/form_x_object.test.ts +1 -1
- package/src/__tests__/e2e/image-showcase.test.ts +133 -0
- package/src/__tests__/e2e/image.test.ts +1 -1
- package/src/__tests__/e2e/line-showcase.test.ts +118 -0
- package/src/__tests__/e2e/line.test.ts +1 -16
- package/src/__tests__/e2e/page-showcase.test.ts +154 -0
- package/src/__tests__/e2e/paragraph-showcase.test.ts +523 -0
- package/src/__tests__/e2e/paragraph.test.ts +8 -8
- package/src/__tests__/e2e/pdf-assertions.ts +10 -3
- package/src/__tests__/e2e/pdfdancer-showcase.test.ts +40 -0
- package/src/__tests__/e2e/snapshot-showcase.test.ts +158 -0
- package/src/__tests__/e2e/snapshot.test.ts +296 -0
- package/src/__tests__/fingerprint.test.ts +36 -0
- package/src/fingerprint.ts +169 -0
- package/src/image-builder.ts +13 -6
- package/src/index.ts +6 -1
- package/src/models.ts +208 -24
- package/src/page-builder.ts +130 -0
- package/src/paragraph-builder.ts +517 -159
- package/src/pdfdancer_v1.ts +630 -51
- package/src/types.ts +145 -2
- package/update-api-spec.sh +3 -0
package/src/paragraph-builder.ts
CHANGED
|
@@ -3,68 +3,172 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import {ValidationException} from './exceptions';
|
|
6
|
-
import {
|
|
7
|
-
|
|
6
|
+
import {
|
|
7
|
+
Color,
|
|
8
|
+
CommandResult,
|
|
9
|
+
Font,
|
|
10
|
+
ObjectRef,
|
|
11
|
+
Paragraph,
|
|
12
|
+
Position,
|
|
13
|
+
StandardFonts,
|
|
14
|
+
TextLine,
|
|
15
|
+
TextObjectRef
|
|
16
|
+
} from './models';
|
|
17
|
+
import {PDFDancer} from './pdfdancer_v1';
|
|
18
|
+
|
|
19
|
+
const DEFAULT_LINE_SPACING_FACTOR = 1.2;
|
|
20
|
+
const DEFAULT_BASE_FONT_SIZE = 12;
|
|
8
21
|
|
|
9
22
|
// 👇 Internal view of PDFDancer methods, not exported
|
|
10
23
|
interface PDFDancerInternals {
|
|
11
|
-
modifyParagraph(
|
|
24
|
+
modifyParagraph(objectRef: ObjectRef, update: Paragraph | string | null): Promise<CommandResult>;
|
|
12
25
|
|
|
13
26
|
addParagraph(paragraph: Paragraph): Promise<boolean>;
|
|
14
27
|
}
|
|
15
28
|
|
|
29
|
+
const cloneColor = (color?: Color | null): Color | undefined => {
|
|
30
|
+
if (!color) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
return new Color(color.r, color.g, color.b, color.a);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const clonePosition = (position?: Position): Position | undefined => {
|
|
37
|
+
return position ? position.copy() : undefined;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const defaultTextColor = (): Color => new Color(0, 0, 0);
|
|
41
|
+
|
|
16
42
|
/**
|
|
17
43
|
* Builder class for constructing Paragraph objects with fluent interface.
|
|
44
|
+
* Aligns with the Python client's ParagraphBuilder behaviour.
|
|
18
45
|
*/
|
|
19
46
|
export class ParagraphBuilder {
|
|
20
|
-
private _paragraph: Paragraph;
|
|
21
|
-
private
|
|
47
|
+
private readonly _paragraph: Paragraph;
|
|
48
|
+
private readonly _internals: PDFDancerInternals;
|
|
49
|
+
|
|
50
|
+
private _lineSpacingFactor?: number;
|
|
22
51
|
private _textColor?: Color;
|
|
23
52
|
private _text?: string;
|
|
53
|
+
private _ttfSource?: Uint8Array | File | string;
|
|
24
54
|
private _font?: Font;
|
|
25
|
-
private
|
|
55
|
+
private _fontExplicitlyChanged = false;
|
|
56
|
+
private _originalParagraphPosition?: Position;
|
|
57
|
+
private _targetObjectRef?: TextObjectRef;
|
|
58
|
+
private _originalFont?: Font;
|
|
59
|
+
private _originalColor?: Color;
|
|
60
|
+
private _positionChanged = false;
|
|
61
|
+
private _pageIndex?: number;
|
|
62
|
+
|
|
26
63
|
private _pending: Promise<unknown>[] = [];
|
|
27
|
-
private _registeringFont
|
|
28
|
-
private _pageIndex: number;
|
|
29
|
-
private _internals: PDFDancerInternals;
|
|
64
|
+
private _registeringFont = false;
|
|
30
65
|
|
|
31
|
-
constructor(private _client: PDFDancer,
|
|
66
|
+
constructor(private readonly _client: PDFDancer, objectRefOrPageIndex?: TextObjectRef | number) {
|
|
32
67
|
if (!_client) {
|
|
33
68
|
throw new ValidationException("Client cannot be null");
|
|
34
69
|
}
|
|
35
70
|
|
|
36
|
-
this._pageIndex = objectRefOrPageIndex instanceof ObjectRef ? objectRefOrPageIndex.position.pageIndex! : objectRefOrPageIndex!;
|
|
37
71
|
this._paragraph = new Paragraph();
|
|
38
|
-
|
|
39
|
-
// Cast to the internal interface to get access
|
|
40
72
|
this._internals = this._client as unknown as PDFDancerInternals;
|
|
73
|
+
|
|
74
|
+
if (objectRefOrPageIndex instanceof TextObjectRef) {
|
|
75
|
+
this.target(objectRefOrPageIndex);
|
|
76
|
+
} else if (typeof objectRefOrPageIndex === 'number') {
|
|
77
|
+
this._pageIndex = objectRefOrPageIndex;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static fromObjectRef(client: PDFDancer, objectRef: TextObjectRef): ParagraphBuilder {
|
|
82
|
+
if (!objectRef) {
|
|
83
|
+
throw new ValidationException("Object reference cannot be null");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const builder = new ParagraphBuilder(client, objectRef);
|
|
87
|
+
builder.target(objectRef);
|
|
88
|
+
builder.setOriginalParagraphPosition(objectRef.position);
|
|
89
|
+
|
|
90
|
+
if (objectRef.lineSpacings) {
|
|
91
|
+
builder._paragraph.setLineSpacings(objectRef.lineSpacings);
|
|
92
|
+
const [firstSpacing] = objectRef.lineSpacings;
|
|
93
|
+
if (firstSpacing !== undefined) {
|
|
94
|
+
builder._paragraph.lineSpacing = firstSpacing;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (objectRef.fontName && objectRef.fontSize) {
|
|
99
|
+
builder._originalFont = new Font(objectRef.fontName, objectRef.fontSize);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (objectRef.color) {
|
|
103
|
+
builder._originalColor = cloneColor(objectRef.color);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (objectRef.children && objectRef.children.length > 0) {
|
|
107
|
+
objectRef.children.forEach(child => builder.addTextLine(child));
|
|
108
|
+
} else if (objectRef.text) {
|
|
109
|
+
builder._splitText(objectRef.text).forEach(segment => builder.addTextLine(segment));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return builder;
|
|
41
113
|
}
|
|
42
114
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
115
|
+
setFontExplicitlyChanged(changed: boolean): void {
|
|
116
|
+
this._fontExplicitlyChanged = !!changed;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
setOriginalParagraphPosition(position?: Position): void {
|
|
120
|
+
this._originalParagraphPosition = clonePosition(position);
|
|
121
|
+
if (position && !this._paragraph.getPosition()) {
|
|
122
|
+
this._paragraph.setPosition(clonePosition(position)!);
|
|
123
|
+
}
|
|
124
|
+
if (position?.pageIndex !== undefined) {
|
|
125
|
+
this._pageIndex = position.pageIndex;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
target(objectRef: ObjectRef): this {
|
|
130
|
+
if (!objectRef) {
|
|
131
|
+
throw new ValidationException("Object reference cannot be null");
|
|
132
|
+
}
|
|
133
|
+
this._targetObjectRef = objectRef as TextObjectRef;
|
|
134
|
+
if (objectRef.position) {
|
|
135
|
+
this.setOriginalParagraphPosition(objectRef.position);
|
|
136
|
+
}
|
|
137
|
+
if (objectRef.position?.pageIndex !== undefined) {
|
|
138
|
+
this._pageIndex = objectRef.position.pageIndex;
|
|
139
|
+
}
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
onlyTextChanged(): boolean {
|
|
144
|
+
return (
|
|
145
|
+
this._text !== undefined &&
|
|
146
|
+
this._textColor === undefined &&
|
|
147
|
+
this._ttfSource === undefined &&
|
|
148
|
+
(this._font === undefined || !this._fontExplicitlyChanged) &&
|
|
149
|
+
this._lineSpacingFactor === undefined
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
replace(text: string, color?: Color): this {
|
|
154
|
+
return this.text(text, color);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
text(text: string, color?: Color): this {
|
|
47
158
|
if (text === null || text === undefined) {
|
|
48
159
|
throw new ValidationException("Text cannot be null");
|
|
49
160
|
}
|
|
50
|
-
if (!text.trim()) {
|
|
51
|
-
throw new ValidationException("Text cannot be empty");
|
|
52
|
-
}
|
|
53
161
|
|
|
54
162
|
this._text = text;
|
|
55
163
|
if (color) {
|
|
56
|
-
this.
|
|
164
|
+
this.color(color);
|
|
57
165
|
}
|
|
58
|
-
|
|
59
166
|
return this;
|
|
60
167
|
}
|
|
61
168
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
font(font: Font): ParagraphBuilder;
|
|
66
|
-
font(fontName: string, fontSize: number): ParagraphBuilder;
|
|
67
|
-
font(fontOrName: Font | string, fontSize?: number): ParagraphBuilder {
|
|
169
|
+
font(font: Font): this;
|
|
170
|
+
font(fontName: string, fontSize: number): this;
|
|
171
|
+
font(fontOrName: Font | string, fontSize?: number): this {
|
|
68
172
|
if (fontOrName instanceof Font) {
|
|
69
173
|
this._font = fontOrName;
|
|
70
174
|
} else {
|
|
@@ -77,210 +181,464 @@ export class ParagraphBuilder {
|
|
|
77
181
|
this._font = new Font(fontOrName, fontSize);
|
|
78
182
|
}
|
|
79
183
|
|
|
184
|
+
this._fontExplicitlyChanged = true;
|
|
80
185
|
return this;
|
|
81
186
|
}
|
|
82
187
|
|
|
83
|
-
/**
|
|
84
|
-
* Set the font for the paragraph using a TTF file.
|
|
85
|
-
*/
|
|
86
188
|
fontFile(ttfFile: Uint8Array | File | string, fontSize: number): this {
|
|
87
|
-
if (!ttfFile)
|
|
189
|
+
if (!ttfFile) {
|
|
190
|
+
throw new ValidationException("TTF file cannot be null");
|
|
191
|
+
}
|
|
88
192
|
if (fontSize <= 0) {
|
|
89
193
|
throw new ValidationException(`Font size must be positive, got ${fontSize}`);
|
|
90
194
|
}
|
|
91
195
|
|
|
196
|
+
this._ttfSource = ttfFile;
|
|
92
197
|
this._registeringFont = true;
|
|
93
|
-
const job = this._registerTtf(ttfFile, fontSize)
|
|
94
|
-
|
|
95
|
-
|
|
198
|
+
const job = this._registerTtf(ttfFile, fontSize)
|
|
199
|
+
.then(font => {
|
|
200
|
+
this._font = font;
|
|
201
|
+
this._fontExplicitlyChanged = true;
|
|
202
|
+
})
|
|
203
|
+
.finally(() => {
|
|
204
|
+
this._registeringFont = false;
|
|
205
|
+
});
|
|
96
206
|
|
|
97
207
|
this._pending.push(job);
|
|
98
208
|
return this;
|
|
99
209
|
}
|
|
100
210
|
|
|
101
|
-
|
|
102
|
-
* Set the line spacing for the paragraph.
|
|
103
|
-
*/
|
|
104
|
-
lineSpacing(spacing: number): ParagraphBuilder {
|
|
211
|
+
lineSpacing(spacing: number): this {
|
|
105
212
|
if (spacing <= 0) {
|
|
106
213
|
throw new ValidationException(`Line spacing must be positive, got ${spacing}`);
|
|
107
214
|
}
|
|
108
|
-
|
|
109
|
-
this._lineSpacing = spacing;
|
|
215
|
+
this._lineSpacingFactor = spacing;
|
|
110
216
|
return this;
|
|
111
217
|
}
|
|
112
218
|
|
|
113
|
-
|
|
114
|
-
* Set the text color for the paragraph.
|
|
115
|
-
*/
|
|
116
|
-
color(color: Color): ParagraphBuilder {
|
|
219
|
+
color(color: Color): this {
|
|
117
220
|
if (!color) {
|
|
118
221
|
throw new ValidationException("Color cannot be null");
|
|
119
222
|
}
|
|
120
|
-
|
|
121
223
|
this._textColor = color;
|
|
122
224
|
return this;
|
|
123
225
|
}
|
|
124
226
|
|
|
125
|
-
|
|
126
|
-
* Set the position for the paragraph.
|
|
127
|
-
*/
|
|
128
|
-
moveTo(x: number, y: number): ParagraphBuilder {
|
|
227
|
+
moveTo(x: number, y: number): this {
|
|
129
228
|
if (x === null || x === undefined || y === null || y === undefined) {
|
|
130
229
|
throw new ValidationException("Coordinates cannot be null or undefined");
|
|
131
230
|
}
|
|
132
231
|
|
|
133
|
-
|
|
134
|
-
|
|
232
|
+
let position = this._paragraph.getPosition();
|
|
233
|
+
if (!position && this._targetObjectRef?.position) {
|
|
234
|
+
position = clonePosition(this._targetObjectRef.position);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const pageIndex = position?.pageIndex ?? this._pageIndex;
|
|
238
|
+
if (pageIndex === undefined) {
|
|
239
|
+
throw new ValidationException("Paragraph position must include a page index to move");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
this._paragraph.setPosition(Position.atPageCoordinates(pageIndex, x, y));
|
|
243
|
+
this._positionChanged = true;
|
|
135
244
|
return this;
|
|
136
245
|
}
|
|
137
246
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
247
|
+
atPosition(position: Position): this {
|
|
248
|
+
if (!position) {
|
|
249
|
+
throw new ValidationException("Position cannot be null");
|
|
250
|
+
}
|
|
251
|
+
this._paragraph.setPosition(clonePosition(position)!);
|
|
252
|
+
this._positionChanged = true;
|
|
253
|
+
if (position.pageIndex !== undefined) {
|
|
254
|
+
this._pageIndex = position.pageIndex;
|
|
145
255
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
this._paragraph.color = this._textColor ?? new Color(0, 0, 0);
|
|
149
|
-
this._paragraph.lineSpacing = this._lineSpacing ?? 1.2; // Default 1.2 like Python
|
|
256
|
+
return this;
|
|
257
|
+
}
|
|
150
258
|
|
|
151
|
-
|
|
152
|
-
|
|
259
|
+
at(x: number, y: number): this;
|
|
260
|
+
at(pageIndex: number, x: number, y: number): this;
|
|
261
|
+
at(pageIndexOrX: number, xOrY: number, maybeY?: number): this {
|
|
262
|
+
if (maybeY === undefined) {
|
|
263
|
+
const pageIndex = this._pageIndex ?? this._paragraph.getPosition()?.pageIndex;
|
|
264
|
+
if (pageIndex === undefined) {
|
|
265
|
+
throw new ValidationException("Page index must be provided before calling at(x, y)");
|
|
266
|
+
}
|
|
267
|
+
return this._setPosition(pageIndex, pageIndexOrX, xOrY);
|
|
268
|
+
}
|
|
153
269
|
|
|
154
|
-
return this.
|
|
270
|
+
return this._setPosition(pageIndexOrX, xOrY, maybeY);
|
|
155
271
|
}
|
|
156
272
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
273
|
+
private _setPosition(pageIndex: number, x: number, y: number): this {
|
|
274
|
+
this._pageIndex = pageIndex;
|
|
275
|
+
return this.atPosition(Position.atPageCoordinates(pageIndex, x, y));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
addTextLine(textLine: TextLine | TextObjectRef | string): this {
|
|
279
|
+
this._paragraph.addLine(this._coerceTextLine(textLine));
|
|
280
|
+
return this;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
getText(): string | undefined {
|
|
284
|
+
return this._text;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async add(): Promise<boolean> {
|
|
288
|
+
await this._prepareAsync();
|
|
289
|
+
if (this._targetObjectRef) {
|
|
290
|
+
throw new ValidationException("Target object reference provided; use modify() for updates");
|
|
167
291
|
}
|
|
292
|
+
const paragraph = this._finalizeParagraph();
|
|
293
|
+
return this._internals.addParagraph(paragraph);
|
|
168
294
|
}
|
|
169
295
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
296
|
+
async modify(objectRef?: ObjectRef): Promise<CommandResult> {
|
|
297
|
+
await this._prepareAsync();
|
|
298
|
+
const target = (objectRef as TextObjectRef) ?? this._targetObjectRef;
|
|
299
|
+
if (!target) {
|
|
300
|
+
throw new ValidationException("Object reference must be provided to modify a paragraph");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (this.onlyTextChanged()) {
|
|
304
|
+
const result = await this._internals.modifyParagraph(target, this._text ?? '');
|
|
305
|
+
return this._withWarning(result);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const paragraph = this._finalizeParagraph();
|
|
309
|
+
const result = await this._internals.modifyParagraph(target, paragraph);
|
|
310
|
+
return this._withWarning(result);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async apply(): Promise<boolean | CommandResult> {
|
|
314
|
+
await this._prepareAsync();
|
|
315
|
+
if (this._targetObjectRef) {
|
|
316
|
+
return this.modify(this._targetObjectRef);
|
|
177
317
|
}
|
|
318
|
+
return this.add();
|
|
178
319
|
}
|
|
179
320
|
|
|
180
|
-
private
|
|
181
|
-
if (this.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
321
|
+
private async _prepareAsync(): Promise<void> {
|
|
322
|
+
if (this._pending.length) {
|
|
323
|
+
await Promise.all(this._pending);
|
|
324
|
+
this._pending = [];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (this._registeringFont) {
|
|
328
|
+
throw new ValidationException("Font registration is not complete");
|
|
187
329
|
}
|
|
188
330
|
}
|
|
189
331
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
private async _registerTtf(ttfFile: Uint8Array | File | string, fontSize: number): Promise<Font> {
|
|
194
|
-
try {
|
|
195
|
-
const fontName = await this._client.registerFont(ttfFile);
|
|
196
|
-
return new Font(fontName, fontSize);
|
|
197
|
-
} catch (error) {
|
|
198
|
-
throw new ValidationException(`Failed to register font file: ${error}`);
|
|
332
|
+
private _withWarning(result: CommandResult): CommandResult {
|
|
333
|
+
if (result && result.warning) {
|
|
334
|
+
process.stderr.write(`WARNING: ${result.warning}\n`);
|
|
199
335
|
}
|
|
336
|
+
return result;
|
|
200
337
|
}
|
|
201
338
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
*/
|
|
208
|
-
private _processTextLines(text: string): string[] {
|
|
209
|
-
// Handle escaped newlines (\\n) as actual newlines
|
|
210
|
-
const processedText = text.replace(/\\\\n/g, '\n');
|
|
339
|
+
private _finalizeParagraph(): Paragraph {
|
|
340
|
+
const position = this._paragraph.getPosition();
|
|
341
|
+
if (!position) {
|
|
342
|
+
throw new ValidationException("Paragraph position is null, you need to specify a position for the new paragraph, using .at(x,y)");
|
|
343
|
+
}
|
|
211
344
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
345
|
+
if (!this._targetObjectRef && !this._font && !this._paragraph.font) {
|
|
346
|
+
throw new ValidationException("Font must be set before building paragraph");
|
|
347
|
+
}
|
|
215
348
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
349
|
+
if (this._text !== undefined) {
|
|
350
|
+
this._finalizeLinesFromText();
|
|
351
|
+
} else if (!this._paragraph.textLines || this._paragraph.textLines.length === 0) {
|
|
352
|
+
throw new ValidationException("Either text must be provided or existing lines supplied");
|
|
353
|
+
} else {
|
|
354
|
+
this._finalizeExistingLines();
|
|
219
355
|
}
|
|
220
356
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
357
|
+
this._repositionLines();
|
|
358
|
+
|
|
359
|
+
const shouldSkipLines = (
|
|
360
|
+
this._positionChanged &&
|
|
361
|
+
this._text === undefined &&
|
|
362
|
+
this._textColor === undefined &&
|
|
363
|
+
(this._font === undefined || !this._fontExplicitlyChanged) &&
|
|
364
|
+
this._lineSpacingFactor === undefined
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
if (shouldSkipLines) {
|
|
368
|
+
this._paragraph.textLines = undefined;
|
|
369
|
+
this._paragraph.setLineSpacings(null);
|
|
224
370
|
}
|
|
225
371
|
|
|
226
|
-
|
|
372
|
+
let finalFont = this._font ?? this._paragraph.font ?? this._originalFont;
|
|
373
|
+
if (!finalFont) {
|
|
374
|
+
finalFont = new Font(StandardFonts.HELVETICA, DEFAULT_BASE_FONT_SIZE);
|
|
375
|
+
}
|
|
376
|
+
this._paragraph.font = finalFont;
|
|
377
|
+
|
|
378
|
+
let finalColor: Color | undefined;
|
|
379
|
+
if (this._textColor) {
|
|
380
|
+
finalColor = cloneColor(this._textColor);
|
|
381
|
+
} else if (this._text !== undefined) {
|
|
382
|
+
finalColor = cloneColor(this._originalColor) ?? defaultTextColor();
|
|
383
|
+
} else {
|
|
384
|
+
finalColor = cloneColor(this._originalColor);
|
|
385
|
+
}
|
|
386
|
+
this._paragraph.color = finalColor;
|
|
387
|
+
|
|
388
|
+
return this._paragraph;
|
|
227
389
|
}
|
|
228
390
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
391
|
+
private _finalizeLinesFromText(): void {
|
|
392
|
+
const baseFont = this._font ?? this._originalFont;
|
|
393
|
+
const baseColor = this._textColor ?? cloneColor(this._originalColor) ?? defaultTextColor();
|
|
394
|
+
|
|
395
|
+
let spacing: number;
|
|
396
|
+
if (this._lineSpacingFactor !== undefined) {
|
|
397
|
+
spacing = this._lineSpacingFactor;
|
|
398
|
+
} else {
|
|
399
|
+
const existingSpacings = this._paragraph.getLineSpacings();
|
|
400
|
+
if (existingSpacings && existingSpacings.length > 0) {
|
|
401
|
+
spacing = existingSpacings[0];
|
|
402
|
+
} else if (this._paragraph.lineSpacing !== undefined && this._paragraph.lineSpacing !== null) {
|
|
403
|
+
spacing = this._paragraph.lineSpacing;
|
|
404
|
+
} else {
|
|
405
|
+
spacing = DEFAULT_LINE_SPACING_FACTOR;
|
|
406
|
+
}
|
|
234
407
|
}
|
|
235
408
|
|
|
236
|
-
|
|
237
|
-
|
|
409
|
+
this._paragraph.clearLines();
|
|
410
|
+
const lines: TextLine[] = [];
|
|
411
|
+
|
|
412
|
+
this._splitText(this._text ?? '').forEach((lineText, index) => {
|
|
413
|
+
const linePosition = this._calculateLinePosition(index, spacing);
|
|
414
|
+
lines.push(new TextLine(linePosition, baseFont, cloneColor(baseColor), spacing, lineText));
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
this._paragraph.setLines(lines);
|
|
418
|
+
if (lines.length > 1) {
|
|
419
|
+
this._paragraph.setLineSpacings(Array(lines.length - 1).fill(spacing));
|
|
420
|
+
} else {
|
|
421
|
+
this._paragraph.setLineSpacings(null);
|
|
238
422
|
}
|
|
423
|
+
this._paragraph.lineSpacing = spacing;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
private _finalizeExistingLines(): void {
|
|
427
|
+
const lines = this._paragraph.getLines();
|
|
428
|
+
const spacingOverride = this._lineSpacingFactor;
|
|
429
|
+
let spacingForCalc = spacingOverride;
|
|
239
430
|
|
|
240
|
-
if (
|
|
241
|
-
|
|
242
|
-
|
|
431
|
+
if (spacingForCalc === undefined) {
|
|
432
|
+
const existingSpacings = this._paragraph.getLineSpacings();
|
|
433
|
+
if (existingSpacings && existingSpacings.length > 0) {
|
|
434
|
+
spacingForCalc = existingSpacings[0];
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (spacingForCalc === undefined) {
|
|
438
|
+
spacingForCalc = this._paragraph.lineSpacing ?? DEFAULT_LINE_SPACING_FACTOR;
|
|
439
|
+
}
|
|
243
440
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
441
|
+
const updatedLines: TextLine[] = [];
|
|
442
|
+
lines.forEach((line, index) => {
|
|
443
|
+
if (line instanceof TextLine) {
|
|
444
|
+
if (spacingOverride !== undefined) {
|
|
445
|
+
line.lineSpacing = spacingOverride;
|
|
446
|
+
}
|
|
447
|
+
if (this._textColor) {
|
|
448
|
+
line.color = cloneColor(this._textColor);
|
|
449
|
+
}
|
|
450
|
+
if (this._font && this._fontExplicitlyChanged) {
|
|
451
|
+
line.font = this._font;
|
|
253
452
|
}
|
|
254
|
-
|
|
453
|
+
updatedLines.push(line);
|
|
255
454
|
} else {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
455
|
+
const linePosition = this._calculateLinePosition(index, spacingForCalc!);
|
|
456
|
+
updatedLines.push(new TextLine(
|
|
457
|
+
linePosition,
|
|
458
|
+
this._font ?? this._originalFont,
|
|
459
|
+
this._textColor ?? cloneColor(this._originalColor) ?? defaultTextColor(),
|
|
460
|
+
spacingOverride ?? spacingForCalc!,
|
|
461
|
+
String(line)
|
|
462
|
+
));
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
this._paragraph.setLines(updatedLines);
|
|
467
|
+
|
|
468
|
+
if (spacingOverride !== undefined) {
|
|
469
|
+
if (updatedLines.length > 1) {
|
|
470
|
+
this._paragraph.setLineSpacings(Array(updatedLines.length - 1).fill(spacingOverride));
|
|
471
|
+
} else {
|
|
472
|
+
this._paragraph.setLineSpacings(null);
|
|
473
|
+
}
|
|
474
|
+
this._paragraph.lineSpacing = spacingOverride;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
private _repositionLines(): void {
|
|
479
|
+
if (this._text !== undefined) {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const paragraphPos = this._paragraph.getPosition();
|
|
484
|
+
const lines = this._paragraph.textLines;
|
|
485
|
+
if (!paragraphPos || !lines || lines.length === 0) {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
let basePosition = this._originalParagraphPosition;
|
|
490
|
+
if (!basePosition) {
|
|
491
|
+
for (const line of lines) {
|
|
492
|
+
if (line instanceof TextLine && line.position) {
|
|
493
|
+
basePosition = line.position;
|
|
494
|
+
break;
|
|
267
495
|
}
|
|
268
|
-
return result;
|
|
269
496
|
}
|
|
270
|
-
} else {
|
|
271
|
-
// Adding new paragraph
|
|
272
|
-
let paragraph = this.build();
|
|
273
|
-
return await this._internals.addParagraph(paragraph);
|
|
274
497
|
}
|
|
498
|
+
|
|
499
|
+
if (!basePosition) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const targetX = paragraphPos.getX();
|
|
504
|
+
const targetY = paragraphPos.getY();
|
|
505
|
+
const baseX = basePosition.getX();
|
|
506
|
+
const baseY = basePosition.getY();
|
|
507
|
+
|
|
508
|
+
if (targetX === undefined || targetY === undefined || baseX === undefined || baseY === undefined) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const dx = targetX - baseX;
|
|
513
|
+
const dy = targetY - baseY;
|
|
514
|
+
if (dx === 0 && dy === 0) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
lines.forEach(line => {
|
|
519
|
+
if (line instanceof TextLine && line.position) {
|
|
520
|
+
const currentX = line.position.getX();
|
|
521
|
+
const currentY = line.position.getY();
|
|
522
|
+
if (currentX === undefined || currentY === undefined) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const updatedPosition = line.position.copy();
|
|
526
|
+
updatedPosition.atCoordinates({x: currentX + dx, y: currentY + dy});
|
|
527
|
+
line.setPosition(updatedPosition);
|
|
528
|
+
}
|
|
529
|
+
});
|
|
275
530
|
}
|
|
276
531
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
532
|
+
private _coerceTextLine(source: TextLine | TextObjectRef | string): TextLine {
|
|
533
|
+
if (source instanceof TextLine) {
|
|
534
|
+
return source;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (source instanceof TextObjectRef) {
|
|
538
|
+
let font: Font | undefined;
|
|
539
|
+
if (source.fontName && source.fontSize) {
|
|
540
|
+
font = new Font(source.fontName, source.fontSize);
|
|
541
|
+
} else if (source.children) {
|
|
542
|
+
for (const child of source.children) {
|
|
543
|
+
if (child.fontName && child.fontSize) {
|
|
544
|
+
font = new Font(child.fontName, child.fontSize);
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
if (!font) {
|
|
550
|
+
font = this._originalFont;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
let spacing = this._lineSpacingFactor;
|
|
554
|
+
if (spacing === undefined && source.lineSpacings && source.lineSpacings.length > 0) {
|
|
555
|
+
spacing = source.lineSpacings[0];
|
|
556
|
+
}
|
|
557
|
+
if (spacing === undefined) {
|
|
558
|
+
spacing = this._paragraph.lineSpacing ?? DEFAULT_LINE_SPACING_FACTOR;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const color = source.color ?? this._originalColor;
|
|
562
|
+
|
|
563
|
+
if (!this._originalFont && font) {
|
|
564
|
+
this._originalFont = font;
|
|
565
|
+
}
|
|
566
|
+
if (!this._originalColor && color) {
|
|
567
|
+
this._originalColor = color;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return new TextLine(
|
|
571
|
+
clonePosition(source.position),
|
|
572
|
+
font,
|
|
573
|
+
cloneColor(color),
|
|
574
|
+
spacing,
|
|
575
|
+
source.text ?? ''
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const currentIndex = this._paragraph.getLines().length;
|
|
580
|
+
const spacing = this._lineSpacingFactor ?? this._paragraph.lineSpacing ?? DEFAULT_LINE_SPACING_FACTOR;
|
|
581
|
+
const linePosition = this._calculateLinePosition(currentIndex, spacing);
|
|
582
|
+
|
|
583
|
+
return new TextLine(
|
|
584
|
+
linePosition,
|
|
585
|
+
this._font ?? this._originalFont,
|
|
586
|
+
this._textColor ?? cloneColor(this._originalColor) ?? defaultTextColor(),
|
|
587
|
+
spacing,
|
|
588
|
+
source
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
private _splitText(text: string): string[] {
|
|
593
|
+
const processed = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').replace(/\\n/g, '\n');
|
|
594
|
+
const parts = processed.split('\n');
|
|
595
|
+
while (parts.length > 0 && parts[parts.length - 1] === '') {
|
|
596
|
+
parts.pop();
|
|
597
|
+
}
|
|
598
|
+
if (parts.length === 0) {
|
|
599
|
+
parts.push('');
|
|
600
|
+
}
|
|
601
|
+
return parts;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
private _calculateLinePosition(lineIndex: number, spacingFactor: number): Position | undefined {
|
|
605
|
+
const paragraphPosition = this._paragraph.getPosition();
|
|
606
|
+
if (!paragraphPosition) {
|
|
607
|
+
return undefined;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const pageIndex = paragraphPosition.pageIndex;
|
|
611
|
+
const baseX = paragraphPosition.getX();
|
|
612
|
+
const baseY = paragraphPosition.getY();
|
|
613
|
+
if (pageIndex === undefined || baseX === undefined || baseY === undefined) {
|
|
614
|
+
return undefined;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const offset = lineIndex * this._calculateBaselineDistance(spacingFactor);
|
|
618
|
+
return Position.atPageCoordinates(pageIndex, baseX, baseY + offset);
|
|
280
619
|
}
|
|
281
620
|
|
|
282
|
-
|
|
283
|
-
|
|
621
|
+
private _calculateBaselineDistance(spacingFactor: number): number {
|
|
622
|
+
const factor = spacingFactor > 0 ? spacingFactor : DEFAULT_LINE_SPACING_FACTOR;
|
|
623
|
+
return this._baselineFontSize() * factor;
|
|
284
624
|
}
|
|
285
625
|
|
|
626
|
+
private _baselineFontSize(): number {
|
|
627
|
+
if (this._font?.size) {
|
|
628
|
+
return this._font.size;
|
|
629
|
+
}
|
|
630
|
+
if (this._originalFont?.size) {
|
|
631
|
+
return this._originalFont.size;
|
|
632
|
+
}
|
|
633
|
+
return DEFAULT_BASE_FONT_SIZE;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
private async _registerTtf(ttfFile: Uint8Array | File | string, fontSize: number): Promise<Font> {
|
|
637
|
+
try {
|
|
638
|
+
const fontName = await this._client.registerFont(ttfFile);
|
|
639
|
+
return new Font(fontName, fontSize);
|
|
640
|
+
} catch (error: any) {
|
|
641
|
+
throw new ValidationException(`Failed to register font file: ${error}`);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
286
644
|
}
|