modern-pdf-lib 0.25.0 → 0.26.0

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 (69) hide show
  1. package/README.md +1 -1
  2. package/dist/{batchOptimize-DtRwBOqR.mjs → batchOptimize-BCJEEN8J.mjs} +4 -4
  3. package/dist/{batchOptimize-Ba_pWw71.cjs → batchOptimize-C1W3O68Q.cjs} +3 -3
  4. package/dist/{bridge-DYCQzxF7.cjs → bridge-DMEuGtfn.cjs} +2 -2
  5. package/dist/{bridge-CcivG_Sm.mjs → bridge-DaS-gzEd.mjs} +3 -3
  6. package/dist/browser.cjs +31 -10
  7. package/dist/browser.d.cts +5 -5
  8. package/dist/browser.d.mts +5 -5
  9. package/dist/browser.mjs +12 -12
  10. package/dist/cli/index.cjs +2 -2
  11. package/dist/cli/index.mjs +3 -3
  12. package/dist/{compressionAnalysis-B84FPXaQ.cjs → compressionAnalysis-B-FPzgzw.cjs} +7 -7
  13. package/dist/{compressionAnalysis-DGs-MqTe.d.mts → compressionAnalysis-Ch7t-HXn.d.mts} +2 -2
  14. package/dist/{compressionAnalysis-DGs-MqTe.d.mts.map → compressionAnalysis-Ch7t-HXn.d.mts.map} +1 -1
  15. package/dist/{compressionAnalysis-odbHC7Uk.mjs → compressionAnalysis-CwknBtqx.mjs} +7 -7
  16. package/dist/{compressionAnalysis-VtYV9Zmq.d.cts → compressionAnalysis-Dgv1TtHJ.d.cts} +2 -2
  17. package/dist/{compressionAnalysis-VtYV9Zmq.d.cts.map → compressionAnalysis-Dgv1TtHJ.d.cts.map} +1 -1
  18. package/dist/create.cjs +3 -3
  19. package/dist/create.d.cts +2 -2
  20. package/dist/create.d.mts +2 -2
  21. package/dist/create.mjs +3 -3
  22. package/dist/{deduplicateImages-MfUDPxQz.mjs → deduplicateImages-DIon68zB.mjs} +2 -2
  23. package/dist/{fflateAdapter-PSiW_ML7.mjs → fflateAdapter-DuNiByKx.mjs} +2 -2
  24. package/dist/{fontEmbed-BN842wlb.d.cts → fontEmbed-3YhUPLFj.d.cts} +2 -2
  25. package/dist/{fontEmbed-BN842wlb.d.cts.map → fontEmbed-3YhUPLFj.d.cts.map} +1 -1
  26. package/dist/{fontEmbed-Dgq5K89o.d.mts → fontEmbed-DlVnVCZU.d.mts} +2 -2
  27. package/dist/{fontEmbed-Dgq5K89o.d.mts.map → fontEmbed-DlVnVCZU.d.mts.map} +1 -1
  28. package/dist/{fontSubset-D-vQQems.mjs → fontSubset-BGFDIMmT.mjs} +2 -2
  29. package/dist/forms.cjs +1 -1
  30. package/dist/forms.mjs +1 -1
  31. package/dist/{index-DCSbmXWh.d.mts → index-B4S61WjK.d.mts} +589 -21
  32. package/dist/index-B4S61WjK.d.mts.map +1 -0
  33. package/dist/{index-J1W3FdL8.d.cts → index-xfJP6Ycm.d.cts} +589 -21
  34. package/dist/index-xfJP6Ycm.d.cts.map +1 -0
  35. package/dist/index.cjs +31 -10
  36. package/dist/index.d.cts +5 -5
  37. package/dist/index.d.mts +5 -5
  38. package/dist/index.mjs +12 -12
  39. package/dist/{layout-D6EUKSP8.mjs → layout-CrqeJBMI.mjs} +3 -3
  40. package/dist/{layout-DH61a1iR.cjs → layout-DQh05VP-.cjs} +3 -3
  41. package/dist/{libdeflateWasm-8b91Vmia.mjs → libdeflateWasm-CcA1W04G.mjs} +3 -3
  42. package/dist/{libdeflateWasm-BdiDEJOj.cjs → libdeflateWasm-DLw-I1CY.cjs} +2 -2
  43. package/dist/{loader-C7B5dVCI.mjs → loader-CVB-c_3Z.mjs} +16 -6
  44. package/dist/{loader-I4zdkoWc.cjs → loader-DYCH3n7d.cjs} +15 -5
  45. package/dist/parse.cjs +2 -2
  46. package/dist/parse.d.cts +2 -2
  47. package/dist/parse.d.mts +2 -2
  48. package/dist/parse.mjs +2 -2
  49. package/dist/{pdfCatalog-CYy4NXEY.cjs → pdfCatalog-Bqq4FiLn.cjs} +2 -1
  50. package/dist/{pdfCatalog-3yMIhJtt.mjs → pdfCatalog-CnKMAtIo.mjs} +3 -2
  51. package/dist/{pdfDocument-CbU-2TjT.d.cts → pdfDocument-Bc_vAO74.d.cts} +257 -172
  52. package/dist/pdfDocument-Bc_vAO74.d.cts.map +1 -0
  53. package/dist/{pdfDocument-BgvEP5Po.d.mts → pdfDocument-Bi-NoyXv.d.mts} +257 -172
  54. package/dist/pdfDocument-Bi-NoyXv.d.mts.map +1 -0
  55. package/dist/{pdfDocument-B0_XwS4X.mjs → pdfDocument-D7aFSQEY.mjs} +219 -40
  56. package/dist/{pdfDocument-CEbbUP9i.cjs → pdfDocument-D9uYNSb-.cjs} +233 -36
  57. package/dist/{pdfForm-9gd40uz9.cjs → pdfForm-BpqDGp29.cjs} +7 -1
  58. package/dist/{pdfForm-Cn-cVicP.mjs → pdfForm-C3mC9FoQ.mjs} +2 -2
  59. package/dist/{pdfPage-B_d9HmkG.mjs → pdfPage-BJIE5hc6.mjs} +189 -24
  60. package/dist/{pdfPage-Cd8jOJp6.cjs → pdfPage-C_tjEjj1.cjs} +188 -23
  61. package/dist/{pngEmbed-BWAbEUKF.mjs → pngEmbed-CGi7cym7.mjs} +3 -3
  62. package/dist/{pngEmbed-D4X4ZN-3.cjs → pngEmbed-CTn9IeNB.cjs} +2 -2
  63. package/dist/{src-Db6Qknoz.mjs → src-QZWP21qF.mjs} +1738 -157
  64. package/dist/{src-B1iDGLCL.cjs → src-lFqJHb1C.cjs} +1874 -185
  65. package/package.json +2 -1
  66. package/dist/index-DCSbmXWh.d.mts.map +0 -1
  67. package/dist/index-J1W3FdL8.d.cts.map +0 -1
  68. package/dist/pdfDocument-BgvEP5Po.d.mts.map +0 -1
  69. package/dist/pdfDocument-CbU-2TjT.d.cts.map +0 -1
@@ -1,6 +1,8 @@
1
- import { A as buildAnnotationDict, Bt as rectangle, Gt as setLineWidth, Jt as beginText, K as restoreState, O as PdfAnnotation, Pt as fill, Xt as moveText, Y as saveState, Yt as endText, en as setFont, et as applyFillColor, qt as stroke, sn as showText, tt as applyStrokeColor } from "./pdfPage-B_d9HmkG.mjs";
1
+ import { A as buildAnnotationDict, Bt as rectangle, Gt as setLineWidth, Jt as beginText, K as restoreState, O as PdfAnnotation, Pt as fill, Xt as moveText, Y as saveState, Yt as endText, en as setFont, et as applyFillColor, qt as stroke, sn as showText, tt as applyStrokeColor } from "./pdfPage-BJIE5hc6.mjs";
2
2
  import { i as PdfName, l as PdfStream, n as PdfBool, o as PdfNumber, r as PdfDict, t as PdfArray, u as PdfString } from "./pdfObjects-uEsWlfzU.mjs";
3
- import { A as parseDerTlv, E as encodeUTCTime, O as extractIssuerAndSerial, P as findSignatures, S as encodeOctetString, T as encodeSet, _ as detectNamedCurve, g as detectKeyAlgorithm, h as decodeOidBytes, j as toBuffer, k as getSubtle, v as encodeContextTag, w as encodeSequence, x as encodeOID, y as encodeInteger } from "./pdfDocument-B0_XwS4X.mjs";
3
+ import { B as mergePdfs, D as encodeSequence, L as findSignatures, M as getSubtle, N as parseDerTlv, O as encodeSet, P as toBuffer, S as encodeInteger, T as encodeOctetString, b as detectNamedCurve, j as extractIssuerAndSerial, k as encodeUTCTime, r as createPdf, t as PdfDocument, v as decodeOidBytes, w as encodeOID, x as encodeContextTag, y as detectKeyAlgorithm } from "./pdfDocument-D7aFSQEY.mjs";
4
+ import { v as numVal } from "./pdfForm-C3mC9FoQ.mjs";
5
+ import { r as detectRuntime } from "./loader-CVB-c_3Z.mjs";
4
6
  import { deflateSync } from "fflate";
5
7
 
6
8
  //#region src/core/incrementalWriter.ts
@@ -274,8 +276,8 @@ function compressStream(stream, level) {
274
276
  * @returns The incremental save result.
275
277
  */
276
278
  async function saveDocumentIncremental(originalBytes, doc, options) {
277
- const { buildDocumentStructure } = await import("./pdfCatalog-3yMIhJtt.mjs").then((n) => n.o);
278
- const { PdfPage: _PdfPage } = await import("./pdfPage-B_d9HmkG.mjs").then((n) => n.r);
279
+ const { buildDocumentStructure } = await import("./pdfCatalog-CnKMAtIo.mjs").then((n) => n.o);
280
+ const { PdfPage: _PdfPage } = await import("./pdfPage-BJIE5hc6.mjs").then((n) => n.r);
279
281
  const registry = doc.getRegistry();
280
282
  const structure = buildDocumentStructure(doc.getInternalPages().map((p) => p.finalize()), {
281
283
  producer: doc.getProducer(),
@@ -342,6 +344,457 @@ var PDFOperator = class PDFOperator {
342
344
  }
343
345
  };
344
346
 
347
+ //#endregion
348
+ //#region src/accessibility/pdfUaValidator.ts
349
+ /** Heading elements for hierarchy validation. */
350
+ const HEADING_TYPES = new Set([
351
+ "H1",
352
+ "H2",
353
+ "H3",
354
+ "H4",
355
+ "H5",
356
+ "H6"
357
+ ]);
358
+ /** Heading level by type name. */
359
+ const HEADING_LEVEL = {
360
+ H1: 1,
361
+ H2: 2,
362
+ H3: 3,
363
+ H4: 4,
364
+ H5: 5,
365
+ H6: 6
366
+ };
367
+ /** Illustration types that require alt text per PDF/UA. */
368
+ const ILLUSTRATION_TYPES = new Set([
369
+ "Figure",
370
+ "Formula",
371
+ "Form"
372
+ ]);
373
+ /** Standard 14 PDF fonts that must be embedded for PDF/UA. */
374
+ const STANDARD_14_FONTS = new Set([
375
+ "Courier",
376
+ "Courier-Bold",
377
+ "Courier-Oblique",
378
+ "Courier-BoldOblique",
379
+ "Helvetica",
380
+ "Helvetica-Bold",
381
+ "Helvetica-Oblique",
382
+ "Helvetica-BoldOblique",
383
+ "Times-Roman",
384
+ "Times-Bold",
385
+ "Times-Italic",
386
+ "Times-BoldItalic",
387
+ "Symbol",
388
+ "ZapfDingbats"
389
+ ]);
390
+ /**
391
+ * Validate a PDF document against PDF/UA-1 (ISO 14289-1) requirements.
392
+ *
393
+ * Performs the following checks:
394
+ * 1. Structure tree presence (/StructTreeRoot, /MarkInfo)
395
+ * 2. Document language (/Lang)
396
+ * 3. Document title and /DisplayDocTitle
397
+ * 4. Heading hierarchy (sequential, no skips)
398
+ * 5. Alt text on all illustration elements (Figure, Formula, Form)
399
+ * 6. Table header cells (TH) with scope
400
+ * 7. List structure (L/LI/Lbl/LBody)
401
+ * 8. Reading order via structure tree
402
+ * 9. Font embedding (no unembedded standard 14 fonts)
403
+ * 10. Color contrast (AA: 4.5:1, AAA: 7:1)
404
+ * 11. Bookmarks for navigation
405
+ * 12. Tab order (/Tabs /S) on pages
406
+ *
407
+ * @param doc The PDF document to validate.
408
+ * @param level The PDF/UA conformance level (default: `'UA1'`).
409
+ * @returns A {@link PdfUaValidationResult} with errors and warnings.
410
+ *
411
+ * @example
412
+ * ```ts
413
+ * import { createPdf } from 'modern-pdf-lib';
414
+ * import { validatePdfUa } from 'modern-pdf-lib/accessibility';
415
+ *
416
+ * const doc = createPdf();
417
+ * const result = validatePdfUa(doc);
418
+ * if (!result.valid) {
419
+ * for (const err of result.errors) {
420
+ * console.error(`[${err.code}] ${err.message}`);
421
+ * }
422
+ * }
423
+ * ```
424
+ */
425
+ function validatePdfUa(doc, level = "UA1") {
426
+ const errors = [];
427
+ const warnings = [];
428
+ checkStructureTree(doc, errors);
429
+ checkLanguage(doc, errors);
430
+ checkTitle(doc, errors, warnings);
431
+ const tree = doc.getStructureTree();
432
+ if (tree) {
433
+ const allElements = tree.getAllElements();
434
+ checkHeadingHierarchy(allElements, errors);
435
+ checkAltText(allElements, errors);
436
+ checkTableHeaders(allElements, errors, warnings);
437
+ checkListStructure(allElements, errors, warnings);
438
+ checkReadingOrder(doc, warnings);
439
+ }
440
+ checkFontEmbedding$1(doc, errors);
441
+ checkColorContrast(doc, warnings);
442
+ checkBookmarks(doc, warnings);
443
+ checkTabOrder(doc, warnings);
444
+ return {
445
+ valid: errors.length === 0,
446
+ level,
447
+ errors,
448
+ warnings
449
+ };
450
+ }
451
+ /**
452
+ * Auto-fix PDF/UA issues that can be corrected programmatically.
453
+ *
454
+ * Applies the following corrections when the relevant requirement is
455
+ * not already satisfied:
456
+ * - Sets `/Lang` to `'en'` if the document has no language.
457
+ * - Sets the document title to `'Untitled'` if missing, and enables
458
+ * `/DisplayDocTitle` in viewer preferences.
459
+ * - Adds `/MarkInfo` by creating a structure tree if none exists.
460
+ * - Sets `/Tabs /S` (structure order) on every page.
461
+ *
462
+ * Returns a result listing what was fixed and what remains unfixable
463
+ * (e.g. missing alt text, heading skips — those require manual
464
+ * content changes).
465
+ *
466
+ * @param doc The PDF document to fix in-place.
467
+ * @returns A {@link PdfUaEnforcementResult} describing what was done.
468
+ *
469
+ * @example
470
+ * ```ts
471
+ * import { createPdf } from 'modern-pdf-lib';
472
+ * import { enforcePdfUa, validatePdfUa } from 'modern-pdf-lib/accessibility';
473
+ *
474
+ * const doc = createPdf();
475
+ * doc.addPage();
476
+ * const result = enforcePdfUa(doc);
477
+ * console.log('Fixed:', result.fixed);
478
+ * console.log('Unfixable:', result.unfixable.length);
479
+ * ```
480
+ */
481
+ function enforcePdfUa(doc) {
482
+ const fixed = [];
483
+ if (doc.getLanguage() === void 0 || doc.getLanguage().length === 0) {
484
+ doc.setLanguage("en");
485
+ fixed.push("Set document language to \"en\"");
486
+ }
487
+ if (doc.getTitle() === void 0 || doc.getTitle().trim().length === 0) {
488
+ doc.setTitle("Untitled", { showInWindowTitleBar: true });
489
+ fixed.push("Set document title to \"Untitled\" with DisplayDocTitle");
490
+ } else {
491
+ const prefs = doc.getViewerPreferences();
492
+ if (!prefs.getDisplayDocTitle()) {
493
+ prefs.setDisplayDocTitle(true);
494
+ fixed.push("Enabled DisplayDocTitle in viewer preferences");
495
+ }
496
+ }
497
+ if (!doc.getStructureTree()) {
498
+ doc.createStructureTree();
499
+ fixed.push("Created structure tree (MarkInfo / StructTreeRoot)");
500
+ }
501
+ const pageCount = doc.getPageCount();
502
+ let tabOrderFixed = false;
503
+ for (let i = 0; i < pageCount; i++) {
504
+ const page = doc.getPage(i);
505
+ if (page.getTabOrder() !== "S") {
506
+ page.setTabOrder("S");
507
+ tabOrderFixed = true;
508
+ }
509
+ }
510
+ if (tabOrderFixed) fixed.push("Set tab order to structure order (/Tabs /S) on all pages");
511
+ return {
512
+ fixed,
513
+ unfixable: validatePdfUa(doc).errors
514
+ };
515
+ }
516
+ /**
517
+ * Check that the document has a structure tree with MarkInfo.
518
+ * PDF/UA-1 requires /StructTreeRoot and /MarkInfo with /Marked true.
519
+ *
520
+ * @internal
521
+ */
522
+ function checkStructureTree(doc, errors) {
523
+ if (!doc.getStructureTree()) {
524
+ errors.push({
525
+ code: "UA-STRUCT-001",
526
+ message: "Document has no structure tree (/StructTreeRoot). PDF/UA requires a tagged document with a complete structure tree.",
527
+ clause: "7.1"
528
+ });
529
+ errors.push({
530
+ code: "UA-STRUCT-002",
531
+ message: "Document is not marked (/MarkInfo with /Marked true is required). A structure tree must be created for PDF/UA compliance.",
532
+ clause: "7.1"
533
+ });
534
+ }
535
+ }
536
+ /**
537
+ * Check that the document language is set (/Lang).
538
+ *
539
+ * @internal
540
+ */
541
+ function checkLanguage(doc, errors) {
542
+ const lang = doc.getLanguage();
543
+ if (lang === void 0) errors.push({
544
+ code: "UA-META-001",
545
+ message: "Document has no language set (/Lang). PDF/UA requires the document catalog to specify a natural language.",
546
+ clause: "7.2"
547
+ });
548
+ else if (lang.length === 0) errors.push({
549
+ code: "UA-META-002",
550
+ message: "Document language (/Lang) is an empty string.",
551
+ clause: "7.2"
552
+ });
553
+ else if (!/^[a-zA-Z]{2,3}(-[a-zA-Z0-9]+)*$/.test(lang)) errors.push({
554
+ code: "UA-META-003",
555
+ message: `Document language "${lang}" does not appear to be a valid BCP 47 tag.`,
556
+ clause: "7.2"
557
+ });
558
+ }
559
+ /**
560
+ * Check that the document title is set and /DisplayDocTitle is enabled.
561
+ *
562
+ * @internal
563
+ */
564
+ function checkTitle(doc, errors, warnings) {
565
+ const title = doc.getTitle();
566
+ if (title === void 0) errors.push({
567
+ code: "UA-META-004",
568
+ message: "Document has no title. PDF/UA requires a descriptive document title in the metadata.",
569
+ clause: "7.1"
570
+ });
571
+ else if (title.trim().length === 0) errors.push({
572
+ code: "UA-META-005",
573
+ message: "Document title is empty or whitespace-only.",
574
+ clause: "7.1"
575
+ });
576
+ if (!doc.getViewerPreferences().getDisplayDocTitle()) errors.push({
577
+ code: "UA-META-006",
578
+ message: "/DisplayDocTitle must be true in viewer preferences. PDF/UA requires the document title to be displayed in the title bar.",
579
+ clause: "7.1"
580
+ });
581
+ }
582
+ /**
583
+ * Check that heading hierarchy is sequential (no skipped levels).
584
+ * For example, H1 followed directly by H3 without an H2 is a violation.
585
+ *
586
+ * @internal
587
+ */
588
+ function checkHeadingHierarchy(elements, errors) {
589
+ const headings = elements.filter((e) => HEADING_TYPES.has(e.type));
590
+ if (headings.length === 0) return;
591
+ const firstHeading = headings[0];
592
+ if (firstHeading.type !== "H1") errors.push({
593
+ code: "UA-STRUCT-003",
594
+ message: `First heading should be H1, found ${firstHeading.type}. The document heading hierarchy must start at level 1.`,
595
+ clause: "7.4.2",
596
+ element: firstHeading,
597
+ pageIndex: firstHeading.pageIndex
598
+ });
599
+ let lastLevel = 0;
600
+ for (const h of headings) {
601
+ const level = HEADING_LEVEL[h.type];
602
+ if (level === void 0) continue;
603
+ if (level > lastLevel + 1 && lastLevel > 0) errors.push({
604
+ code: "UA-STRUCT-004",
605
+ message: `Heading level skipped: ${h.type} follows H${lastLevel} (expected H${lastLevel + 1}). Heading levels must be sequential without gaps.`,
606
+ clause: "7.4.2",
607
+ element: h,
608
+ pageIndex: h.pageIndex
609
+ });
610
+ lastLevel = level;
611
+ }
612
+ }
613
+ /**
614
+ * Check that all illustration elements have /Alt or /ActualText.
615
+ *
616
+ * @internal
617
+ */
618
+ function checkAltText(elements, errors) {
619
+ for (const elem of elements) {
620
+ if (!ILLUSTRATION_TYPES.has(elem.type)) continue;
621
+ if (elem.options.altText === void 0 && elem.options.actualText === void 0) errors.push({
622
+ code: "UA-STRUCT-005",
623
+ message: `${elem.type} element has no /Alt or /ActualText. PDF/UA requires alternative text for all illustration elements.`,
624
+ clause: "7.3",
625
+ element: elem,
626
+ pageIndex: elem.pageIndex
627
+ });
628
+ else if (elem.options.altText !== void 0 && elem.options.altText.trim().length === 0) errors.push({
629
+ code: "UA-STRUCT-006",
630
+ message: `${elem.type} element has empty /Alt text. Alternative text must be meaningful and descriptive.`,
631
+ clause: "7.3",
632
+ element: elem,
633
+ pageIndex: elem.pageIndex
634
+ });
635
+ }
636
+ }
637
+ /**
638
+ * Check table structure: tables must have TH (header) cells.
639
+ * Tables without headers get an error; tables with TH but without
640
+ * scope attributes get a warning.
641
+ *
642
+ * @internal
643
+ */
644
+ function checkTableHeaders(elements, errors, warnings) {
645
+ const tables = elements.filter((e) => e.type === "Table");
646
+ for (const table of tables) {
647
+ if (!table.children.some((c) => c.type === "TR" || c.type === "THead" || c.type === "TBody" || c.type === "TFoot")) {
648
+ errors.push({
649
+ code: "UA-TABLE-001",
650
+ message: "Table element has no TR, THead, TBody, or TFoot children. Tables must have a valid row structure.",
651
+ clause: "7.5",
652
+ element: table,
653
+ pageIndex: table.pageIndex
654
+ });
655
+ continue;
656
+ }
657
+ if (table.findAll("TH").length === 0) warnings.push({
658
+ code: "UA-TABLE-002",
659
+ message: "Table has no TH (header) cells. PDF/UA recommends that tables identify header cells for accessibility.",
660
+ element: table,
661
+ pageIndex: table.pageIndex
662
+ });
663
+ const rows = table.findAll("TR");
664
+ for (const row of rows) if (!row.children.some((c) => c.type === "TH" || c.type === "TD")) errors.push({
665
+ code: "UA-TABLE-003",
666
+ message: "TR element has no TH or TD children.",
667
+ clause: "7.5",
668
+ element: row,
669
+ pageIndex: row.pageIndex
670
+ });
671
+ }
672
+ }
673
+ /**
674
+ * Check list structure: L must contain LI, LI should contain LBody.
675
+ *
676
+ * @internal
677
+ */
678
+ function checkListStructure(elements, errors, warnings) {
679
+ const lists = elements.filter((e) => e.type === "L");
680
+ for (const list of lists) if (!list.children.some((c) => c.type === "LI")) errors.push({
681
+ code: "UA-LIST-001",
682
+ message: "L (list) element has no LI children. Lists must contain list item elements.",
683
+ clause: "7.4.3",
684
+ element: list,
685
+ pageIndex: list.pageIndex
686
+ });
687
+ const items = elements.filter((e) => e.type === "LI");
688
+ for (const item of items) {
689
+ if (!item.children.some((c) => c.type === "LBody")) warnings.push({
690
+ code: "UA-LIST-002",
691
+ message: "LI element has no LBody child. List items should contain an LBody element for proper structure.",
692
+ element: item,
693
+ pageIndex: item.pageIndex
694
+ });
695
+ if (!item.parent || item.parent.type !== "L") errors.push({
696
+ code: "UA-LIST-003",
697
+ message: "LI element is not a direct child of an L (list) element.",
698
+ clause: "7.4.3",
699
+ element: item,
700
+ pageIndex: item.pageIndex
701
+ });
702
+ }
703
+ }
704
+ /**
705
+ * Check that content has a logical reading order via the structure tree.
706
+ * Verifies that MCIDs are in ascending order within each page.
707
+ *
708
+ * @internal
709
+ */
710
+ function checkReadingOrder(doc, warnings) {
711
+ const tree = doc.getStructureTree();
712
+ if (!tree) return;
713
+ const withMcid = tree.getAllElements().filter((e) => e.mcid !== void 0 && e.pageIndex !== void 0);
714
+ if (withMcid.length === 0 && doc.getPageCount() > 0) {
715
+ warnings.push({
716
+ code: "UA-STRUCT-007",
717
+ message: "Structure tree has no elements with MCIDs assigned. Content may not be properly tagged for reading order."
718
+ });
719
+ return;
720
+ }
721
+ const byPage = Map.groupBy(withMcid, (elem) => elem.pageIndex);
722
+ for (const [pageIdx, elems] of byPage) for (let i = 1; i < elems.length; i++) {
723
+ const curr = elems[i];
724
+ const prev = elems[i - 1];
725
+ if (curr.mcid < prev.mcid) {
726
+ warnings.push({
727
+ code: "UA-STRUCT-008",
728
+ message: `Page ${pageIdx + 1}: MCID ${curr.mcid} (${curr.type}) appears after MCID ${prev.mcid} (${prev.type}) but has a lower MCID, indicating a potential reading order issue.`,
729
+ element: curr,
730
+ pageIndex: pageIdx
731
+ });
732
+ break;
733
+ }
734
+ }
735
+ }
736
+ /**
737
+ * Check that all fonts are embedded (no standard 14 fallback).
738
+ * PDF/UA requires all fonts to be embedded.
739
+ *
740
+ * @internal
741
+ */
742
+ function checkFontEmbedding$1(doc, errors) {
743
+ const embeddedFonts = doc.getEmbeddedFonts();
744
+ for (const [fontName] of embeddedFonts) if (STANDARD_14_FONTS.has(fontName)) errors.push({
745
+ code: "UA-FONT-001",
746
+ message: `Font "${fontName}" is a standard 14 font used without full embedding. PDF/UA requires all fonts to be embedded with their glyph data.`,
747
+ clause: "7.21.3.1"
748
+ });
749
+ }
750
+ /**
751
+ * Check color contrast (informational / best-practice).
752
+ *
753
+ * Full contrast checking requires rendered pixel data, which is
754
+ * beyond the scope of a structural validator. This check issues a
755
+ * warning when the document has content, reminding authors to verify
756
+ * contrast manually.
757
+ *
758
+ * WCAG 2.1 thresholds:
759
+ * - AA: 4.5:1 for normal text, 3:1 for large text
760
+ * - AAA: 7:1 for normal text, 4.5:1 for large text
761
+ *
762
+ * @internal
763
+ */
764
+ function checkColorContrast(doc, warnings) {
765
+ if (doc.getPageCount() > 0) warnings.push({
766
+ code: "UA-CONTRAST-001",
767
+ message: "Color contrast cannot be fully validated structurally. Verify that text meets WCAG 2.1 contrast ratios (AA: 4.5:1 for normal text, AAA: 7:1)."
768
+ });
769
+ }
770
+ /**
771
+ * Check that the document has bookmarks for navigation.
772
+ * PDF/UA recommends bookmarks for documents with multiple pages.
773
+ *
774
+ * @internal
775
+ */
776
+ function checkBookmarks(doc, warnings) {
777
+ if (doc.getOutlines().items.length === 0 && doc.getPageCount() > 1) warnings.push({
778
+ code: "UA-NAV-001",
779
+ message: "Document has multiple pages but no bookmarks (outlines). PDF/UA recommends bookmarks for navigating multi-page documents."
780
+ });
781
+ }
782
+ /**
783
+ * Check that pages specify tab order as structure order (/Tabs /S).
784
+ * PDF/UA requires /Tabs /S to ensure assistive technology follows
785
+ * the logical structure order.
786
+ *
787
+ * @internal
788
+ */
789
+ function checkTabOrder(doc, warnings) {
790
+ const pageCount = doc.getPageCount();
791
+ for (let i = 0; i < pageCount; i++) if (doc.getPage(i).getTabOrder() !== "S") warnings.push({
792
+ code: "UA-PAGE-001",
793
+ message: `Page ${i + 1} does not specify tab order as structure order (/Tabs /S). PDF/UA requires pages to use structure-based tab order.`,
794
+ pageIndex: i
795
+ });
796
+ }
797
+
345
798
  //#endregion
346
799
  //#region src/annotation/types/textAnnotation.ts
347
800
  /**
@@ -528,7 +981,7 @@ var PdfLinkAnnotation = class PdfLinkAnnotation extends PdfAnnotation {
528
981
  * Reference: PDF 1.7 spec, Section 12.5.5 (Appearance Streams).
529
982
  */
530
983
  /** Format a number for content stream operators. */
531
- function n$3(value) {
984
+ function n$5(value) {
532
985
  if (Number.isInteger(value)) return value.toString();
533
986
  const s = value.toFixed(4).replace(/\.?0+$/, "");
534
987
  return s === "-0" ? "0" : s;
@@ -575,16 +1028,16 @@ function generateSquareAppearance(annot) {
575
1028
  if (wObj && wObj.kind === "number") borderWidth = wObj.value;
576
1029
  }
577
1030
  let ops = "";
578
- if (opacity < 1) ops += `${n$3(opacity)} ca ${n$3(opacity)} CA\n`;
1031
+ if (opacity < 1) ops += `${n$5(opacity)} ca ${n$5(opacity)} CA\n`;
579
1032
  if (interiorColor) {
580
- ops += `${n$3(interiorColor.r)} ${n$3(interiorColor.g)} ${n$3(interiorColor.b)} rg\n`;
581
- ops += `${n$3(borderWidth / 2)} ${n$3(borderWidth / 2)} ${n$3(w - borderWidth)} ${n$3(h - borderWidth)} re\n`;
1033
+ ops += `${n$5(interiorColor.r)} ${n$5(interiorColor.g)} ${n$5(interiorColor.b)} rg\n`;
1034
+ ops += `${n$5(borderWidth / 2)} ${n$5(borderWidth / 2)} ${n$5(w - borderWidth)} ${n$5(h - borderWidth)} re\n`;
582
1035
  ops += "f\n";
583
1036
  }
584
- if (color) ops += `${n$3(color.r)} ${n$3(color.g)} ${n$3(color.b)} RG\n`;
1037
+ if (color) ops += `${n$5(color.r)} ${n$5(color.g)} ${n$5(color.b)} RG\n`;
585
1038
  else ops += "0 0 0 RG\n";
586
- ops += `${n$3(borderWidth)} w\n`;
587
- ops += `${n$3(borderWidth / 2)} ${n$3(borderWidth / 2)} ${n$3(w - borderWidth)} ${n$3(h - borderWidth)} re\n`;
1039
+ ops += `${n$5(borderWidth)} w\n`;
1040
+ ops += `${n$5(borderWidth / 2)} ${n$5(borderWidth / 2)} ${n$5(w - borderWidth)} ${n$5(h - borderWidth)} re\n`;
588
1041
  ops += "S\n";
589
1042
  return buildAppearanceStream(ops, [
590
1043
  0,
@@ -625,19 +1078,19 @@ function generateCircleAppearance(annot) {
625
1078
  const kx = KAPPA * adjRx;
626
1079
  const ky = KAPPA * adjRy;
627
1080
  let ops = "";
628
- if (opacity < 1) ops += `${n$3(opacity)} ca ${n$3(opacity)} CA\n`;
629
- const ellipsePath = `${n$3(cx)} ${n$3(cy + adjRy)} m\n${n$3(cx + kx)} ${n$3(cy + adjRy)} ${n$3(cx + adjRx)} ${n$3(cy + ky)} ${n$3(cx + adjRx)} ${n$3(cy)} c\n${n$3(cx + adjRx)} ${n$3(cy - ky)} ${n$3(cx + kx)} ${n$3(cy - adjRy)} ${n$3(cx)} ${n$3(cy - adjRy)} c\n${n$3(cx - kx)} ${n$3(cy - adjRy)} ${n$3(cx - adjRx)} ${n$3(cy - ky)} ${n$3(cx - adjRx)} ${n$3(cy)} c\n${n$3(cx - adjRx)} ${n$3(cy + ky)} ${n$3(cx - kx)} ${n$3(cy + adjRy)} ${n$3(cx)} ${n$3(cy + adjRy)} c\n`;
1081
+ if (opacity < 1) ops += `${n$5(opacity)} ca ${n$5(opacity)} CA\n`;
1082
+ const ellipsePath = `${n$5(cx)} ${n$5(cy + adjRy)} m\n${n$5(cx + kx)} ${n$5(cy + adjRy)} ${n$5(cx + adjRx)} ${n$5(cy + ky)} ${n$5(cx + adjRx)} ${n$5(cy)} c\n${n$5(cx + adjRx)} ${n$5(cy - ky)} ${n$5(cx + kx)} ${n$5(cy - adjRy)} ${n$5(cx)} ${n$5(cy - adjRy)} c\n${n$5(cx - kx)} ${n$5(cy - adjRy)} ${n$5(cx - adjRx)} ${n$5(cy - ky)} ${n$5(cx - adjRx)} ${n$5(cy)} c\n${n$5(cx - adjRx)} ${n$5(cy + ky)} ${n$5(cx - kx)} ${n$5(cy + adjRy)} ${n$5(cx)} ${n$5(cy + adjRy)} c\n`;
630
1083
  if (interiorColor) {
631
- ops += `${n$3(interiorColor.r)} ${n$3(interiorColor.g)} ${n$3(interiorColor.b)} rg\n`;
632
- if (color) ops += `${n$3(color.r)} ${n$3(color.g)} ${n$3(color.b)} RG\n`;
1084
+ ops += `${n$5(interiorColor.r)} ${n$5(interiorColor.g)} ${n$5(interiorColor.b)} rg\n`;
1085
+ if (color) ops += `${n$5(color.r)} ${n$5(color.g)} ${n$5(color.b)} RG\n`;
633
1086
  else ops += "0 0 0 RG\n";
634
- ops += `${n$3(borderWidth)} w\n`;
1087
+ ops += `${n$5(borderWidth)} w\n`;
635
1088
  ops += ellipsePath;
636
1089
  ops += "B\n";
637
1090
  } else {
638
- if (color) ops += `${n$3(color.r)} ${n$3(color.g)} ${n$3(color.b)} RG\n`;
1091
+ if (color) ops += `${n$5(color.r)} ${n$5(color.g)} ${n$5(color.b)} RG\n`;
639
1092
  else ops += "0 0 0 RG\n";
640
- ops += `${n$3(borderWidth)} w\n`;
1093
+ ops += `${n$5(borderWidth)} w\n`;
641
1094
  ops += ellipsePath;
642
1095
  ops += "S\n";
643
1096
  }
@@ -677,12 +1130,12 @@ function generateLineAppearance(annot) {
677
1130
  if (wObj && wObj.kind === "number") borderWidth = wObj.value;
678
1131
  }
679
1132
  let ops = "";
680
- if (opacity < 1) ops += `${n$3(opacity)} ca ${n$3(opacity)} CA\n`;
681
- if (color) ops += `${n$3(color.r)} ${n$3(color.g)} ${n$3(color.b)} RG\n`;
1133
+ if (opacity < 1) ops += `${n$5(opacity)} ca ${n$5(opacity)} CA\n`;
1134
+ if (color) ops += `${n$5(color.r)} ${n$5(color.g)} ${n$5(color.b)} RG\n`;
682
1135
  else ops += "0 0 0 RG\n";
683
- ops += `${n$3(borderWidth)} w\n`;
684
- ops += `${n$3(lx1)} ${n$3(ly1)} m\n`;
685
- ops += `${n$3(lx2)} ${n$3(ly2)} l\n`;
1136
+ ops += `${n$5(borderWidth)} w\n`;
1137
+ ops += `${n$5(lx1)} ${n$5(ly1)} m\n`;
1138
+ ops += `${n$5(lx2)} ${n$5(ly2)} l\n`;
686
1139
  ops += "S\n";
687
1140
  return buildAppearanceStream(ops, [
688
1141
  0,
@@ -705,8 +1158,8 @@ function generateHighlightAppearance(annot) {
705
1158
  };
706
1159
  const opacity = annot.getOpacity();
707
1160
  let ops = "";
708
- if (opacity < 1) ops += `${n$3(opacity)} ca\n`;
709
- ops += `${n$3(color.r)} ${n$3(color.g)} ${n$3(color.b)} rg\n`;
1161
+ if (opacity < 1) ops += `${n$5(opacity)} ca\n`;
1162
+ ops += `${n$5(color.r)} ${n$5(color.g)} ${n$5(color.b)} rg\n`;
710
1163
  const qpObj = annot.getDict().get("/QuadPoints");
711
1164
  if (qpObj && qpObj.kind === "array" && qpObj.items.length >= 8) {
712
1165
  const points = qpObj.items.filter((item) => item.kind === "number").map((item) => item.value);
@@ -719,14 +1172,14 @@ function generateHighlightAppearance(annot) {
719
1172
  const qy3 = points[i + 5] - y1;
720
1173
  const qx4 = points[i + 6] - x1;
721
1174
  const qy4 = points[i + 7] - y1;
722
- ops += `${n$3(qx1)} ${n$3(qy1)} m\n`;
723
- ops += `${n$3(qx2)} ${n$3(qy2)} l\n`;
724
- ops += `${n$3(qx4)} ${n$3(qy4)} l\n`;
725
- ops += `${n$3(qx3)} ${n$3(qy3)} l\n`;
1175
+ ops += `${n$5(qx1)} ${n$5(qy1)} m\n`;
1176
+ ops += `${n$5(qx2)} ${n$5(qy2)} l\n`;
1177
+ ops += `${n$5(qx4)} ${n$5(qy4)} l\n`;
1178
+ ops += `${n$5(qx3)} ${n$5(qy3)} l\n`;
726
1179
  ops += "f\n";
727
1180
  }
728
1181
  } else {
729
- ops += `0 0 ${n$3(w)} ${n$3(h)} re\n`;
1182
+ ops += `0 0 ${n$5(w)} ${n$5(h)} re\n`;
730
1183
  ops += "f\n";
731
1184
  }
732
1185
  return buildAppearanceStream(ops, [
@@ -749,7 +1202,7 @@ function generateUnderlineAppearance(annot) {
749
1202
  b: 1
750
1203
  };
751
1204
  let ops = "";
752
- ops += `${n$3(color.r)} ${n$3(color.g)} ${n$3(color.b)} RG\n`;
1205
+ ops += `${n$5(color.r)} ${n$5(color.g)} ${n$5(color.b)} RG\n`;
753
1206
  ops += "1 w\n";
754
1207
  const qpObj = annot.getDict().get("/QuadPoints");
755
1208
  if (qpObj && qpObj.kind === "array" && qpObj.items.length >= 8) {
@@ -759,13 +1212,13 @@ function generateUnderlineAppearance(annot) {
759
1212
  const by1 = points[i + 5] - y1;
760
1213
  const bx2 = points[i + 6] - x1;
761
1214
  const by2 = points[i + 7] - y1;
762
- ops += `${n$3(bx1)} ${n$3(by1)} m\n`;
763
- ops += `${n$3(bx2)} ${n$3(by2)} l\n`;
1215
+ ops += `${n$5(bx1)} ${n$5(by1)} m\n`;
1216
+ ops += `${n$5(bx2)} ${n$5(by2)} l\n`;
764
1217
  ops += "S\n";
765
1218
  }
766
1219
  } else {
767
1220
  ops += `0 0 m\n`;
768
- ops += `${n$3(w)} 0 l\n`;
1221
+ ops += `${n$5(w)} 0 l\n`;
769
1222
  ops += "S\n";
770
1223
  }
771
1224
  return buildAppearanceStream(ops, [
@@ -788,18 +1241,18 @@ function generateSquigglyAppearance(annot) {
788
1241
  b: 0
789
1242
  };
790
1243
  let ops = "";
791
- ops += `${n$3(color.r)} ${n$3(color.g)} ${n$3(color.b)} RG\n`;
1244
+ ops += `${n$5(color.r)} ${n$5(color.g)} ${n$5(color.b)} RG\n`;
792
1245
  ops += "0.5 w\n";
793
1246
  const amplitude = 2;
794
1247
  const period = 4;
795
- ops += `0 ${n$3(amplitude)} m\n`;
1248
+ ops += `0 ${n$5(amplitude)} m\n`;
796
1249
  for (let x = 0; x < w; x += period) {
797
1250
  const x1p = Math.min(x + period / 2, w);
798
1251
  const x2p = Math.min(x + period, w);
799
1252
  const y1p = x % (period * 2) < period ? 0 : amplitude * 2;
800
1253
  const y2p = x % (period * 2) < period ? amplitude * 2 : 0;
801
- ops += `${n$3(x1p)} ${n$3(y1p)} l\n`;
802
- if (x2p <= w) ops += `${n$3(x2p)} ${n$3(y2p)} l\n`;
1254
+ ops += `${n$5(x1p)} ${n$5(y1p)} l\n`;
1255
+ if (x2p <= w) ops += `${n$5(x2p)} ${n$5(y2p)} l\n`;
803
1256
  }
804
1257
  ops += "S\n";
805
1258
  return buildAppearanceStream(ops, [
@@ -822,7 +1275,7 @@ function generateStrikeOutAppearance(annot) {
822
1275
  b: 0
823
1276
  };
824
1277
  let ops = "";
825
- ops += `${n$3(color.r)} ${n$3(color.g)} ${n$3(color.b)} RG\n`;
1278
+ ops += `${n$5(color.r)} ${n$5(color.g)} ${n$5(color.b)} RG\n`;
826
1279
  ops += "1 w\n";
827
1280
  const qpObj = annot.getDict().get("/QuadPoints");
828
1281
  if (qpObj && qpObj.kind === "array" && qpObj.items.length >= 8) {
@@ -831,14 +1284,14 @@ function generateStrikeOutAppearance(annot) {
831
1284
  const midY = ((points[i + 1] - y1 + (points[i + 3] - y1)) / 2 + (points[i + 5] - y1 + (points[i + 7] - y1)) / 2) / 2;
832
1285
  const leftX = Math.min(points[i] - x1, points[i + 4] - x1);
833
1286
  const rightX = Math.max(points[i + 2] - x1, points[i + 6] - x1);
834
- ops += `${n$3(leftX)} ${n$3(midY)} m\n`;
835
- ops += `${n$3(rightX)} ${n$3(midY)} l\n`;
1287
+ ops += `${n$5(leftX)} ${n$5(midY)} m\n`;
1288
+ ops += `${n$5(rightX)} ${n$5(midY)} l\n`;
836
1289
  ops += "S\n";
837
1290
  }
838
1291
  } else {
839
1292
  const midY = h / 2;
840
- ops += `0 ${n$3(midY)} m\n`;
841
- ops += `${n$3(w)} ${n$3(midY)} l\n`;
1293
+ ops += `0 ${n$5(midY)} m\n`;
1294
+ ops += `${n$5(w)} ${n$5(midY)} l\n`;
842
1295
  ops += "S\n";
843
1296
  }
844
1297
  return buildAppearanceStream(ops, [
@@ -862,8 +1315,8 @@ function generateInkAppearance(annot) {
862
1315
  };
863
1316
  const opacity = annot.getOpacity();
864
1317
  let ops = "";
865
- if (opacity < 1) ops += `${n$3(opacity)} ca ${n$3(opacity)} CA\n`;
866
- ops += `${n$3(color.r)} ${n$3(color.g)} ${n$3(color.b)} RG\n`;
1318
+ if (opacity < 1) ops += `${n$5(opacity)} ca ${n$5(opacity)} CA\n`;
1319
+ ops += `${n$5(color.r)} ${n$5(color.g)} ${n$5(color.b)} RG\n`;
867
1320
  ops += "1 w\n";
868
1321
  ops += "1 J\n";
869
1322
  const inkListObj = annot.getDict().get("/InkList");
@@ -871,8 +1324,8 @@ function generateInkAppearance(annot) {
871
1324
  if (pathObj.kind !== "array") continue;
872
1325
  const points = pathObj.items.filter((item) => item.kind === "number").map((item) => item.value);
873
1326
  if (points.length >= 2) {
874
- ops += `${n$3(points[0] - x1)} ${n$3(points[1] - y1)} m\n`;
875
- for (let i = 2; i + 1 < points.length; i += 2) ops += `${n$3(points[i] - x1)} ${n$3(points[i + 1] - y1)} l\n`;
1327
+ ops += `${n$5(points[0] - x1)} ${n$5(points[1] - y1)} m\n`;
1328
+ for (let i = 2; i + 1 < points.length; i += 2) ops += `${n$5(points[i] - x1)} ${n$5(points[i + 1] - y1)} l\n`;
876
1329
  ops += "S\n";
877
1330
  }
878
1331
  }
@@ -910,9 +1363,9 @@ function generateFreeTextAppearance(annot) {
910
1363
  ops += "BT\n";
911
1364
  const margin = 2;
912
1365
  const textY = h - fontSize - margin;
913
- if (align === 1) ops += `${n$3(w / 2)} ${n$3(textY)} Td\n`;
914
- else if (align === 2) ops += `${n$3(w - margin)} ${n$3(textY)} Td\n`;
915
- else ops += `${n$3(margin)} ${n$3(textY)} Td\n`;
1366
+ if (align === 1) ops += `${n$5(w / 2)} ${n$5(textY)} Td\n`;
1367
+ else if (align === 2) ops += `${n$5(w - margin)} ${n$5(textY)} Td\n`;
1368
+ else ops += `${n$5(margin)} ${n$5(textY)} Td\n`;
916
1369
  const escapedText = text.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
917
1370
  ops += `(${escapedText}) Tj\n`;
918
1371
  ops += "ET\n";
@@ -4019,6 +4472,29 @@ function isLinearized(pdfBytes) {
4019
4472
  const header = decoder$2.decode(pdfBytes.subarray(0, searchWindow));
4020
4473
  return /\/Linearized\s+[\d.]+/.test(header);
4021
4474
  }
4475
+ /**
4476
+ * Extract linearization information from a linearized PDF.
4477
+ *
4478
+ * @param pdfBytes The raw PDF bytes.
4479
+ * @returns The linearization info, or `null` if not linearized.
4480
+ */
4481
+ function getLinearizationInfo(pdfBytes) {
4482
+ if (!isLinearized(pdfBytes)) return null;
4483
+ const searchWindow = Math.min(pdfBytes.length, 2048);
4484
+ const header = decoder$2.decode(pdfBytes.subarray(0, searchWindow));
4485
+ const versionMatch = /\/Linearized\s+([\d.]+)/.exec(header);
4486
+ const lengthMatch = /\/L\s+(\d+)/.exec(header);
4487
+ const primaryMatch = /\/O\s+(\d+)/.exec(header);
4488
+ const pageCountMatch = /\/N\s+(\d+)/.exec(header);
4489
+ const endFirstMatch = /\/E\s+(\d+)/.exec(header);
4490
+ return {
4491
+ version: versionMatch ? parseFloat(versionMatch[1]) : 1,
4492
+ length: lengthMatch ? parseInt(lengthMatch[1], 10) : pdfBytes.length,
4493
+ primaryPage: primaryMatch ? parseInt(primaryMatch[1], 10) : 0,
4494
+ pageCount: pageCountMatch ? parseInt(pageCountMatch[1], 10) : 0,
4495
+ firstPageOffset: endFirstMatch ? parseInt(endFirstMatch[1], 10) : 0
4496
+ };
4497
+ }
4022
4498
  /** Find a string in the byte array, starting from `start`. */
4023
4499
  function findString(data, str, start = 0) {
4024
4500
  const bytes = encoder$2.encode(str);
@@ -4048,7 +4524,7 @@ function findStartXref(data) {
4048
4524
  if (idx < 0) return -1;
4049
4525
  const lines = tail.slice(idx + 9).trim().split(/[\r\n]+/);
4050
4526
  const xrefOffset = parseInt(lines[0], 10);
4051
- return isNaN(xrefOffset) ? -1 : xrefOffset;
4527
+ return Number.isNaN(xrefOffset) ? -1 : xrefOffset;
4052
4528
  }
4053
4529
  function parseXrefTable(data, xrefOffset) {
4054
4530
  const entries = /* @__PURE__ */ new Map();
@@ -4058,7 +4534,7 @@ function parseXrefTable(data, xrefOffset) {
4058
4534
  const line = readLine(data, pos);
4059
4535
  if (line.line.startsWith("trailer")) break;
4060
4536
  const parts = line.line.trim().split(/\s+/);
4061
- if (parts.length === 2) {
4537
+ if (parts.length === 2 && /^\d+$/.test(parts[0]) && /^\d+$/.test(parts[1])) {
4062
4538
  const startObj = parseInt(parts[0], 10);
4063
4539
  const count = parseInt(parts[1], 10);
4064
4540
  pos = line.nextOffset;
@@ -4085,7 +4561,11 @@ function parseXrefTable(data, xrefOffset) {
4085
4561
  trailer: {
4086
4562
  size: sizeMatch ? parseInt(sizeMatch[1], 10) : 0,
4087
4563
  rootRef: rootMatch ? `${rootMatch[1]} ${rootMatch[2]} R` : "1 0 R",
4088
- infoRef: infoMatch ? `${infoMatch[1]} ${infoMatch[2]} R` : void 0
4564
+ rootObjNum: rootMatch ? parseInt(rootMatch[1], 10) : 1,
4565
+ rootGenNum: rootMatch ? parseInt(rootMatch[2], 10) : 0,
4566
+ infoRef: infoMatch ? `${infoMatch[1]} ${infoMatch[2]} R` : void 0,
4567
+ infoObjNum: infoMatch ? parseInt(infoMatch[1], 10) : 0,
4568
+ infoGenNum: infoMatch ? parseInt(infoMatch[2], 10) : 0
4089
4569
  }
4090
4570
  };
4091
4571
  }
@@ -4096,16 +4576,266 @@ function extractObject(data, offset) {
4096
4576
  return data.subarray(offset, endIdx + 6);
4097
4577
  }
4098
4578
  /**
4579
+ * Recursively walk references from a set of root object numbers.
4580
+ * Returns all transitively referenced object numbers.
4581
+ */
4582
+ function walkReferences(rootObjNums, objects, maxDepth = 20) {
4583
+ const visited = /* @__PURE__ */ new Set();
4584
+ function walk(objNum, depth) {
4585
+ if (depth > maxDepth) return;
4586
+ if (visited.has(objNum)) return;
4587
+ visited.add(objNum);
4588
+ const objBytes = objects.get(objNum);
4589
+ if (!objBytes) return;
4590
+ const refMatches = decoder$2.decode(objBytes).matchAll(/(\d+)\s+\d+\s+R/g);
4591
+ for (const rm of refMatches) {
4592
+ const refNum = parseInt(rm[1], 10);
4593
+ if (objects.has(refNum)) walk(refNum, depth + 1);
4594
+ }
4595
+ }
4596
+ for (const root of rootObjNums) walk(root, 0);
4597
+ return visited;
4598
+ }
4599
+ /**
4600
+ * Find all page object numbers from the /Pages tree.
4601
+ */
4602
+ function findPageObjectNumbers(pagesObjNum, objects) {
4603
+ const pageNums = [];
4604
+ function walkPages(objNum) {
4605
+ const objBytes = objects.get(objNum);
4606
+ if (!objBytes) return;
4607
+ const str = decoder$2.decode(objBytes);
4608
+ const typeMatch = /\/Type\s*\/(\w+)/.exec(str);
4609
+ if (!typeMatch) return;
4610
+ if (typeMatch[1] === "Page") {
4611
+ pageNums.push(objNum);
4612
+ return;
4613
+ }
4614
+ if (typeMatch[1] === "Pages") {
4615
+ const kidsMatch = /\/Kids\s*\[([\s\S]*?)\]/.exec(str);
4616
+ if (kidsMatch) {
4617
+ const refs = kidsMatch[1].match(/(\d+)\s+\d+\s+R/g);
4618
+ if (refs) for (const ref of refs) walkPages(parseInt(ref.split(" ")[0], 10));
4619
+ }
4620
+ }
4621
+ }
4622
+ walkPages(pagesObjNum);
4623
+ return pageNums;
4624
+ }
4625
+ /**
4626
+ * Classify objects into per-page sets.
4627
+ * Shared objects (referenced by multiple pages) are tracked separately.
4628
+ */
4629
+ function classifyObjects(pageObjNums, catalogObjNum, pagesObjNum, objects) {
4630
+ const pages = [];
4631
+ for (const pageObjNum of pageObjNums) {
4632
+ const referencedObjects = walkReferences([pageObjNum], objects);
4633
+ pages.push({
4634
+ pageObjNum,
4635
+ referencedObjects
4636
+ });
4637
+ }
4638
+ const catalogObjects = /* @__PURE__ */ new Set();
4639
+ catalogObjects.add(catalogObjNum);
4640
+ catalogObjects.add(pagesObjNum);
4641
+ const refCount = /* @__PURE__ */ new Map();
4642
+ for (const page of pages) for (const objNum of page.referencedObjects) refCount.set(objNum, (refCount.get(objNum) ?? 0) + 1);
4643
+ const sharedObjects = /* @__PURE__ */ new Set();
4644
+ for (const [objNum, count] of refCount) if (count > 1 || catalogObjects.has(objNum)) sharedObjects.add(objNum);
4645
+ for (const page of pages) for (const objNum of sharedObjects) page.referencedObjects.delete(objNum);
4646
+ return {
4647
+ pages,
4648
+ sharedObjects,
4649
+ catalogObjects
4650
+ };
4651
+ }
4652
+ /**
4653
+ * Build a page offset hint table (§F.4.1).
4654
+ *
4655
+ * The table is a header followed by per-page entries that describe
4656
+ * how many objects and bytes each page consumes.
4657
+ */
4658
+ function buildPageOffsetHintTable(pages, offsets, objectSizes) {
4659
+ const nPages = pages.length;
4660
+ if (nPages === 0) return new Uint8Array(0);
4661
+ const pageStats = [];
4662
+ for (const page of pages) {
4663
+ const allObjs = [page.pageObjNum, ...page.referencedObjects];
4664
+ let totalBytes = 0;
4665
+ let firstObj = page.pageObjNum;
4666
+ for (const objNum of allObjs) {
4667
+ const size = objectSizes.get(objNum) ?? 0;
4668
+ totalBytes += size;
4669
+ if (objNum < firstObj) firstObj = objNum;
4670
+ }
4671
+ pageStats.push({
4672
+ objCount: allObjs.length,
4673
+ totalBytes,
4674
+ firstObjNum: firstObj
4675
+ });
4676
+ }
4677
+ const leastObjCount = Math.min(...pageStats.map((p) => p.objCount));
4678
+ const maxObjDiff = Math.max(...pageStats.map((p) => p.objCount - leastObjCount));
4679
+ const bitsForObjDiff = maxObjDiff > 0 ? Math.ceil(Math.log2(maxObjDiff + 1)) : 0;
4680
+ const leastPageLen = Math.min(...pageStats.map((p) => p.totalBytes));
4681
+ const maxLenDiff = Math.max(...pageStats.map((p) => p.totalBytes - leastPageLen));
4682
+ const bitsForLenDiff = maxLenDiff > 0 ? Math.ceil(Math.log2(maxLenDiff + 1)) : 0;
4683
+ const firstPageObjOffset = offsets.get(pages[0].pageObjNum) ?? 0;
4684
+ const totalSize = 36 + nPages * 8;
4685
+ const data = new Uint8Array(totalSize);
4686
+ let pos = 0;
4687
+ writeU32BE(data, pos, leastObjCount);
4688
+ pos += 4;
4689
+ writeU32BE(data, pos, firstPageObjOffset);
4690
+ pos += 4;
4691
+ writeU32BE(data, pos, bitsForObjDiff);
4692
+ pos += 4;
4693
+ writeU32BE(data, pos, leastPageLen);
4694
+ pos += 4;
4695
+ writeU32BE(data, pos, bitsForLenDiff);
4696
+ pos += 4;
4697
+ writeU32BE(data, pos, 0);
4698
+ pos += 4;
4699
+ writeU32BE(data, pos, 0);
4700
+ pos += 4;
4701
+ writeU32BE(data, pos, 0);
4702
+ pos += 4;
4703
+ writeU32BE(data, pos, 0);
4704
+ pos += 4;
4705
+ for (const stat of pageStats) {
4706
+ writeU32BE(data, pos, stat.objCount - leastObjCount);
4707
+ pos += 4;
4708
+ writeU32BE(data, pos, stat.totalBytes - leastPageLen);
4709
+ pos += 4;
4710
+ }
4711
+ return data;
4712
+ }
4713
+ /**
4714
+ * Build a shared object hint table (§F.4.2).
4715
+ *
4716
+ * This describes shared objects — objects referenced by multiple pages.
4717
+ */
4718
+ function buildSharedObjectHintTable(sharedObjectNums, offsets, objectSizes) {
4719
+ const nShared = sharedObjectNums.length;
4720
+ if (nShared === 0) return new Uint8Array(0);
4721
+ const totalSize = 20 + nShared * 8;
4722
+ const data = new Uint8Array(totalSize);
4723
+ let pos = 0;
4724
+ const sizes = sharedObjectNums.map((n) => objectSizes.get(n) ?? 0);
4725
+ const leastLen = sizes.length > 0 ? Math.min(...sizes) : 0;
4726
+ const maxLenDiff = sizes.length > 0 ? Math.max(...sizes) - leastLen : 0;
4727
+ const bitsForLenDiff = maxLenDiff > 0 ? Math.ceil(Math.log2(maxLenDiff + 1)) : 0;
4728
+ const firstObjNum = sharedObjectNums.length > 0 ? Math.min(...sharedObjectNums) : 0;
4729
+ const firstObjOffset = offsets.get(firstObjNum) ?? 0;
4730
+ writeU32BE(data, pos, firstObjNum);
4731
+ pos += 4;
4732
+ writeU32BE(data, pos, firstObjOffset);
4733
+ pos += 4;
4734
+ writeU32BE(data, pos, nShared);
4735
+ pos += 4;
4736
+ writeU32BE(data, pos, leastLen);
4737
+ pos += 4;
4738
+ writeU32BE(data, pos, bitsForLenDiff);
4739
+ pos += 4;
4740
+ for (let i = 0; i < nShared; i++) {
4741
+ const off = offsets.get(sharedObjectNums[i]) ?? 0;
4742
+ const size = objectSizes.get(sharedObjectNums[i]) ?? 0;
4743
+ writeU32BE(data, pos, off);
4744
+ pos += 4;
4745
+ writeU32BE(data, pos, size);
4746
+ pos += 4;
4747
+ }
4748
+ return data;
4749
+ }
4750
+ /** Write a 4-byte big-endian unsigned integer. */
4751
+ function writeU32BE(buf, offset, value) {
4752
+ buf[offset] = value >>> 24 & 255;
4753
+ buf[offset + 1] = value >>> 16 & 255;
4754
+ buf[offset + 2] = value >>> 8 & 255;
4755
+ buf[offset + 3] = value & 255;
4756
+ }
4757
+ /** Compute minimum bytes to represent a value in big-endian. */
4758
+ function bytesNeeded(value) {
4759
+ if (value <= 255) return 1;
4760
+ if (value <= 65535) return 2;
4761
+ if (value <= 16777215) return 3;
4762
+ return 4;
4763
+ }
4764
+ /** Write big-endian integer of given byte width. */
4765
+ function writeIntBE(buf, offset, width, value) {
4766
+ for (let i = width - 1; i >= 0; i--) {
4767
+ buf[offset + i] = value & 255;
4768
+ value = value >>> 8;
4769
+ }
4770
+ }
4771
+ /**
4772
+ * Build a cross-reference stream (PDF 1.5+, §7.5.8).
4773
+ *
4774
+ * Returns the complete indirect object bytes including the `N G obj`
4775
+ * header and `endobj`.
4776
+ */
4777
+ function buildXrefStream(xrefObjNum, totalSize, entries, rootRef, infoRef, prevXrefOffset) {
4778
+ let maxField2 = 0;
4779
+ let maxField3 = 65535;
4780
+ for (const e of entries.values()) {
4781
+ if (e.field2 > maxField2) maxField2 = e.field2;
4782
+ if (e.field3 > maxField3) maxField3 = e.field3;
4783
+ }
4784
+ const w0 = 1;
4785
+ const w1 = bytesNeeded(maxField2);
4786
+ const w2 = bytesNeeded(maxField3);
4787
+ const entryWidth = w0 + w1 + w2;
4788
+ const streamData = new Uint8Array(totalSize * entryWidth);
4789
+ let pos = 0;
4790
+ writeIntBE(streamData, pos, w0, 0);
4791
+ pos += w0;
4792
+ writeIntBE(streamData, pos, w1, 0);
4793
+ pos += w1;
4794
+ writeIntBE(streamData, pos, w2, 65535);
4795
+ pos += w2;
4796
+ for (let i = 1; i < totalSize; i++) {
4797
+ const entry = entries.get(i);
4798
+ if (entry) {
4799
+ writeIntBE(streamData, pos, w0, entry.type);
4800
+ pos += w0;
4801
+ writeIntBE(streamData, pos, w1, entry.field2);
4802
+ pos += w1;
4803
+ writeIntBE(streamData, pos, w2, entry.field3);
4804
+ pos += w2;
4805
+ } else {
4806
+ writeIntBE(streamData, pos, w0, 0);
4807
+ pos += w0;
4808
+ writeIntBE(streamData, pos, w1, 0);
4809
+ pos += w1;
4810
+ writeIntBE(streamData, pos, w2, 0);
4811
+ pos += w2;
4812
+ }
4813
+ }
4814
+ let dictStr = `<< /Type /XRef /Size ${totalSize}`;
4815
+ dictStr += ` /W [${w0} ${w1} ${w2}]`;
4816
+ dictStr += ` /Root ${rootRef}`;
4817
+ if (infoRef) dictStr += ` /Info ${infoRef}`;
4818
+ if (prevXrefOffset !== void 0) dictStr += ` /Prev ${prevXrefOffset}`;
4819
+ dictStr += ` /Length ${streamData.length}`;
4820
+ dictStr += " >>";
4821
+ const objStr = `${xrefObjNum} 0 obj\n${dictStr}\nstream\n`;
4822
+ const objEnd = "\nendstream\nendobj\n";
4823
+ const headerBytes = encoder$2.encode(objStr);
4824
+ const footerBytes = encoder$2.encode(objEnd);
4825
+ const result = new Uint8Array(headerBytes.length + streamData.length + footerBytes.length);
4826
+ result.set(headerBytes, 0);
4827
+ result.set(streamData, headerBytes.length);
4828
+ result.set(footerBytes, headerBytes.length + streamData.length);
4829
+ return result;
4830
+ }
4831
+ /**
4099
4832
  * Linearize a PDF document for fast web viewing.
4100
4833
  *
4101
4834
  * This reorganizes the PDF so that:
4102
- * 1. A linearization parameter dictionary appears first
4835
+ * 1. A linearization parameter dictionary appears first (§F.2)
4103
4836
  * 2. Objects needed for the first page appear early in the file
4104
- * 3. A hint table describes page offsets
4105
- *
4106
- * Note: This is a simplified linearization. For production use with
4107
- * very large documents, a full implementation following PDF spec
4108
- * Appendix F is recommended.
4837
+ * 3. A primary hint stream describes page offsets and shared objects (§F.4)
4838
+ * 4. Cross-reference streams are used for all xref data (§7.5.8)
4109
4839
  *
4110
4840
  * @param pdfBytes The raw PDF bytes.
4111
4841
  * @param options Linearization options.
@@ -4125,52 +4855,38 @@ async function linearizePdf(pdfBytes, options) {
4125
4855
  const { entries, trailer } = xrefResult;
4126
4856
  const objects = /* @__PURE__ */ new Map();
4127
4857
  for (const [objNum, entry] of entries) if (entry.inUse && objNum > 0) objects.set(objNum, extractObject(pdfBytes, entry.offset));
4128
- const rootObjNum = parseInt(trailer.rootRef.split(" ")[0], 10);
4858
+ if (objects.size === 0) throw new Error("Cannot linearize: no objects found");
4859
+ const rootObjNum = trailer.rootObjNum;
4129
4860
  const catalogBytes = objects.get(rootObjNum);
4130
- let pagesObjNum = 0;
4131
- if (catalogBytes) {
4132
- const catalogStr = decoder$2.decode(catalogBytes);
4133
- const pagesMatch = /\/Pages\s+(\d+)\s+\d+\s+R/.exec(catalogStr);
4134
- if (pagesMatch) pagesObjNum = parseInt(pagesMatch[1], 10);
4135
- }
4136
- let firstPageObjNum = 0;
4137
- const pagesBytes = objects.get(pagesObjNum);
4138
- if (pagesBytes) {
4139
- const pagesStr = decoder$2.decode(pagesBytes);
4140
- const kidsMatch = /\/Kids\s*\[([\s\S]*?)\]/.exec(pagesStr);
4141
- if (kidsMatch) {
4142
- const refs = kidsMatch[1].match(/(\d+)\s+\d+\s+R/g);
4143
- if (refs && refs.length > _firstPage) {
4144
- const ref = refs[_firstPage];
4145
- firstPageObjNum = parseInt(ref.split(" ")[0], 10);
4146
- }
4147
- }
4148
- }
4149
- const firstPageObjects = /* @__PURE__ */ new Set();
4150
- firstPageObjects.add(rootObjNum);
4151
- firstPageObjects.add(pagesObjNum);
4152
- if (firstPageObjNum > 0) firstPageObjects.add(firstPageObjNum);
4153
- if (firstPageObjNum > 0) {
4154
- const pageBytes = objects.get(firstPageObjNum);
4155
- if (pageBytes) {
4156
- const refMatches = decoder$2.decode(pageBytes).matchAll(/(\d+)\s+\d+\s+R/g);
4157
- for (const rm of refMatches) {
4158
- const refNum = parseInt(rm[1], 10);
4159
- if (objects.has(refNum)) {
4160
- firstPageObjects.add(refNum);
4161
- const innerBytes = objects.get(refNum);
4162
- if (innerBytes) {
4163
- const innerRefs = decoder$2.decode(innerBytes).matchAll(/(\d+)\s+\d+\s+R/g);
4164
- for (const ir of innerRefs) {
4165
- const irNum = parseInt(ir[1], 10);
4166
- if (objects.has(irNum)) firstPageObjects.add(irNum);
4167
- }
4168
- }
4169
- }
4170
- }
4171
- }
4172
- }
4173
- const remainingObjects = [...new Set(objects.keys()).difference(firstPageObjects)].sort((a, b) => a - b);
4861
+ if (!catalogBytes) throw new Error("Cannot linearize: catalog object not found");
4862
+ const catalogStr = decoder$2.decode(catalogBytes);
4863
+ const pagesMatch = /\/Pages\s+(\d+)\s+\d+\s+R/.exec(catalogStr);
4864
+ const pagesObjNum = pagesMatch ? parseInt(pagesMatch[1], 10) : 0;
4865
+ if (pagesObjNum === 0) throw new Error("Cannot linearize: /Pages not found in catalog");
4866
+ const pageObjNums = findPageObjectNumbers(pagesObjNum, objects);
4867
+ const pageCount = pageObjNums.length;
4868
+ if (pageCount === 0) throw new Error("Cannot linearize: no pages found");
4869
+ const firstPageIdx = Math.min(_firstPage, pageCount - 1);
4870
+ const firstPageObjNum = pageObjNums[firstPageIdx];
4871
+ const classification = classifyObjects(pageObjNums, rootObjNum, pagesObjNum, objects);
4872
+ const firstPageExclusiveObjects = classification.pages[firstPageIdx].referencedObjects;
4873
+ const firstPageObjNums = /* @__PURE__ */ new Set();
4874
+ firstPageObjNums.add(rootObjNum);
4875
+ firstPageObjNums.add(pagesObjNum);
4876
+ firstPageObjNums.add(firstPageObjNum);
4877
+ for (const objNum of firstPageExclusiveObjects) firstPageObjNums.add(objNum);
4878
+ for (const objNum of classification.sharedObjects) firstPageObjNums.add(objNum);
4879
+ if (trailer.infoObjNum > 0 && objects.has(trailer.infoObjNum)) firstPageObjNums.add(trailer.infoObjNum);
4880
+ const remainingObjNums = [];
4881
+ for (const objNum of objects.keys()) if (!firstPageObjNums.has(objNum)) remainingObjNums.push(objNum);
4882
+ remainingObjNums.sort((a, b) => a - b);
4883
+ const maxOrigObjNum = Math.max(...objects.keys(), 0);
4884
+ const linObjNum = maxOrigObjNum + 1;
4885
+ const hintObjNum = maxOrigObjNum + 2;
4886
+ const totalObjectCount = maxOrigObjNum + 3;
4887
+ const firstPageObjNumsSorted = [...firstPageObjNums].sort((a, b) => a - b);
4888
+ const objectSizes = /* @__PURE__ */ new Map();
4889
+ for (const [objNum, objBytes] of objects) objectSizes.set(objNum, objBytes.length + 1);
4174
4890
  const chunks = [];
4175
4891
  const newOffsets = /* @__PURE__ */ new Map();
4176
4892
  let currentOffset = 0;
@@ -4185,10 +4901,7 @@ async function linearizePdf(pdfBytes, options) {
4185
4901
  }
4186
4902
  writeStr("%PDF-1.7\n");
4187
4903
  writeStr("%âãÏÓ\n");
4188
- const linObjNum = trailer.size;
4189
4904
  const linDictOffset = currentOffset;
4190
- const linDictPlaceholder = currentOffset;
4191
- const pageCount = pagesBytes ? decoder$2.decode(pagesBytes).match(/\/Count\s+(\d+)/)?.[1] ?? "1" : "1";
4192
4905
  writeStr(`${linObjNum} 0 obj\n`);
4193
4906
  writeStr(`<< /Linearized 1.0\n`);
4194
4907
  writeStr(` /L 0000000000\n`);
@@ -4196,11 +4909,11 @@ async function linearizePdf(pdfBytes, options) {
4196
4909
  writeStr(` /E 0000000000\n`);
4197
4910
  writeStr(` /N ${pageCount}\n`);
4198
4911
  writeStr(` /T 0000000000\n`);
4912
+ writeStr(` /H [0000000000 0000000000]\n`);
4199
4913
  writeStr(">>\n");
4200
4914
  writeStr("endobj\n");
4201
4915
  newOffsets.set(linObjNum, linDictOffset);
4202
- const firstPageObjNums = firstPageObjects.values().toArray().sort((a, b) => a - b);
4203
- for (const objNum of firstPageObjNums) {
4916
+ for (const objNum of firstPageObjNumsSorted) {
4204
4917
  const objBytes = objects.get(objNum);
4205
4918
  if (objBytes) {
4206
4919
  newOffsets.set(objNum, currentOffset);
@@ -4209,7 +4922,22 @@ async function linearizePdf(pdfBytes, options) {
4209
4922
  }
4210
4923
  }
4211
4924
  const endOfFirstPage = currentOffset;
4212
- for (const objNum of remainingObjects) {
4925
+ const hintStreamStart = currentOffset;
4926
+ const pageOffsetHints = buildPageOffsetHintTable(classification.pages, newOffsets, objectSizes);
4927
+ const sharedObjectHints = buildSharedObjectHintTable([...classification.sharedObjects].sort((a, b) => a - b), newOffsets, objectSizes);
4928
+ const hintStreamData = new Uint8Array(pageOffsetHints.length + sharedObjectHints.length);
4929
+ hintStreamData.set(pageOffsetHints, 0);
4930
+ hintStreamData.set(sharedObjectHints, pageOffsetHints.length);
4931
+ const sharedHintOffset = pageOffsetHints.length;
4932
+ writeStr(`${hintObjNum} 0 obj\n`);
4933
+ writeStr(`<< /Length ${hintStreamData.length} /S ${sharedHintOffset} >>\n`);
4934
+ writeStr("stream\n");
4935
+ writeBytes(hintStreamData);
4936
+ writeStr("\nendstream\n");
4937
+ writeStr("endobj\n");
4938
+ newOffsets.set(hintObjNum, hintStreamStart);
4939
+ const hintStreamLength = currentOffset - hintStreamStart;
4940
+ for (const objNum of remainingObjNums) {
4213
4941
  const objBytes = objects.get(objNum);
4214
4942
  if (objBytes) {
4215
4943
  newOffsets.set(objNum, currentOffset);
@@ -4217,34 +4945,200 @@ async function linearizePdf(pdfBytes, options) {
4217
4945
  writeStr("\n");
4218
4946
  }
4219
4947
  }
4220
- const hintObjNum = linObjNum + 1;
4221
- const hintData = buildSimpleHintStream(firstPageObjNums, remainingObjects, newOffsets);
4222
- const hintOffset = currentOffset;
4223
- writeStr(`${hintObjNum} 0 obj\n`);
4224
- writeStr(`<< /Length ${hintData.length} /S ${hintData.length} >>\n`);
4225
- writeStr("stream\n");
4226
- writeBytes(hintData);
4227
- writeStr("\nendstream\n");
4228
- writeStr("endobj\n");
4229
- newOffsets.set(hintObjNum, hintOffset);
4230
- const totalSize = linObjNum + 2;
4231
- const mainXrefOffset = currentOffset;
4948
+ const xrefEntries = /* @__PURE__ */ new Map();
4949
+ for (const [objNum, offset] of newOffsets) xrefEntries.set(objNum, {
4950
+ type: 1,
4951
+ field2: offset,
4952
+ field3: 0
4953
+ });
4954
+ const mainXrefObjNum = totalObjectCount;
4955
+ const mainXrefOffset = currentOffset;
4956
+ writeBytes(buildXrefStream(mainXrefObjNum, mainXrefObjNum + 1, xrefEntries, trailer.rootRef, trailer.infoRef));
4957
+ writeStr("startxref\n");
4958
+ writeStr(`${mainXrefOffset}\n`);
4959
+ writeStr("%%EOF\n");
4960
+ const fileLength = currentOffset;
4961
+ const result = new Uint8Array(fileLength);
4962
+ let pos = 0;
4963
+ for (const chunk of chunks) {
4964
+ result.set(chunk, pos);
4965
+ pos += chunk.length;
4966
+ }
4967
+ patchNumber(result, linDictOffset, "/L ", fileLength);
4968
+ patchNumber(result, linDictOffset, "/E ", endOfFirstPage);
4969
+ patchNumber(result, linDictOffset, "/T ", mainXrefOffset);
4970
+ patchHintArray(result, linDictOffset, hintStreamStart, hintStreamLength);
4971
+ return result;
4972
+ }
4973
+ /**
4974
+ * Remove linearization artifacts from a PDF, producing a normal
4975
+ * (non-linearized) PDF.
4976
+ *
4977
+ * This:
4978
+ * 1. Strips the linearization parameter dictionary
4979
+ * 2. Removes hint streams
4980
+ * 3. Rebuilds the xref table without linearization ordering constraints
4981
+ * 4. Removes any /Linearized key from the output
4982
+ *
4983
+ * If the input PDF is not linearized, it is returned unchanged.
4984
+ *
4985
+ * @param pdfBytes The raw PDF bytes.
4986
+ * @returns A non-linearized PDF.
4987
+ */
4988
+ async function delinearizePdf(pdfBytes) {
4989
+ if (!isLinearized(pdfBytes)) return pdfBytes;
4990
+ const startXref = findStartXref(pdfBytes);
4991
+ if (startXref < 0) return pdfBytes;
4992
+ if (!decoder$2.decode(pdfBytes.subarray(startXref, Math.min(startXref + 40, pdfBytes.length))).startsWith("xref")) return delinearizeByScanning(pdfBytes);
4993
+ let xrefResult;
4994
+ try {
4995
+ xrefResult = parseXrefTable(pdfBytes, startXref);
4996
+ } catch {
4997
+ return delinearizeByScanning(pdfBytes);
4998
+ }
4999
+ const { entries, trailer } = xrefResult;
5000
+ if (entries.size === 0 || entries.size === 1 && entries.has(0)) return delinearizeByScanning(pdfBytes);
5001
+ const trailerStart = findString(pdfBytes, "trailer", startXref);
5002
+ if (trailerStart >= 0) {
5003
+ const trailerEnd = findString(pdfBytes, ">>", trailerStart) + 2;
5004
+ const trailerStr = decoder$2.decode(pdfBytes.subarray(trailerStart, trailerEnd));
5005
+ const prevMatch = /\/Prev\s+(\d+)/.exec(trailerStr);
5006
+ if (prevMatch) {
5007
+ const prevOffset = parseInt(prevMatch[1], 10);
5008
+ try {
5009
+ const prevXref = parseXrefTable(pdfBytes, prevOffset);
5010
+ for (const [objNum, entry] of prevXref.entries) if (!entries.has(objNum)) entries.set(objNum, entry);
5011
+ } catch {}
5012
+ }
5013
+ }
5014
+ const objects = /* @__PURE__ */ new Map();
5015
+ const linDictObjNums = /* @__PURE__ */ new Set();
5016
+ for (const [objNum, entry] of entries) if (entry.inUse && objNum > 0) {
5017
+ const objBytes = extractObject(pdfBytes, entry.offset);
5018
+ const objStr = decoder$2.decode(objBytes);
5019
+ if (/\/Linearized\s+[\d.]+/.test(objStr)) {
5020
+ linDictObjNums.add(objNum);
5021
+ continue;
5022
+ }
5023
+ if (linDictObjNums.size > 0 && isHintStream(objStr)) continue;
5024
+ objects.set(objNum, objBytes);
5025
+ }
5026
+ return rebuildNormalPdf(objects, trailer);
5027
+ }
5028
+ /**
5029
+ * Check if an object string looks like a hint stream.
5030
+ * Hint streams are streams with specific characteristics:
5031
+ * they contain /S entries and /Length but no /Type.
5032
+ */
5033
+ function isHintStream(objStr) {
5034
+ return /<<[^>]*\/S\s+\d+[^>]*>>[\s\S]*\bstream\b/.test(objStr) && !/\/Type\s*\//.test(objStr) && !/\/Subtype\s*\//.test(objStr) && !/\/Filter\s*\//.test(objStr);
5035
+ }
5036
+ /**
5037
+ * Fallback delinearization that scans for indirect objects
5038
+ * instead of relying on xref parsing (handles xref streams).
5039
+ *
5040
+ * Uses byte-level scanning with `findString` to locate object
5041
+ * markers, which avoids problems with binary stream content being
5042
+ * misinterpreted as text by regex.
5043
+ */
5044
+ function delinearizeByScanning(pdfBytes) {
5045
+ const objects = /* @__PURE__ */ new Map();
5046
+ const str = decoder$2.decode(pdfBytes);
5047
+ const rootMatch = /\/Root\s+(\d+)\s+(\d+)\s+R/.exec(str);
5048
+ const infoMatch = /\/Info\s+(\d+)\s+(\d+)\s+R/.exec(str);
5049
+ const trailer = {
5050
+ size: 0,
5051
+ rootRef: rootMatch ? `${rootMatch[1]} ${rootMatch[2]} R` : "1 0 R",
5052
+ rootObjNum: rootMatch ? parseInt(rootMatch[1], 10) : 1,
5053
+ rootGenNum: rootMatch ? parseInt(rootMatch[2], 10) : 0,
5054
+ infoRef: infoMatch ? `${infoMatch[1]} ${infoMatch[2]} R` : void 0,
5055
+ infoObjNum: infoMatch ? parseInt(infoMatch[1], 10) : 0,
5056
+ infoGenNum: infoMatch ? parseInt(infoMatch[2], 10) : 0
5057
+ };
5058
+ let searchPos = 0;
5059
+ while (searchPos < pdfBytes.length) {
5060
+ const markerPos = findString(pdfBytes, " 0 obj", searchPos);
5061
+ if (markerPos < 0) break;
5062
+ let numStart = markerPos - 1;
5063
+ while (numStart >= 0 && pdfBytes[numStart] >= 48 && pdfBytes[numStart] <= 57) numStart--;
5064
+ numStart++;
5065
+ if (numStart > 0) {
5066
+ const prevByte = pdfBytes[numStart - 1];
5067
+ if (prevByte !== 10 && prevByte !== 13 && prevByte !== 32) {
5068
+ searchPos = markerPos + 6;
5069
+ continue;
5070
+ }
5071
+ }
5072
+ if (numStart >= markerPos) {
5073
+ searchPos = markerPos + 6;
5074
+ continue;
5075
+ }
5076
+ const objNumStr = decoder$2.decode(pdfBytes.subarray(numStart, markerPos));
5077
+ const objNum = parseInt(objNumStr, 10);
5078
+ if (Number.isNaN(objNum)) {
5079
+ searchPos = markerPos + 6;
5080
+ continue;
5081
+ }
5082
+ const objStart = numStart;
5083
+ const objBytes = extractObject(pdfBytes, objStart);
5084
+ const objStr = decoder$2.decode(objBytes);
5085
+ const endObjPos = findString(pdfBytes, "endobj", objStart);
5086
+ if (endObjPos < 0) {
5087
+ searchPos = markerPos + 6;
5088
+ continue;
5089
+ }
5090
+ searchPos = endObjPos + 6;
5091
+ if (/\/Linearized\s+[\d.]+/.test(objStr)) continue;
5092
+ if (/\/Type\s*\/XRef/.test(objStr)) continue;
5093
+ if (isHintStream(objStr)) continue;
5094
+ objects.set(objNum, objBytes);
5095
+ }
5096
+ trailer.size = objects.size > 0 ? Math.max(...objects.keys(), 0) + 1 : 1;
5097
+ return rebuildNormalPdf(objects, trailer);
5098
+ }
5099
+ /**
5100
+ * Rebuild a normal (non-linearized) PDF from collected objects.
5101
+ */
5102
+ function rebuildNormalPdf(objects, trailer) {
5103
+ const chunks = [];
5104
+ const newOffsets = /* @__PURE__ */ new Map();
5105
+ let currentOffset = 0;
5106
+ function writeStr(s) {
5107
+ const bytes = encoder$2.encode(s);
5108
+ chunks.push(bytes);
5109
+ currentOffset += bytes.length;
5110
+ }
5111
+ function writeBytes(data) {
5112
+ chunks.push(data);
5113
+ currentOffset += data.length;
5114
+ }
5115
+ writeStr("%PDF-1.7\n");
5116
+ writeStr("%âãÏÓ\n");
5117
+ const sortedObjNums = [...objects.keys()].sort((a, b) => a - b);
5118
+ for (const objNum of sortedObjNums) {
5119
+ const objBytes = objects.get(objNum);
5120
+ newOffsets.set(objNum, currentOffset);
5121
+ writeBytes(objBytes);
5122
+ writeStr("\n");
5123
+ }
5124
+ const xrefSize = (sortedObjNums.length > 0 ? Math.max(...sortedObjNums) : 0) + 1;
5125
+ const xrefOffset = currentOffset;
4232
5126
  writeStr("xref\n");
4233
- writeStr(`0 ${totalSize}\n`);
5127
+ writeStr(`0 ${xrefSize}\n`);
4234
5128
  writeStr("0000000000 65535 f \n");
4235
- for (let i = 1; i < totalSize; i++) {
5129
+ for (let i = 1; i < xrefSize; i++) {
4236
5130
  const off = newOffsets.get(i);
4237
5131
  if (off !== void 0) writeStr(`${off.toString().padStart(10, "0")} 00000 n \n`);
4238
5132
  else writeStr("0000000000 00000 f \n");
4239
5133
  }
4240
5134
  writeStr("trailer\n");
4241
5135
  writeStr("<<\n");
4242
- writeStr(`/Size ${totalSize}\n`);
5136
+ writeStr(`/Size ${xrefSize}\n`);
4243
5137
  writeStr(`/Root ${trailer.rootRef}\n`);
4244
5138
  if (trailer.infoRef) writeStr(`/Info ${trailer.infoRef}\n`);
4245
5139
  writeStr(">>\n");
4246
5140
  writeStr("startxref\n");
4247
- writeStr(`${mainXrefOffset}\n`);
5141
+ writeStr(`${xrefOffset}\n`);
4248
5142
  writeStr("%%EOF\n");
4249
5143
  const fileLength = currentOffset;
4250
5144
  const result = new Uint8Array(fileLength);
@@ -4253,24 +5147,8 @@ async function linearizePdf(pdfBytes, options) {
4253
5147
  result.set(chunk, pos);
4254
5148
  pos += chunk.length;
4255
5149
  }
4256
- patchNumber(result, linDictPlaceholder, "/L ", fileLength);
4257
- patchNumber(result, linDictPlaceholder, "/E ", endOfFirstPage);
4258
- patchNumber(result, linDictPlaceholder, "/T ", mainXrefOffset);
4259
5150
  return result;
4260
5151
  }
4261
- /** Build a simplified hint stream. */
4262
- function buildSimpleHintStream(firstPageObjs, remainingObjs, offsets) {
4263
- const allObjNums = [...firstPageObjs, ...remainingObjs];
4264
- const data = new Uint8Array(allObjNums.length * 4);
4265
- for (let i = 0; i < allObjNums.length; i++) {
4266
- const off = offsets.get(allObjNums[i]) ?? 0;
4267
- data[i * 4] = off >>> 24 & 255;
4268
- data[i * 4 + 1] = off >>> 16 & 255;
4269
- data[i * 4 + 2] = off >>> 8 & 255;
4270
- data[i * 4 + 3] = off & 255;
4271
- }
4272
- return data;
4273
- }
4274
5152
  /** Patch a 10-digit number placeholder in the output. */
4275
5153
  function patchNumber(data, searchStart, prefix, value) {
4276
5154
  const prefixBytes = encoder$2.encode(prefix);
@@ -4283,6 +5161,21 @@ function patchNumber(data, searchStart, prefix, value) {
4283
5161
  return;
4284
5162
  }
4285
5163
  }
5164
+ /**
5165
+ * Patch the /H [offset length] hint array in the linearization dict.
5166
+ */
5167
+ function patchHintArray(data, searchStart, hintOffset, hintLength) {
5168
+ const prefix = encoder$2.encode("/H [");
5169
+ const searchEnd = Math.min(searchStart + 500, data.length);
5170
+ outer: for (let i = searchStart; i < searchEnd - prefix.length; i++) {
5171
+ for (let j = 0; j < prefix.length; j++) if (data[i + j] !== prefix[j]) continue outer;
5172
+ const combined = `${hintOffset.toString().padStart(10, "0")} ${hintLength.toString().padStart(10, "0")}`;
5173
+ const numBytes = encoder$2.encode(combined);
5174
+ const writeStart = i + prefix.length;
5175
+ for (let k = 0; k < numBytes.length && writeStart + k < data.length; k++) data[writeStart + k] = numBytes[k];
5176
+ return;
5177
+ }
5178
+ }
4286
5179
 
4287
5180
  //#endregion
4288
5181
  //#region src/utils/binary.ts
@@ -6321,6 +7214,99 @@ async function enforcePdfAFull(pdfBytes, level, options = {}) {
6321
7214
  };
6322
7215
  }
6323
7216
 
7217
+ //#endregion
7218
+ //#region src/annotation/applyRedactions.ts
7219
+ /** Format a number for PDF operator output. */
7220
+ function n$4(value) {
7221
+ if (Number.isInteger(value)) return value.toString();
7222
+ const s = value.toFixed(6).replace(/\.?0+$/, "");
7223
+ return s === "-0" ? "0" : s;
7224
+ }
7225
+ /**
7226
+ * Check whether an annotation is a /Redact subtype.
7227
+ */
7228
+ function isRedactAnnotation(annot) {
7229
+ return annot.getType() === "Redact";
7230
+ }
7231
+ /**
7232
+ * Build the PDF operator string that renders the redaction overlay.
7233
+ *
7234
+ * The operators draw a filled rectangle in the interior colour
7235
+ * and optionally render overlay text on top.
7236
+ */
7237
+ function buildRedactionOperators(rect, interiorColor, overlayText) {
7238
+ const [x1, y1, x2, y2] = rect;
7239
+ const x = Math.min(x1, x2);
7240
+ const y = Math.min(y1, y2);
7241
+ const w = Math.abs(x2 - x1);
7242
+ const h = Math.abs(y2 - y1);
7243
+ const { r, g, b } = interiorColor ?? {
7244
+ r: 0,
7245
+ g: 0,
7246
+ b: 0
7247
+ };
7248
+ let ops = "";
7249
+ ops += "q\n";
7250
+ ops += `${n$4(r)} ${n$4(g)} ${n$4(b)} rg\n`;
7251
+ ops += `${n$4(x)} ${n$4(y)} ${n$4(w)} ${n$4(h)} re\n`;
7252
+ ops += "f\n";
7253
+ if (overlayText) {
7254
+ const tc = r * .299 + g * .587 + b * .114 > .5 ? 0 : 1;
7255
+ const fontSize = Math.min(h * .6, 10);
7256
+ const textX = x + 2;
7257
+ const textY = y + h / 2 - fontSize / 3;
7258
+ ops += `${n$4(tc)} ${n$4(tc)} ${n$4(tc)} rg\n`;
7259
+ ops += "BT\n";
7260
+ ops += `/Helvetica ${n$4(fontSize)} Tf\n`;
7261
+ ops += `${n$4(textX)} ${n$4(textY)} Td\n`;
7262
+ const escaped = overlayText.replaceAll("\\", "\\\\").replaceAll("(", "\\(").replaceAll(")", "\\)");
7263
+ ops += `(${escaped}) Tj\n`;
7264
+ ops += "ET\n";
7265
+ }
7266
+ ops += "Q\n";
7267
+ return ops;
7268
+ }
7269
+ /**
7270
+ * Apply a single redaction annotation on a page.
7271
+ *
7272
+ * Draws the redaction overlay and removes the annotation from the page.
7273
+ *
7274
+ * @returns `true` if the annotation was a redaction and was applied.
7275
+ */
7276
+ function applyOneRedaction(page, annot) {
7277
+ if (!isRedactAnnotation(annot)) return false;
7278
+ const ops = buildRedactionOperators(annot.getRect(), annot.getInteriorColor(), annot.getOverlayText());
7279
+ page.pushOperators(ops);
7280
+ page.removeAnnotation(annot);
7281
+ return true;
7282
+ }
7283
+ /**
7284
+ * Apply a single redaction annotation identified by page and annotation
7285
+ * index.
7286
+ *
7287
+ * @param doc The PDF document.
7288
+ * @param pageIndex Zero-based page index.
7289
+ * @param annotIndex Zero-based annotation index within the page.
7290
+ * @returns A {@link RedactionResult} (appliedCount 0 or 1).
7291
+ * @throws RangeError if pageIndex or annotIndex is out of bounds.
7292
+ * @throws TypeError if the annotation at annotIndex is not a
7293
+ * /Redact annotation.
7294
+ */
7295
+ function applyRedaction(doc, pageIndex, annotIndex) {
7296
+ const pages = doc.getPages();
7297
+ if (pageIndex < 0 || pageIndex >= pages.length) throw new RangeError(`applyRedaction: pageIndex ${pageIndex} out of range [0, ${pages.length - 1}]`);
7298
+ const page = pages[pageIndex];
7299
+ const annotations = page.getAnnotations();
7300
+ if (annotIndex < 0 || annotIndex >= annotations.length) throw new RangeError(`applyRedaction: annotIndex ${annotIndex} out of range [0, ${annotations.length - 1}]`);
7301
+ const annot = annotations[annotIndex];
7302
+ if (!isRedactAnnotation(annot)) throw new TypeError(`applyRedaction: annotation at index ${annotIndex} is type '${annot.getType()}', not 'Redact'`);
7303
+ applyOneRedaction(page, annot);
7304
+ return {
7305
+ appliedCount: 1,
7306
+ pages: [pageIndex]
7307
+ };
7308
+ }
7309
+
6324
7310
  //#endregion
6325
7311
  //#region src/assets/image/imageOptimize.ts
6326
7312
  /**
@@ -6786,7 +7772,7 @@ async function recompressDeflate(image, level) {
6786
7772
  * @internal
6787
7773
  */
6788
7774
  async function recompressJpeg(image, quality, progressive = false, chromaSubsampling = "4:2:0") {
6789
- const { encodeJpegWasm, isJpegWasmReady } = await import("./bridge-CcivG_Sm.mjs").then((n) => n.t);
7775
+ const { encodeJpegWasm, isJpegWasmReady } = await import("./bridge-DaS-gzEd.mjs").then((n) => n.t);
6790
7776
  if (isJpegWasmReady()) {
6791
7777
  let pixels = image.pixels;
6792
7778
  let channels = image.channels;
@@ -10018,7 +11004,7 @@ function encodeCode128(data) {
10018
11004
  return valuesToModules(encodeCode128Values(data));
10019
11005
  }
10020
11006
  /** Format a number for PDF output. */
10021
- function n$2(value) {
11007
+ function n$3(value) {
10022
11008
  if (Number.isInteger(value)) return value.toString();
10023
11009
  const s = value.toFixed(6).replace(/\.?0+$/, "");
10024
11010
  return s === "-0" ? "0" : s;
@@ -10054,7 +11040,7 @@ function code128ToOperators(matrix, x, y, options) {
10054
11040
  else if (!isBar && barStart !== -1) {
10055
11041
  const barX = startX + barStart * moduleWidth;
10056
11042
  const barWidth = (i - barStart) * moduleWidth;
10057
- ops += `${n$2(barX)} ${n$2(y)} ${n$2(barWidth)} ${n$2(height)} re\n`;
11043
+ ops += `${n$3(barX)} ${n$3(y)} ${n$3(barWidth)} ${n$3(height)} re\n`;
10058
11044
  barStart = -1;
10059
11045
  }
10060
11046
  }
@@ -10629,7 +11615,7 @@ function encodeCode39(data, includeCheckDigit = false, wideToNarrowRatio = 3) {
10629
11615
  };
10630
11616
  }
10631
11617
  /** Format a number for PDF output. */
10632
- function n$1(value) {
11618
+ function n$2(value) {
10633
11619
  if (Number.isInteger(value)) return value.toString();
10634
11620
  const s = value.toFixed(6).replace(/\.?0+$/, "");
10635
11621
  return s === "-0" ? "0" : s;
@@ -10665,7 +11651,7 @@ function code39ToOperators(matrix, x, y, options) {
10665
11651
  else if (!isBar && barStart !== -1) {
10666
11652
  const barX = startX + barStart * moduleWidth;
10667
11653
  const barWidth = (i - barStart) * moduleWidth;
10668
- ops += `${n$1(barX)} ${n$1(y)} ${n$1(barWidth)} ${n$1(height)} re\n`;
11654
+ ops += `${n$2(barX)} ${n$2(y)} ${n$2(barWidth)} ${n$2(height)} re\n`;
10669
11655
  barStart = -1;
10670
11656
  }
10671
11657
  }
@@ -10738,7 +11724,7 @@ function encodeItf(data, wideToNarrowRatio = 3) {
10738
11724
  };
10739
11725
  }
10740
11726
  /** Format a number for PDF output. */
10741
- function n(value) {
11727
+ function n$1(value) {
10742
11728
  if (Number.isInteger(value)) return value.toString();
10743
11729
  const s = value.toFixed(6).replace(/\.?0+$/, "");
10744
11730
  return s === "-0" ? "0" : s;
@@ -10772,8 +11758,8 @@ function itfToOperators(matrix, x, y, options) {
10772
11758
  const startX = x + quietZone * moduleWidth;
10773
11759
  const totalWidth = matrix.width * moduleWidth;
10774
11760
  if (bearerBars) {
10775
- ops += `${n(startX)} ${n(y)} ${n(totalWidth)} ${n(bearerBarWidth)} re\n`;
10776
- ops += `${n(startX)} ${n(y + height - bearerBarWidth)} ${n(totalWidth)} ${n(bearerBarWidth)} re\n`;
11761
+ ops += `${n$1(startX)} ${n$1(y)} ${n$1(totalWidth)} ${n$1(bearerBarWidth)} re\n`;
11762
+ ops += `${n$1(startX)} ${n$1(y + height - bearerBarWidth)} ${n$1(totalWidth)} ${n$1(bearerBarWidth)} re\n`;
10777
11763
  }
10778
11764
  const barY = bearerBars ? y + bearerBarWidth : y;
10779
11765
  const barHeight = bearerBars ? height - 2 * bearerBarWidth : height;
@@ -10784,7 +11770,7 @@ function itfToOperators(matrix, x, y, options) {
10784
11770
  else if (!isBar && barStart !== -1) {
10785
11771
  const barX = startX + barStart * moduleWidth;
10786
11772
  const barW = (i - barStart) * moduleWidth;
10787
- ops += `${n(barX)} ${n(barY)} ${n(barW)} ${n(barHeight)} re\n`;
11773
+ ops += `${n$1(barX)} ${n$1(barY)} ${n$1(barW)} ${n$1(barHeight)} re\n`;
10788
11774
  barStart = -1;
10789
11775
  }
10790
11776
  }
@@ -14611,6 +15597,601 @@ var ExceededMaxLengthError = class extends Error {
14611
15597
  }
14612
15598
  };
14613
15599
 
15600
+ //#endregion
15601
+ //#region src/form/formFlatten.ts
15602
+ /**
15603
+ * @module form/formFlatten
15604
+ *
15605
+ * Flatten interactive form fields into static page content.
15606
+ *
15607
+ * Form flattening converts interactive AcroForm fields into non-editable
15608
+ * page content by merging each field's appearance stream into the page's
15609
+ * content stream as a Form XObject, then removing the interactive form
15610
+ * structure (widget annotations and the AcroForm dictionary).
15611
+ *
15612
+ * This is a common enterprise requirement for producing final,
15613
+ * non-editable PDFs (e.g. invoices, contracts, government forms).
15614
+ *
15615
+ * Reference: PDF 1.7 spec, SS12.5.5 (Appearance Streams),
15616
+ * SS12.7 (Interactive Forms).
15617
+ */
15618
+ /** Format a number for PDF content stream output. */
15619
+ function n(value) {
15620
+ if (Number.isInteger(value)) return value.toString();
15621
+ const s = value.toFixed(6).replace(/\.?0+$/, "");
15622
+ return s === "-0" ? "0" : s;
15623
+ }
15624
+ /**
15625
+ * Get the normal (/N) appearance stream from a field's widget dictionary.
15626
+ *
15627
+ * Checks the /AP dictionary for a /N entry that is a PdfStream.
15628
+ * If /AP is absent or /N is not a stream, returns `undefined`.
15629
+ */
15630
+ function getAppearanceStream(widgetDict) {
15631
+ const ap = widgetDict.get("/AP");
15632
+ if (ap === void 0 || ap.kind !== "dict") return void 0;
15633
+ const nObj = ap.get("/N");
15634
+ if (nObj === void 0) return void 0;
15635
+ if (nObj.kind === "stream") return nObj;
15636
+ if (nObj.kind === "dict") {
15637
+ const nDict = nObj;
15638
+ const asObj = widgetDict.get("/AS");
15639
+ if (asObj !== void 0 && asObj.kind === "name") {
15640
+ const stateName = asObj.value;
15641
+ const stateStream = nDict.get(stateName);
15642
+ if (stateStream !== void 0 && stateStream.kind === "stream") return stateStream;
15643
+ }
15644
+ for (const [key, value] of nDict) if ((key.startsWith("/") ? key.slice(1) : key) !== "Off" && value.kind === "stream") return value;
15645
+ }
15646
+ }
15647
+ /**
15648
+ * Extract the widget rectangle as `[x1, y1, x2, y2]`.
15649
+ */
15650
+ function getWidgetRect(widgetDict) {
15651
+ const rectObj = widgetDict.get("/Rect");
15652
+ if (rectObj !== void 0 && rectObj.kind === "array") {
15653
+ const arr = rectObj;
15654
+ return [
15655
+ numVal(arr.items[0]) ?? 0,
15656
+ numVal(arr.items[1]) ?? 0,
15657
+ numVal(arr.items[2]) ?? 0,
15658
+ numVal(arr.items[3]) ?? 0
15659
+ ];
15660
+ }
15661
+ return [
15662
+ 0,
15663
+ 0,
15664
+ 0,
15665
+ 0
15666
+ ];
15667
+ }
15668
+ /**
15669
+ * Build the content stream operators that paint a Form XObject
15670
+ * at the position and size defined by the widget rectangle.
15671
+ *
15672
+ * Uses the XObject's BBox to determine scaling. If no BBox is found,
15673
+ * the widget dimensions are used as a 1:1 mapping.
15674
+ */
15675
+ function buildFlattenOps(xObjectName, rect, appearance) {
15676
+ const [x1, y1, x2, y2] = rect;
15677
+ const width = Math.abs(x2 - x1);
15678
+ const height = Math.abs(y2 - y1);
15679
+ if (width <= 0 || height <= 0) return "";
15680
+ let bboxWidth = width;
15681
+ let bboxHeight = height;
15682
+ const bboxObj = appearance.dict.get("/BBox");
15683
+ if (bboxObj !== void 0 && bboxObj.kind === "array") {
15684
+ const bboxArr = bboxObj;
15685
+ const bx1 = numVal(bboxArr.items[0]) ?? 0;
15686
+ const by1 = numVal(bboxArr.items[1]) ?? 0;
15687
+ const bx2 = numVal(bboxArr.items[2]) ?? width;
15688
+ const by2 = numVal(bboxArr.items[3]) ?? height;
15689
+ bboxWidth = Math.abs(bx2 - bx1);
15690
+ bboxHeight = Math.abs(by2 - by1);
15691
+ }
15692
+ const sx = bboxWidth > 0 ? width / bboxWidth : 1;
15693
+ const sy = bboxHeight > 0 ? height / bboxHeight : 1;
15694
+ let ops = "";
15695
+ ops += "q\n";
15696
+ ops += `${n(sx)} 0 0 ${n(sy)} ${n(Math.min(x1, x2))} ${n(Math.min(y1, y2))} cm\n`;
15697
+ ops += `/${xObjectName} Do\n`;
15698
+ ops += "Q\n";
15699
+ return ops;
15700
+ }
15701
+ /**
15702
+ * Check whether a field should be skipped based on flatten options.
15703
+ */
15704
+ function shouldSkipField(field, options) {
15705
+ if (options.preserveReadOnly && field.isReadOnly()) return true;
15706
+ return false;
15707
+ }
15708
+ /** Monotonically increasing counter for unique XObject names. */
15709
+ let flattenXObjectCounter = 0;
15710
+ /**
15711
+ * Flatten a single field: extract its appearance, build operators,
15712
+ * and return the result without mutating any form/page state.
15713
+ */
15714
+ function flattenSingleField(field) {
15715
+ const result = {
15716
+ ops: "",
15717
+ xObjects: []
15718
+ };
15719
+ const generatedAppearance = field.generateAppearance();
15720
+ const widgetDict = field.getWidgetDict();
15721
+ const appearance = getAppearanceStream(widgetDict) ?? generatedAppearance;
15722
+ const rect = getWidgetRect(widgetDict);
15723
+ const w = Math.abs(rect[2] - rect[0]);
15724
+ const h = Math.abs(rect[3] - rect[1]);
15725
+ if (w <= 0 || h <= 0) return result;
15726
+ if (!appearance.dict.has("/Type")) appearance.dict.set("/Type", PdfName.of("XObject"));
15727
+ if (!appearance.dict.has("/Subtype")) appearance.dict.set("/Subtype", PdfName.of("Form"));
15728
+ if (!appearance.dict.has("/BBox")) appearance.dict.set("/BBox", PdfArray.fromNumbers([
15729
+ 0,
15730
+ 0,
15731
+ w,
15732
+ h
15733
+ ]));
15734
+ const xObjName = `FlatField${flattenXObjectCounter++}`;
15735
+ const ops = buildFlattenOps(xObjName, rect, appearance);
15736
+ if (ops.length > 0) {
15737
+ result.ops = ops;
15738
+ result.xObjects.push({
15739
+ name: xObjName,
15740
+ stream: appearance
15741
+ });
15742
+ }
15743
+ return result;
15744
+ }
15745
+ /**
15746
+ * Flatten ALL form fields into static page content.
15747
+ *
15748
+ * For each field in the document's AcroForm:
15749
+ * 1. Generate / retrieve the field's appearance stream
15750
+ * 2. Embed the appearance as a Form XObject in the page's content stream
15751
+ * 3. Remove the widget annotation from the page
15752
+ * 4. Remove the field from the AcroForm
15753
+ *
15754
+ * After all fields are processed, the /AcroForm dictionary is cleared.
15755
+ *
15756
+ * @param form The document's PdfForm.
15757
+ * @param options Optional flatten options.
15758
+ * @returns An object describing the flatten operations performed, suitable
15759
+ * for the caller to apply to page content streams and resources.
15760
+ */
15761
+ function flattenForm(form, options = {}) {
15762
+ return flattenFieldList(form, form.getFields(), options, true);
15763
+ }
15764
+ /**
15765
+ * Flatten a SINGLE field by name.
15766
+ *
15767
+ * Locates the field in the form, merges its appearance into the page
15768
+ * content, removes the widget annotation, and removes the field from
15769
+ * the AcroForm's /Fields array. Other fields remain interactive.
15770
+ *
15771
+ * @param form The document's PdfForm.
15772
+ * @param fieldName The name of the field to flatten (partial or fully-qualified).
15773
+ * @param options Optional flatten options.
15774
+ * @returns An object describing the flatten operations performed.
15775
+ * @throws If the field is not found.
15776
+ */
15777
+ function flattenField(form, fieldName, options = {}) {
15778
+ const field = form.getField(fieldName);
15779
+ if (field === void 0) throw new Error(`Form field "${fieldName}" not found.`);
15780
+ return flattenFieldList(form, [field], options, false);
15781
+ }
15782
+ /**
15783
+ * Flatten specific fields by name.
15784
+ *
15785
+ * @param form The document's PdfForm.
15786
+ * @param fieldNames Array of field names to flatten.
15787
+ * @param options Optional flatten options.
15788
+ * @returns An object describing the flatten operations performed.
15789
+ * @throws If any field name is not found.
15790
+ */
15791
+ function flattenFields(form, fieldNames, options = {}) {
15792
+ const fields = [];
15793
+ for (const name of fieldNames) {
15794
+ const field = form.getField(name);
15795
+ if (field === void 0) throw new Error(`Form field "${name}" not found.`);
15796
+ fields.push(field);
15797
+ }
15798
+ return flattenFieldList(form, fields, options, false);
15799
+ }
15800
+ /**
15801
+ * Internal workhorse: flatten a list of fields and optionally
15802
+ * clear the AcroForm entirely.
15803
+ */
15804
+ function flattenFieldList(form, fields, options, removeAcroForm) {
15805
+ const result = {
15806
+ contentOps: "",
15807
+ xObjects: [],
15808
+ flattenedFields: [],
15809
+ skippedFields: [],
15810
+ acroFormRemoved: false
15811
+ };
15812
+ for (const field of fields) {
15813
+ if (shouldSkipField(field, options)) {
15814
+ result.skippedFields.push(field.getFullName());
15815
+ continue;
15816
+ }
15817
+ const fieldResult = flattenSingleField(field);
15818
+ result.contentOps += fieldResult.ops;
15819
+ result.xObjects.push(...fieldResult.xObjects);
15820
+ result.flattenedFields.push(field.getFullName());
15821
+ try {
15822
+ form.removeField(field.getFullName());
15823
+ } catch {
15824
+ try {
15825
+ form.removeField(field.getName());
15826
+ } catch {}
15827
+ }
15828
+ }
15829
+ if (removeAcroForm) {
15830
+ if (form.getFields().length === 0 && result.skippedFields.length === 0) result.acroFormRemoved = true;
15831
+ }
15832
+ return result;
15833
+ }
15834
+
15835
+ //#endregion
15836
+ //#region src/core/outlines.ts
15837
+ /**
15838
+ * Add a bookmark entry to the document's outline tree.
15839
+ *
15840
+ * @param doc The document to add the bookmark to.
15841
+ * @param options Bookmark configuration (title, page, nesting, style).
15842
+ * @returns A {@link BookmarkRef} identifying the new bookmark.
15843
+ */
15844
+ function addBookmark(doc, options) {
15845
+ const { title, pageIndex, parent, y, bold, italic, color, isOpen } = options;
15846
+ const dest = y !== void 0 ? {
15847
+ type: "page",
15848
+ pageIndex,
15849
+ fit: "FitH",
15850
+ top: y
15851
+ } : {
15852
+ type: "page",
15853
+ pageIndex,
15854
+ fit: "Fit"
15855
+ };
15856
+ const itemOptions = {};
15857
+ if (bold !== void 0) itemOptions.bold = bold;
15858
+ if (italic !== void 0) itemOptions.italic = italic;
15859
+ if (color !== void 0) itemOptions.color = color;
15860
+ if (isOpen !== void 0) itemOptions.isOpen = isOpen;
15861
+ let item;
15862
+ if (parent !== void 0) item = parent._item.addChild(title, dest, itemOptions);
15863
+ else item = doc.getOutlines().addItem(title, dest, itemOptions);
15864
+ return { _item: item };
15865
+ }
15866
+ /**
15867
+ * Return the bookmark tree for the document.
15868
+ *
15869
+ * Returns an array of top-level {@link BookmarkNode} objects, each
15870
+ * with a `children` array for nested bookmarks.
15871
+ *
15872
+ * @param doc The document to read bookmarks from.
15873
+ * @returns The bookmark tree.
15874
+ */
15875
+ function getBookmarks(doc) {
15876
+ return doc.getOutlines().items.map(itemToNode);
15877
+ }
15878
+ /**
15879
+ * Remove a specific bookmark from the document.
15880
+ *
15881
+ * If the bookmark has children, they are also removed.
15882
+ *
15883
+ * @param doc The document to modify.
15884
+ * @param ref The handle of the bookmark to remove.
15885
+ * @throws If the bookmark is not found in the tree.
15886
+ */
15887
+ function removeBookmark(doc, ref) {
15888
+ const tree = doc.getOutlines();
15889
+ const target = ref._item;
15890
+ const topIndex = tree.items.indexOf(target);
15891
+ if (topIndex !== -1) {
15892
+ tree.items.splice(topIndex, 1);
15893
+ return;
15894
+ }
15895
+ if (removeFromChildren(tree.items, target)) return;
15896
+ throw new Error("Bookmark not found in the outline tree");
15897
+ }
15898
+ /**
15899
+ * Remove all bookmarks from the document.
15900
+ *
15901
+ * @param doc The document to clear bookmarks from.
15902
+ */
15903
+ function removeAllBookmarks(doc) {
15904
+ const tree = doc.getOutlines();
15905
+ tree.items.length = 0;
15906
+ }
15907
+ /**
15908
+ * Convert a PdfOutlineItem to a BookmarkNode recursively.
15909
+ * @internal
15910
+ */
15911
+ function itemToNode(item) {
15912
+ const dest = item.destination;
15913
+ return {
15914
+ title: item.title,
15915
+ pageIndex: dest.pageIndex ?? 0,
15916
+ y: dest.top,
15917
+ bold: item.bold,
15918
+ italic: item.italic,
15919
+ color: item.color,
15920
+ children: item.children.map(itemToNode),
15921
+ ref: { _item: item }
15922
+ };
15923
+ }
15924
+ /**
15925
+ * Recursively search for and remove a target item from nested children.
15926
+ * @internal
15927
+ */
15928
+ function removeFromChildren(items, target) {
15929
+ for (const item of items) {
15930
+ const childIndex = item.children.indexOf(target);
15931
+ if (childIndex !== -1) {
15932
+ item.children.splice(childIndex, 1);
15933
+ return true;
15934
+ }
15935
+ if (removeFromChildren(item.children, target)) return true;
15936
+ }
15937
+ return false;
15938
+ }
15939
+
15940
+ //#endregion
15941
+ //#region src/batch/batchProcessor.ts
15942
+ /**
15943
+ * @module batch/batchProcessor
15944
+ *
15945
+ * Parallel batch processing for multiple PDF documents.
15946
+ *
15947
+ * Uses `worker_threads` when running in Node.js for true parallelism,
15948
+ * and falls back to concurrent (but single-threaded) `Promise.all`
15949
+ * processing in other runtimes (browsers, Cloudflare Workers, Deno, Bun).
15950
+ *
15951
+ * Entry points:
15952
+ * - {@link processBatch} — run an arbitrary operation on multiple PDFs
15953
+ * - {@link batchMerge} — merge multiple PDFs in parallel chunks
15954
+ * - {@link batchFlatten} — flatten forms across many PDFs
15955
+ *
15956
+ * @packageDocumentation
15957
+ */
15958
+ /**
15959
+ * Process an array of tasks with bounded concurrency.
15960
+ *
15961
+ * Unlike a simple `Promise.all`, this ensures at most `concurrency`
15962
+ * tasks run simultaneously, which is critical when each task may
15963
+ * allocate significant memory (parsing/saving a PDF).
15964
+ */
15965
+ async function runWithConcurrency(tasks, concurrency) {
15966
+ const results = new Array(tasks.length);
15967
+ let nextIndex = 0;
15968
+ const worker = async () => {
15969
+ while (nextIndex < tasks.length) {
15970
+ const idx = nextIndex++;
15971
+ results[idx] = await tasks[idx]();
15972
+ }
15973
+ };
15974
+ const workers = Array.from({ length: Math.min(concurrency, tasks.length) }, () => worker());
15975
+ await Promise.all(workers);
15976
+ return results;
15977
+ }
15978
+ /**
15979
+ * Process multiple PDFs in parallel (or with bounded concurrency).
15980
+ *
15981
+ * Each input `Uint8Array` is loaded as a {@link PdfDocument}, the
15982
+ * caller-supplied `operation` is applied, and the resulting bytes
15983
+ * are collected.
15984
+ *
15985
+ * In Node.js, true parallelism is available via `worker_threads`
15986
+ * (though this implementation uses async concurrency for simplicity
15987
+ * and to avoid serialization overhead). In all runtimes the
15988
+ * concurrency limiter ensures memory pressure stays manageable.
15989
+ *
15990
+ * @param files Array of raw PDF bytes.
15991
+ * @param operation An async function that receives a loaded
15992
+ * {@link PdfDocument} and returns the processed
15993
+ * PDF as `Uint8Array`.
15994
+ * @param options Concurrency and progress options.
15995
+ * @returns A {@link BatchResult} with outputs, success
15996
+ * count, and any per-file errors.
15997
+ */
15998
+ async function processBatch(files, operation, options) {
15999
+ if (files.length === 0) return {
16000
+ outputs: [],
16001
+ successCount: 0,
16002
+ errors: /* @__PURE__ */ new Map()
16003
+ };
16004
+ const defaultConcurrency = detectRuntime() === "node" ? 4 : files.length;
16005
+ const concurrency = options?.concurrency ?? defaultConcurrency;
16006
+ const onProgress = options?.onProgress;
16007
+ let doneCount = 0;
16008
+ const errors = /* @__PURE__ */ new Map();
16009
+ const outputs = new Array(files.length);
16010
+ await runWithConcurrency(files.map((fileBytes, idx) => async () => {
16011
+ try {
16012
+ outputs[idx] = await operation(await PdfDocument.load(fileBytes));
16013
+ } catch (err) {
16014
+ errors.set(idx, err instanceof Error ? err : new Error(String(err)));
16015
+ outputs[idx] = new Uint8Array(0);
16016
+ } finally {
16017
+ doneCount++;
16018
+ onProgress?.(doneCount, files.length);
16019
+ }
16020
+ }), Math.max(1, concurrency));
16021
+ return {
16022
+ outputs,
16023
+ successCount: files.length - errors.size,
16024
+ errors
16025
+ };
16026
+ }
16027
+ /**
16028
+ * Merge multiple PDFs in parallel chunks, then merge the chunks.
16029
+ *
16030
+ * For large collections this is significantly faster than sequential
16031
+ * merging because parsing and page-copying can overlap.
16032
+ *
16033
+ * @param files Array of raw PDF bytes to merge (in order).
16034
+ * @param options Concurrency and progress options.
16035
+ * @returns The merged PDF as `Uint8Array`.
16036
+ */
16037
+ async function batchMerge(files, options) {
16038
+ if (files.length === 0) return createPdf().save();
16039
+ if (files.length === 1) return (await PdfDocument.load(files[0])).save();
16040
+ const concurrency = options?.concurrency ?? 4;
16041
+ const onProgress = options?.onProgress;
16042
+ const chunkSize = Math.max(1, Math.ceil(files.length / concurrency));
16043
+ const chunks = [];
16044
+ for (let i = 0; i < files.length; i += chunkSize) chunks.push(files.slice(i, i + chunkSize));
16045
+ let doneChunks = 0;
16046
+ const result = await mergePdfs(await runWithConcurrency(chunks.map((chunk) => async () => {
16047
+ const docs = [];
16048
+ for (const fileBytes of chunk) docs.push(await PdfDocument.load(fileBytes));
16049
+ const merged = await mergePdfs(docs);
16050
+ doneChunks++;
16051
+ onProgress?.(doneChunks, chunks.length + 1);
16052
+ return merged;
16053
+ }), concurrency));
16054
+ onProgress?.(chunks.length + 1, chunks.length + 1);
16055
+ return result.save();
16056
+ }
16057
+ /**
16058
+ * Flatten interactive form fields across many PDFs.
16059
+ *
16060
+ * Each PDF is loaded, its form is flattened (field values are burned
16061
+ * into page content), and the result is saved.
16062
+ *
16063
+ * @param files Array of raw PDF bytes.
16064
+ * @param options Concurrency and progress options.
16065
+ * @returns A {@link BatchResult} with flattened PDF outputs.
16066
+ */
16067
+ async function batchFlatten(files, options) {
16068
+ return processBatch(files, async (doc) => {
16069
+ try {
16070
+ doc.getForm().flatten();
16071
+ } catch {}
16072
+ return doc.save();
16073
+ }, options);
16074
+ }
16075
+
16076
+ //#endregion
16077
+ //#region src/wasm/inlineWasm.ts
16078
+ /**
16079
+ * @module wasm/inlineWasm
16080
+ *
16081
+ * Provides runtime access to inline (base64-encoded) WASM module bytes.
16082
+ *
16083
+ * This module is the runtime companion to the build-time code generation
16084
+ * script `scripts/generate-inline-wasm.ts`. It imports the generated
16085
+ * constants from `inlineWasm.generated.ts` and exposes a single function,
16086
+ * {@link getInlineWasmBytes}, that decodes a module's base64 string into
16087
+ * a `Uint8Array` on demand.
16088
+ *
16089
+ * Design:
16090
+ * - The heavy lifting (reading WASM files, base64 encoding) happens at
16091
+ * **build time** via the code generation script.
16092
+ * - At **runtime**, only a single base64 decode is needed per module,
16093
+ * and the result is cached.
16094
+ * - This approach avoids shipping base64 strings in the default bundle.
16095
+ * The generated file is only imported when inline WASM is needed
16096
+ * (e.g., as a fallback when no WASM path is configured).
16097
+ *
16098
+ * @packageDocumentation
16099
+ */
16100
+ /**
16101
+ * The set of all WASM module names supported by the library.
16102
+ *
16103
+ * These correspond to the crate directories under `src/wasm/`.
16104
+ */
16105
+ const WASM_MODULE_NAMES = [
16106
+ "libdeflate",
16107
+ "png",
16108
+ "ttf",
16109
+ "shaping",
16110
+ "jbig2",
16111
+ "jpeg"
16112
+ ];
16113
+ /** Cache of decoded WASM bytes, keyed by module name. */
16114
+ const decodedCache = /* @__PURE__ */ new Map();
16115
+ /**
16116
+ * Retrieve the decoded WASM bytes for a given module name.
16117
+ *
16118
+ * On first call for a module, the base64 string from the generated file
16119
+ * is decoded into a `Uint8Array`. Subsequent calls return the cached
16120
+ * result.
16121
+ *
16122
+ * @param name The WASM module name (e.g., `'libdeflate'`, `'png'`).
16123
+ * @returns The decoded WASM binary as a `Uint8Array`.
16124
+ * @throws If the module name is unknown or the generated file does
16125
+ * not contain data for the requested module.
16126
+ *
16127
+ * @example
16128
+ * ```ts
16129
+ * const wasmBytes = getInlineWasmBytes('libdeflate');
16130
+ * const module = await WebAssembly.compile(wasmBytes);
16131
+ * ```
16132
+ */
16133
+ function getInlineWasmBytes(name) {
16134
+ if (!isValidModuleName(name)) throw new Error(`Unknown WASM module: "${name}". Valid modules: ${WASM_MODULE_NAMES.join(", ")}`);
16135
+ const cached = decodedCache.get(name);
16136
+ if (cached) return cached;
16137
+ let base64Data;
16138
+ try {
16139
+ const moduleData = requireGenerated().INLINE_WASM_MODULES[name];
16140
+ if (!moduleData) throw new Error(`WASM module "${name}" is not available in the generated inline data. Run \`npm run generate:wasm-inline\` to regenerate.`);
16141
+ base64Data = moduleData;
16142
+ } catch (err) {
16143
+ if (err instanceof Error && err.message.includes("is not available")) throw err;
16144
+ throw new Error(`Failed to load inline WASM data for module "${name}". The generated file may not exist. Run \`npm run generate:wasm-inline\` to generate it. Original error: ${err instanceof Error ? err.message : String(err)}`);
16145
+ }
16146
+ const bytes = Uint8Array.fromBase64(base64Data);
16147
+ decodedCache.set(name, bytes);
16148
+ return bytes;
16149
+ }
16150
+ /**
16151
+ * Check whether a given module name is a valid WASM module name.
16152
+ *
16153
+ * @param name The name to check.
16154
+ * @returns `true` if the name is one of the known WASM modules.
16155
+ */
16156
+ function isValidModuleName(name) {
16157
+ return WASM_MODULE_NAMES.includes(name);
16158
+ }
16159
+ /**
16160
+ * Check whether inline WASM data is available for a given module.
16161
+ *
16162
+ * Returns `true` if the generated file contains base64 data for the
16163
+ * requested module. Returns `false` if the generated file does not
16164
+ * exist or does not include the module.
16165
+ *
16166
+ * @param name The WASM module name.
16167
+ * @returns `true` if inline bytes can be retrieved for this module.
16168
+ */
16169
+ function hasInlineWasmData(name) {
16170
+ if (!isValidModuleName(name)) return false;
16171
+ try {
16172
+ return name in requireGenerated().INLINE_WASM_MODULES;
16173
+ } catch {
16174
+ return false;
16175
+ }
16176
+ }
16177
+ /** Cached reference to the generated module, loaded once. */
16178
+ let generatedModule;
16179
+ /**
16180
+ * Lazily load and cache the generated module.
16181
+ *
16182
+ * Uses a dynamic import expression to avoid hard compile-time dependency
16183
+ * on the generated file (which may not exist during development).
16184
+ */
16185
+ function requireGenerated() {
16186
+ if (generatedModule) return generatedModule;
16187
+ try {
16188
+ throw new Error("Inline WASM data not loaded. Call loadInlineWasmModule() first, or run `npm run generate:wasm-inline`.");
16189
+ } catch (err) {
16190
+ if (generatedModule) return generatedModule;
16191
+ throw err;
16192
+ }
16193
+ }
16194
+
14614
16195
  //#endregion
14615
16196
  //#region src/index.ts
14616
16197
  /** Whether initWasm has already completed successfully. */
@@ -14638,19 +16219,19 @@ async function initWasm(options) {
14638
16219
  if (wasmInitialized) return;
14639
16220
  const inits = [];
14640
16221
  if (options.deflate || options.deflateWasm) inits.push((async () => {
14641
- const { initDeflateWasm } = await import("./libdeflateWasm-8b91Vmia.mjs").then((n) => n.r);
16222
+ const { initDeflateWasm } = await import("./libdeflateWasm-CcA1W04G.mjs").then((n) => n.r);
14642
16223
  await initDeflateWasm(options.deflateWasm);
14643
16224
  })());
14644
16225
  if (options.png || options.pngWasm) inits.push((async () => {
14645
- const { initPngWasm } = await import("./pngEmbed-BWAbEUKF.mjs").then((n) => n.n);
16226
+ const { initPngWasm } = await import("./pngEmbed-CGi7cym7.mjs").then((n) => n.n);
14646
16227
  await initPngWasm(options.pngWasm);
14647
16228
  })());
14648
16229
  if (options.fonts || options.fontWasm) inits.push((async () => {
14649
- const { initSubsetWasm } = await import("./fontSubset-D-vQQems.mjs").then((n) => n.r);
16230
+ const { initSubsetWasm } = await import("./fontSubset-BGFDIMmT.mjs").then((n) => n.r);
14650
16231
  await initSubsetWasm(options.fontWasm);
14651
16232
  })());
14652
16233
  if (options.jpeg || options.jpegWasm) inits.push((async () => {
14653
- const { initJpegWasm } = await import("./bridge-CcivG_Sm.mjs").then((n) => n.t);
16234
+ const { initJpegWasm } = await import("./bridge-DaS-gzEd.mjs").then((n) => n.t);
14654
16235
  await initJpegWasm(options.jpegWasm);
14655
16236
  })());
14656
16237
  await Promise.all(inits);
@@ -14658,5 +16239,5 @@ async function initWasm(options) {
14658
16239
  }
14659
16240
 
14660
16241
  //#endregion
14661
- export { encodeCode128Values as $, addFieldLock as $t, readEan13 as A, PdfUnderlineAnnotation as An, isValidLevel as At, upcAToOperators as B, generateUnderlineAppearance as Bn, enforcePdfA as Bt, borderedPreset as C, PdfLineAnnotation as Cn, buildAfArray as Ct, readBarcode as D, PdfHighlightAnnotation as Dn, validateXmpMetadata as Dt, stripedPreset as E, PdfSquareAnnotation as En, parseXmpMetadata as Et, pdf417ToOperators as F, generateInkAppearance as Fn, buildOutputIntent as Ft, encodeEan8 as G, saveDocumentIncremental as Gn, findChangedObjects as Gt, ean13ToOperators as H, PdfTextAnnotation as Hn, isLinearized as Ht, dataMatrixToOperators as I, generateLineAppearance as In, SRGB_ICC_PROFILE as It, code39ToOperators as J, embedLtvData as Jt, encodeItf as K, saveIncremental as Kn, optimizeIncrementalSave as Kt, encodeDataMatrix as L, generateSquareAppearance as Ln, generateSrgbIccProfile as Lt, calculateBarcodeDimensions as M, generateCircleAppearance as Mn, generateWinAnsiToUnicodeCmap as Mt, renderStyledBarcode as N, generateFreeTextAppearance as Nn, generateZapfDingbatsToUnicodeCmap as Nt, readCode128 as O, PdfSquigglyAnnotation as On, getProfile as Ot, encodePdf417 as P, generateHighlightAppearance as Pn, getToUnicodeCmap as Pt, encodeCode128 as Q, diffSignedContent as Qt, calculateUpcCheckDigit as R, generateSquigglyAppearance as Rn, detectTransparency as Rt, applyTablePreset as S, PdfCircleAnnotation as Sn, stripProhibitedFeatures as St, professionalPreset as T, PdfPolygonAnnotation as Tn, extractXmpMetadata as Tt, ean8ToOperators as U, PDFOperator as Un, linearizePdf as Ut, calculateEanCheckDigit as V, PdfLinkAnnotation as Vn, validatePdfA as Vt, encodeEan13 as W, ChangeTracker as Wn, computeObjectHash as Wt, encodeCode39 as X, addCounterSignature as Xt, computeCode39CheckDigit as Y, hasLtvData as Yt, code128ToOperators as Z, getCounterSignatures as Zt, estimateTextWidth as _, PdfCaretAnnotation as _n, recompressImage as _t, FieldAlreadyExistsError as a, getCertificationLevel as an, recompressWebP as at, wrapText as b, PdfInkAnnotation as bn, generatePdfAXmpBytes as bt, ForeignPageError as c, appendIncrementalUpdate as cn, convertTiffCmykToRgb as ct, NoSuchFieldError as d, saveIncrementalWithSignaturePreservation as dn, extractJpegMetadata as dt, buildFieldLockDict as en, valuesToModules as et, RemovePageFromEmptyDocumentError as f, validateByteRangeIntegrity as fn, injectJpegMetadata as ft, ellipsisText as g, PdfFileAttachmentAnnotation as gn, optimizeImage as gt, applyOverflow as h, requestTimestamp as hn, estimateJpegQuality as ht, ExceededMaxLengthError as i, buildDocMdpReference as in, encodePngFromPixels as it, readEan8 as j, PdfFreeTextAnnotation as jn, generateSymbolToUnicodeCmap as jt, readCode39 as k, PdfStrikeOutAnnotation as kn, getSupportedLevels as kt, InvalidFieldNamePartError as l, findExistingSignatures as ln, embedTiffCmyk as lt, UnexpectedFieldTypeError as m, parseTimestampResponse as mn, downscaleImage as mt, CombedTextLayoutError as n, detectModifications as nn, canDirectEmbed as nt, FieldExistsAsNonTerminalError as o, setCertificationLevel as on, webpToJpeg as ot, RichTextFieldReadError as p, buildTimestampRequest as pn, analyzeJpegMarkers as pt, itfToOperators as q, buildDssDictionary as qt, EncryptedPdfError as r, MdpPermission as rn, embedTiffDirect as rt, FontNotEmbeddedError as s, validateSignatureChain as sn, webpToPng as st, initWasm as t, getFieldLocks as tn, PdfWorker as tt, MissingOnValueCheckError as u, parseExistingTrailer as un, isCmykTiff as ut, shrinkFontSize as v, PdfPopupAnnotation as vn, enforcePdfAFull as vt, minimalPreset as w, PdfPolyLineAnnotation as wn, createAssociatedFile as wt, applyPreset as x, PdfStampAnnotation as xn, countOccurrences as xt, truncateText as y, PdfRedactAnnotation as yn, generatePdfAXmp as yt, encodeUpcA as z, generateStrikeOutAppearance as zn, flattenTransparency as zt };
14662
- //# sourceMappingURL=src-Db6Qknoz.mjs.map
16242
+ export { upcAToOperators as $, generateSquareAppearance as $n, flattenTransparency as $t, estimateTextWidth as A, parseTimestampResponse as An, recompressImage as At, readBarcode as B, PdfPolyLineAnnotation as Bn, parseXmpMetadata as Bt, MissingOnValueCheckError as C, validateSignatureChain as Cn, isCmykTiff as Ct, UnexpectedFieldTypeError as D, saveIncrementalWithSignaturePreservation as Dn, downscaleImage as Dt, RichTextFieldReadError as E, parseExistingTrailer as En, analyzeJpegMarkers as Et, applyTablePreset as F, PdfRedactAnnotation as Fn, countOccurrences as Ft, calculateBarcodeDimensions as G, PdfStrikeOutAnnotation as Gn, generateSymbolToUnicodeCmap as Gt, readCode39 as H, PdfSquareAnnotation as Hn, getProfile as Ht, borderedPreset as I, PdfInkAnnotation as In, stripProhibitedFeatures as It, pdf417ToOperators as J, generateCircleAppearance as Jn, getToUnicodeCmap as Jt, renderStyledBarcode as K, PdfUnderlineAnnotation as Kn, generateWinAnsiToUnicodeCmap as Kt, minimalPreset as L, PdfStampAnnotation as Ln, buildAfArray as Lt, truncateText as M, PdfFileAttachmentAnnotation as Mn, enforcePdfAFull as Mt, wrapText as N, PdfCaretAnnotation as Nn, generatePdfAXmp as Nt, applyOverflow as O, validateByteRangeIntegrity as On, estimateJpegQuality as Ot, applyPreset as P, PdfPopupAnnotation as Pn, generatePdfAXmpBytes as Pt, encodeUpcA as Q, generateLineAppearance as Qn, detectTransparency as Qt, professionalPreset as R, PdfCircleAnnotation as Rn, createAssociatedFile as Rt, InvalidFieldNamePartError as S, setCertificationLevel as Sn, embedTiffCmyk as St, RemovePageFromEmptyDocumentError as T, findExistingSignatures as Tn, injectJpegMetadata as Tt, readEan13 as U, PdfHighlightAnnotation as Un, getSupportedLevels as Ut, readCode128 as V, PdfPolygonAnnotation as Vn, validateXmpMetadata as Vt, readEan8 as W, PdfSquigglyAnnotation as Wn, isValidLevel as Wt, encodeDataMatrix as X, generateHighlightAppearance as Xn, SRGB_ICC_PROFILE as Xt, dataMatrixToOperators as Y, generateFreeTextAppearance as Yn, buildOutputIntent as Yt, calculateUpcCheckDigit as Z, generateInkAppearance as Zn, generateSrgbIccProfile as Zt, ExceededMaxLengthError as _, getFieldLocks as _n, encodePngFromPixels as _t, batchFlatten as a, linearizePdf as an, enforcePdfUa as ar, encodeItf as at, FontNotEmbeddedError as b, buildDocMdpReference as bn, webpToPng as bt, addBookmark as c, optimizeIncrementalSave as cn, ChangeTracker as cr, computeCode39CheckDigit as ct, removeBookmark as d, hasLtvData as dn, encodeCode128 as dt, enforcePdfA as en, generateSquigglyAppearance as er, calculateEanCheckDigit as et, flattenField as f, addCounterSignature as fn, encodeCode128Values as ft, EncryptedPdfError as g, buildFieldLockDict as gn, embedTiffDirect as gt, CombedTextLayoutError as h, addFieldLock as hn, canDirectEmbed as ht, isValidModuleName as i, isLinearized as in, PdfTextAnnotation as ir, encodeEan8 as it, shrinkFontSize as j, requestTimestamp as jn, applyRedaction as jt, ellipsisText as k, buildTimestampRequest as kn, optimizeImage as kt, getBookmarks as l, buildDssDictionary as ln, saveDocumentIncremental as lr, encodeCode39 as lt, flattenForm as m, diffSignedContent as mn, PdfWorker as mt, getInlineWasmBytes as n, delinearizePdf as nn, generateUnderlineAppearance as nr, ean8ToOperators as nt, batchMerge as o, computeObjectHash as on, validatePdfUa as or, itfToOperators as ot, flattenFields as p, getCounterSignatures as pn, valuesToModules as pt, encodePdf417 as q, PdfFreeTextAnnotation as qn, generateZapfDingbatsToUnicodeCmap as qt, hasInlineWasmData as r, getLinearizationInfo as rn, PdfLinkAnnotation as rr, encodeEan13 as rt, processBatch as s, findChangedObjects as sn, PDFOperator as sr, code39ToOperators as st, initWasm as t, validatePdfA as tn, generateStrikeOutAppearance as tr, ean13ToOperators as tt, removeAllBookmarks as u, embedLtvData as un, saveIncremental as ur, code128ToOperators as ut, FieldAlreadyExistsError as v, detectModifications as vn, recompressWebP as vt, NoSuchFieldError as w, appendIncrementalUpdate as wn, extractJpegMetadata as wt, ForeignPageError as x, getCertificationLevel as xn, convertTiffCmykToRgb as xt, FieldExistsAsNonTerminalError as y, MdpPermission as yn, webpToJpeg as yt, stripedPreset as z, PdfLineAnnotation as zn, extractXmpMetadata as zt };
16243
+ //# sourceMappingURL=src-QZWP21qF.mjs.map