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.
- package/README.md +1 -1
- package/dist/{batchOptimize-DtRwBOqR.mjs → batchOptimize-BCJEEN8J.mjs} +4 -4
- package/dist/{batchOptimize-Ba_pWw71.cjs → batchOptimize-C1W3O68Q.cjs} +3 -3
- package/dist/{bridge-DYCQzxF7.cjs → bridge-DMEuGtfn.cjs} +2 -2
- package/dist/{bridge-CcivG_Sm.mjs → bridge-DaS-gzEd.mjs} +3 -3
- package/dist/browser.cjs +31 -10
- package/dist/browser.d.cts +5 -5
- package/dist/browser.d.mts +5 -5
- package/dist/browser.mjs +12 -12
- package/dist/cli/index.cjs +2 -2
- package/dist/cli/index.mjs +3 -3
- package/dist/{compressionAnalysis-B84FPXaQ.cjs → compressionAnalysis-B-FPzgzw.cjs} +7 -7
- package/dist/{compressionAnalysis-DGs-MqTe.d.mts → compressionAnalysis-Ch7t-HXn.d.mts} +2 -2
- package/dist/{compressionAnalysis-DGs-MqTe.d.mts.map → compressionAnalysis-Ch7t-HXn.d.mts.map} +1 -1
- package/dist/{compressionAnalysis-odbHC7Uk.mjs → compressionAnalysis-CwknBtqx.mjs} +7 -7
- package/dist/{compressionAnalysis-VtYV9Zmq.d.cts → compressionAnalysis-Dgv1TtHJ.d.cts} +2 -2
- package/dist/{compressionAnalysis-VtYV9Zmq.d.cts.map → compressionAnalysis-Dgv1TtHJ.d.cts.map} +1 -1
- package/dist/create.cjs +3 -3
- package/dist/create.d.cts +2 -2
- package/dist/create.d.mts +2 -2
- package/dist/create.mjs +3 -3
- package/dist/{deduplicateImages-MfUDPxQz.mjs → deduplicateImages-DIon68zB.mjs} +2 -2
- package/dist/{fflateAdapter-PSiW_ML7.mjs → fflateAdapter-DuNiByKx.mjs} +2 -2
- package/dist/{fontEmbed-BN842wlb.d.cts → fontEmbed-3YhUPLFj.d.cts} +2 -2
- package/dist/{fontEmbed-BN842wlb.d.cts.map → fontEmbed-3YhUPLFj.d.cts.map} +1 -1
- package/dist/{fontEmbed-Dgq5K89o.d.mts → fontEmbed-DlVnVCZU.d.mts} +2 -2
- package/dist/{fontEmbed-Dgq5K89o.d.mts.map → fontEmbed-DlVnVCZU.d.mts.map} +1 -1
- package/dist/{fontSubset-D-vQQems.mjs → fontSubset-BGFDIMmT.mjs} +2 -2
- package/dist/forms.cjs +1 -1
- package/dist/forms.mjs +1 -1
- package/dist/{index-DCSbmXWh.d.mts → index-B4S61WjK.d.mts} +589 -21
- package/dist/index-B4S61WjK.d.mts.map +1 -0
- package/dist/{index-J1W3FdL8.d.cts → index-xfJP6Ycm.d.cts} +589 -21
- package/dist/index-xfJP6Ycm.d.cts.map +1 -0
- package/dist/index.cjs +31 -10
- package/dist/index.d.cts +5 -5
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +12 -12
- package/dist/{layout-D6EUKSP8.mjs → layout-CrqeJBMI.mjs} +3 -3
- package/dist/{layout-DH61a1iR.cjs → layout-DQh05VP-.cjs} +3 -3
- package/dist/{libdeflateWasm-8b91Vmia.mjs → libdeflateWasm-CcA1W04G.mjs} +3 -3
- package/dist/{libdeflateWasm-BdiDEJOj.cjs → libdeflateWasm-DLw-I1CY.cjs} +2 -2
- package/dist/{loader-C7B5dVCI.mjs → loader-CVB-c_3Z.mjs} +16 -6
- package/dist/{loader-I4zdkoWc.cjs → loader-DYCH3n7d.cjs} +15 -5
- package/dist/parse.cjs +2 -2
- package/dist/parse.d.cts +2 -2
- package/dist/parse.d.mts +2 -2
- package/dist/parse.mjs +2 -2
- package/dist/{pdfCatalog-CYy4NXEY.cjs → pdfCatalog-Bqq4FiLn.cjs} +2 -1
- package/dist/{pdfCatalog-3yMIhJtt.mjs → pdfCatalog-CnKMAtIo.mjs} +3 -2
- package/dist/{pdfDocument-CbU-2TjT.d.cts → pdfDocument-Bc_vAO74.d.cts} +257 -172
- package/dist/pdfDocument-Bc_vAO74.d.cts.map +1 -0
- package/dist/{pdfDocument-BgvEP5Po.d.mts → pdfDocument-Bi-NoyXv.d.mts} +257 -172
- package/dist/pdfDocument-Bi-NoyXv.d.mts.map +1 -0
- package/dist/{pdfDocument-B0_XwS4X.mjs → pdfDocument-D7aFSQEY.mjs} +219 -40
- package/dist/{pdfDocument-CEbbUP9i.cjs → pdfDocument-D9uYNSb-.cjs} +233 -36
- package/dist/{pdfForm-9gd40uz9.cjs → pdfForm-BpqDGp29.cjs} +7 -1
- package/dist/{pdfForm-Cn-cVicP.mjs → pdfForm-C3mC9FoQ.mjs} +2 -2
- package/dist/{pdfPage-B_d9HmkG.mjs → pdfPage-BJIE5hc6.mjs} +189 -24
- package/dist/{pdfPage-Cd8jOJp6.cjs → pdfPage-C_tjEjj1.cjs} +188 -23
- package/dist/{pngEmbed-BWAbEUKF.mjs → pngEmbed-CGi7cym7.mjs} +3 -3
- package/dist/{pngEmbed-D4X4ZN-3.cjs → pngEmbed-CTn9IeNB.cjs} +2 -2
- package/dist/{src-Db6Qknoz.mjs → src-QZWP21qF.mjs} +1738 -157
- package/dist/{src-B1iDGLCL.cjs → src-lFqJHb1C.cjs} +1874 -185
- package/package.json +2 -1
- package/dist/index-DCSbmXWh.d.mts.map +0 -1
- package/dist/index-J1W3FdL8.d.cts.map +0 -1
- package/dist/pdfDocument-BgvEP5Po.d.mts.map +0 -1
- 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-
|
|
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 {
|
|
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-
|
|
278
|
-
const { PdfPage: _PdfPage } = await import("./pdfPage-
|
|
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$
|
|
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$
|
|
1031
|
+
if (opacity < 1) ops += `${n$5(opacity)} ca ${n$5(opacity)} CA\n`;
|
|
579
1032
|
if (interiorColor) {
|
|
580
|
-
ops += `${n$
|
|
581
|
-
ops += `${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$
|
|
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$
|
|
587
|
-
ops += `${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$
|
|
629
|
-
const ellipsePath = `${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$
|
|
632
|
-
if (color) ops += `${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$
|
|
1087
|
+
ops += `${n$5(borderWidth)} w\n`;
|
|
635
1088
|
ops += ellipsePath;
|
|
636
1089
|
ops += "B\n";
|
|
637
1090
|
} else {
|
|
638
|
-
if (color) ops += `${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$
|
|
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$
|
|
681
|
-
if (color) ops += `${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$
|
|
684
|
-
ops += `${n$
|
|
685
|
-
ops += `${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$
|
|
709
|
-
ops += `${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$
|
|
723
|
-
ops += `${n$
|
|
724
|
-
ops += `${n$
|
|
725
|
-
ops += `${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$
|
|
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$
|
|
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$
|
|
763
|
-
ops += `${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$
|
|
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$
|
|
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$
|
|
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$
|
|
802
|
-
if (x2p <= w) ops += `${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$
|
|
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$
|
|
835
|
-
ops += `${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$
|
|
841
|
-
ops += `${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$
|
|
866
|
-
ops += `${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$
|
|
875
|
-
for (let i = 2; i + 1 < points.length; i += 2) ops += `${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$
|
|
914
|
-
else if (align === 2) ops += `${n$
|
|
915
|
-
else ops += `${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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
const
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
4221
|
-
const
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
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 ${
|
|
5127
|
+
writeStr(`0 ${xrefSize}\n`);
|
|
4234
5128
|
writeStr("0000000000 65535 f \n");
|
|
4235
|
-
for (let i = 1; 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 ${
|
|
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(`${
|
|
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-
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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 {
|
|
14662
|
-
//# sourceMappingURL=src-
|
|
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
|