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/dist/pdfdancer_v1.js
CHANGED
|
@@ -12,10 +12,97 @@ exports.PDFDancer = void 0;
|
|
|
12
12
|
const exceptions_1 = require("./exceptions");
|
|
13
13
|
const models_1 = require("./models");
|
|
14
14
|
const paragraph_builder_1 = require("./paragraph-builder");
|
|
15
|
+
const page_builder_1 = require("./page-builder");
|
|
15
16
|
const types_1 = require("./types");
|
|
16
17
|
const image_builder_1 = require("./image-builder");
|
|
18
|
+
const fingerprint_1 = require("./fingerprint");
|
|
17
19
|
const fs_1 = __importDefault(require("fs"));
|
|
18
20
|
const node_path_1 = __importDefault(require("node:path"));
|
|
21
|
+
const DEFAULT_TOLERANCE = 0.01;
|
|
22
|
+
// Debug flag - set to true to enable timing logs
|
|
23
|
+
const DEBUG = (process.env.PDFDANCER_CLIENT_DEBUG ?? '').toLowerCase() === 'true' ||
|
|
24
|
+
(process.env.PDFDANCER_CLIENT_DEBUG ?? '') === '1' ||
|
|
25
|
+
(process.env.PDFDANCER_CLIENT_DEBUG ?? '').toLowerCase() === 'yes';
|
|
26
|
+
/**
|
|
27
|
+
* Generate a timestamp string in the format expected by the API.
|
|
28
|
+
* Format: YYYY-MM-DDTHH:MM:SS.ffffffZ (with microseconds)
|
|
29
|
+
*/
|
|
30
|
+
function generateTimestamp() {
|
|
31
|
+
const now = new Date();
|
|
32
|
+
const year = now.getUTCFullYear();
|
|
33
|
+
const month = String(now.getUTCMonth() + 1).padStart(2, '0');
|
|
34
|
+
const day = String(now.getUTCDate()).padStart(2, '0');
|
|
35
|
+
const hours = String(now.getUTCHours()).padStart(2, '0');
|
|
36
|
+
const minutes = String(now.getUTCMinutes()).padStart(2, '0');
|
|
37
|
+
const seconds = String(now.getUTCSeconds()).padStart(2, '0');
|
|
38
|
+
const milliseconds = String(now.getUTCMilliseconds()).padStart(3, '0');
|
|
39
|
+
// Add 3 more zeros for microseconds (JavaScript doesn't have microsecond precision)
|
|
40
|
+
const microseconds = milliseconds + '000';
|
|
41
|
+
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${microseconds}Z`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Parse timestamp string, handling both microseconds and nanoseconds precision.
|
|
45
|
+
* @param timestampStr Timestamp string in format YYYY-MM-DDTHH:MM:SS.fffffffZ (with 6 or 9 fractional digits)
|
|
46
|
+
*/
|
|
47
|
+
function parseTimestamp(timestampStr) {
|
|
48
|
+
// Remove the 'Z' suffix
|
|
49
|
+
let ts = timestampStr.replace(/Z$/, '');
|
|
50
|
+
// Handle nanoseconds (9 digits) by truncating to milliseconds (3 digits)
|
|
51
|
+
// JavaScript's Date only supports millisecond precision
|
|
52
|
+
if (ts.includes('.')) {
|
|
53
|
+
const [datePart, fracPart] = ts.split('.');
|
|
54
|
+
// Truncate to 3 digits (milliseconds)
|
|
55
|
+
const truncatedFrac = fracPart.substring(0, 3);
|
|
56
|
+
ts = `${datePart}.${truncatedFrac}`;
|
|
57
|
+
}
|
|
58
|
+
return new Date(ts + 'Z');
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Check for X-Generated-At and X-Received-At headers and log timing information if DEBUG=true.
|
|
62
|
+
*
|
|
63
|
+
* Expected timestamp formats:
|
|
64
|
+
* - 2025-10-24T08:49:39.161945Z (microseconds - 6 digits)
|
|
65
|
+
* - 2025-10-24T08:58:45.468131265Z (nanoseconds - 9 digits)
|
|
66
|
+
*/
|
|
67
|
+
function logGeneratedAtHeader(response, method, path) {
|
|
68
|
+
if (!DEBUG) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const generatedAt = response.headers.get('X-Generated-At');
|
|
72
|
+
const receivedAt = response.headers.get('X-Received-At');
|
|
73
|
+
if (generatedAt || receivedAt) {
|
|
74
|
+
try {
|
|
75
|
+
const logParts = [];
|
|
76
|
+
const currentTime = new Date();
|
|
77
|
+
// Parse and log X-Received-At
|
|
78
|
+
let receivedTime = null;
|
|
79
|
+
if (receivedAt) {
|
|
80
|
+
receivedTime = parseTimestamp(receivedAt);
|
|
81
|
+
const timeSinceReceived = (currentTime.getTime() - receivedTime.getTime()) / 1000;
|
|
82
|
+
logParts.push(`X-Received-At: ${receivedAt}, time since received on backend: ${timeSinceReceived.toFixed(3)}s`);
|
|
83
|
+
}
|
|
84
|
+
// Parse and log X-Generated-At
|
|
85
|
+
let generatedTime = null;
|
|
86
|
+
if (generatedAt) {
|
|
87
|
+
generatedTime = parseTimestamp(generatedAt);
|
|
88
|
+
const timeSinceGenerated = (currentTime.getTime() - generatedTime.getTime()) / 1000;
|
|
89
|
+
logParts.push(`X-Generated-At: ${generatedAt}, time since generated on backend: ${timeSinceGenerated.toFixed(3)}s`);
|
|
90
|
+
}
|
|
91
|
+
// Calculate processing time (X-Generated-At - X-Received-At)
|
|
92
|
+
if (receivedTime && generatedTime) {
|
|
93
|
+
const processingTime = (generatedTime.getTime() - receivedTime.getTime()) / 1000;
|
|
94
|
+
logParts.push(`processing time on backend: ${processingTime.toFixed(3)}s`);
|
|
95
|
+
}
|
|
96
|
+
if (logParts.length > 0) {
|
|
97
|
+
console.log(`${Date.now() / 1000}|${method} ${path} - ${logParts.join(', ')}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
102
|
+
console.log(`${Date.now() / 1000}|${method} ${path} - Header parse error: ${errorMessage}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
19
106
|
class PageClient {
|
|
20
107
|
constructor(client, pageIndex, pageRef) {
|
|
21
108
|
this.type = models_1.ObjectType.PAGE;
|
|
@@ -28,8 +115,8 @@ class PageClient {
|
|
|
28
115
|
// Cast to the internal interface to get access
|
|
29
116
|
this._internals = this._client;
|
|
30
117
|
}
|
|
31
|
-
async selectPathsAt(x, y) {
|
|
32
|
-
return this._internals.toPathObjects(await this._internals.findPaths(models_1.Position.atPageCoordinates(this._pageIndex, x, y)));
|
|
118
|
+
async selectPathsAt(x, y, tolerance = 0) {
|
|
119
|
+
return this._internals.toPathObjects(await this._internals.findPaths(models_1.Position.atPageCoordinates(this._pageIndex, x, y, tolerance)));
|
|
33
120
|
}
|
|
34
121
|
async selectPaths() {
|
|
35
122
|
return this._internals.toPathObjects(await this._internals.findPaths(models_1.Position.atPage(this._pageIndex)));
|
|
@@ -37,8 +124,8 @@ class PageClient {
|
|
|
37
124
|
async selectImages() {
|
|
38
125
|
return this._internals.toImageObjects(await this._internals._findImages(models_1.Position.atPage(this._pageIndex)));
|
|
39
126
|
}
|
|
40
|
-
async selectImagesAt(x, y) {
|
|
41
|
-
return this._internals.toImageObjects(await this._internals._findImages(models_1.Position.atPageCoordinates(this._pageIndex, x, y)));
|
|
127
|
+
async selectImagesAt(x, y, tolerance = 0) {
|
|
128
|
+
return this._internals.toImageObjects(await this._internals._findImages(models_1.Position.atPageCoordinates(this._pageIndex, x, y, tolerance)));
|
|
42
129
|
}
|
|
43
130
|
async delete() {
|
|
44
131
|
return this._client.deletePage(this._pageIndex);
|
|
@@ -56,14 +143,14 @@ class PageClient {
|
|
|
56
143
|
async selectForms() {
|
|
57
144
|
return this._internals.toFormXObjects(await this._internals.findFormXObjects(models_1.Position.atPage(this._pageIndex)));
|
|
58
145
|
}
|
|
59
|
-
async selectFormsAt(x, y) {
|
|
60
|
-
return this._internals.toFormXObjects(await this._internals.findFormXObjects(models_1.Position.atPageCoordinates(this._pageIndex, x, y)));
|
|
146
|
+
async selectFormsAt(x, y, tolerance = 0) {
|
|
147
|
+
return this._internals.toFormXObjects(await this._internals.findFormXObjects(models_1.Position.atPageCoordinates(this._pageIndex, x, y, tolerance)));
|
|
61
148
|
}
|
|
62
149
|
async selectFormFields() {
|
|
63
150
|
return this._internals.toFormFields(await this._internals.findFormFields(models_1.Position.atPage(this._pageIndex)));
|
|
64
151
|
}
|
|
65
|
-
async selectFormFieldsAt(x, y) {
|
|
66
|
-
return this._internals.toFormFields(await this._internals.findFormFields(models_1.Position.atPageCoordinates(this._pageIndex, x, y)));
|
|
152
|
+
async selectFormFieldsAt(x, y, tolerance = 0) {
|
|
153
|
+
return this._internals.toFormFields(await this._internals.findFormFields(models_1.Position.atPageCoordinates(this._pageIndex, x, y, tolerance)));
|
|
67
154
|
}
|
|
68
155
|
// noinspection JSUnusedGlobalSymbols
|
|
69
156
|
async selectFormFieldsByName(fieldName) {
|
|
@@ -74,6 +161,10 @@ class PageClient {
|
|
|
74
161
|
async selectParagraphs() {
|
|
75
162
|
return this._internals.toParagraphObjects(await this._internals.findParagraphs(models_1.Position.atPage(this._pageIndex)));
|
|
76
163
|
}
|
|
164
|
+
async selectElements(types) {
|
|
165
|
+
const snapshot = await this._client.getPageSnapshot(this._pageIndex, types);
|
|
166
|
+
return snapshot.elements;
|
|
167
|
+
}
|
|
77
168
|
async selectParagraphsStartingWith(text) {
|
|
78
169
|
let pos = models_1.Position.atPage(this._pageIndex);
|
|
79
170
|
pos.textStartsWith = text;
|
|
@@ -84,8 +175,8 @@ class PageClient {
|
|
|
84
175
|
pos.textPattern = pattern;
|
|
85
176
|
return this._internals.toParagraphObjects(await this._internals.findParagraphs(pos));
|
|
86
177
|
}
|
|
87
|
-
async selectParagraphsAt(x, y) {
|
|
88
|
-
return this._internals.toParagraphObjects(await this._internals.findParagraphs(models_1.Position.atPageCoordinates(this._pageIndex, x, y)));
|
|
178
|
+
async selectParagraphsAt(x, y, tolerance = DEFAULT_TOLERANCE) {
|
|
179
|
+
return this._internals.toParagraphObjects(await this._internals.findParagraphs(models_1.Position.atPageCoordinates(this._pageIndex, x, y, tolerance)));
|
|
89
180
|
}
|
|
90
181
|
async selectTextLinesStartingWith(text) {
|
|
91
182
|
let pos = models_1.Position.atPage(this._pageIndex);
|
|
@@ -95,8 +186,13 @@ class PageClient {
|
|
|
95
186
|
/**
|
|
96
187
|
* Creates a new ParagraphBuilder for fluent paragraph construction.
|
|
97
188
|
*/
|
|
98
|
-
newParagraph() {
|
|
99
|
-
|
|
189
|
+
newParagraph(pageIndex) {
|
|
190
|
+
const targetIndex = pageIndex ?? this.position.pageIndex;
|
|
191
|
+
return new paragraph_builder_1.ParagraphBuilder(this._client, targetIndex);
|
|
192
|
+
}
|
|
193
|
+
newImage(pageIndex) {
|
|
194
|
+
const targetIndex = pageIndex ?? this.position.pageIndex;
|
|
195
|
+
return new image_builder_1.ImageBuilder(this._client, targetIndex);
|
|
100
196
|
}
|
|
101
197
|
async selectTextLines() {
|
|
102
198
|
return this._internals.toTextLineObjects(await this._internals.findTextLines(models_1.Position.atPage(this._pageIndex)));
|
|
@@ -108,8 +204,15 @@ class PageClient {
|
|
|
108
204
|
return this._internals.toTextLineObjects(await this._internals.findTextLines(pos));
|
|
109
205
|
}
|
|
110
206
|
// noinspection JSUnusedGlobalSymbols
|
|
111
|
-
async selectTextLinesAt(x, y) {
|
|
112
|
-
return this._internals.toTextLineObjects(await this._internals.findTextLines(models_1.Position.atPageCoordinates(this._pageIndex, x, y)));
|
|
207
|
+
async selectTextLinesAt(x, y, tolerance = DEFAULT_TOLERANCE) {
|
|
208
|
+
return this._internals.toTextLineObjects(await this._internals.findTextLines(models_1.Position.atPageCoordinates(this._pageIndex, x, y, tolerance)));
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Gets a snapshot of this page, including all elements.
|
|
212
|
+
* Optionally filter by object types.
|
|
213
|
+
*/
|
|
214
|
+
async getSnapshot(types) {
|
|
215
|
+
return this._client.getPageSnapshot(this._pageIndex, types);
|
|
113
216
|
}
|
|
114
217
|
}
|
|
115
218
|
// noinspection ExceptionCaughtLocallyJS,JSUnusedLocalSymbols
|
|
@@ -127,6 +230,10 @@ class PDFDancer {
|
|
|
127
230
|
* a new session, and prepares the client for PDF manipulation operations.
|
|
128
231
|
*/
|
|
129
232
|
constructor(token, pdfData, baseUrl = null, readTimeout = 30000) {
|
|
233
|
+
// Snapshot caches for optimizing find operations
|
|
234
|
+
this._documentSnapshotCache = null;
|
|
235
|
+
this._pageSnapshotCache = new Map();
|
|
236
|
+
this._pagesCache = null;
|
|
130
237
|
if (!token || !token.trim()) {
|
|
131
238
|
throw new exceptions_1.ValidationException("Authentication token cannot be null or empty");
|
|
132
239
|
}
|
|
@@ -146,6 +253,10 @@ class PDFDancer {
|
|
|
146
253
|
this._readTimeout = readTimeout;
|
|
147
254
|
// Process PDF data with validation
|
|
148
255
|
this._pdfBytes = this._processPdfData(pdfData);
|
|
256
|
+
// Initialize caches
|
|
257
|
+
this._documentSnapshotCache = null;
|
|
258
|
+
this._pageSnapshotCache = new Map();
|
|
259
|
+
this._pagesCache = null;
|
|
149
260
|
}
|
|
150
261
|
/**
|
|
151
262
|
* Initialize the client by creating a session.
|
|
@@ -200,16 +311,21 @@ class PDFDancer {
|
|
|
200
311
|
const base = resolvedBaseUrl.replace(/\/+$/, '');
|
|
201
312
|
const endpoint = '/session/new'.replace(/^\/+/, '');
|
|
202
313
|
const url = `${base}/${endpoint}`;
|
|
314
|
+
// Generate fingerprint for this request
|
|
315
|
+
const fingerprint = await (0, fingerprint_1.generateFingerprint)();
|
|
203
316
|
// Make request to create endpoint
|
|
204
317
|
const response = await fetch(url, {
|
|
205
318
|
method: 'POST',
|
|
206
319
|
headers: {
|
|
207
320
|
'Authorization': `Bearer ${resolvedToken}`,
|
|
208
|
-
'Content-Type': 'application/json'
|
|
321
|
+
'Content-Type': 'application/json',
|
|
322
|
+
'X-Generated-At': generateTimestamp(),
|
|
323
|
+
'X-Fingerprint': fingerprint
|
|
209
324
|
},
|
|
210
325
|
body: JSON.stringify(createRequest.toDict()),
|
|
211
326
|
signal: resolvedTimeout > 0 ? AbortSignal.timeout(resolvedTimeout) : undefined
|
|
212
327
|
});
|
|
328
|
+
logGeneratedAtHeader(response, 'POST', '/session/new');
|
|
213
329
|
if (!response.ok) {
|
|
214
330
|
const errorText = await response.text();
|
|
215
331
|
throw new exceptions_1.HttpClientException(`Failed to create new PDF: ${errorText}`, response);
|
|
@@ -224,6 +340,10 @@ class PDFDancer {
|
|
|
224
340
|
client._readTimeout = resolvedTimeout;
|
|
225
341
|
client._pdfBytes = new Uint8Array();
|
|
226
342
|
client._sessionId = sessionId;
|
|
343
|
+
// Initialize caches
|
|
344
|
+
client._documentSnapshotCache = null;
|
|
345
|
+
client._pageSnapshotCache = new Map();
|
|
346
|
+
client._pagesCache = null;
|
|
227
347
|
return client;
|
|
228
348
|
}
|
|
229
349
|
catch (error) {
|
|
@@ -332,14 +452,18 @@ class PDFDancer {
|
|
|
332
452
|
const blob = new Blob([this._pdfBytes.buffer], { type: 'application/pdf' });
|
|
333
453
|
formData.append('pdf', blob, 'document.pdf');
|
|
334
454
|
}
|
|
455
|
+
const fingerprint = await this._getFingerprint();
|
|
335
456
|
const response = await fetch(this._buildUrl('/session/create'), {
|
|
336
457
|
method: 'POST',
|
|
337
458
|
headers: {
|
|
338
|
-
'Authorization': `Bearer ${this._token}
|
|
459
|
+
'Authorization': `Bearer ${this._token}`,
|
|
460
|
+
'X-Generated-At': generateTimestamp(),
|
|
461
|
+
'X-Fingerprint': fingerprint
|
|
339
462
|
},
|
|
340
463
|
body: formData,
|
|
341
464
|
signal: this._readTimeout > 0 ? AbortSignal.timeout(this._readTimeout) : undefined
|
|
342
465
|
});
|
|
466
|
+
logGeneratedAtHeader(response, 'POST', '/session/create');
|
|
343
467
|
if (!response.ok) {
|
|
344
468
|
const errorMessage = await this._extractErrorMessage(response);
|
|
345
469
|
if (response.status === 401 || response.status === 403) {
|
|
@@ -366,6 +490,15 @@ class PDFDancer {
|
|
|
366
490
|
throw new exceptions_1.HttpClientException(`Failed to create session: ${errorMessage}`, undefined, error);
|
|
367
491
|
}
|
|
368
492
|
}
|
|
493
|
+
/**
|
|
494
|
+
* Get or generate the fingerprint for this client
|
|
495
|
+
*/
|
|
496
|
+
async _getFingerprint() {
|
|
497
|
+
if (!this._fingerprintCache) {
|
|
498
|
+
this._fingerprintCache = await (0, fingerprint_1.generateFingerprint)(this._userId);
|
|
499
|
+
}
|
|
500
|
+
return this._fingerprintCache;
|
|
501
|
+
}
|
|
369
502
|
/**
|
|
370
503
|
* Make HTTP request with session headers and error handling.
|
|
371
504
|
*/
|
|
@@ -376,10 +509,13 @@ class PDFDancer {
|
|
|
376
509
|
url.searchParams.append(key, value);
|
|
377
510
|
});
|
|
378
511
|
}
|
|
512
|
+
const fingerprint = await this._getFingerprint();
|
|
379
513
|
const headers = {
|
|
380
514
|
'Authorization': `Bearer ${this._token}`,
|
|
381
515
|
'X-Session-Id': this._sessionId,
|
|
382
|
-
'Content-Type': 'application/json'
|
|
516
|
+
'Content-Type': 'application/json',
|
|
517
|
+
'X-Generated-At': generateTimestamp(),
|
|
518
|
+
'X-Fingerprint': fingerprint
|
|
383
519
|
};
|
|
384
520
|
try {
|
|
385
521
|
const response = await fetch(url.toString(), {
|
|
@@ -388,6 +524,7 @@ class PDFDancer {
|
|
|
388
524
|
body: data ? JSON.stringify(data) : undefined,
|
|
389
525
|
signal: this._readTimeout > 0 ? AbortSignal.timeout(this._readTimeout) : undefined
|
|
390
526
|
});
|
|
527
|
+
logGeneratedAtHeader(response, method, path);
|
|
391
528
|
// Handle FontNotFoundException
|
|
392
529
|
if (response.status === 404) {
|
|
393
530
|
try {
|
|
@@ -422,12 +559,39 @@ class PDFDancer {
|
|
|
422
559
|
* Searches for PDF objects matching the specified criteria.
|
|
423
560
|
* This method provides flexible search capabilities across all PDF content,
|
|
424
561
|
* allowing filtering by object type and position constraints.
|
|
562
|
+
*
|
|
563
|
+
* Now uses snapshot caching for better performance.
|
|
425
564
|
*/
|
|
426
565
|
async find(objectType, position) {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
const
|
|
430
|
-
|
|
566
|
+
// Determine if we should use snapshot or fall back to HTTP
|
|
567
|
+
// For paths with coordinates, we need to use HTTP (backend requirement)
|
|
568
|
+
const isPathWithCoordinates = objectType === models_1.ObjectType.PATH &&
|
|
569
|
+
position?.shape === models_1.ShapeType.POINT;
|
|
570
|
+
if (isPathWithCoordinates) {
|
|
571
|
+
// Fall back to HTTP for path coordinate queries
|
|
572
|
+
const requestData = new models_1.FindRequest(objectType, position).toDict();
|
|
573
|
+
const response = await this._makeRequest('POST', '/pdf/find', requestData);
|
|
574
|
+
const objectsData = await response.json();
|
|
575
|
+
return objectsData.map((objData) => this._parseObjectRef(objData));
|
|
576
|
+
}
|
|
577
|
+
// Use snapshot-based search
|
|
578
|
+
let elements;
|
|
579
|
+
if (position?.pageIndex !== undefined) {
|
|
580
|
+
// Page-specific query - use page snapshot
|
|
581
|
+
const pageSnapshot = await this._getOrFetchPageSnapshot(position.pageIndex);
|
|
582
|
+
elements = pageSnapshot.elements;
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
// Document-wide query - use document snapshot
|
|
586
|
+
const docSnapshot = await this._getOrFetchDocumentSnapshot();
|
|
587
|
+
elements = docSnapshot.getAllElements();
|
|
588
|
+
}
|
|
589
|
+
// Filter by object type
|
|
590
|
+
if (objectType) {
|
|
591
|
+
elements = elements.filter(el => el.type === objectType);
|
|
592
|
+
}
|
|
593
|
+
// Apply position-based filtering
|
|
594
|
+
return this._filterByPosition(elements, position);
|
|
431
595
|
}
|
|
432
596
|
/**
|
|
433
597
|
* Searches for paragraph objects at the specified position.
|
|
@@ -477,36 +641,76 @@ class PDFDancer {
|
|
|
477
641
|
/**
|
|
478
642
|
* Searches for form fields at the specified position.
|
|
479
643
|
* Returns FormFieldRef objects with name and value properties.
|
|
644
|
+
*
|
|
645
|
+
* Now uses snapshot caching for better performance.
|
|
480
646
|
*/
|
|
481
647
|
async findFormFields(position) {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
648
|
+
// Use snapshot-based search
|
|
649
|
+
let elements;
|
|
650
|
+
if (position?.pageIndex !== undefined) {
|
|
651
|
+
// Page-specific query - use page snapshot
|
|
652
|
+
const pageSnapshot = await this._getOrFetchPageSnapshot(position.pageIndex);
|
|
653
|
+
elements = pageSnapshot.elements;
|
|
654
|
+
}
|
|
655
|
+
else {
|
|
656
|
+
// Document-wide query - use document snapshot
|
|
657
|
+
const docSnapshot = await this._getOrFetchDocumentSnapshot();
|
|
658
|
+
elements = docSnapshot.getAllElements();
|
|
659
|
+
}
|
|
660
|
+
// Filter by form field types (FORM_FIELD, TEXT_FIELD, CHECKBOX, RADIO_BUTTON)
|
|
661
|
+
const formFieldTypes = [
|
|
662
|
+
models_1.ObjectType.FORM_FIELD,
|
|
663
|
+
models_1.ObjectType.TEXT_FIELD,
|
|
664
|
+
models_1.ObjectType.CHECKBOX,
|
|
665
|
+
models_1.ObjectType.RADIO_BUTTON
|
|
666
|
+
];
|
|
667
|
+
const formFields = elements.filter(el => formFieldTypes.includes(el.type));
|
|
668
|
+
// Apply position-based filtering
|
|
669
|
+
return this._filterFormFieldsByPosition(formFields, position);
|
|
486
670
|
}
|
|
487
671
|
// Page Operations
|
|
488
672
|
/**
|
|
489
673
|
* Retrieves references to all pages in the PDF document.
|
|
674
|
+
* Now uses snapshot caching to avoid HTTP requests.
|
|
490
675
|
*/
|
|
491
676
|
async getPages() {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
677
|
+
// Check if we have cached pages
|
|
678
|
+
if (this._pagesCache) {
|
|
679
|
+
return this._pagesCache;
|
|
680
|
+
}
|
|
681
|
+
// Try to get from document snapshot cache first
|
|
682
|
+
if (this._documentSnapshotCache) {
|
|
683
|
+
this._pagesCache = this._documentSnapshotCache.pages.map(p => p.pageRef);
|
|
684
|
+
return this._pagesCache;
|
|
685
|
+
}
|
|
686
|
+
// Fetch document snapshot to get pages (this will cache it)
|
|
687
|
+
const docSnapshot = await this._getOrFetchDocumentSnapshot();
|
|
688
|
+
this._pagesCache = docSnapshot.pages.map(p => p.pageRef);
|
|
689
|
+
return this._pagesCache;
|
|
495
690
|
}
|
|
496
691
|
/**
|
|
497
692
|
* Retrieves a reference to a specific page by its page index.
|
|
693
|
+
* Now uses snapshot caching to avoid HTTP requests.
|
|
498
694
|
*/
|
|
499
695
|
async _getPage(pageIndex) {
|
|
500
696
|
if (pageIndex < 0) {
|
|
501
697
|
throw new exceptions_1.ValidationException(`Page index must be >= 0, got ${pageIndex}`);
|
|
502
698
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
699
|
+
// Try page snapshot cache first
|
|
700
|
+
if (this._pageSnapshotCache.has(pageIndex)) {
|
|
701
|
+
return this._pageSnapshotCache.get(pageIndex).pageRef;
|
|
702
|
+
}
|
|
703
|
+
// Try document snapshot cache
|
|
704
|
+
if (this._documentSnapshotCache) {
|
|
705
|
+
const pageSnapshot = this._documentSnapshotCache.getPageSnapshot(pageIndex);
|
|
706
|
+
if (pageSnapshot) {
|
|
707
|
+
return pageSnapshot.pageRef;
|
|
708
|
+
}
|
|
508
709
|
}
|
|
509
|
-
|
|
710
|
+
// Fetch document snapshot to get page (this will cache it)
|
|
711
|
+
const docSnapshot = await this._getOrFetchDocumentSnapshot();
|
|
712
|
+
const pageSnapshot = docSnapshot.getPageSnapshot(pageIndex);
|
|
713
|
+
return pageSnapshot?.pageRef ?? null;
|
|
510
714
|
}
|
|
511
715
|
/**
|
|
512
716
|
* Moves an existing page to a new index.
|
|
@@ -522,6 +726,8 @@ class PDFDancer {
|
|
|
522
726
|
if (!success) {
|
|
523
727
|
throw new exceptions_1.HttpClientException(`Failed to move page from ${pageIndex} to ${targetPageIndex}`, response);
|
|
524
728
|
}
|
|
729
|
+
// Invalidate cache after mutation
|
|
730
|
+
this._invalidateCache();
|
|
525
731
|
// Fetch the page again at its new position for up-to-date metadata
|
|
526
732
|
return await this._requirePageRef(targetPageIndex);
|
|
527
733
|
}
|
|
@@ -531,7 +737,10 @@ class PDFDancer {
|
|
|
531
737
|
async deletePage(pageIndex) {
|
|
532
738
|
this._validatePageIndex(pageIndex, 'pageIndex');
|
|
533
739
|
const pageRef = await this._requirePageRef(pageIndex);
|
|
534
|
-
|
|
740
|
+
const result = await this._deletePage(pageRef);
|
|
741
|
+
// Invalidate cache after mutation
|
|
742
|
+
this._invalidateCache();
|
|
743
|
+
return result;
|
|
535
744
|
}
|
|
536
745
|
_validatePageIndex(pageIndex, fieldName) {
|
|
537
746
|
if (!Number.isInteger(pageIndex)) {
|
|
@@ -559,6 +768,164 @@ class PDFDancer {
|
|
|
559
768
|
const response = await this._makeRequest('DELETE', '/pdf/page/delete', requestData);
|
|
560
769
|
return await response.json();
|
|
561
770
|
}
|
|
771
|
+
// Snapshot Operations
|
|
772
|
+
/**
|
|
773
|
+
* Gets a snapshot of the entire PDF document.
|
|
774
|
+
* Returns page count, fonts, and snapshots of all pages with their elements.
|
|
775
|
+
*
|
|
776
|
+
* @param types Optional array of ObjectType to filter elements by type
|
|
777
|
+
* @returns DocumentSnapshot containing all document information
|
|
778
|
+
*/
|
|
779
|
+
async getDocumentSnapshot(types) {
|
|
780
|
+
const params = {};
|
|
781
|
+
if (types && types.length > 0) {
|
|
782
|
+
params.types = types.join(',');
|
|
783
|
+
}
|
|
784
|
+
const response = await this._makeRequest('GET', '/pdf/document/snapshot', undefined, params);
|
|
785
|
+
const data = await response.json();
|
|
786
|
+
return this._parseDocumentSnapshot(data);
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Gets a snapshot of a specific page.
|
|
790
|
+
* Returns the page reference and all elements on that page.
|
|
791
|
+
*
|
|
792
|
+
* @param pageIndex Zero-based page index
|
|
793
|
+
* @param types Optional array of ObjectType to filter elements by type
|
|
794
|
+
* @returns PageSnapshot containing page information and elements
|
|
795
|
+
*/
|
|
796
|
+
async getPageSnapshot(pageIndex, types) {
|
|
797
|
+
this._validatePageIndex(pageIndex, 'pageIndex');
|
|
798
|
+
const params = {};
|
|
799
|
+
if (types && types.length > 0) {
|
|
800
|
+
params.types = types.join(',');
|
|
801
|
+
}
|
|
802
|
+
const response = await this._makeRequest('GET', `/pdf/page/${pageIndex}/snapshot`, undefined, params);
|
|
803
|
+
const data = await response.json();
|
|
804
|
+
return this._parsePageSnapshot(data);
|
|
805
|
+
}
|
|
806
|
+
// Cache Management
|
|
807
|
+
/**
|
|
808
|
+
* Gets a page snapshot from cache or fetches it.
|
|
809
|
+
* First checks page cache, then document cache, then fetches from server.
|
|
810
|
+
*/
|
|
811
|
+
async _getOrFetchPageSnapshot(pageIndex) {
|
|
812
|
+
// Check page cache first
|
|
813
|
+
if (this._pageSnapshotCache.has(pageIndex)) {
|
|
814
|
+
return this._pageSnapshotCache.get(pageIndex);
|
|
815
|
+
}
|
|
816
|
+
// Check if we have document snapshot and can extract the page
|
|
817
|
+
if (this._documentSnapshotCache) {
|
|
818
|
+
const pageSnapshot = this._documentSnapshotCache.getPageSnapshot(pageIndex);
|
|
819
|
+
if (pageSnapshot) {
|
|
820
|
+
// Cache it for future use
|
|
821
|
+
this._pageSnapshotCache.set(pageIndex, pageSnapshot);
|
|
822
|
+
return pageSnapshot;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
// Fetch page snapshot from server
|
|
826
|
+
const pageSnapshot = await this.getPageSnapshot(pageIndex);
|
|
827
|
+
this._pageSnapshotCache.set(pageIndex, pageSnapshot);
|
|
828
|
+
return pageSnapshot;
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Gets the document snapshot from cache or fetches it.
|
|
832
|
+
*/
|
|
833
|
+
async _getOrFetchDocumentSnapshot() {
|
|
834
|
+
if (!this._documentSnapshotCache) {
|
|
835
|
+
this._documentSnapshotCache = await this.getDocumentSnapshot();
|
|
836
|
+
}
|
|
837
|
+
return this._documentSnapshotCache;
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Invalidates all snapshot caches.
|
|
841
|
+
* Called after any mutation operation.
|
|
842
|
+
*/
|
|
843
|
+
_invalidateCache() {
|
|
844
|
+
this._documentSnapshotCache = null;
|
|
845
|
+
this._pageSnapshotCache.clear();
|
|
846
|
+
this._pagesCache = null;
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Filters snapshot elements by Position criteria.
|
|
850
|
+
* Handles coordinates, text matching, and field name filtering.
|
|
851
|
+
*/
|
|
852
|
+
_filterByPosition(elements, position) {
|
|
853
|
+
if (!position) {
|
|
854
|
+
return elements;
|
|
855
|
+
}
|
|
856
|
+
let filtered = elements;
|
|
857
|
+
// Filter by page index
|
|
858
|
+
if (position.pageIndex !== undefined) {
|
|
859
|
+
filtered = filtered.filter(el => el.position.pageIndex === position.pageIndex);
|
|
860
|
+
}
|
|
861
|
+
// Filter by coordinates (point containment with tolerance)
|
|
862
|
+
if (position.boundingRect && position.shape === models_1.ShapeType.POINT) {
|
|
863
|
+
const x = position.boundingRect.x;
|
|
864
|
+
const y = position.boundingRect.y;
|
|
865
|
+
const tolerance = position.tolerance || 0;
|
|
866
|
+
filtered = filtered.filter(el => {
|
|
867
|
+
const rect = el.position.boundingRect;
|
|
868
|
+
if (!rect)
|
|
869
|
+
return false;
|
|
870
|
+
return x >= rect.x - tolerance && x <= rect.x + rect.width + tolerance &&
|
|
871
|
+
y >= rect.y - tolerance && y <= rect.y + rect.height + tolerance;
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
// Filter by text starts with
|
|
875
|
+
if (position.textStartsWith && filtered.length > 0) {
|
|
876
|
+
const textLower = position.textStartsWith.toLowerCase();
|
|
877
|
+
filtered = filtered.filter(el => {
|
|
878
|
+
const textObj = el;
|
|
879
|
+
return textObj.text && textObj.text.toLowerCase().startsWith(textLower);
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
// Filter by text pattern (regex)
|
|
883
|
+
if (position.textPattern && filtered.length > 0) {
|
|
884
|
+
const regex = this._compileTextPattern(position.textPattern);
|
|
885
|
+
filtered = filtered.filter(el => {
|
|
886
|
+
const textObj = el;
|
|
887
|
+
return textObj.text && regex.test(textObj.text);
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
// Filter by name (for form fields)
|
|
891
|
+
if (position.name && filtered.length > 0) {
|
|
892
|
+
filtered = filtered.filter(el => {
|
|
893
|
+
const formField = el;
|
|
894
|
+
return formField.name === position.name;
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
return filtered;
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Filters FormFieldRef elements by Position criteria.
|
|
901
|
+
*/
|
|
902
|
+
_filterFormFieldsByPosition(elements, position) {
|
|
903
|
+
return this._filterByPosition(elements, position);
|
|
904
|
+
}
|
|
905
|
+
_compileTextPattern(pattern) {
|
|
906
|
+
try {
|
|
907
|
+
return new RegExp(pattern);
|
|
908
|
+
}
|
|
909
|
+
catch {
|
|
910
|
+
const inlineMatch = pattern.match(/^\(\?([a-z]+)\)/i);
|
|
911
|
+
if (inlineMatch) {
|
|
912
|
+
const supportedFlags = inlineMatch[1]
|
|
913
|
+
.toLowerCase()
|
|
914
|
+
.split('')
|
|
915
|
+
.filter(flag => 'gimsuy'.includes(flag));
|
|
916
|
+
const flags = Array.from(new Set(supportedFlags)).join('');
|
|
917
|
+
const source = pattern.slice(inlineMatch[0].length);
|
|
918
|
+
try {
|
|
919
|
+
return new RegExp(source, flags);
|
|
920
|
+
}
|
|
921
|
+
catch {
|
|
922
|
+
// fall through to literal fallback
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
926
|
+
return new RegExp(escaped);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
562
929
|
// Manipulation Operations
|
|
563
930
|
/**
|
|
564
931
|
* Deletes the specified PDF object from the document.
|
|
@@ -569,7 +936,10 @@ class PDFDancer {
|
|
|
569
936
|
}
|
|
570
937
|
const requestData = new models_1.DeleteRequest(objectRef).toDict();
|
|
571
938
|
const response = await this._makeRequest('DELETE', '/pdf/delete', requestData);
|
|
572
|
-
|
|
939
|
+
const result = await response.json();
|
|
940
|
+
// Invalidate cache after mutation
|
|
941
|
+
this._invalidateCache();
|
|
942
|
+
return result;
|
|
573
943
|
}
|
|
574
944
|
/**
|
|
575
945
|
* Moves a PDF object to a new position within the document.
|
|
@@ -583,7 +953,10 @@ class PDFDancer {
|
|
|
583
953
|
}
|
|
584
954
|
const requestData = new models_1.MoveRequest(objectRef, position).toDict();
|
|
585
955
|
const response = await this._makeRequest('PUT', '/pdf/move', requestData);
|
|
586
|
-
|
|
956
|
+
const result = await response.json();
|
|
957
|
+
// Invalidate cache after mutation
|
|
958
|
+
this._invalidateCache();
|
|
959
|
+
return result;
|
|
587
960
|
}
|
|
588
961
|
/**
|
|
589
962
|
* Changes the value of a form field.
|
|
@@ -594,7 +967,10 @@ class PDFDancer {
|
|
|
594
967
|
}
|
|
595
968
|
const requestData = new models_1.ChangeFormFieldRequest(formFieldRef, newValue).toDict();
|
|
596
969
|
const response = await this._makeRequest('PUT', '/pdf/modify/formField', requestData);
|
|
597
|
-
|
|
970
|
+
const result = await response.json();
|
|
971
|
+
// Invalidate cache after mutation
|
|
972
|
+
this._invalidateCache();
|
|
973
|
+
return result;
|
|
598
974
|
}
|
|
599
975
|
// Add Operations
|
|
600
976
|
/**
|
|
@@ -630,13 +1006,28 @@ class PDFDancer {
|
|
|
630
1006
|
}
|
|
631
1007
|
return this._addObject(paragraph);
|
|
632
1008
|
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Adds a page to the PDF document.
|
|
1011
|
+
*/
|
|
1012
|
+
async addPage(request) {
|
|
1013
|
+
const payload = request ? request.toDict() : {};
|
|
1014
|
+
const data = Object.keys(payload).length > 0 ? payload : undefined;
|
|
1015
|
+
const response = await this._makeRequest('POST', '/pdf/page/add', data);
|
|
1016
|
+
const result = await response.json();
|
|
1017
|
+
const pageRef = this._parsePageRef(result);
|
|
1018
|
+
this._invalidateCache();
|
|
1019
|
+
return pageRef;
|
|
1020
|
+
}
|
|
633
1021
|
/**
|
|
634
1022
|
* Internal method to add any PDF object.
|
|
635
1023
|
*/
|
|
636
1024
|
async _addObject(pdfObject) {
|
|
637
1025
|
const requestData = new models_1.AddRequest(pdfObject).toDict();
|
|
638
1026
|
const response = await this._makeRequest('POST', '/pdf/add', requestData);
|
|
639
|
-
|
|
1027
|
+
const result = await response.json();
|
|
1028
|
+
// Invalidate cache after mutation
|
|
1029
|
+
this._invalidateCache();
|
|
1030
|
+
return result;
|
|
640
1031
|
}
|
|
641
1032
|
// Modify Operations
|
|
642
1033
|
/**
|
|
@@ -649,18 +1040,22 @@ class PDFDancer {
|
|
|
649
1040
|
if (newParagraph === null || newParagraph === undefined) {
|
|
650
1041
|
return models_1.CommandResult.empty("ModifyParagraph", objectRef.internalId);
|
|
651
1042
|
}
|
|
1043
|
+
let result;
|
|
652
1044
|
if (typeof newParagraph === 'string') {
|
|
653
1045
|
// Text modification - returns CommandResult
|
|
654
1046
|
const requestData = new models_1.ModifyTextRequest(objectRef, newParagraph).toDict();
|
|
655
1047
|
const response = await this._makeRequest('PUT', '/pdf/text/paragraph', requestData);
|
|
656
|
-
|
|
1048
|
+
result = models_1.CommandResult.fromDict(await response.json());
|
|
657
1049
|
}
|
|
658
1050
|
else {
|
|
659
1051
|
// Object modification
|
|
660
1052
|
const requestData = new models_1.ModifyRequest(objectRef, newParagraph).toDict();
|
|
661
1053
|
const response = await this._makeRequest('PUT', '/pdf/modify', requestData);
|
|
662
|
-
|
|
1054
|
+
result = models_1.CommandResult.fromDict(await response.json());
|
|
663
1055
|
}
|
|
1056
|
+
// Invalidate cache after mutation
|
|
1057
|
+
this._invalidateCache();
|
|
1058
|
+
return result;
|
|
664
1059
|
}
|
|
665
1060
|
/**
|
|
666
1061
|
* Modifies a text line object.
|
|
@@ -674,7 +1069,10 @@ class PDFDancer {
|
|
|
674
1069
|
}
|
|
675
1070
|
const requestData = new models_1.ModifyTextRequest(objectRef, newText).toDict();
|
|
676
1071
|
const response = await this._makeRequest('PUT', '/pdf/text/line', requestData);
|
|
677
|
-
|
|
1072
|
+
const result = models_1.CommandResult.fromDict(await response.json());
|
|
1073
|
+
// Invalidate cache after mutation
|
|
1074
|
+
this._invalidateCache();
|
|
1075
|
+
return result;
|
|
678
1076
|
}
|
|
679
1077
|
// Font Operations
|
|
680
1078
|
/**
|
|
@@ -730,15 +1128,19 @@ class PDFDancer {
|
|
|
730
1128
|
const formData = new FormData();
|
|
731
1129
|
const blob = new Blob([fontData.buffer], { type: 'font/ttf' });
|
|
732
1130
|
formData.append('ttfFile', blob, filename);
|
|
1131
|
+
const fingerprint = await this._getFingerprint();
|
|
733
1132
|
const response = await fetch(this._buildUrl('/font/register'), {
|
|
734
1133
|
method: 'POST',
|
|
735
1134
|
headers: {
|
|
736
1135
|
'Authorization': `Bearer ${this._token}`,
|
|
737
|
-
'X-Session-Id': this._sessionId
|
|
1136
|
+
'X-Session-Id': this._sessionId,
|
|
1137
|
+
'X-Generated-At': generateTimestamp(),
|
|
1138
|
+
'X-Fingerprint': fingerprint
|
|
738
1139
|
},
|
|
739
1140
|
body: formData,
|
|
740
1141
|
signal: AbortSignal.timeout(30000)
|
|
741
1142
|
});
|
|
1143
|
+
logGeneratedAtHeader(response, 'POST', '/font/register');
|
|
742
1144
|
if (!response.ok) {
|
|
743
1145
|
const errorMessage = await this._extractErrorMessage(response);
|
|
744
1146
|
throw new exceptions_1.HttpClientException(`Font registration failed: ${errorMessage}`, response);
|
|
@@ -792,11 +1194,22 @@ class PDFDancer {
|
|
|
792
1194
|
if (this._isTextObjectData(objData, objectType)) {
|
|
793
1195
|
return this._parseTextObjectRef(objData);
|
|
794
1196
|
}
|
|
1197
|
+
// Check if this is a form field type
|
|
1198
|
+
const formFieldTypes = [
|
|
1199
|
+
models_1.ObjectType.FORM_FIELD,
|
|
1200
|
+
models_1.ObjectType.TEXT_FIELD,
|
|
1201
|
+
models_1.ObjectType.CHECKBOX,
|
|
1202
|
+
models_1.ObjectType.RADIO_BUTTON
|
|
1203
|
+
];
|
|
1204
|
+
if (formFieldTypes.includes(objectType)) {
|
|
1205
|
+
return this._parseFormFieldRef(objData);
|
|
1206
|
+
}
|
|
795
1207
|
return new models_1.ObjectRef(objData.internalId, position, objectType);
|
|
796
1208
|
}
|
|
797
1209
|
_isTextObjectData(objData, objectType) {
|
|
798
1210
|
return objectType === models_1.ObjectType.PARAGRAPH ||
|
|
799
1211
|
objectType === models_1.ObjectType.TEXT_LINE ||
|
|
1212
|
+
objectType === models_1.ObjectType.TEXT_ELEMENT ||
|
|
800
1213
|
typeof objData.text === 'string' ||
|
|
801
1214
|
typeof objData.fontName === 'string' ||
|
|
802
1215
|
Array.isArray(objData.children);
|
|
@@ -825,10 +1238,16 @@ class PDFDancer {
|
|
|
825
1238
|
}
|
|
826
1239
|
const textObject = new models_1.TextObjectRef(internalId, position, objectType, typeof objData.text === 'string' ? objData.text : undefined, typeof objData.fontName === 'string' ? objData.fontName : undefined, typeof objData.fontSize === 'number' ? objData.fontSize : undefined, lineSpacings, undefined, this._parseColor(objData.color), status);
|
|
827
1240
|
if (Array.isArray(objData.children) && objData.children.length > 0) {
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
1241
|
+
try {
|
|
1242
|
+
textObject.children = objData.children.map((childData, index) => {
|
|
1243
|
+
const childFallbackId = `${internalId || 'child'}-${index}`;
|
|
1244
|
+
return this._parseTextObjectRef(childData, childFallbackId);
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
catch (error) {
|
|
1248
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1249
|
+
console.error(`Failed to parse children of ${internalId}: ${message}`);
|
|
1250
|
+
}
|
|
832
1251
|
}
|
|
833
1252
|
return textObject;
|
|
834
1253
|
}
|
|
@@ -906,6 +1325,47 @@ class PDFDancer {
|
|
|
906
1325
|
}
|
|
907
1326
|
return position;
|
|
908
1327
|
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Parse JSON data into DocumentSnapshot instance.
|
|
1330
|
+
*/
|
|
1331
|
+
_parseDocumentSnapshot(data) {
|
|
1332
|
+
const pageCount = typeof data.pageCount === 'number' ? data.pageCount : 0;
|
|
1333
|
+
// Parse fonts
|
|
1334
|
+
const fonts = [];
|
|
1335
|
+
if (Array.isArray(data.fonts)) {
|
|
1336
|
+
for (const fontData of data.fonts) {
|
|
1337
|
+
if (fontData && typeof fontData === 'object') {
|
|
1338
|
+
const fontName = fontData.fontName || '';
|
|
1339
|
+
const fontType = fontData.fontType || models_1.FontType.SYSTEM;
|
|
1340
|
+
const similarityScore = typeof fontData.similarityScore === 'number' ? fontData.similarityScore : 0;
|
|
1341
|
+
fonts.push(new models_1.FontRecommendation(fontName, fontType, similarityScore));
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
// Parse pages
|
|
1346
|
+
const pages = [];
|
|
1347
|
+
if (Array.isArray(data.pages)) {
|
|
1348
|
+
for (const pageData of data.pages) {
|
|
1349
|
+
pages.push(this._parsePageSnapshot(pageData));
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
return new models_1.DocumentSnapshot(pageCount, fonts, pages);
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* Parse JSON data into PageSnapshot instance.
|
|
1356
|
+
*/
|
|
1357
|
+
_parsePageSnapshot(data) {
|
|
1358
|
+
// Parse page reference
|
|
1359
|
+
const pageRef = this._parsePageRef(data.pageRef || {});
|
|
1360
|
+
// Parse elements
|
|
1361
|
+
const elements = [];
|
|
1362
|
+
if (Array.isArray(data.elements)) {
|
|
1363
|
+
for (const elementData of data.elements) {
|
|
1364
|
+
elements.push(this._parseObjectRef(elementData));
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
return new models_1.PageSnapshot(pageRef, elements);
|
|
1368
|
+
}
|
|
909
1369
|
// Builder Pattern Support
|
|
910
1370
|
toPathObjects(objectRefs) {
|
|
911
1371
|
return objectRefs.map(ref => types_1.PathObject.fromRef(this, ref));
|
|
@@ -916,8 +1376,14 @@ class PDFDancer {
|
|
|
916
1376
|
toImageObjects(objectRefs) {
|
|
917
1377
|
return objectRefs.map(ref => types_1.ImageObject.fromRef(this, ref));
|
|
918
1378
|
}
|
|
919
|
-
newImage() {
|
|
920
|
-
return new image_builder_1.ImageBuilder(this);
|
|
1379
|
+
newImage(pageIndex) {
|
|
1380
|
+
return new image_builder_1.ImageBuilder(this, pageIndex);
|
|
1381
|
+
}
|
|
1382
|
+
newParagraph(pageIndex) {
|
|
1383
|
+
return new paragraph_builder_1.ParagraphBuilder(this, pageIndex);
|
|
1384
|
+
}
|
|
1385
|
+
newPage() {
|
|
1386
|
+
return new page_builder_1.PageBuilder(this);
|
|
921
1387
|
}
|
|
922
1388
|
page(pageIndex) {
|
|
923
1389
|
if (pageIndex < 0) {
|
|
@@ -932,18 +1398,37 @@ class PDFDancer {
|
|
|
932
1398
|
toFormFields(objectRefs) {
|
|
933
1399
|
return objectRefs.map(ref => types_1.FormFieldObject.fromRef(this, ref));
|
|
934
1400
|
}
|
|
1401
|
+
async selectElements(types) {
|
|
1402
|
+
const snapshot = await this.getDocumentSnapshot(types);
|
|
1403
|
+
const elements = [];
|
|
1404
|
+
for (const pageSnapshot of snapshot.pages) {
|
|
1405
|
+
elements.push(...pageSnapshot.elements);
|
|
1406
|
+
}
|
|
1407
|
+
return elements;
|
|
1408
|
+
}
|
|
935
1409
|
async selectParagraphs() {
|
|
936
1410
|
return this.toParagraphObjects(await this.findParagraphs());
|
|
937
1411
|
}
|
|
1412
|
+
async selectParagraphsMatching(pattern) {
|
|
1413
|
+
if (!pattern) {
|
|
1414
|
+
throw new exceptions_1.ValidationException('Pattern cannot be empty');
|
|
1415
|
+
}
|
|
1416
|
+
const position = new models_1.Position();
|
|
1417
|
+
position.textPattern = pattern;
|
|
1418
|
+
return this.toParagraphObjects(await this.findParagraphs(position));
|
|
1419
|
+
}
|
|
938
1420
|
toParagraphObjects(objectRefs) {
|
|
939
1421
|
return objectRefs.map(ref => types_1.ParagraphObject.fromRef(this, ref));
|
|
940
1422
|
}
|
|
941
1423
|
toTextLineObjects(objectRefs) {
|
|
942
1424
|
return objectRefs.map(ref => types_1.TextLineObject.fromRef(this, ref));
|
|
943
1425
|
}
|
|
944
|
-
async
|
|
1426
|
+
async selectTextLines() {
|
|
945
1427
|
return this.toTextLineObjects(await this.findTextLines());
|
|
946
1428
|
}
|
|
1429
|
+
async selectLines() {
|
|
1430
|
+
return this.selectTextLines();
|
|
1431
|
+
}
|
|
947
1432
|
}
|
|
948
1433
|
exports.PDFDancer = PDFDancer;
|
|
949
1434
|
//# sourceMappingURL=pdfdancer_v1.js.map
|