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.
Files changed (68) hide show
  1. package/.claude/commands/discuss.md +4 -0
  2. package/.github/workflows/ci.yml +2 -2
  3. package/README.md +2 -2
  4. package/dist/__tests__/e2e/pdf-assertions.d.ts +1 -0
  5. package/dist/__tests__/e2e/pdf-assertions.d.ts.map +1 -1
  6. package/dist/__tests__/e2e/pdf-assertions.js +9 -3
  7. package/dist/__tests__/e2e/pdf-assertions.js.map +1 -1
  8. package/dist/fingerprint.d.ts +12 -0
  9. package/dist/fingerprint.d.ts.map +1 -0
  10. package/dist/fingerprint.js +196 -0
  11. package/dist/fingerprint.js.map +1 -0
  12. package/dist/image-builder.d.ts +4 -2
  13. package/dist/image-builder.d.ts.map +1 -1
  14. package/dist/image-builder.js +12 -3
  15. package/dist/image-builder.js.map +1 -1
  16. package/dist/index.d.ts +2 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +7 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/models.d.ts +75 -8
  21. package/dist/models.d.ts.map +1 -1
  22. package/dist/models.js +179 -21
  23. package/dist/models.js.map +1 -1
  24. package/dist/page-builder.d.ts +24 -0
  25. package/dist/page-builder.d.ts.map +1 -0
  26. package/dist/page-builder.js +107 -0
  27. package/dist/page-builder.js.map +1 -0
  28. package/dist/paragraph-builder.d.ts +48 -54
  29. package/dist/paragraph-builder.d.ts.map +1 -1
  30. package/dist/paragraph-builder.js +408 -135
  31. package/dist/paragraph-builder.js.map +1 -1
  32. package/dist/pdfdancer_v1.d.ts +90 -9
  33. package/dist/pdfdancer_v1.d.ts.map +1 -1
  34. package/dist/pdfdancer_v1.js +559 -55
  35. package/dist/pdfdancer_v1.js.map +1 -1
  36. package/dist/types.d.ts +24 -3
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/types.js +117 -2
  39. package/dist/types.js.map +1 -1
  40. package/docs/openapi.yml +2076 -0
  41. package/fixtures/Showcase.pdf +0 -0
  42. package/jest.config.js +1 -1
  43. package/package.json +1 -1
  44. package/src/__tests__/e2e/acroform.test.ts +5 -5
  45. package/src/__tests__/e2e/context-manager-showcase.test.ts +267 -0
  46. package/src/__tests__/e2e/form_x_object.test.ts +1 -1
  47. package/src/__tests__/e2e/image-showcase.test.ts +133 -0
  48. package/src/__tests__/e2e/image.test.ts +1 -1
  49. package/src/__tests__/e2e/line-showcase.test.ts +118 -0
  50. package/src/__tests__/e2e/line.test.ts +1 -16
  51. package/src/__tests__/e2e/page-showcase.test.ts +154 -0
  52. package/src/__tests__/e2e/paragraph-showcase.test.ts +523 -0
  53. package/src/__tests__/e2e/paragraph.test.ts +8 -8
  54. package/src/__tests__/e2e/pdf-assertions.ts +10 -3
  55. package/src/__tests__/e2e/pdfdancer-showcase.test.ts +40 -0
  56. package/src/__tests__/e2e/snapshot-showcase.test.ts +158 -0
  57. package/src/__tests__/e2e/snapshot.test.ts +296 -0
  58. package/src/__tests__/e2e/token_from_env.test.ts +85 -25
  59. package/src/__tests__/fingerprint.test.ts +36 -0
  60. package/src/fingerprint.ts +169 -0
  61. package/src/image-builder.ts +13 -6
  62. package/src/index.ts +6 -1
  63. package/src/models.ts +208 -24
  64. package/src/page-builder.ts +130 -0
  65. package/src/paragraph-builder.ts +517 -159
  66. package/src/pdfdancer_v1.ts +662 -58
  67. package/src/types.ts +145 -2
  68. package/update-api-spec.sh +3 -0
@@ -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
- return new paragraph_builder_1.ParagraphBuilder(this._client, this.position.pageIndex);
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 = "http://localhost:8080", readTimeout = 30000) {
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 = baseUrl.replace(/\/$/, ''); // Remove trailing slash
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 Error("Missing PDFDancer token (pass it explicitly or set PDFDANCER_TOKEN in environment).");
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 Error("Missing PDFDancer token (pass it explicitly or set PDFDANCER_TOKEN in environment).");
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
- const requestData = new models_1.FindRequest(objectType, position).toDict();
409
- const response = await this._makeRequest('POST', '/pdf/find', requestData);
410
- const objectsData = await response.json();
411
- return objectsData.map((objData) => this._parseObjectRef(objData));
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
- const requestData = new models_1.FindRequest(models_1.ObjectType.FORM_FIELD, position).toDict();
464
- const response = await this._makeRequest('POST', '/pdf/find', requestData);
465
- const objectsData = await response.json();
466
- return objectsData.map((objData) => this._parseFormFieldRef(objData));
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
- const response = await this._makeRequest('POST', '/pdf/page/find');
474
- const pagesData = await response.json();
475
- return pagesData.map((pageData) => this._parsePageRef(pageData));
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
- const params = { pageIndex: pageIndex.toString() };
485
- const response = await this._makeRequest('POST', '/pdf/page/find', undefined, params);
486
- const pagesData = await response.json();
487
- if (!pagesData || pagesData.length === 0) {
488
- return null;
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
- return this._parsePageRef(pagesData[0]);
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
- return this._deletePage(pageRef);
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
- return await response.json();
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
- return await response.json();
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
- return await response.json();
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
- return await response.json();
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
- return models_1.CommandResult.fromDict(await response.json());
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
- return models_1.CommandResult.fromDict(await response.json());
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
- return models_1.CommandResult.fromDict(await response.json());
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
- textObject.children = objData.children.map((childData, index) => {
810
- const childFallbackId = `${internalId || 'child'}-${index}`;
811
- return this._parseTextObjectRef(childData, childFallbackId);
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 selectLines() {
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