pdfdancer-client-typescript 1.0.12 → 1.0.13

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