@signiphi/page-assembly 0.2.0-beta.2 → 0.2.0-beta.20

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/dist/index.d.mts CHANGED
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
  import React__default from 'react';
3
3
  import * as class_variance_authority_types from 'class-variance-authority/types';
4
4
  import { VariantProps } from 'class-variance-authority';
5
+ import { PDFDocument } from 'pdf-lib';
5
6
 
6
7
  /**
7
8
  * Represents a single page in the page assembly system
@@ -39,6 +40,59 @@ type FileInfo = {
39
40
  /** Pages extracted from this file */
40
41
  pages: PageInfo[];
41
42
  };
43
+ /**
44
+ * Form field with page tracking for assembly operations
45
+ */
46
+ type TrackedFormField = {
47
+ /** Field name */
48
+ name: string;
49
+ /** Field type */
50
+ type: string;
51
+ /** Field position */
52
+ position: {
53
+ x: number;
54
+ y: number;
55
+ width: number;
56
+ height: number;
57
+ page: number;
58
+ };
59
+ /** Page ID this field is associated with (for tracking across page operations) */
60
+ pageId?: string;
61
+ /** Original file ID this field came from */
62
+ originalFileId?: string;
63
+ /** All other field properties */
64
+ label?: string;
65
+ required?: boolean;
66
+ options?: string[];
67
+ /** RADIO: each option's circle rect, top-origin, on `position.page`. */
68
+ optionPositions?: Array<{
69
+ x: number;
70
+ y: number;
71
+ width: number;
72
+ height: number;
73
+ }>;
74
+ placeholder?: string;
75
+ defaultValue?: string;
76
+ assignedSignerEmail?: string;
77
+ fieldId?: string;
78
+ fontSize?: number;
79
+ multiline?: boolean;
80
+ maxLength?: number;
81
+ acknowledgements?: Array<{
82
+ id: string;
83
+ title: string;
84
+ description: string;
85
+ }>;
86
+ };
87
+ /**
88
+ * Extracted fields from a file
89
+ */
90
+ type FileExtractedFields = {
91
+ /** File ID these fields came from */
92
+ fileId: string;
93
+ /** Extracted form fields */
94
+ fields: TrackedFormField[];
95
+ };
42
96
  /**
43
97
  * State of the page assembler
44
98
  */
@@ -49,6 +103,10 @@ type PageAssemblerState = {
49
103
  selection: string[];
50
104
  /** ID of the last selected page (for range selection) */
51
105
  lastSelectedPageId?: string;
106
+ /** Form fields tracked across page operations */
107
+ formFields?: TrackedFormField[];
108
+ /** Extracted fields per file (for mapping after assembly) */
109
+ extractedFieldsByFile?: FileExtractedFields[];
52
110
  };
53
111
  /**
54
112
  * Options for assembling the final PDF
@@ -136,6 +194,18 @@ declare const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAtt
136
194
  declare function pdfToImages(pdfBytes: Uint8Array, options?: {
137
195
  hideFormFields?: boolean;
138
196
  }): Promise<PageImage[]>;
197
+ declare function rasterizePdfToPdf(pdfBytes: Uint8Array): Promise<Uint8Array>;
198
+ /**
199
+ * Prepares uploaded PDF bytes for the pdf-lib based assembly pipeline.
200
+ * Regular PDFs pass through untouched. Encrypted PDFs (typically
201
+ * permission-restricted documents that open without a password) are
202
+ * decrypted once here so every downstream `PDFDocument.load` — assembly,
203
+ * field extraction — sees a normal document. When structural decryption is
204
+ * impossible the document is rebuilt from rendered pages as a last resort.
205
+ *
206
+ * @throws {PdfPasswordRequiredError} when the PDF needs a real password
207
+ */
208
+ declare function normalizeUploadedPdfBytes(pdfBytes: Uint8Array): Promise<Uint8Array>;
139
209
  /**
140
210
  * Converts an image file to a PDF document with a single page
141
211
  *
@@ -175,4 +245,280 @@ declare function createPdfBlobUrl(pdfBytes: Uint8Array): string;
175
245
  */
176
246
  declare function downloadPdf(pdfBytes: Uint8Array, filename: string): void;
177
247
 
178
- export { type AssembleOptions, Button, type FileInfo, PageAssembler, type PageAssemblerHandle, type PageAssemblerProps, type PageAssemblerState, type PageImage, type PageInfo, createPdfBlobUrl, PageAssembler as default, downloadPdf, imageToPdf, pdfToImages };
248
+ interface PdfPasswordRequiredErrorConstructor {
249
+ new (): PdfPasswordRequiredError;
250
+ readonly prototype: PdfPasswordRequiredError;
251
+ }
252
+ /**
253
+ * Thrown when the PDF needs a non-empty user password, which this package
254
+ * cannot supply. Match it with {@link isPdfPasswordRequiredError} rather than
255
+ * `instanceof` — see the header note on copies.
256
+ */
257
+ interface PdfPasswordRequiredError extends Error {
258
+ /** Always `'PDF_PASSWORD_REQUIRED'`. The check that survives package boundaries. */
259
+ code: string;
260
+ recoverable: boolean;
261
+ userMessage: string;
262
+ }
263
+ declare const PdfPasswordRequiredError: PdfPasswordRequiredErrorConstructor;
264
+ interface UnsupportedPdfEncryptionErrorConstructor {
265
+ new (detail: string): UnsupportedPdfEncryptionError;
266
+ readonly prototype: UnsupportedPdfEncryptionError;
267
+ }
268
+ /** Thrown for security handlers this decryptor does not implement. */
269
+ interface UnsupportedPdfEncryptionError extends Error {
270
+ /** Always `'UnsupportedPdfEncryptionError'`. */
271
+ name: string;
272
+ /** The scheme that is not supported, e.g. `'V5 /R6'`. */
273
+ readonly detail: string;
274
+ }
275
+ declare const UnsupportedPdfEncryptionError: UnsupportedPdfEncryptionErrorConstructor;
276
+ /**
277
+ * Decrypt an encrypted PDF that opens with an empty user password.
278
+ * Returns re-saved, fully decrypted bytes with `/Encrypt` removed.
279
+ *
280
+ * @throws {PdfPasswordRequiredError} when a non-empty password is required
281
+ * @throws {UnsupportedPdfEncryptionError} for non-standard security handlers
282
+ */
283
+ declare const decryptPdf: (pdfBytes: Uint8Array) => Promise<Uint8Array>;
284
+ /**
285
+ * Ingestion gate — see `@signiphi/pdf-crypto`'s `normalizePdfEncryption`.
286
+ * Returns the bytes untouched (same reference) for ordinary PDFs; decrypts and
287
+ * re-saves encrypted ones so every downstream `PDFDocument.load` (page
288
+ * assembly, field extraction) sees a normal document.
289
+ *
290
+ * @throws {PdfPasswordRequiredError} when a non-empty password is required
291
+ * @throws {UnsupportedPdfEncryptionError} for non-standard security handlers
292
+ */
293
+ declare function normalizePdfEncryption(pdfBytes: Uint8Array): Promise<Uint8Array>;
294
+
295
+ /**
296
+ * PDF Field Extraction Utilities
297
+ * Extract form fields from PDFs with metadata support for preserving field properties
298
+ */
299
+
300
+ /**
301
+ * Extracted field information
302
+ */
303
+ interface ExtractedField {
304
+ name: string;
305
+ type: 'text' | 'signature' | 'initials' | 'date' | 'date_signed' | 'checkbox' | 'radio' | 'dropdown' | 'text_label';
306
+ page: number;
307
+ x: number;
308
+ y: number;
309
+ width: number;
310
+ height: number;
311
+ required: boolean;
312
+ label?: string;
313
+ placeholder?: string;
314
+ options?: string[];
315
+ fieldId?: string;
316
+ signer?: string;
317
+ /**
318
+ * RADIO: each option's circle widget rect on this page, top-origin, in
319
+ * option order — exactly as read, so a re-add can put every circle back
320
+ * where it was. `x/y/width/height` above is only the first circle.
321
+ */
322
+ optionPositions?: Array<{
323
+ x: number;
324
+ y: number;
325
+ width: number;
326
+ height: number;
327
+ }>;
328
+ }
329
+ /**
330
+ * One field's metadata entry, as stored in the block.
331
+ */
332
+ interface SigniphiFieldMetadata {
333
+ fieldId?: string;
334
+ /**
335
+ * Persisted field type. Read for one value: `date_signed`, which the name
336
+ * guess can only see as a date. Every other key (`autofillTag`, `dateFormat`,
337
+ * …) is carried through the merge untouched without being declared here.
338
+ */
339
+ type?: ExtractedField['type'];
340
+ label?: string;
341
+ signer?: string;
342
+ placeholder?: string;
343
+ required?: boolean;
344
+ options?: string[];
345
+ acknowledgements?: Array<{
346
+ id: string;
347
+ title: string;
348
+ description: string;
349
+ }>;
350
+ /**
351
+ * TEXT_LABEL only: this entry's text is already in the page stream, so the
352
+ * sign-time pass must not redraw it. See `mergeSourceMetadata`, which sets it
353
+ * when a source that had baked its labels is merged with one that deferred.
354
+ */
355
+ textLabelBaked?: boolean;
356
+ /**
357
+ * CHECKBOX only: this field's caption is already in the page stream, written
358
+ * by document-prepare's bake. Declared so the merge carries it through — it
359
+ * is what pdf-signer's `checkboxLabelSource` actually decides on, the
360
+ * document-level `checkboxLabelsBaked` below being only the bake's mode.
361
+ * Nothing here sets or reads it.
362
+ */
363
+ checkboxCaptionBaked?: boolean;
364
+ /**
365
+ * Persisted geometry, written by `document-prepare` for RADIO and TEXT_LABEL
366
+ * fields only. Declared here for ONE reason: its `page` is the SOURCE
367
+ * document's page number, and the merge has to rewrite it to the combined
368
+ * one. Nothing in page-assembly reads x/y/width/height.
369
+ */
370
+ position?: SigniphiFieldPosition;
371
+ /** Radio-only, per-option. Re-paged alongside {@link SigniphiFieldMetadata.position}. */
372
+ radioPositions?: SigniphiFieldPosition[];
373
+ /** Radio-only bound list (options ↔ circle positions); its pages are re-paged too. */
374
+ radioOptions?: Array<{
375
+ label: string;
376
+ exportValue: string;
377
+ position: SigniphiFieldPosition;
378
+ }>;
379
+ }
380
+ /**
381
+ * A persisted field rect, as `document-prepare` writes it. `page` is 1-indexed
382
+ * and scoped to the document it was written in — which is exactly why the merge
383
+ * cannot carry it across unchanged.
384
+ */
385
+ interface SigniphiFieldPosition {
386
+ x: number;
387
+ y: number;
388
+ width: number;
389
+ height: number;
390
+ page: number;
391
+ }
392
+ /**
393
+ * SigniphiMetadata structure stored in PDF Info dictionary.
394
+ *
395
+ * Exported because `getSigniphiMetadata` / `setSigniphiMetadata` are public
396
+ * (`utils/index.ts`) and a caller cannot name their argument otherwise — which
397
+ * is what pushed the call sites here into `as never` casts that would have
398
+ * silenced any future shape mismatch.
399
+ */
400
+ interface SigniphiMetadata {
401
+ version: string;
402
+ fields: Record<string, SigniphiFieldMetadata>;
403
+ fieldIdIndex?: Record<string, string>;
404
+ /**
405
+ * Document-level: radio group and option text is already drawn into the page
406
+ * stream, so the sign-time pass must not redraw it. Absent on legacy PDFs,
407
+ * which pdf-signer reads as "draw".
408
+ */
409
+ radioLabelsBaked?: boolean;
410
+ /**
411
+ * Document-level: `false` means TEXT_LABEL text was deliberately NOT baked
412
+ * (multi-signer), so the sign-time pass redraws it per signer. Absent/`true`
413
+ * means it is already in the page stream.
414
+ */
415
+ textLabelsBaked?: boolean;
416
+ /**
417
+ * Document-level: `false` means CHECKBOX captions were deliberately NOT baked
418
+ * beside their boxes (multi-signer), so the sign-time pass draws them per
419
+ * signer. `true` means they are already in the page stream. Absent means the
420
+ * source predates baked captions, which pdf-signer also reads as "draw".
421
+ */
422
+ checkboxLabelsBaked?: boolean;
423
+ }
424
+ /**
425
+ * Extract SigniphiMetadata from a PDF document. Delegates to the contract
426
+ * package's reader; null when there is no block or it will not parse.
427
+ */
428
+ declare function getSigniphiMetadata(pdfDoc: PDFDocument): SigniphiMetadata | null;
429
+ /**
430
+ * Extract form fields from a PDF document
431
+ * @param pdfBytes - PDF bytes
432
+ * @returns Array of extracted fields with all properties
433
+ */
434
+ declare function extractFieldsFromPdf(pdfBytes: Uint8Array): Promise<ExtractedField[]>;
435
+ /**
436
+ * Set SigniphiMetadata in a PDF document. Delegates to the contract package's
437
+ * writer, which stores hex and throws on failure.
438
+ */
439
+ declare function setSigniphiMetadata(pdfDoc: PDFDocument, metadata: SigniphiMetadata): void;
440
+
441
+ /**
442
+ * Add Form Fields to PDF
443
+ * Re-adds form fields to a PDF after page assembly operations
444
+ * This is the key to preserving form fields - don't try to preserve during copyPages,
445
+ * instead extract fields first, copy pages, then re-add fields with adjusted positions.
446
+ */
447
+ /**
448
+ * Form field type enum matching document-prepare
449
+ */
450
+ declare enum FormFieldType {
451
+ TEXT = "text",
452
+ SIGNATURE = "signature",
453
+ INITIALS = "initials",
454
+ DATE = "date",
455
+ CHECKBOX = "checkbox",
456
+ RADIO = "radio",
457
+ DROPDOWN = "dropdown",
458
+ TEXT_LABEL = "text_label",
459
+ /** A DATE the signer never edits: stamped at signing. Re-added exactly like DATE. */
460
+ DATE_SIGNED = "date_signed"
461
+ }
462
+ /**
463
+ * Form field position
464
+ */
465
+ interface FormFieldPosition {
466
+ x: number;
467
+ y: number;
468
+ width: number;
469
+ height: number;
470
+ page: number;
471
+ }
472
+ /**
473
+ * Form field for adding to PDF
474
+ */
475
+ interface FormFieldForPdf {
476
+ name: string;
477
+ type: FormFieldType | string;
478
+ position: FormFieldPosition;
479
+ label?: string;
480
+ required?: boolean;
481
+ options?: string[];
482
+ defaultValue?: string;
483
+ placeholder?: string;
484
+ fontSize?: number;
485
+ multiline?: boolean;
486
+ maxLength?: number;
487
+ assignedSignerEmail?: string;
488
+ /**
489
+ * RADIO: each option's circle widget rect, top-origin, on `position.page`,
490
+ * as `extractFieldsFromPdf` read it. When it covers every option, the circles
491
+ * are re-added exactly there instead of being laid out afresh in `position`.
492
+ */
493
+ optionPositions?: Array<{
494
+ x: number;
495
+ y: number;
496
+ width: number;
497
+ height: number;
498
+ }>;
499
+ }
500
+ /**
501
+ * Add form fields to a PDF document
502
+ *
503
+ * @param pdfBytes - The PDF document bytes
504
+ * @param formFields - Array of form fields to add
505
+ * @param options - Options for field addition
506
+ * @returns Modified PDF bytes with form fields added
507
+ */
508
+ declare function addFormFieldsToPdf(pdfBytes: Uint8Array, formFields: FormFieldForPdf[], options?: {
509
+ removeExistingFields?: boolean;
510
+ drawLabels?: boolean;
511
+ }): Promise<Uint8Array>;
512
+ /**
513
+ * Map field positions after page assembly
514
+ * Updates field page numbers based on the final page arrangement
515
+ *
516
+ * @param fields - Original fields with positions
517
+ * @param pageMapping - Map of "originalFileId_originalPageIndex" -> newPageNumber
518
+ * @returns Fields with updated page positions
519
+ */
520
+ declare function mapFieldPositionsAfterAssembly<T extends {
521
+ position: FormFieldPosition;
522
+ }>(fields: T[], pageMapping: Map<string, number>): T[];
523
+
524
+ export { type AssembleOptions, Button, type ExtractedField, type FileExtractedFields, type FileInfo, type FormFieldForPdf, type FormFieldPosition, FormFieldType, PageAssembler, type PageAssemblerHandle, type PageAssemblerProps, type PageAssemblerState, type PageImage, type PageInfo, PdfPasswordRequiredError, type TrackedFormField, UnsupportedPdfEncryptionError, addFormFieldsToPdf, createPdfBlobUrl, decryptPdf, downloadPdf, extractFieldsFromPdf, getSigniphiMetadata, imageToPdf, mapFieldPositionsAfterAssembly, normalizePdfEncryption, normalizeUploadedPdfBytes, pdfToImages, rasterizePdfToPdf, setSigniphiMetadata };