pdfdancer-client-typescript 1.0.11 → 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/.claude/commands/discuss.md +4 -0
- package/.github/workflows/ci.yml +2 -2
- package/README.md +2 -2
- 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 +559 -55
- 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/jest.config.js +1 -1
- 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__/e2e/token_from_env.test.ts +85 -25
- 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 +662 -58
- 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
|
|
@@ -126,15 +229,34 @@ class PDFDancer {
|
|
|
126
229
|
* This constructor initializes the client, uploads the PDF data to open
|
|
127
230
|
* a new session, and prepares the client for PDF manipulation operations.
|
|
128
231
|
*/
|
|
129
|
-
constructor(token, pdfData, baseUrl =
|
|
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
|
}
|
|
240
|
+
// Normalize baseUrl
|
|
241
|
+
const resolvedBaseUrl = (baseUrl && baseUrl.trim()) ||
|
|
242
|
+
process.env.PDFDANCER_BASE_URL ||
|
|
243
|
+
"https://api.pdfdancer.com";
|
|
244
|
+
// Basic validation — ensures it's a valid absolute URL
|
|
245
|
+
try {
|
|
246
|
+
new URL(resolvedBaseUrl);
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
throw new exceptions_1.ValidationException(`Invalid base URL: ${resolvedBaseUrl}`);
|
|
250
|
+
}
|
|
133
251
|
this._token = token.trim();
|
|
134
|
-
this._baseUrl =
|
|
252
|
+
this._baseUrl = resolvedBaseUrl.replace(/\/$/, ''); // Remove trailing slash
|
|
135
253
|
this._readTimeout = readTimeout;
|
|
136
254
|
// Process PDF data with validation
|
|
137
255
|
this._pdfBytes = this._processPdfData(pdfData);
|
|
256
|
+
// Initialize caches
|
|
257
|
+
this._documentSnapshotCache = null;
|
|
258
|
+
this._pageSnapshotCache = new Map();
|
|
259
|
+
this._pagesCache = null;
|
|
138
260
|
}
|
|
139
261
|
/**
|
|
140
262
|
* Initialize the client by creating a session.
|
|
@@ -151,7 +273,7 @@ class PDFDancer {
|
|
|
151
273
|
"https://api.pdfdancer.com";
|
|
152
274
|
const resolvedTimeout = timeout ?? 30000;
|
|
153
275
|
if (!resolvedToken) {
|
|
154
|
-
throw new
|
|
276
|
+
throw new exceptions_1.ValidationException("Missing PDFDancer API token. Pass a token via the `token` argument or set the PDFDANCER_TOKEN environment variable.");
|
|
155
277
|
}
|
|
156
278
|
const client = new PDFDancer(resolvedToken, pdfData, resolvedBaseUrl, resolvedTimeout);
|
|
157
279
|
return await client.init();
|
|
@@ -174,7 +296,7 @@ class PDFDancer {
|
|
|
174
296
|
"https://api.pdfdancer.com";
|
|
175
297
|
const resolvedTimeout = timeout ?? 30000;
|
|
176
298
|
if (!resolvedToken) {
|
|
177
|
-
throw new
|
|
299
|
+
throw new exceptions_1.ValidationException("Missing PDFDancer token (pass it explicitly or set PDFDANCER_TOKEN in environment).");
|
|
178
300
|
}
|
|
179
301
|
let createRequest;
|
|
180
302
|
try {
|
|
@@ -189,16 +311,21 @@ class PDFDancer {
|
|
|
189
311
|
const base = resolvedBaseUrl.replace(/\/+$/, '');
|
|
190
312
|
const endpoint = '/session/new'.replace(/^\/+/, '');
|
|
191
313
|
const url = `${base}/${endpoint}`;
|
|
314
|
+
// Generate fingerprint for this request
|
|
315
|
+
const fingerprint = await (0, fingerprint_1.generateFingerprint)();
|
|
192
316
|
// Make request to create endpoint
|
|
193
317
|
const response = await fetch(url, {
|
|
194
318
|
method: 'POST',
|
|
195
319
|
headers: {
|
|
196
320
|
'Authorization': `Bearer ${resolvedToken}`,
|
|
197
|
-
'Content-Type': 'application/json'
|
|
321
|
+
'Content-Type': 'application/json',
|
|
322
|
+
'X-Generated-At': generateTimestamp(),
|
|
323
|
+
'X-Fingerprint': fingerprint
|
|
198
324
|
},
|
|
199
325
|
body: JSON.stringify(createRequest.toDict()),
|
|
200
326
|
signal: resolvedTimeout > 0 ? AbortSignal.timeout(resolvedTimeout) : undefined
|
|
201
327
|
});
|
|
328
|
+
logGeneratedAtHeader(response, 'POST', '/session/new');
|
|
202
329
|
if (!response.ok) {
|
|
203
330
|
const errorText = await response.text();
|
|
204
331
|
throw new exceptions_1.HttpClientException(`Failed to create new PDF: ${errorText}`, response);
|
|
@@ -213,6 +340,10 @@ class PDFDancer {
|
|
|
213
340
|
client._readTimeout = resolvedTimeout;
|
|
214
341
|
client._pdfBytes = new Uint8Array();
|
|
215
342
|
client._sessionId = sessionId;
|
|
343
|
+
// Initialize caches
|
|
344
|
+
client._documentSnapshotCache = null;
|
|
345
|
+
client._pageSnapshotCache = new Map();
|
|
346
|
+
client._pagesCache = null;
|
|
216
347
|
return client;
|
|
217
348
|
}
|
|
218
349
|
catch (error) {
|
|
@@ -321,16 +452,28 @@ class PDFDancer {
|
|
|
321
452
|
const blob = new Blob([this._pdfBytes.buffer], { type: 'application/pdf' });
|
|
322
453
|
formData.append('pdf', blob, 'document.pdf');
|
|
323
454
|
}
|
|
455
|
+
const fingerprint = await this._getFingerprint();
|
|
324
456
|
const response = await fetch(this._buildUrl('/session/create'), {
|
|
325
457
|
method: 'POST',
|
|
326
458
|
headers: {
|
|
327
|
-
'Authorization': `Bearer ${this._token}
|
|
459
|
+
'Authorization': `Bearer ${this._token}`,
|
|
460
|
+
'X-Generated-At': generateTimestamp(),
|
|
461
|
+
'X-Fingerprint': fingerprint
|
|
328
462
|
},
|
|
329
463
|
body: formData,
|
|
330
464
|
signal: this._readTimeout > 0 ? AbortSignal.timeout(this._readTimeout) : undefined
|
|
331
465
|
});
|
|
466
|
+
logGeneratedAtHeader(response, 'POST', '/session/create');
|
|
332
467
|
if (!response.ok) {
|
|
333
468
|
const errorMessage = await this._extractErrorMessage(response);
|
|
469
|
+
if (response.status === 401 || response.status === 403) {
|
|
470
|
+
const defaultMessage = "Authentication with the PDFDancer API failed. Confirm that your API token is valid, has not expired, and is authorized for the requested environment.";
|
|
471
|
+
const normalized = errorMessage?.trim() ?? "";
|
|
472
|
+
const message = normalized && normalized !== "Unauthorized" && normalized !== "Forbidden"
|
|
473
|
+
? normalized
|
|
474
|
+
: defaultMessage;
|
|
475
|
+
throw new exceptions_1.ValidationException(message);
|
|
476
|
+
}
|
|
334
477
|
throw new exceptions_1.HttpClientException(`Failed to create session: ${errorMessage}`, response);
|
|
335
478
|
}
|
|
336
479
|
const sessionId = (await response.text()).trim();
|
|
@@ -340,13 +483,22 @@ class PDFDancer {
|
|
|
340
483
|
return sessionId;
|
|
341
484
|
}
|
|
342
485
|
catch (error) {
|
|
343
|
-
if (error instanceof exceptions_1.HttpClientException || error instanceof exceptions_1.SessionException) {
|
|
486
|
+
if (error instanceof exceptions_1.HttpClientException || error instanceof exceptions_1.SessionException || error instanceof exceptions_1.ValidationException) {
|
|
344
487
|
throw error;
|
|
345
488
|
}
|
|
346
489
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
347
490
|
throw new exceptions_1.HttpClientException(`Failed to create session: ${errorMessage}`, undefined, error);
|
|
348
491
|
}
|
|
349
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
|
+
}
|
|
350
502
|
/**
|
|
351
503
|
* Make HTTP request with session headers and error handling.
|
|
352
504
|
*/
|
|
@@ -357,10 +509,13 @@ class PDFDancer {
|
|
|
357
509
|
url.searchParams.append(key, value);
|
|
358
510
|
});
|
|
359
511
|
}
|
|
512
|
+
const fingerprint = await this._getFingerprint();
|
|
360
513
|
const headers = {
|
|
361
514
|
'Authorization': `Bearer ${this._token}`,
|
|
362
515
|
'X-Session-Id': this._sessionId,
|
|
363
|
-
'Content-Type': 'application/json'
|
|
516
|
+
'Content-Type': 'application/json',
|
|
517
|
+
'X-Generated-At': generateTimestamp(),
|
|
518
|
+
'X-Fingerprint': fingerprint
|
|
364
519
|
};
|
|
365
520
|
try {
|
|
366
521
|
const response = await fetch(url.toString(), {
|
|
@@ -369,6 +524,7 @@ class PDFDancer {
|
|
|
369
524
|
body: data ? JSON.stringify(data) : undefined,
|
|
370
525
|
signal: this._readTimeout > 0 ? AbortSignal.timeout(this._readTimeout) : undefined
|
|
371
526
|
});
|
|
527
|
+
logGeneratedAtHeader(response, method, path);
|
|
372
528
|
// Handle FontNotFoundException
|
|
373
529
|
if (response.status === 404) {
|
|
374
530
|
try {
|
|
@@ -403,12 +559,39 @@ class PDFDancer {
|
|
|
403
559
|
* Searches for PDF objects matching the specified criteria.
|
|
404
560
|
* This method provides flexible search capabilities across all PDF content,
|
|
405
561
|
* allowing filtering by object type and position constraints.
|
|
562
|
+
*
|
|
563
|
+
* Now uses snapshot caching for better performance.
|
|
406
564
|
*/
|
|
407
565
|
async find(objectType, position) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
const
|
|
411
|
-
|
|
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);
|
|
412
595
|
}
|
|
413
596
|
/**
|
|
414
597
|
* Searches for paragraph objects at the specified position.
|
|
@@ -458,36 +641,76 @@ class PDFDancer {
|
|
|
458
641
|
/**
|
|
459
642
|
* Searches for form fields at the specified position.
|
|
460
643
|
* Returns FormFieldRef objects with name and value properties.
|
|
644
|
+
*
|
|
645
|
+
* Now uses snapshot caching for better performance.
|
|
461
646
|
*/
|
|
462
647
|
async findFormFields(position) {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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);
|
|
467
670
|
}
|
|
468
671
|
// Page Operations
|
|
469
672
|
/**
|
|
470
673
|
* Retrieves references to all pages in the PDF document.
|
|
674
|
+
* Now uses snapshot caching to avoid HTTP requests.
|
|
471
675
|
*/
|
|
472
676
|
async getPages() {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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;
|
|
476
690
|
}
|
|
477
691
|
/**
|
|
478
692
|
* Retrieves a reference to a specific page by its page index.
|
|
693
|
+
* Now uses snapshot caching to avoid HTTP requests.
|
|
479
694
|
*/
|
|
480
695
|
async _getPage(pageIndex) {
|
|
481
696
|
if (pageIndex < 0) {
|
|
482
697
|
throw new exceptions_1.ValidationException(`Page index must be >= 0, got ${pageIndex}`);
|
|
483
698
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
+
}
|
|
489
709
|
}
|
|
490
|
-
|
|
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;
|
|
491
714
|
}
|
|
492
715
|
/**
|
|
493
716
|
* Moves an existing page to a new index.
|
|
@@ -503,6 +726,8 @@ class PDFDancer {
|
|
|
503
726
|
if (!success) {
|
|
504
727
|
throw new exceptions_1.HttpClientException(`Failed to move page from ${pageIndex} to ${targetPageIndex}`, response);
|
|
505
728
|
}
|
|
729
|
+
// Invalidate cache after mutation
|
|
730
|
+
this._invalidateCache();
|
|
506
731
|
// Fetch the page again at its new position for up-to-date metadata
|
|
507
732
|
return await this._requirePageRef(targetPageIndex);
|
|
508
733
|
}
|
|
@@ -512,7 +737,10 @@ class PDFDancer {
|
|
|
512
737
|
async deletePage(pageIndex) {
|
|
513
738
|
this._validatePageIndex(pageIndex, 'pageIndex');
|
|
514
739
|
const pageRef = await this._requirePageRef(pageIndex);
|
|
515
|
-
|
|
740
|
+
const result = await this._deletePage(pageRef);
|
|
741
|
+
// Invalidate cache after mutation
|
|
742
|
+
this._invalidateCache();
|
|
743
|
+
return result;
|
|
516
744
|
}
|
|
517
745
|
_validatePageIndex(pageIndex, fieldName) {
|
|
518
746
|
if (!Number.isInteger(pageIndex)) {
|
|
@@ -540,6 +768,164 @@ class PDFDancer {
|
|
|
540
768
|
const response = await this._makeRequest('DELETE', '/pdf/page/delete', requestData);
|
|
541
769
|
return await response.json();
|
|
542
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
|
+
}
|
|
543
929
|
// Manipulation Operations
|
|
544
930
|
/**
|
|
545
931
|
* Deletes the specified PDF object from the document.
|
|
@@ -550,7 +936,10 @@ class PDFDancer {
|
|
|
550
936
|
}
|
|
551
937
|
const requestData = new models_1.DeleteRequest(objectRef).toDict();
|
|
552
938
|
const response = await this._makeRequest('DELETE', '/pdf/delete', requestData);
|
|
553
|
-
|
|
939
|
+
const result = await response.json();
|
|
940
|
+
// Invalidate cache after mutation
|
|
941
|
+
this._invalidateCache();
|
|
942
|
+
return result;
|
|
554
943
|
}
|
|
555
944
|
/**
|
|
556
945
|
* Moves a PDF object to a new position within the document.
|
|
@@ -564,7 +953,10 @@ class PDFDancer {
|
|
|
564
953
|
}
|
|
565
954
|
const requestData = new models_1.MoveRequest(objectRef, position).toDict();
|
|
566
955
|
const response = await this._makeRequest('PUT', '/pdf/move', requestData);
|
|
567
|
-
|
|
956
|
+
const result = await response.json();
|
|
957
|
+
// Invalidate cache after mutation
|
|
958
|
+
this._invalidateCache();
|
|
959
|
+
return result;
|
|
568
960
|
}
|
|
569
961
|
/**
|
|
570
962
|
* Changes the value of a form field.
|
|
@@ -575,7 +967,10 @@ class PDFDancer {
|
|
|
575
967
|
}
|
|
576
968
|
const requestData = new models_1.ChangeFormFieldRequest(formFieldRef, newValue).toDict();
|
|
577
969
|
const response = await this._makeRequest('PUT', '/pdf/modify/formField', requestData);
|
|
578
|
-
|
|
970
|
+
const result = await response.json();
|
|
971
|
+
// Invalidate cache after mutation
|
|
972
|
+
this._invalidateCache();
|
|
973
|
+
return result;
|
|
579
974
|
}
|
|
580
975
|
// Add Operations
|
|
581
976
|
/**
|
|
@@ -611,13 +1006,28 @@ class PDFDancer {
|
|
|
611
1006
|
}
|
|
612
1007
|
return this._addObject(paragraph);
|
|
613
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
|
+
}
|
|
614
1021
|
/**
|
|
615
1022
|
* Internal method to add any PDF object.
|
|
616
1023
|
*/
|
|
617
1024
|
async _addObject(pdfObject) {
|
|
618
1025
|
const requestData = new models_1.AddRequest(pdfObject).toDict();
|
|
619
1026
|
const response = await this._makeRequest('POST', '/pdf/add', requestData);
|
|
620
|
-
|
|
1027
|
+
const result = await response.json();
|
|
1028
|
+
// Invalidate cache after mutation
|
|
1029
|
+
this._invalidateCache();
|
|
1030
|
+
return result;
|
|
621
1031
|
}
|
|
622
1032
|
// Modify Operations
|
|
623
1033
|
/**
|
|
@@ -630,18 +1040,22 @@ class PDFDancer {
|
|
|
630
1040
|
if (newParagraph === null || newParagraph === undefined) {
|
|
631
1041
|
return models_1.CommandResult.empty("ModifyParagraph", objectRef.internalId);
|
|
632
1042
|
}
|
|
1043
|
+
let result;
|
|
633
1044
|
if (typeof newParagraph === 'string') {
|
|
634
1045
|
// Text modification - returns CommandResult
|
|
635
1046
|
const requestData = new models_1.ModifyTextRequest(objectRef, newParagraph).toDict();
|
|
636
1047
|
const response = await this._makeRequest('PUT', '/pdf/text/paragraph', requestData);
|
|
637
|
-
|
|
1048
|
+
result = models_1.CommandResult.fromDict(await response.json());
|
|
638
1049
|
}
|
|
639
1050
|
else {
|
|
640
1051
|
// Object modification
|
|
641
1052
|
const requestData = new models_1.ModifyRequest(objectRef, newParagraph).toDict();
|
|
642
1053
|
const response = await this._makeRequest('PUT', '/pdf/modify', requestData);
|
|
643
|
-
|
|
1054
|
+
result = models_1.CommandResult.fromDict(await response.json());
|
|
644
1055
|
}
|
|
1056
|
+
// Invalidate cache after mutation
|
|
1057
|
+
this._invalidateCache();
|
|
1058
|
+
return result;
|
|
645
1059
|
}
|
|
646
1060
|
/**
|
|
647
1061
|
* Modifies a text line object.
|
|
@@ -655,7 +1069,10 @@ class PDFDancer {
|
|
|
655
1069
|
}
|
|
656
1070
|
const requestData = new models_1.ModifyTextRequest(objectRef, newText).toDict();
|
|
657
1071
|
const response = await this._makeRequest('PUT', '/pdf/text/line', requestData);
|
|
658
|
-
|
|
1072
|
+
const result = models_1.CommandResult.fromDict(await response.json());
|
|
1073
|
+
// Invalidate cache after mutation
|
|
1074
|
+
this._invalidateCache();
|
|
1075
|
+
return result;
|
|
659
1076
|
}
|
|
660
1077
|
// Font Operations
|
|
661
1078
|
/**
|
|
@@ -711,15 +1128,19 @@ class PDFDancer {
|
|
|
711
1128
|
const formData = new FormData();
|
|
712
1129
|
const blob = new Blob([fontData.buffer], { type: 'font/ttf' });
|
|
713
1130
|
formData.append('ttfFile', blob, filename);
|
|
1131
|
+
const fingerprint = await this._getFingerprint();
|
|
714
1132
|
const response = await fetch(this._buildUrl('/font/register'), {
|
|
715
1133
|
method: 'POST',
|
|
716
1134
|
headers: {
|
|
717
1135
|
'Authorization': `Bearer ${this._token}`,
|
|
718
|
-
'X-Session-Id': this._sessionId
|
|
1136
|
+
'X-Session-Id': this._sessionId,
|
|
1137
|
+
'X-Generated-At': generateTimestamp(),
|
|
1138
|
+
'X-Fingerprint': fingerprint
|
|
719
1139
|
},
|
|
720
1140
|
body: formData,
|
|
721
1141
|
signal: AbortSignal.timeout(30000)
|
|
722
1142
|
});
|
|
1143
|
+
logGeneratedAtHeader(response, 'POST', '/font/register');
|
|
723
1144
|
if (!response.ok) {
|
|
724
1145
|
const errorMessage = await this._extractErrorMessage(response);
|
|
725
1146
|
throw new exceptions_1.HttpClientException(`Font registration failed: ${errorMessage}`, response);
|
|
@@ -773,11 +1194,22 @@ class PDFDancer {
|
|
|
773
1194
|
if (this._isTextObjectData(objData, objectType)) {
|
|
774
1195
|
return this._parseTextObjectRef(objData);
|
|
775
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
|
+
}
|
|
776
1207
|
return new models_1.ObjectRef(objData.internalId, position, objectType);
|
|
777
1208
|
}
|
|
778
1209
|
_isTextObjectData(objData, objectType) {
|
|
779
1210
|
return objectType === models_1.ObjectType.PARAGRAPH ||
|
|
780
1211
|
objectType === models_1.ObjectType.TEXT_LINE ||
|
|
1212
|
+
objectType === models_1.ObjectType.TEXT_ELEMENT ||
|
|
781
1213
|
typeof objData.text === 'string' ||
|
|
782
1214
|
typeof objData.fontName === 'string' ||
|
|
783
1215
|
Array.isArray(objData.children);
|
|
@@ -806,10 +1238,16 @@ class PDFDancer {
|
|
|
806
1238
|
}
|
|
807
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);
|
|
808
1240
|
if (Array.isArray(objData.children) && objData.children.length > 0) {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
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
|
+
}
|
|
813
1251
|
}
|
|
814
1252
|
return textObject;
|
|
815
1253
|
}
|
|
@@ -887,6 +1325,47 @@ class PDFDancer {
|
|
|
887
1325
|
}
|
|
888
1326
|
return position;
|
|
889
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
|
+
}
|
|
890
1369
|
// Builder Pattern Support
|
|
891
1370
|
toPathObjects(objectRefs) {
|
|
892
1371
|
return objectRefs.map(ref => types_1.PathObject.fromRef(this, ref));
|
|
@@ -897,8 +1376,14 @@ class PDFDancer {
|
|
|
897
1376
|
toImageObjects(objectRefs) {
|
|
898
1377
|
return objectRefs.map(ref => types_1.ImageObject.fromRef(this, ref));
|
|
899
1378
|
}
|
|
900
|
-
newImage() {
|
|
901
|
-
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);
|
|
902
1387
|
}
|
|
903
1388
|
page(pageIndex) {
|
|
904
1389
|
if (pageIndex < 0) {
|
|
@@ -913,18 +1398,37 @@ class PDFDancer {
|
|
|
913
1398
|
toFormFields(objectRefs) {
|
|
914
1399
|
return objectRefs.map(ref => types_1.FormFieldObject.fromRef(this, ref));
|
|
915
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
|
+
}
|
|
916
1409
|
async selectParagraphs() {
|
|
917
1410
|
return this.toParagraphObjects(await this.findParagraphs());
|
|
918
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
|
+
}
|
|
919
1420
|
toParagraphObjects(objectRefs) {
|
|
920
1421
|
return objectRefs.map(ref => types_1.ParagraphObject.fromRef(this, ref));
|
|
921
1422
|
}
|
|
922
1423
|
toTextLineObjects(objectRefs) {
|
|
923
1424
|
return objectRefs.map(ref => types_1.TextLineObject.fromRef(this, ref));
|
|
924
1425
|
}
|
|
925
|
-
async
|
|
1426
|
+
async selectTextLines() {
|
|
926
1427
|
return this.toTextLineObjects(await this.findTextLines());
|
|
927
1428
|
}
|
|
1429
|
+
async selectLines() {
|
|
1430
|
+
return this.selectTextLines();
|
|
1431
|
+
}
|
|
928
1432
|
}
|
|
929
1433
|
exports.PDFDancer = PDFDancer;
|
|
930
1434
|
//# sourceMappingURL=pdfdancer_v1.js.map
|