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/pdfdancer_v1.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
ValidationException
|
|
13
13
|
} from './exceptions';
|
|
14
14
|
import {
|
|
15
|
+
AddPageRequest,
|
|
15
16
|
AddRequest,
|
|
16
17
|
BoundingRect,
|
|
17
18
|
ChangeFormFieldRequest,
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
CommandResult,
|
|
20
21
|
CreatePdfRequest,
|
|
21
22
|
DeleteRequest,
|
|
23
|
+
DocumentSnapshot,
|
|
22
24
|
FindRequest,
|
|
23
25
|
Font,
|
|
24
26
|
FontRecommendation,
|
|
@@ -35,6 +37,7 @@ import {
|
|
|
35
37
|
PageRef,
|
|
36
38
|
PageSize,
|
|
37
39
|
PageSizeInput,
|
|
40
|
+
PageSnapshot,
|
|
38
41
|
Paragraph,
|
|
39
42
|
Position,
|
|
40
43
|
PositionMode,
|
|
@@ -43,11 +46,111 @@ import {
|
|
|
43
46
|
TextStatus
|
|
44
47
|
} from './models';
|
|
45
48
|
import {ParagraphBuilder} from './paragraph-builder';
|
|
49
|
+
import {PageBuilder} from './page-builder';
|
|
46
50
|
import {FormFieldObject, FormXObject, ImageObject, ParagraphObject, PathObject, TextLineObject} from "./types";
|
|
47
51
|
import {ImageBuilder} from "./image-builder";
|
|
52
|
+
import {generateFingerprint} from "./fingerprint";
|
|
48
53
|
import fs from "fs";
|
|
49
54
|
import path from "node:path";
|
|
50
55
|
|
|
56
|
+
const DEFAULT_TOLERANCE = 0.01;
|
|
57
|
+
|
|
58
|
+
// Debug flag - set to true to enable timing logs
|
|
59
|
+
const DEBUG =
|
|
60
|
+
(process.env.PDFDANCER_CLIENT_DEBUG ?? '').toLowerCase() === 'true' ||
|
|
61
|
+
(process.env.PDFDANCER_CLIENT_DEBUG ?? '') === '1' ||
|
|
62
|
+
(process.env.PDFDANCER_CLIENT_DEBUG ?? '').toLowerCase() === 'yes';
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Generate a timestamp string in the format expected by the API.
|
|
66
|
+
* Format: YYYY-MM-DDTHH:MM:SS.ffffffZ (with microseconds)
|
|
67
|
+
*/
|
|
68
|
+
function generateTimestamp(): string {
|
|
69
|
+
const now = new Date();
|
|
70
|
+
const year = now.getUTCFullYear();
|
|
71
|
+
const month = String(now.getUTCMonth() + 1).padStart(2, '0');
|
|
72
|
+
const day = String(now.getUTCDate()).padStart(2, '0');
|
|
73
|
+
const hours = String(now.getUTCHours()).padStart(2, '0');
|
|
74
|
+
const minutes = String(now.getUTCMinutes()).padStart(2, '0');
|
|
75
|
+
const seconds = String(now.getUTCSeconds()).padStart(2, '0');
|
|
76
|
+
const milliseconds = String(now.getUTCMilliseconds()).padStart(3, '0');
|
|
77
|
+
// Add 3 more zeros for microseconds (JavaScript doesn't have microsecond precision)
|
|
78
|
+
const microseconds = milliseconds + '000';
|
|
79
|
+
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${microseconds}Z`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Parse timestamp string, handling both microseconds and nanoseconds precision.
|
|
84
|
+
* @param timestampStr Timestamp string in format YYYY-MM-DDTHH:MM:SS.fffffffZ (with 6 or 9 fractional digits)
|
|
85
|
+
*/
|
|
86
|
+
function parseTimestamp(timestampStr: string): Date {
|
|
87
|
+
// Remove the 'Z' suffix
|
|
88
|
+
let ts = timestampStr.replace(/Z$/, '');
|
|
89
|
+
|
|
90
|
+
// Handle nanoseconds (9 digits) by truncating to milliseconds (3 digits)
|
|
91
|
+
// JavaScript's Date only supports millisecond precision
|
|
92
|
+
if (ts.includes('.')) {
|
|
93
|
+
const [datePart, fracPart] = ts.split('.');
|
|
94
|
+
// Truncate to 3 digits (milliseconds)
|
|
95
|
+
const truncatedFrac = fracPart.substring(0, 3);
|
|
96
|
+
ts = `${datePart}.${truncatedFrac}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return new Date(ts + 'Z');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check for X-Generated-At and X-Received-At headers and log timing information if DEBUG=true.
|
|
104
|
+
*
|
|
105
|
+
* Expected timestamp formats:
|
|
106
|
+
* - 2025-10-24T08:49:39.161945Z (microseconds - 6 digits)
|
|
107
|
+
* - 2025-10-24T08:58:45.468131265Z (nanoseconds - 9 digits)
|
|
108
|
+
*/
|
|
109
|
+
function logGeneratedAtHeader(response: Response, method: string, path: string): void {
|
|
110
|
+
if (!DEBUG) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const generatedAt = response.headers.get('X-Generated-At');
|
|
115
|
+
const receivedAt = response.headers.get('X-Received-At');
|
|
116
|
+
|
|
117
|
+
if (generatedAt || receivedAt) {
|
|
118
|
+
try {
|
|
119
|
+
const logParts: string[] = [];
|
|
120
|
+
const currentTime = new Date();
|
|
121
|
+
|
|
122
|
+
// Parse and log X-Received-At
|
|
123
|
+
let receivedTime: Date | null = null;
|
|
124
|
+
if (receivedAt) {
|
|
125
|
+
receivedTime = parseTimestamp(receivedAt);
|
|
126
|
+
const timeSinceReceived = (currentTime.getTime() - receivedTime.getTime()) / 1000;
|
|
127
|
+
logParts.push(`X-Received-At: ${receivedAt}, time since received on backend: ${timeSinceReceived.toFixed(3)}s`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Parse and log X-Generated-At
|
|
131
|
+
let generatedTime: Date | null = null;
|
|
132
|
+
if (generatedAt) {
|
|
133
|
+
generatedTime = parseTimestamp(generatedAt);
|
|
134
|
+
const timeSinceGenerated = (currentTime.getTime() - generatedTime.getTime()) / 1000;
|
|
135
|
+
logParts.push(`X-Generated-At: ${generatedAt}, time since generated on backend: ${timeSinceGenerated.toFixed(3)}s`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Calculate processing time (X-Generated-At - X-Received-At)
|
|
139
|
+
if (receivedTime && generatedTime) {
|
|
140
|
+
const processingTime = (generatedTime.getTime() - receivedTime.getTime()) / 1000;
|
|
141
|
+
logParts.push(`processing time on backend: ${processingTime.toFixed(3)}s`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (logParts.length > 0) {
|
|
145
|
+
console.log(`${Date.now() / 1000}|${method} ${path} - ${logParts.join(', ')}`);
|
|
146
|
+
}
|
|
147
|
+
} catch (e) {
|
|
148
|
+
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
149
|
+
console.log(`${Date.now() / 1000}|${method} ${path} - Header parse error: ${errorMessage}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
51
154
|
// 👇 Internal view of PDFDancer methods, not exported
|
|
52
155
|
interface PDFDancerInternals {
|
|
53
156
|
|
|
@@ -98,8 +201,8 @@ class PageClient {
|
|
|
98
201
|
this._internals = this._client as unknown as PDFDancerInternals;
|
|
99
202
|
}
|
|
100
203
|
|
|
101
|
-
async selectPathsAt(x: number, y: number) {
|
|
102
|
-
return this._internals.toPathObjects(await this._internals.findPaths(Position.atPageCoordinates(this._pageIndex, x, y)));
|
|
204
|
+
async selectPathsAt(x: number, y: number, tolerance: number = 0) {
|
|
205
|
+
return this._internals.toPathObjects(await this._internals.findPaths(Position.atPageCoordinates(this._pageIndex, x, y, tolerance)));
|
|
103
206
|
}
|
|
104
207
|
|
|
105
208
|
async selectPaths() {
|
|
@@ -110,8 +213,8 @@ class PageClient {
|
|
|
110
213
|
return this._internals.toImageObjects(await this._internals._findImages(Position.atPage(this._pageIndex)));
|
|
111
214
|
}
|
|
112
215
|
|
|
113
|
-
async selectImagesAt(x: number, y: number) {
|
|
114
|
-
return this._internals.toImageObjects(await this._internals._findImages(Position.atPageCoordinates(this._pageIndex, x, y)));
|
|
216
|
+
async selectImagesAt(x: number, y: number, tolerance: number = 0) {
|
|
217
|
+
return this._internals.toImageObjects(await this._internals._findImages(Position.atPageCoordinates(this._pageIndex, x, y, tolerance)));
|
|
115
218
|
}
|
|
116
219
|
|
|
117
220
|
async delete(): Promise<boolean> {
|
|
@@ -133,16 +236,16 @@ class PageClient {
|
|
|
133
236
|
return this._internals.toFormXObjects(await this._internals.findFormXObjects(Position.atPage(this._pageIndex)));
|
|
134
237
|
}
|
|
135
238
|
|
|
136
|
-
async selectFormsAt(x: number, y: number) {
|
|
137
|
-
return this._internals.toFormXObjects(await this._internals.findFormXObjects(Position.atPageCoordinates(this._pageIndex, x, y)));
|
|
239
|
+
async selectFormsAt(x: number, y: number, tolerance: number = 0) {
|
|
240
|
+
return this._internals.toFormXObjects(await this._internals.findFormXObjects(Position.atPageCoordinates(this._pageIndex, x, y, tolerance)));
|
|
138
241
|
}
|
|
139
242
|
|
|
140
243
|
async selectFormFields() {
|
|
141
244
|
return this._internals.toFormFields(await this._internals.findFormFields(Position.atPage(this._pageIndex)));
|
|
142
245
|
}
|
|
143
246
|
|
|
144
|
-
async selectFormFieldsAt(x: number, y: number) {
|
|
145
|
-
return this._internals.toFormFields(await this._internals.findFormFields(Position.atPageCoordinates(this._pageIndex, x, y)));
|
|
247
|
+
async selectFormFieldsAt(x: number, y: number, tolerance: number = 0) {
|
|
248
|
+
return this._internals.toFormFields(await this._internals.findFormFields(Position.atPageCoordinates(this._pageIndex, x, y, tolerance)));
|
|
146
249
|
}
|
|
147
250
|
|
|
148
251
|
// noinspection JSUnusedGlobalSymbols
|
|
@@ -156,6 +259,11 @@ class PageClient {
|
|
|
156
259
|
return this._internals.toParagraphObjects(await this._internals.findParagraphs(Position.atPage(this._pageIndex)));
|
|
157
260
|
}
|
|
158
261
|
|
|
262
|
+
async selectElements(types?: ObjectType[]) {
|
|
263
|
+
const snapshot = await this._client.getPageSnapshot(this._pageIndex, types);
|
|
264
|
+
return snapshot.elements;
|
|
265
|
+
}
|
|
266
|
+
|
|
159
267
|
async selectParagraphsStartingWith(text: string) {
|
|
160
268
|
let pos = Position.atPage(this._pageIndex);
|
|
161
269
|
pos.textStartsWith = text;
|
|
@@ -168,8 +276,10 @@ class PageClient {
|
|
|
168
276
|
return this._internals.toParagraphObjects(await this._internals.findParagraphs(pos));
|
|
169
277
|
}
|
|
170
278
|
|
|
171
|
-
async selectParagraphsAt(x: number, y: number) {
|
|
172
|
-
return this._internals.toParagraphObjects(
|
|
279
|
+
async selectParagraphsAt(x: number, y: number, tolerance: number = DEFAULT_TOLERANCE) {
|
|
280
|
+
return this._internals.toParagraphObjects(
|
|
281
|
+
await this._internals.findParagraphs(Position.atPageCoordinates(this._pageIndex, x, y, tolerance))
|
|
282
|
+
);
|
|
173
283
|
}
|
|
174
284
|
|
|
175
285
|
async selectTextLinesStartingWith(text: string) {
|
|
@@ -181,8 +291,14 @@ class PageClient {
|
|
|
181
291
|
/**
|
|
182
292
|
* Creates a new ParagraphBuilder for fluent paragraph construction.
|
|
183
293
|
*/
|
|
184
|
-
newParagraph(): ParagraphBuilder {
|
|
185
|
-
|
|
294
|
+
newParagraph(pageIndex?: number): ParagraphBuilder {
|
|
295
|
+
const targetIndex = pageIndex ?? this.position.pageIndex;
|
|
296
|
+
return new ParagraphBuilder(this._client, targetIndex);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
newImage(pageIndex?: number) {
|
|
300
|
+
const targetIndex = pageIndex ?? this.position.pageIndex;
|
|
301
|
+
return new ImageBuilder(this._client, targetIndex);
|
|
186
302
|
}
|
|
187
303
|
|
|
188
304
|
async selectTextLines() {
|
|
@@ -197,8 +313,18 @@ class PageClient {
|
|
|
197
313
|
}
|
|
198
314
|
|
|
199
315
|
// noinspection JSUnusedGlobalSymbols
|
|
200
|
-
async selectTextLinesAt(x: number, y: number) {
|
|
201
|
-
return this._internals.toTextLineObjects(
|
|
316
|
+
async selectTextLinesAt(x: number, y: number, tolerance: number = DEFAULT_TOLERANCE) {
|
|
317
|
+
return this._internals.toTextLineObjects(
|
|
318
|
+
await this._internals.findTextLines(Position.atPageCoordinates(this._pageIndex, x, y, tolerance))
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Gets a snapshot of this page, including all elements.
|
|
324
|
+
* Optionally filter by object types.
|
|
325
|
+
*/
|
|
326
|
+
async getSnapshot(types?: ObjectType[]): Promise<PageSnapshot> {
|
|
327
|
+
return this._client.getPageSnapshot(this._pageIndex, types);
|
|
202
328
|
}
|
|
203
329
|
}
|
|
204
330
|
|
|
@@ -216,6 +342,13 @@ export class PDFDancer {
|
|
|
216
342
|
private _readTimeout: number;
|
|
217
343
|
private _pdfBytes: Uint8Array;
|
|
218
344
|
private _sessionId!: string;
|
|
345
|
+
private _userId?: string;
|
|
346
|
+
private _fingerprintCache?: string;
|
|
347
|
+
|
|
348
|
+
// Snapshot caches for optimizing find operations
|
|
349
|
+
private _documentSnapshotCache: DocumentSnapshot | null = null;
|
|
350
|
+
private _pageSnapshotCache: Map<number, PageSnapshot> = new Map();
|
|
351
|
+
private _pagesCache: PageRef[] | null = null;
|
|
219
352
|
|
|
220
353
|
/**
|
|
221
354
|
* Creates a new client with PDF data.
|
|
@@ -253,6 +386,11 @@ export class PDFDancer {
|
|
|
253
386
|
|
|
254
387
|
// Process PDF data with validation
|
|
255
388
|
this._pdfBytes = this._processPdfData(pdfData);
|
|
389
|
+
|
|
390
|
+
// Initialize caches
|
|
391
|
+
this._documentSnapshotCache = null;
|
|
392
|
+
this._pageSnapshotCache = new Map();
|
|
393
|
+
this._pagesCache = null;
|
|
256
394
|
}
|
|
257
395
|
|
|
258
396
|
/**
|
|
@@ -330,17 +468,24 @@ export class PDFDancer {
|
|
|
330
468
|
const endpoint = '/session/new'.replace(/^\/+/, '');
|
|
331
469
|
const url = `${base}/${endpoint}`;
|
|
332
470
|
|
|
471
|
+
// Generate fingerprint for this request
|
|
472
|
+
const fingerprint = await generateFingerprint();
|
|
473
|
+
|
|
333
474
|
// Make request to create endpoint
|
|
334
475
|
const response = await fetch(url, {
|
|
335
476
|
method: 'POST',
|
|
336
477
|
headers: {
|
|
337
478
|
'Authorization': `Bearer ${resolvedToken}`,
|
|
338
|
-
'Content-Type': 'application/json'
|
|
479
|
+
'Content-Type': 'application/json',
|
|
480
|
+
'X-Generated-At': generateTimestamp(),
|
|
481
|
+
'X-Fingerprint': fingerprint
|
|
339
482
|
},
|
|
340
483
|
body: JSON.stringify(createRequest.toDict()),
|
|
341
484
|
signal: resolvedTimeout > 0 ? AbortSignal.timeout(resolvedTimeout) : undefined
|
|
342
485
|
});
|
|
343
486
|
|
|
487
|
+
logGeneratedAtHeader(response, 'POST', '/session/new');
|
|
488
|
+
|
|
344
489
|
if (!response.ok) {
|
|
345
490
|
const errorText = await response.text();
|
|
346
491
|
throw new HttpClientException(`Failed to create new PDF: ${errorText}`, response);
|
|
@@ -358,6 +503,10 @@ export class PDFDancer {
|
|
|
358
503
|
client._readTimeout = resolvedTimeout;
|
|
359
504
|
client._pdfBytes = new Uint8Array();
|
|
360
505
|
client._sessionId = sessionId;
|
|
506
|
+
// Initialize caches
|
|
507
|
+
client._documentSnapshotCache = null;
|
|
508
|
+
client._pageSnapshotCache = new Map();
|
|
509
|
+
client._pagesCache = null;
|
|
361
510
|
return client;
|
|
362
511
|
} catch (error) {
|
|
363
512
|
if (error instanceof HttpClientException || error instanceof SessionException || error instanceof ValidationException) {
|
|
@@ -470,15 +619,21 @@ export class PDFDancer {
|
|
|
470
619
|
formData.append('pdf', blob, 'document.pdf');
|
|
471
620
|
}
|
|
472
621
|
|
|
622
|
+
const fingerprint = await this._getFingerprint();
|
|
623
|
+
|
|
473
624
|
const response = await fetch(this._buildUrl('/session/create'), {
|
|
474
625
|
method: 'POST',
|
|
475
626
|
headers: {
|
|
476
|
-
'Authorization': `Bearer ${this._token}
|
|
627
|
+
'Authorization': `Bearer ${this._token}`,
|
|
628
|
+
'X-Generated-At': generateTimestamp(),
|
|
629
|
+
'X-Fingerprint': fingerprint
|
|
477
630
|
},
|
|
478
631
|
body: formData,
|
|
479
632
|
signal: this._readTimeout > 0 ? AbortSignal.timeout(this._readTimeout) : undefined
|
|
480
633
|
});
|
|
481
634
|
|
|
635
|
+
logGeneratedAtHeader(response, 'POST', '/session/create');
|
|
636
|
+
|
|
482
637
|
if (!response.ok) {
|
|
483
638
|
const errorMessage = await this._extractErrorMessage(response);
|
|
484
639
|
|
|
@@ -510,6 +665,16 @@ export class PDFDancer {
|
|
|
510
665
|
}
|
|
511
666
|
}
|
|
512
667
|
|
|
668
|
+
/**
|
|
669
|
+
* Get or generate the fingerprint for this client
|
|
670
|
+
*/
|
|
671
|
+
private async _getFingerprint(): Promise<string> {
|
|
672
|
+
if (!this._fingerprintCache) {
|
|
673
|
+
this._fingerprintCache = await generateFingerprint(this._userId);
|
|
674
|
+
}
|
|
675
|
+
return this._fingerprintCache;
|
|
676
|
+
}
|
|
677
|
+
|
|
513
678
|
/**
|
|
514
679
|
* Make HTTP request with session headers and error handling.
|
|
515
680
|
*/
|
|
@@ -526,10 +691,14 @@ export class PDFDancer {
|
|
|
526
691
|
});
|
|
527
692
|
}
|
|
528
693
|
|
|
694
|
+
const fingerprint = await this._getFingerprint();
|
|
695
|
+
|
|
529
696
|
const headers: Record<string, string> = {
|
|
530
697
|
'Authorization': `Bearer ${this._token}`,
|
|
531
698
|
'X-Session-Id': this._sessionId,
|
|
532
|
-
'Content-Type': 'application/json'
|
|
699
|
+
'Content-Type': 'application/json',
|
|
700
|
+
'X-Generated-At': generateTimestamp(),
|
|
701
|
+
'X-Fingerprint': fingerprint
|
|
533
702
|
};
|
|
534
703
|
|
|
535
704
|
try {
|
|
@@ -540,6 +709,8 @@ export class PDFDancer {
|
|
|
540
709
|
signal: this._readTimeout > 0 ? AbortSignal.timeout(this._readTimeout) : undefined
|
|
541
710
|
});
|
|
542
711
|
|
|
712
|
+
logGeneratedAtHeader(response, method, path);
|
|
713
|
+
|
|
543
714
|
// Handle FontNotFoundException
|
|
544
715
|
if (response.status === 404) {
|
|
545
716
|
try {
|
|
@@ -576,13 +747,43 @@ export class PDFDancer {
|
|
|
576
747
|
* Searches for PDF objects matching the specified criteria.
|
|
577
748
|
* This method provides flexible search capabilities across all PDF content,
|
|
578
749
|
* allowing filtering by object type and position constraints.
|
|
750
|
+
*
|
|
751
|
+
* Now uses snapshot caching for better performance.
|
|
579
752
|
*/
|
|
580
753
|
private async find(objectType?: ObjectType, position?: Position): Promise<ObjectRef[]> {
|
|
581
|
-
|
|
582
|
-
|
|
754
|
+
// Determine if we should use snapshot or fall back to HTTP
|
|
755
|
+
// For paths with coordinates, we need to use HTTP (backend requirement)
|
|
756
|
+
const isPathWithCoordinates = objectType === ObjectType.PATH &&
|
|
757
|
+
position?.shape === ShapeType.POINT;
|
|
758
|
+
|
|
759
|
+
if (isPathWithCoordinates) {
|
|
760
|
+
// Fall back to HTTP for path coordinate queries
|
|
761
|
+
const requestData = new FindRequest(objectType, position).toDict();
|
|
762
|
+
const response = await this._makeRequest('POST', '/pdf/find', requestData);
|
|
763
|
+
const objectsData = await response.json() as any[];
|
|
764
|
+
return objectsData.map((objData: any) => this._parseObjectRef(objData));
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Use snapshot-based search
|
|
768
|
+
let elements: ObjectRef[];
|
|
769
|
+
|
|
770
|
+
if (position?.pageIndex !== undefined) {
|
|
771
|
+
// Page-specific query - use page snapshot
|
|
772
|
+
const pageSnapshot = await this._getOrFetchPageSnapshot(position.pageIndex);
|
|
773
|
+
elements = pageSnapshot.elements;
|
|
774
|
+
} else {
|
|
775
|
+
// Document-wide query - use document snapshot
|
|
776
|
+
const docSnapshot = await this._getOrFetchDocumentSnapshot();
|
|
777
|
+
elements = docSnapshot.getAllElements();
|
|
778
|
+
}
|
|
583
779
|
|
|
584
|
-
|
|
585
|
-
|
|
780
|
+
// Filter by object type
|
|
781
|
+
if (objectType) {
|
|
782
|
+
elements = elements.filter(el => el.type === objectType);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Apply position-based filtering
|
|
786
|
+
return this._filterByPosition(elements, position);
|
|
586
787
|
}
|
|
587
788
|
|
|
588
789
|
/**
|
|
@@ -643,43 +844,86 @@ export class PDFDancer {
|
|
|
643
844
|
/**
|
|
644
845
|
* Searches for form fields at the specified position.
|
|
645
846
|
* Returns FormFieldRef objects with name and value properties.
|
|
847
|
+
*
|
|
848
|
+
* Now uses snapshot caching for better performance.
|
|
646
849
|
*/
|
|
647
850
|
private async findFormFields(position?: Position): Promise<FormFieldRef[]> {
|
|
648
|
-
|
|
649
|
-
|
|
851
|
+
// Use snapshot-based search
|
|
852
|
+
let elements: ObjectRef[];
|
|
853
|
+
|
|
854
|
+
if (position?.pageIndex !== undefined) {
|
|
855
|
+
// Page-specific query - use page snapshot
|
|
856
|
+
const pageSnapshot = await this._getOrFetchPageSnapshot(position.pageIndex);
|
|
857
|
+
elements = pageSnapshot.elements;
|
|
858
|
+
} else {
|
|
859
|
+
// Document-wide query - use document snapshot
|
|
860
|
+
const docSnapshot = await this._getOrFetchDocumentSnapshot();
|
|
861
|
+
elements = docSnapshot.getAllElements();
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// Filter by form field types (FORM_FIELD, TEXT_FIELD, CHECKBOX, RADIO_BUTTON)
|
|
865
|
+
const formFieldTypes = [
|
|
866
|
+
ObjectType.FORM_FIELD,
|
|
867
|
+
ObjectType.TEXT_FIELD,
|
|
868
|
+
ObjectType.CHECKBOX,
|
|
869
|
+
ObjectType.RADIO_BUTTON
|
|
870
|
+
];
|
|
871
|
+
const formFields = elements.filter(el => formFieldTypes.includes(el.type)) as FormFieldRef[];
|
|
650
872
|
|
|
651
|
-
|
|
652
|
-
return
|
|
873
|
+
// Apply position-based filtering
|
|
874
|
+
return this._filterFormFieldsByPosition(formFields, position);
|
|
653
875
|
}
|
|
654
876
|
|
|
655
877
|
// Page Operations
|
|
656
878
|
|
|
657
879
|
/**
|
|
658
880
|
* Retrieves references to all pages in the PDF document.
|
|
881
|
+
* Now uses snapshot caching to avoid HTTP requests.
|
|
659
882
|
*/
|
|
660
883
|
private async getPages(): Promise<PageRef[]> {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
884
|
+
// Check if we have cached pages
|
|
885
|
+
if (this._pagesCache) {
|
|
886
|
+
return this._pagesCache;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// Try to get from document snapshot cache first
|
|
890
|
+
if (this._documentSnapshotCache) {
|
|
891
|
+
this._pagesCache = this._documentSnapshotCache.pages.map(p => p.pageRef);
|
|
892
|
+
return this._pagesCache;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// Fetch document snapshot to get pages (this will cache it)
|
|
896
|
+
const docSnapshot = await this._getOrFetchDocumentSnapshot();
|
|
897
|
+
this._pagesCache = docSnapshot.pages.map(p => p.pageRef);
|
|
898
|
+
return this._pagesCache;
|
|
664
899
|
}
|
|
665
900
|
|
|
666
901
|
/**
|
|
667
902
|
* Retrieves a reference to a specific page by its page index.
|
|
903
|
+
* Now uses snapshot caching to avoid HTTP requests.
|
|
668
904
|
*/
|
|
669
905
|
private async _getPage(pageIndex: number): Promise<PageRef | null> {
|
|
670
906
|
if (pageIndex < 0) {
|
|
671
907
|
throw new ValidationException(`Page index must be >= 0, got ${pageIndex}`);
|
|
672
908
|
}
|
|
673
909
|
|
|
674
|
-
|
|
675
|
-
|
|
910
|
+
// Try page snapshot cache first
|
|
911
|
+
if (this._pageSnapshotCache.has(pageIndex)) {
|
|
912
|
+
return this._pageSnapshotCache.get(pageIndex)!.pageRef;
|
|
913
|
+
}
|
|
676
914
|
|
|
677
|
-
|
|
678
|
-
if (
|
|
679
|
-
|
|
915
|
+
// Try document snapshot cache
|
|
916
|
+
if (this._documentSnapshotCache) {
|
|
917
|
+
const pageSnapshot = this._documentSnapshotCache.getPageSnapshot(pageIndex);
|
|
918
|
+
if (pageSnapshot) {
|
|
919
|
+
return pageSnapshot.pageRef;
|
|
920
|
+
}
|
|
680
921
|
}
|
|
681
922
|
|
|
682
|
-
|
|
923
|
+
// Fetch document snapshot to get page (this will cache it)
|
|
924
|
+
const docSnapshot = await this._getOrFetchDocumentSnapshot();
|
|
925
|
+
const pageSnapshot = docSnapshot.getPageSnapshot(pageIndex);
|
|
926
|
+
return pageSnapshot?.pageRef ?? null;
|
|
683
927
|
}
|
|
684
928
|
|
|
685
929
|
/**
|
|
@@ -700,6 +944,9 @@ export class PDFDancer {
|
|
|
700
944
|
throw new HttpClientException(`Failed to move page from ${pageIndex} to ${targetPageIndex}`, response);
|
|
701
945
|
}
|
|
702
946
|
|
|
947
|
+
// Invalidate cache after mutation
|
|
948
|
+
this._invalidateCache();
|
|
949
|
+
|
|
703
950
|
// Fetch the page again at its new position for up-to-date metadata
|
|
704
951
|
return await this._requirePageRef(targetPageIndex);
|
|
705
952
|
}
|
|
@@ -711,7 +958,12 @@ export class PDFDancer {
|
|
|
711
958
|
this._validatePageIndex(pageIndex, 'pageIndex');
|
|
712
959
|
|
|
713
960
|
const pageRef = await this._requirePageRef(pageIndex);
|
|
714
|
-
|
|
961
|
+
const result = await this._deletePage(pageRef);
|
|
962
|
+
|
|
963
|
+
// Invalidate cache after mutation
|
|
964
|
+
this._invalidateCache();
|
|
965
|
+
|
|
966
|
+
return result;
|
|
715
967
|
}
|
|
716
968
|
|
|
717
969
|
private _validatePageIndex(pageIndex: number, fieldName: string): void {
|
|
@@ -744,6 +996,186 @@ export class PDFDancer {
|
|
|
744
996
|
return await response.json() as boolean;
|
|
745
997
|
}
|
|
746
998
|
|
|
999
|
+
// Snapshot Operations
|
|
1000
|
+
|
|
1001
|
+
/**
|
|
1002
|
+
* Gets a snapshot of the entire PDF document.
|
|
1003
|
+
* Returns page count, fonts, and snapshots of all pages with their elements.
|
|
1004
|
+
*
|
|
1005
|
+
* @param types Optional array of ObjectType to filter elements by type
|
|
1006
|
+
* @returns DocumentSnapshot containing all document information
|
|
1007
|
+
*/
|
|
1008
|
+
async getDocumentSnapshot(types?: ObjectType[]): Promise<DocumentSnapshot> {
|
|
1009
|
+
const params: Record<string, string> = {};
|
|
1010
|
+
if (types && types.length > 0) {
|
|
1011
|
+
params.types = types.join(',');
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
const response = await this._makeRequest('GET', '/pdf/document/snapshot', undefined, params);
|
|
1015
|
+
const data = await response.json() as any;
|
|
1016
|
+
|
|
1017
|
+
return this._parseDocumentSnapshot(data);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* Gets a snapshot of a specific page.
|
|
1022
|
+
* Returns the page reference and all elements on that page.
|
|
1023
|
+
*
|
|
1024
|
+
* @param pageIndex Zero-based page index
|
|
1025
|
+
* @param types Optional array of ObjectType to filter elements by type
|
|
1026
|
+
* @returns PageSnapshot containing page information and elements
|
|
1027
|
+
*/
|
|
1028
|
+
async getPageSnapshot(pageIndex: number, types?: ObjectType[]): Promise<PageSnapshot> {
|
|
1029
|
+
this._validatePageIndex(pageIndex, 'pageIndex');
|
|
1030
|
+
|
|
1031
|
+
const params: Record<string, string> = {};
|
|
1032
|
+
if (types && types.length > 0) {
|
|
1033
|
+
params.types = types.join(',');
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
const response = await this._makeRequest('GET', `/pdf/page/${pageIndex}/snapshot`, undefined, params);
|
|
1037
|
+
const data = await response.json() as any;
|
|
1038
|
+
|
|
1039
|
+
return this._parsePageSnapshot(data);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// Cache Management
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* Gets a page snapshot from cache or fetches it.
|
|
1046
|
+
* First checks page cache, then document cache, then fetches from server.
|
|
1047
|
+
*/
|
|
1048
|
+
private async _getOrFetchPageSnapshot(pageIndex: number): Promise<PageSnapshot> {
|
|
1049
|
+
// Check page cache first
|
|
1050
|
+
if (this._pageSnapshotCache.has(pageIndex)) {
|
|
1051
|
+
return this._pageSnapshotCache.get(pageIndex)!;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// Check if we have document snapshot and can extract the page
|
|
1055
|
+
if (this._documentSnapshotCache) {
|
|
1056
|
+
const pageSnapshot = this._documentSnapshotCache.getPageSnapshot(pageIndex);
|
|
1057
|
+
if (pageSnapshot) {
|
|
1058
|
+
// Cache it for future use
|
|
1059
|
+
this._pageSnapshotCache.set(pageIndex, pageSnapshot);
|
|
1060
|
+
return pageSnapshot;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// Fetch page snapshot from server
|
|
1065
|
+
const pageSnapshot = await this.getPageSnapshot(pageIndex);
|
|
1066
|
+
this._pageSnapshotCache.set(pageIndex, pageSnapshot);
|
|
1067
|
+
return pageSnapshot;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
/**
|
|
1071
|
+
* Gets the document snapshot from cache or fetches it.
|
|
1072
|
+
*/
|
|
1073
|
+
private async _getOrFetchDocumentSnapshot(): Promise<DocumentSnapshot> {
|
|
1074
|
+
if (!this._documentSnapshotCache) {
|
|
1075
|
+
this._documentSnapshotCache = await this.getDocumentSnapshot();
|
|
1076
|
+
}
|
|
1077
|
+
return this._documentSnapshotCache;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* Invalidates all snapshot caches.
|
|
1082
|
+
* Called after any mutation operation.
|
|
1083
|
+
*/
|
|
1084
|
+
private _invalidateCache(): void {
|
|
1085
|
+
this._documentSnapshotCache = null;
|
|
1086
|
+
this._pageSnapshotCache.clear();
|
|
1087
|
+
this._pagesCache = null;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* Filters snapshot elements by Position criteria.
|
|
1092
|
+
* Handles coordinates, text matching, and field name filtering.
|
|
1093
|
+
*/
|
|
1094
|
+
private _filterByPosition(elements: ObjectRef[], position?: Position): ObjectRef[] {
|
|
1095
|
+
if (!position) {
|
|
1096
|
+
return elements;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
let filtered = elements;
|
|
1100
|
+
|
|
1101
|
+
// Filter by page index
|
|
1102
|
+
if (position.pageIndex !== undefined) {
|
|
1103
|
+
filtered = filtered.filter(el => el.position.pageIndex === position.pageIndex);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// Filter by coordinates (point containment with tolerance)
|
|
1107
|
+
if (position.boundingRect && position.shape === ShapeType.POINT) {
|
|
1108
|
+
const x = position.boundingRect.x;
|
|
1109
|
+
const y = position.boundingRect.y;
|
|
1110
|
+
const tolerance = position.tolerance || 0;
|
|
1111
|
+
filtered = filtered.filter(el => {
|
|
1112
|
+
const rect = el.position.boundingRect;
|
|
1113
|
+
if (!rect) return false;
|
|
1114
|
+
return x >= rect.x - tolerance && x <= rect.x + rect.width + tolerance &&
|
|
1115
|
+
y >= rect.y - tolerance && y <= rect.y + rect.height + tolerance;
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// Filter by text starts with
|
|
1120
|
+
if (position.textStartsWith && filtered.length > 0) {
|
|
1121
|
+
const textLower = position.textStartsWith.toLowerCase();
|
|
1122
|
+
filtered = filtered.filter(el => {
|
|
1123
|
+
const textObj = el as TextObjectRef;
|
|
1124
|
+
return textObj.text && textObj.text.toLowerCase().startsWith(textLower);
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// Filter by text pattern (regex)
|
|
1129
|
+
if (position.textPattern && filtered.length > 0) {
|
|
1130
|
+
const regex = this._compileTextPattern(position.textPattern);
|
|
1131
|
+
filtered = filtered.filter(el => {
|
|
1132
|
+
const textObj = el as TextObjectRef;
|
|
1133
|
+
return textObj.text && regex.test(textObj.text);
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// Filter by name (for form fields)
|
|
1138
|
+
if (position.name && filtered.length > 0) {
|
|
1139
|
+
filtered = filtered.filter(el => {
|
|
1140
|
+
const formField = el as FormFieldRef;
|
|
1141
|
+
return formField.name === position.name;
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
return filtered;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Filters FormFieldRef elements by Position criteria.
|
|
1150
|
+
*/
|
|
1151
|
+
private _filterFormFieldsByPosition(elements: FormFieldRef[], position?: Position): FormFieldRef[] {
|
|
1152
|
+
return this._filterByPosition(elements as ObjectRef[], position) as FormFieldRef[];
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
private _compileTextPattern(pattern: string): RegExp {
|
|
1156
|
+
try {
|
|
1157
|
+
return new RegExp(pattern);
|
|
1158
|
+
} catch {
|
|
1159
|
+
const inlineMatch = pattern.match(/^\(\?([a-z]+)\)/i);
|
|
1160
|
+
if (inlineMatch) {
|
|
1161
|
+
const supportedFlags = inlineMatch[1]
|
|
1162
|
+
.toLowerCase()
|
|
1163
|
+
.split('')
|
|
1164
|
+
.filter(flag => 'gimsuy'.includes(flag));
|
|
1165
|
+
const flags = Array.from(new Set(supportedFlags)).join('');
|
|
1166
|
+
const source = pattern.slice(inlineMatch[0].length);
|
|
1167
|
+
try {
|
|
1168
|
+
return new RegExp(source, flags);
|
|
1169
|
+
} catch {
|
|
1170
|
+
// fall through to literal fallback
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1175
|
+
return new RegExp(escaped);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
|
|
747
1179
|
// Manipulation Operations
|
|
748
1180
|
|
|
749
1181
|
/**
|
|
@@ -756,7 +1188,12 @@ export class PDFDancer {
|
|
|
756
1188
|
|
|
757
1189
|
const requestData = new DeleteRequest(objectRef).toDict();
|
|
758
1190
|
const response = await this._makeRequest('DELETE', '/pdf/delete', requestData);
|
|
759
|
-
|
|
1191
|
+
const result = await response.json() as boolean;
|
|
1192
|
+
|
|
1193
|
+
// Invalidate cache after mutation
|
|
1194
|
+
this._invalidateCache();
|
|
1195
|
+
|
|
1196
|
+
return result;
|
|
760
1197
|
}
|
|
761
1198
|
|
|
762
1199
|
/**
|
|
@@ -772,7 +1209,12 @@ export class PDFDancer {
|
|
|
772
1209
|
|
|
773
1210
|
const requestData = new MoveRequest(objectRef, position).toDict();
|
|
774
1211
|
const response = await this._makeRequest('PUT', '/pdf/move', requestData);
|
|
775
|
-
|
|
1212
|
+
const result = await response.json() as boolean;
|
|
1213
|
+
|
|
1214
|
+
// Invalidate cache after mutation
|
|
1215
|
+
this._invalidateCache();
|
|
1216
|
+
|
|
1217
|
+
return result;
|
|
776
1218
|
}
|
|
777
1219
|
|
|
778
1220
|
/**
|
|
@@ -785,7 +1227,12 @@ export class PDFDancer {
|
|
|
785
1227
|
|
|
786
1228
|
const requestData = new ChangeFormFieldRequest(formFieldRef, newValue).toDict();
|
|
787
1229
|
const response = await this._makeRequest('PUT', '/pdf/modify/formField', requestData);
|
|
788
|
-
|
|
1230
|
+
const result = await response.json() as boolean;
|
|
1231
|
+
|
|
1232
|
+
// Invalidate cache after mutation
|
|
1233
|
+
this._invalidateCache();
|
|
1234
|
+
|
|
1235
|
+
return result;
|
|
789
1236
|
}
|
|
790
1237
|
|
|
791
1238
|
// Add Operations
|
|
@@ -829,13 +1276,33 @@ export class PDFDancer {
|
|
|
829
1276
|
return this._addObject(paragraph);
|
|
830
1277
|
}
|
|
831
1278
|
|
|
1279
|
+
/**
|
|
1280
|
+
* Adds a page to the PDF document.
|
|
1281
|
+
*/
|
|
1282
|
+
private async addPage(request?: AddPageRequest | null): Promise<PageRef> {
|
|
1283
|
+
const payload = request ? request.toDict() : {};
|
|
1284
|
+
const data = Object.keys(payload).length > 0 ? payload : undefined;
|
|
1285
|
+
const response = await this._makeRequest('POST', '/pdf/page/add', data);
|
|
1286
|
+
const result = await response.json();
|
|
1287
|
+
const pageRef = this._parsePageRef(result);
|
|
1288
|
+
|
|
1289
|
+
this._invalidateCache();
|
|
1290
|
+
|
|
1291
|
+
return pageRef;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
832
1294
|
/**
|
|
833
1295
|
* Internal method to add any PDF object.
|
|
834
1296
|
*/
|
|
835
1297
|
private async _addObject(pdfObject: Image | Paragraph): Promise<boolean> {
|
|
836
1298
|
const requestData = new AddRequest(pdfObject).toDict();
|
|
837
1299
|
const response = await this._makeRequest('POST', '/pdf/add', requestData);
|
|
838
|
-
|
|
1300
|
+
const result = await response.json() as boolean;
|
|
1301
|
+
|
|
1302
|
+
// Invalidate cache after mutation
|
|
1303
|
+
this._invalidateCache();
|
|
1304
|
+
|
|
1305
|
+
return result;
|
|
839
1306
|
}
|
|
840
1307
|
|
|
841
1308
|
// Modify Operations
|
|
@@ -843,7 +1310,7 @@ export class PDFDancer {
|
|
|
843
1310
|
/**
|
|
844
1311
|
* Modifies a paragraph object or its text content.
|
|
845
1312
|
*/
|
|
846
|
-
private async modifyParagraph(objectRef: ObjectRef, newParagraph: Paragraph | string): Promise<CommandResult> {
|
|
1313
|
+
private async modifyParagraph(objectRef: ObjectRef, newParagraph: Paragraph | string | null): Promise<CommandResult> {
|
|
847
1314
|
if (!objectRef) {
|
|
848
1315
|
throw new ValidationException("Object reference cannot be null");
|
|
849
1316
|
}
|
|
@@ -851,17 +1318,23 @@ export class PDFDancer {
|
|
|
851
1318
|
return CommandResult.empty("ModifyParagraph", objectRef.internalId);
|
|
852
1319
|
}
|
|
853
1320
|
|
|
1321
|
+
let result: CommandResult;
|
|
854
1322
|
if (typeof newParagraph === 'string') {
|
|
855
1323
|
// Text modification - returns CommandResult
|
|
856
1324
|
const requestData = new ModifyTextRequest(objectRef, newParagraph).toDict();
|
|
857
1325
|
const response = await this._makeRequest('PUT', '/pdf/text/paragraph', requestData);
|
|
858
|
-
|
|
1326
|
+
result = CommandResult.fromDict(await response.json());
|
|
859
1327
|
} else {
|
|
860
1328
|
// Object modification
|
|
861
1329
|
const requestData = new ModifyRequest(objectRef, newParagraph).toDict();
|
|
862
1330
|
const response = await this._makeRequest('PUT', '/pdf/modify', requestData);
|
|
863
|
-
|
|
1331
|
+
result = CommandResult.fromDict(await response.json());
|
|
864
1332
|
}
|
|
1333
|
+
|
|
1334
|
+
// Invalidate cache after mutation
|
|
1335
|
+
this._invalidateCache();
|
|
1336
|
+
|
|
1337
|
+
return result;
|
|
865
1338
|
}
|
|
866
1339
|
|
|
867
1340
|
/**
|
|
@@ -877,7 +1350,12 @@ export class PDFDancer {
|
|
|
877
1350
|
|
|
878
1351
|
const requestData = new ModifyTextRequest(objectRef, newText).toDict();
|
|
879
1352
|
const response = await this._makeRequest('PUT', '/pdf/text/line', requestData);
|
|
880
|
-
|
|
1353
|
+
const result = CommandResult.fromDict(await response.json());
|
|
1354
|
+
|
|
1355
|
+
// Invalidate cache after mutation
|
|
1356
|
+
this._invalidateCache();
|
|
1357
|
+
|
|
1358
|
+
return result;
|
|
881
1359
|
}
|
|
882
1360
|
|
|
883
1361
|
// Font Operations
|
|
@@ -939,16 +1417,22 @@ export class PDFDancer {
|
|
|
939
1417
|
const blob = new Blob([fontData.buffer as ArrayBuffer], {type: 'font/ttf'});
|
|
940
1418
|
formData.append('ttfFile', blob, filename);
|
|
941
1419
|
|
|
1420
|
+
const fingerprint = await this._getFingerprint();
|
|
1421
|
+
|
|
942
1422
|
const response = await fetch(this._buildUrl('/font/register'), {
|
|
943
1423
|
method: 'POST',
|
|
944
1424
|
headers: {
|
|
945
1425
|
'Authorization': `Bearer ${this._token}`,
|
|
946
|
-
'X-Session-Id': this._sessionId
|
|
1426
|
+
'X-Session-Id': this._sessionId,
|
|
1427
|
+
'X-Generated-At': generateTimestamp(),
|
|
1428
|
+
'X-Fingerprint': fingerprint
|
|
947
1429
|
},
|
|
948
1430
|
body: formData,
|
|
949
1431
|
signal: AbortSignal.timeout(30000)
|
|
950
1432
|
});
|
|
951
1433
|
|
|
1434
|
+
logGeneratedAtHeader(response, 'POST', '/font/register');
|
|
1435
|
+
|
|
952
1436
|
if (!response.ok) {
|
|
953
1437
|
const errorMessage = await this._extractErrorMessage(response);
|
|
954
1438
|
throw new HttpClientException(`Font registration failed: ${errorMessage}`, response);
|
|
@@ -1011,6 +1495,17 @@ export class PDFDancer {
|
|
|
1011
1495
|
return this._parseTextObjectRef(objData);
|
|
1012
1496
|
}
|
|
1013
1497
|
|
|
1498
|
+
// Check if this is a form field type
|
|
1499
|
+
const formFieldTypes = [
|
|
1500
|
+
ObjectType.FORM_FIELD,
|
|
1501
|
+
ObjectType.TEXT_FIELD,
|
|
1502
|
+
ObjectType.CHECKBOX,
|
|
1503
|
+
ObjectType.RADIO_BUTTON
|
|
1504
|
+
];
|
|
1505
|
+
if (formFieldTypes.includes(objectType)) {
|
|
1506
|
+
return this._parseFormFieldRef(objData);
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1014
1509
|
return new ObjectRef(
|
|
1015
1510
|
objData.internalId,
|
|
1016
1511
|
position,
|
|
@@ -1021,6 +1516,7 @@ export class PDFDancer {
|
|
|
1021
1516
|
private _isTextObjectData(objData: any, objectType: ObjectType): boolean {
|
|
1022
1517
|
return objectType === ObjectType.PARAGRAPH ||
|
|
1023
1518
|
objectType === ObjectType.TEXT_LINE ||
|
|
1519
|
+
objectType === ObjectType.TEXT_ELEMENT ||
|
|
1024
1520
|
typeof objData.text === 'string' ||
|
|
1025
1521
|
typeof objData.fontName === 'string' ||
|
|
1026
1522
|
Array.isArray(objData.children);
|
|
@@ -1074,10 +1570,15 @@ export class PDFDancer {
|
|
|
1074
1570
|
);
|
|
1075
1571
|
|
|
1076
1572
|
if (Array.isArray(objData.children) && objData.children.length > 0) {
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1573
|
+
try {
|
|
1574
|
+
textObject.children = objData.children.map((childData: any, index: number) => {
|
|
1575
|
+
const childFallbackId = `${internalId || 'child'}-${index}`;
|
|
1576
|
+
return this._parseTextObjectRef(childData, childFallbackId);
|
|
1577
|
+
});
|
|
1578
|
+
} catch (error) {
|
|
1579
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1580
|
+
console.error(`Failed to parse children of ${internalId}: ${message}`);
|
|
1581
|
+
}
|
|
1081
1582
|
}
|
|
1082
1583
|
|
|
1083
1584
|
return textObject;
|
|
@@ -1196,6 +1697,54 @@ export class PDFDancer {
|
|
|
1196
1697
|
return position;
|
|
1197
1698
|
}
|
|
1198
1699
|
|
|
1700
|
+
/**
|
|
1701
|
+
* Parse JSON data into DocumentSnapshot instance.
|
|
1702
|
+
*/
|
|
1703
|
+
private _parseDocumentSnapshot(data: any): DocumentSnapshot {
|
|
1704
|
+
const pageCount = typeof data.pageCount === 'number' ? data.pageCount : 0;
|
|
1705
|
+
|
|
1706
|
+
// Parse fonts
|
|
1707
|
+
const fonts: FontRecommendation[] = [];
|
|
1708
|
+
if (Array.isArray(data.fonts)) {
|
|
1709
|
+
for (const fontData of data.fonts) {
|
|
1710
|
+
if (fontData && typeof fontData === 'object') {
|
|
1711
|
+
const fontName = fontData.fontName || '';
|
|
1712
|
+
const fontType = fontData.fontType as FontType || FontType.SYSTEM;
|
|
1713
|
+
const similarityScore = typeof fontData.similarityScore === 'number' ? fontData.similarityScore : 0;
|
|
1714
|
+
fonts.push(new FontRecommendation(fontName, fontType, similarityScore));
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
// Parse pages
|
|
1720
|
+
const pages: PageSnapshot[] = [];
|
|
1721
|
+
if (Array.isArray(data.pages)) {
|
|
1722
|
+
for (const pageData of data.pages) {
|
|
1723
|
+
pages.push(this._parsePageSnapshot(pageData));
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
return new DocumentSnapshot(pageCount, fonts, pages);
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
/**
|
|
1731
|
+
* Parse JSON data into PageSnapshot instance.
|
|
1732
|
+
*/
|
|
1733
|
+
private _parsePageSnapshot(data: any): PageSnapshot {
|
|
1734
|
+
// Parse page reference
|
|
1735
|
+
const pageRef = this._parsePageRef(data.pageRef || {});
|
|
1736
|
+
|
|
1737
|
+
// Parse elements
|
|
1738
|
+
const elements: ObjectRef[] = [];
|
|
1739
|
+
if (Array.isArray(data.elements)) {
|
|
1740
|
+
for (const elementData of data.elements) {
|
|
1741
|
+
elements.push(this._parseObjectRef(elementData));
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
return new PageSnapshot(pageRef, elements);
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1199
1748
|
// Builder Pattern Support
|
|
1200
1749
|
|
|
1201
1750
|
|
|
@@ -1211,8 +1760,16 @@ export class PDFDancer {
|
|
|
1211
1760
|
return objectRefs.map(ref => ImageObject.fromRef(this, ref));
|
|
1212
1761
|
}
|
|
1213
1762
|
|
|
1214
|
-
newImage() {
|
|
1215
|
-
return new ImageBuilder(this);
|
|
1763
|
+
newImage(pageIndex?: number) {
|
|
1764
|
+
return new ImageBuilder(this, pageIndex);
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
newParagraph(pageIndex?: number) {
|
|
1768
|
+
return new ParagraphBuilder(this, pageIndex);
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
newPage() {
|
|
1772
|
+
return new PageBuilder(this);
|
|
1216
1773
|
}
|
|
1217
1774
|
|
|
1218
1775
|
page(pageIndex: number) {
|
|
@@ -1231,10 +1788,28 @@ export class PDFDancer {
|
|
|
1231
1788
|
return objectRefs.map(ref => FormFieldObject.fromRef(this, ref));
|
|
1232
1789
|
}
|
|
1233
1790
|
|
|
1791
|
+
async selectElements(types?: ObjectType[]) {
|
|
1792
|
+
const snapshot = await this.getDocumentSnapshot(types);
|
|
1793
|
+
const elements: ObjectRef[] = [];
|
|
1794
|
+
for (const pageSnapshot of snapshot.pages) {
|
|
1795
|
+
elements.push(...pageSnapshot.elements);
|
|
1796
|
+
}
|
|
1797
|
+
return elements;
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1234
1800
|
async selectParagraphs() {
|
|
1235
1801
|
return this.toParagraphObjects(await this.findParagraphs());
|
|
1236
1802
|
}
|
|
1237
1803
|
|
|
1804
|
+
async selectParagraphsMatching(pattern: string) {
|
|
1805
|
+
if (!pattern) {
|
|
1806
|
+
throw new ValidationException('Pattern cannot be empty');
|
|
1807
|
+
}
|
|
1808
|
+
const position = new Position();
|
|
1809
|
+
position.textPattern = pattern;
|
|
1810
|
+
return this.toParagraphObjects(await this.findParagraphs(position));
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1238
1813
|
private toParagraphObjects(objectRefs: TextObjectRef[]) {
|
|
1239
1814
|
return objectRefs.map(ref => ParagraphObject.fromRef(this, ref));
|
|
1240
1815
|
}
|
|
@@ -1243,7 +1818,11 @@ export class PDFDancer {
|
|
|
1243
1818
|
return objectRefs.map(ref => TextLineObject.fromRef(this, ref));
|
|
1244
1819
|
}
|
|
1245
1820
|
|
|
1246
|
-
async
|
|
1821
|
+
async selectTextLines() {
|
|
1247
1822
|
return this.toTextLineObjects(await this.findTextLines());
|
|
1248
1823
|
}
|
|
1824
|
+
|
|
1825
|
+
async selectLines() {
|
|
1826
|
+
return this.selectTextLines();
|
|
1827
|
+
}
|
|
1249
1828
|
}
|