modern-pdf-lib 0.30.0 → 0.31.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 +3 -3
- package/dist/browser.cjs +15 -1
- package/dist/browser.d.cts +2 -2
- package/dist/browser.d.mts +2 -2
- package/dist/browser.mjs +2 -2
- package/dist/{index-BJ4KxqDL.d.cts → index-CTL7WUTU.d.cts} +310 -3
- package/dist/index-CTL7WUTU.d.cts.map +1 -0
- package/dist/{index-CERay5r2.d.mts → index-D8FO08WM.d.mts} +310 -3
- package/dist/index-D8FO08WM.d.mts.map +1 -0
- package/dist/index.cjs +15 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{src-s9zNsZAT.mjs → src-B7SQNDvy.mjs} +529 -4
- package/dist/{src-Tb7wHOKz.cjs → src-VeeIdf7B.cjs} +612 -3
- package/package.json +1 -1
- package/dist/index-BJ4KxqDL.d.cts.map +0 -1
- package/dist/index-CERay5r2.d.mts.map +0 -1
|
@@ -17060,7 +17060,7 @@ const SECTION_TYPES = /* @__PURE__ */ new Set([
|
|
|
17060
17060
|
"BlockQuote"
|
|
17061
17061
|
]);
|
|
17062
17062
|
/** Illustration types that require alt text per PDF/UA. */
|
|
17063
|
-
const ILLUSTRATION_TYPES = /* @__PURE__ */ new Set([
|
|
17063
|
+
const ILLUSTRATION_TYPES$1 = /* @__PURE__ */ new Set([
|
|
17064
17064
|
"Figure",
|
|
17065
17065
|
"Formula",
|
|
17066
17066
|
"Form"
|
|
@@ -17403,7 +17403,7 @@ function findPreviousHeadingInSection(current, sectionHeadings) {
|
|
|
17403
17403
|
*/
|
|
17404
17404
|
function checkAltText(elements, errors) {
|
|
17405
17405
|
for (const elem of elements) {
|
|
17406
|
-
if (!ILLUSTRATION_TYPES.has(elem.type)) continue;
|
|
17406
|
+
if (!ILLUSTRATION_TYPES$1.has(elem.type)) continue;
|
|
17407
17407
|
if (elem.options.artifact) continue;
|
|
17408
17408
|
if (elem.options.altText === void 0 && elem.options.actualText === void 0) errors.push({
|
|
17409
17409
|
code: "UA-STRUCT-005",
|
|
@@ -30416,6 +30416,531 @@ function buildBlackPointCompensationExtGState(mode) {
|
|
|
30416
30416
|
return gs;
|
|
30417
30417
|
}
|
|
30418
30418
|
//#endregion
|
|
30419
|
+
//#region src/accessibility/taggingHelpers.ts
|
|
30420
|
+
/**
|
|
30421
|
+
* The property key under which {@link tagList} records the chosen
|
|
30422
|
+
* {@link ListNumbering} on a list element's `options` object.
|
|
30423
|
+
*
|
|
30424
|
+
* `StructureElementOptions` has no dedicated `listNumbering` field, so
|
|
30425
|
+
* the value is stored as an extra (string-keyed) property on the same
|
|
30426
|
+
* options object. When the structure tree is serialized this is the
|
|
30427
|
+
* value an attribute writer should emit as the list's `/ListNumbering`
|
|
30428
|
+
* entry inside an `/A` attribute dictionary whose owner is `/List`
|
|
30429
|
+
* (ISO 32000, Table 384):
|
|
30430
|
+
*
|
|
30431
|
+
* ```
|
|
30432
|
+
* /A << /O /List /ListNumbering /Decimal >>
|
|
30433
|
+
* ```
|
|
30434
|
+
*
|
|
30435
|
+
* Using a well-known string key (rather than a `Symbol`) keeps the
|
|
30436
|
+
* value plain-serializable and inspectable from tests.
|
|
30437
|
+
*/
|
|
30438
|
+
const LIST_NUMBERING_KEY = "__listNumbering";
|
|
30439
|
+
/**
|
|
30440
|
+
* Heading level → structure-type mapping for {@link tagHeading}.
|
|
30441
|
+
*
|
|
30442
|
+
* Indexed by the numeric heading level (`1`..`6`); the result is the
|
|
30443
|
+
* corresponding standard heading structure type (`'H1'`..`'H6'`).
|
|
30444
|
+
*/
|
|
30445
|
+
const HEADING_TYPE = {
|
|
30446
|
+
1: "H1",
|
|
30447
|
+
2: "H2",
|
|
30448
|
+
3: "H3",
|
|
30449
|
+
4: "H4",
|
|
30450
|
+
5: "H5",
|
|
30451
|
+
6: "H6"
|
|
30452
|
+
};
|
|
30453
|
+
/**
|
|
30454
|
+
* Tag a heading element of the given level.
|
|
30455
|
+
*
|
|
30456
|
+
* @param tree The structure tree to add the element to.
|
|
30457
|
+
* @param parent The parent element, or `null` to add under the root
|
|
30458
|
+
* `Document` element.
|
|
30459
|
+
* @param level The heading level (`1`..`6`); maps to `H1`..`H6`.
|
|
30460
|
+
* @param options Optional attributes (title, language, id, …).
|
|
30461
|
+
* @returns The newly created heading element (type `H1`..`H6`).
|
|
30462
|
+
*
|
|
30463
|
+
* @example
|
|
30464
|
+
* ```ts
|
|
30465
|
+
* const tree = doc.createStructureTree();
|
|
30466
|
+
* const h2 = tagHeading(tree, null, 2, { title: 'Background' });
|
|
30467
|
+
* // h2.type === 'H2'
|
|
30468
|
+
* ```
|
|
30469
|
+
*/
|
|
30470
|
+
function tagHeading(tree, parent, level, options) {
|
|
30471
|
+
return tree.addElement(parent, HEADING_TYPE[level], options);
|
|
30472
|
+
}
|
|
30473
|
+
/**
|
|
30474
|
+
* Tag a paragraph (`P`) element.
|
|
30475
|
+
*
|
|
30476
|
+
* @param tree The structure tree to add the element to.
|
|
30477
|
+
* @param parent The parent element, or `null` for the root.
|
|
30478
|
+
* @param options Optional attributes.
|
|
30479
|
+
* @returns The newly created `P` element.
|
|
30480
|
+
*/
|
|
30481
|
+
function tagParagraph(tree, parent, options) {
|
|
30482
|
+
return tree.addElement(parent, "P", options);
|
|
30483
|
+
}
|
|
30484
|
+
/**
|
|
30485
|
+
* Tag a figure (`Figure`) element with alternative text.
|
|
30486
|
+
*
|
|
30487
|
+
* The `altText` argument is required because PDF/UA mandates
|
|
30488
|
+
* alternative text for illustration elements. If the caller also
|
|
30489
|
+
* supplies `altText` in `options`, that explicit value takes
|
|
30490
|
+
* precedence over the positional argument.
|
|
30491
|
+
*
|
|
30492
|
+
* @param tree The structure tree to add the element to.
|
|
30493
|
+
* @param parent The parent element, or `null` for the root.
|
|
30494
|
+
* @param altText Alternative text describing the figure.
|
|
30495
|
+
* @param options Optional additional attributes.
|
|
30496
|
+
* @returns The newly created `Figure` element with `/Alt` set.
|
|
30497
|
+
*/
|
|
30498
|
+
function tagFigure(tree, parent, altText, options) {
|
|
30499
|
+
const merged = {
|
|
30500
|
+
altText,
|
|
30501
|
+
...options
|
|
30502
|
+
};
|
|
30503
|
+
return tree.addElement(parent, "Figure", merged);
|
|
30504
|
+
}
|
|
30505
|
+
/**
|
|
30506
|
+
* Tag a link (`Link`) element.
|
|
30507
|
+
*
|
|
30508
|
+
* @param tree The structure tree to add the element to.
|
|
30509
|
+
* @param parent The parent element, or `null` for the root.
|
|
30510
|
+
* @param options Optional attributes.
|
|
30511
|
+
* @returns The newly created `Link` element.
|
|
30512
|
+
*/
|
|
30513
|
+
function tagLink(tree, parent, options) {
|
|
30514
|
+
return tree.addElement(parent, "Link", options);
|
|
30515
|
+
}
|
|
30516
|
+
/**
|
|
30517
|
+
* Tag a list (`L`) element and record its numbering style.
|
|
30518
|
+
*
|
|
30519
|
+
* The chosen {@link ListNumbering} is stored on the returned element's
|
|
30520
|
+
* `options` object under {@link LIST_NUMBERING_KEY} (since
|
|
30521
|
+
* `StructureElementOptions` has no dedicated field). A serializer can
|
|
30522
|
+
* emit it as the list's `/ListNumbering` attribute (ISO 32000,
|
|
30523
|
+
* Table 384) inside an `/A` dictionary owned by `/List`.
|
|
30524
|
+
*
|
|
30525
|
+
* @param tree The structure tree to add the element to.
|
|
30526
|
+
* @param parent The parent element, or `null` for the root.
|
|
30527
|
+
* @param numbering The list numbering style (default `'None'`).
|
|
30528
|
+
* @param options Optional additional attributes.
|
|
30529
|
+
* @returns The newly created `L` element.
|
|
30530
|
+
*/
|
|
30531
|
+
function tagList(tree, parent, numbering = "None", options) {
|
|
30532
|
+
const list = tree.addElement(parent, "L", options);
|
|
30533
|
+
list.options[LIST_NUMBERING_KEY] = numbering;
|
|
30534
|
+
return list;
|
|
30535
|
+
}
|
|
30536
|
+
/**
|
|
30537
|
+
* Tag a list item under a list, building the conventional
|
|
30538
|
+
* `LI` → (`Lbl`, `LBody`) substructure.
|
|
30539
|
+
*
|
|
30540
|
+
* @param tree The structure tree to add the elements to.
|
|
30541
|
+
* @param list The parent `L` (list) element.
|
|
30542
|
+
* @param options Optional attributes for the `LI` element.
|
|
30543
|
+
* @returns The created `item` (`LI`), `label` (`Lbl`) and
|
|
30544
|
+
* `body` (`LBody`) elements.
|
|
30545
|
+
*
|
|
30546
|
+
* @example
|
|
30547
|
+
* ```ts
|
|
30548
|
+
* const list = tagList(tree, null, 'Decimal');
|
|
30549
|
+
* const { item, label, body } = tagListItem(tree, list);
|
|
30550
|
+
* // item.type === 'LI', label.type === 'Lbl', body.type === 'LBody'
|
|
30551
|
+
* ```
|
|
30552
|
+
*/
|
|
30553
|
+
function tagListItem(tree, list, options) {
|
|
30554
|
+
const item = tree.addElement(list, "LI", options);
|
|
30555
|
+
return {
|
|
30556
|
+
item,
|
|
30557
|
+
label: tree.addElement(item, "Lbl"),
|
|
30558
|
+
body: tree.addElement(item, "LBody")
|
|
30559
|
+
};
|
|
30560
|
+
}
|
|
30561
|
+
/**
|
|
30562
|
+
* Tag a table (`Table`) element.
|
|
30563
|
+
*
|
|
30564
|
+
* @param tree The structure tree to add the element to.
|
|
30565
|
+
* @param parent The parent element, or `null` for the root.
|
|
30566
|
+
* @param options Optional attributes.
|
|
30567
|
+
* @returns The newly created `Table` element.
|
|
30568
|
+
*/
|
|
30569
|
+
function tagTable(tree, parent, options) {
|
|
30570
|
+
return tree.addElement(parent, "Table", options);
|
|
30571
|
+
}
|
|
30572
|
+
/**
|
|
30573
|
+
* Tag a table row (`TR`) under a table.
|
|
30574
|
+
*
|
|
30575
|
+
* @param tree The structure tree to add the element to.
|
|
30576
|
+
* @param table The parent `Table` element.
|
|
30577
|
+
* @returns The newly created `TR` element.
|
|
30578
|
+
*/
|
|
30579
|
+
function tagTableRow(tree, table) {
|
|
30580
|
+
return tree.addElement(table, "TR");
|
|
30581
|
+
}
|
|
30582
|
+
/**
|
|
30583
|
+
* Tag a table header cell (`TH`) under a row, recording its scope.
|
|
30584
|
+
*
|
|
30585
|
+
* The `scope` is stored in the element's options (`/Scope` attribute,
|
|
30586
|
+
* one of `Row`, `Column`, or `Both`) and is used by table-header
|
|
30587
|
+
* validation to associate header cells with the cells they describe.
|
|
30588
|
+
*
|
|
30589
|
+
* @param tree The structure tree to add the element to.
|
|
30590
|
+
* @param row The parent `TR` element.
|
|
30591
|
+
* @param scope The header scope (default `'Column'`).
|
|
30592
|
+
* @param options Optional additional attributes (e.g. colSpan/rowSpan).
|
|
30593
|
+
* @returns The newly created `TH` element with `scope` set.
|
|
30594
|
+
*/
|
|
30595
|
+
function tagTableHeaderCell(tree, row, scope = "Column", options) {
|
|
30596
|
+
const merged = {
|
|
30597
|
+
...options,
|
|
30598
|
+
scope
|
|
30599
|
+
};
|
|
30600
|
+
return tree.addElement(row, "TH", merged);
|
|
30601
|
+
}
|
|
30602
|
+
/**
|
|
30603
|
+
* Tag a table data cell (`TD`) under a row.
|
|
30604
|
+
*
|
|
30605
|
+
* @param tree The structure tree to add the element to.
|
|
30606
|
+
* @param row The parent `TR` element.
|
|
30607
|
+
* @param options Optional attributes (e.g. colSpan/rowSpan).
|
|
30608
|
+
* @returns The newly created `TD` element.
|
|
30609
|
+
*/
|
|
30610
|
+
function tagTableDataCell(tree, row, options) {
|
|
30611
|
+
return tree.addElement(row, "TD", options);
|
|
30612
|
+
}
|
|
30613
|
+
//#endregion
|
|
30614
|
+
//#region src/accessibility/pdfUa2.ts
|
|
30615
|
+
/** The AIIM PDF/UA identification namespace URI (used in the XMP packet). */
|
|
30616
|
+
const PDFUA_ID_NS = "http://www.aiim.org/pdfua/ns/id/";
|
|
30617
|
+
/** The PDF/UA part identified by this module's XMP. */
|
|
30618
|
+
const PDFUA2_PART = 2;
|
|
30619
|
+
/**
|
|
30620
|
+
* The PDF/UA-2 revision year. ISO 14289-2 was published in 2024, which the
|
|
30621
|
+
* AIIM `pdfuaid:rev` field records as a four-digit year.
|
|
30622
|
+
*/
|
|
30623
|
+
const PDFUA2_REV = 2024;
|
|
30624
|
+
/** Illustration types that require alternative text under PDF/UA-2. */
|
|
30625
|
+
const ILLUSTRATION_TYPES = /* @__PURE__ */ new Set([
|
|
30626
|
+
"Figure",
|
|
30627
|
+
"Formula",
|
|
30628
|
+
"Form"
|
|
30629
|
+
]);
|
|
30630
|
+
/**
|
|
30631
|
+
* Validate a PDF document against PDF/UA-2 (ISO 14289-2) requirements.
|
|
30632
|
+
*
|
|
30633
|
+
* The check reuses {@link validatePdfUa} for the shared tagging, language,
|
|
30634
|
+
* MarkInfo, and metadata requirements, then layers the PDF 2.0 / UA-2
|
|
30635
|
+
* specific requirements on top:
|
|
30636
|
+
*
|
|
30637
|
+
* - **UA2-STRUCT-001** — a structure tree must exist (tagged PDF).
|
|
30638
|
+
* - **UA2-NS-001** — the structure tree must declare structure
|
|
30639
|
+
* `/Namespaces` (PDF/UA-2 builds on PDF 2.0 namespaces).
|
|
30640
|
+
* - **UA2-LANG-001** — the document must declare a natural language
|
|
30641
|
+
* (`/Lang`).
|
|
30642
|
+
* - **UA2-FIG-001** — every figure must carry alternative text.
|
|
30643
|
+
*
|
|
30644
|
+
* Each failure is mapped to an ISO 14289-2 clause string. The returned
|
|
30645
|
+
* result is `conformant` only when there are no issues.
|
|
30646
|
+
*
|
|
30647
|
+
* @param doc The PDF document to validate.
|
|
30648
|
+
* @returns A {@link PdfUa2Result} describing conformance and issues.
|
|
30649
|
+
*
|
|
30650
|
+
* @example
|
|
30651
|
+
* ```ts
|
|
30652
|
+
* import { createPdf } from 'modern-pdf-lib';
|
|
30653
|
+
* import { validatePdfUa2 } from 'modern-pdf-lib/accessibility';
|
|
30654
|
+
*
|
|
30655
|
+
* const doc = createPdf();
|
|
30656
|
+
* const result = validatePdfUa2(doc);
|
|
30657
|
+
* if (!result.conformant) {
|
|
30658
|
+
* for (const issue of result.issues) {
|
|
30659
|
+
* console.error(`[${issue.code}] ${issue.message} (§${issue.clause})`);
|
|
30660
|
+
* }
|
|
30661
|
+
* }
|
|
30662
|
+
* ```
|
|
30663
|
+
*/
|
|
30664
|
+
function validatePdfUa2(doc) {
|
|
30665
|
+
const issues = [];
|
|
30666
|
+
const tree = doc.getStructureTree();
|
|
30667
|
+
if (!tree) issues.push({
|
|
30668
|
+
code: "UA2-STRUCT-001",
|
|
30669
|
+
message: "Document has no structure tree (/StructTreeRoot). PDF/UA-2 requires a fully tagged PDF 2.0 document with logical structure.",
|
|
30670
|
+
clause: "8.2"
|
|
30671
|
+
});
|
|
30672
|
+
if (!hasDeclaredNamespaces(tree)) issues.push({
|
|
30673
|
+
code: "UA2-NS-001",
|
|
30674
|
+
message: "Structure tree does not declare structure /Namespaces. PDF/UA-2 builds on PDF 2.0 structure namespaces (ISO 32000-2 §14.7.4); the StructTreeRoot must declare its namespaces.",
|
|
30675
|
+
clause: "8.2.2"
|
|
30676
|
+
});
|
|
30677
|
+
const lang = doc.getLanguage();
|
|
30678
|
+
if (lang === void 0 || lang.trim().length === 0) issues.push({
|
|
30679
|
+
code: "UA2-LANG-001",
|
|
30680
|
+
message: "Document has no natural language (/Lang). PDF/UA-2 requires the document catalog to specify a default natural language.",
|
|
30681
|
+
clause: "7.2"
|
|
30682
|
+
});
|
|
30683
|
+
if (tree) for (const elem of tree.getAllElements()) {
|
|
30684
|
+
if (!ILLUSTRATION_TYPES.has(elem.type)) continue;
|
|
30685
|
+
if (elem.options.artifact) continue;
|
|
30686
|
+
const alt = elem.options.altText;
|
|
30687
|
+
const actual = elem.options.actualText;
|
|
30688
|
+
const hasAlt = alt !== void 0 && alt.trim().length > 0;
|
|
30689
|
+
const hasActual = actual !== void 0 && actual.trim().length > 0;
|
|
30690
|
+
if (!hasAlt && !hasActual) issues.push({
|
|
30691
|
+
code: "UA2-FIG-001",
|
|
30692
|
+
message: `${elem.type} element has no /Alt or /ActualText. PDF/UA-2 requires alternative text for all non-decorative illustration elements.`,
|
|
30693
|
+
clause: "7.3"
|
|
30694
|
+
});
|
|
30695
|
+
}
|
|
30696
|
+
const ua1 = validatePdfUa(doc);
|
|
30697
|
+
for (const err of ua1.errors) {
|
|
30698
|
+
if (isCoveredByUa2(err.code)) continue;
|
|
30699
|
+
issues.push({
|
|
30700
|
+
code: `UA2-${err.code}`,
|
|
30701
|
+
message: err.message,
|
|
30702
|
+
clause: mapUa1Clause(err.clause)
|
|
30703
|
+
});
|
|
30704
|
+
}
|
|
30705
|
+
return {
|
|
30706
|
+
conformant: issues.length === 0,
|
|
30707
|
+
issues
|
|
30708
|
+
};
|
|
30709
|
+
}
|
|
30710
|
+
/**
|
|
30711
|
+
* Build an XMP packet (RDF/XML) that identifies a document as PDF/UA-2.
|
|
30712
|
+
*
|
|
30713
|
+
* The packet declares the AIIM PDF/UA identification schema
|
|
30714
|
+
* (`pdfuaid`, namespace `http://www.aiim.org/pdfua/ns/id/`) with:
|
|
30715
|
+
*
|
|
30716
|
+
* - `pdfuaid:part` = `2` — the PDF/UA part (ISO 14289-2);
|
|
30717
|
+
* - `pdfuaid:rev` = the revision year of the standard.
|
|
30718
|
+
*
|
|
30719
|
+
* The result is a serialized, packet-wrapped XMP string suitable for
|
|
30720
|
+
* embedding as the document's `/Metadata` stream.
|
|
30721
|
+
*
|
|
30722
|
+
* @returns The serialized PDF/UA-2 identification XMP packet.
|
|
30723
|
+
*
|
|
30724
|
+
* @example
|
|
30725
|
+
* ```ts
|
|
30726
|
+
* import { buildPdfUa2Xmp } from 'modern-pdf-lib/accessibility';
|
|
30727
|
+
*
|
|
30728
|
+
* doc.setXmpMetadata(buildPdfUa2Xmp());
|
|
30729
|
+
* ```
|
|
30730
|
+
*/
|
|
30731
|
+
function buildPdfUa2Xmp() {
|
|
30732
|
+
return [
|
|
30733
|
+
"<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>",
|
|
30734
|
+
"<x:xmpmeta xmlns:x=\"adobe:ns:meta/\">",
|
|
30735
|
+
" <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">",
|
|
30736
|
+
` <rdf:Description rdf:about=""`,
|
|
30737
|
+
` xmlns:pdfuaid="${PDFUA_ID_NS}">`,
|
|
30738
|
+
` <pdfuaid:part>${PDFUA2_PART}</pdfuaid:part>`,
|
|
30739
|
+
` <pdfuaid:rev>${PDFUA2_REV}</pdfuaid:rev>`,
|
|
30740
|
+
" </rdf:Description>",
|
|
30741
|
+
" </rdf:RDF>",
|
|
30742
|
+
"</x:xmpmeta>",
|
|
30743
|
+
"<?xpacket end=\"w\"?>"
|
|
30744
|
+
].join("\n");
|
|
30745
|
+
}
|
|
30746
|
+
/**
|
|
30747
|
+
* Determine whether a structure tree declares one or more structure
|
|
30748
|
+
* namespaces.
|
|
30749
|
+
*
|
|
30750
|
+
* The in-memory {@link PdfStructureTree} model does not yet carry a
|
|
30751
|
+
* first-class namespaces field, so callers declare namespaces by attaching
|
|
30752
|
+
* a `namespaces` array (of namespace URI strings, or namespace descriptor
|
|
30753
|
+
* objects) to the tree. This helper reads that field defensively and also
|
|
30754
|
+
* recognizes a per-element `namespace` option as evidence of namespace use.
|
|
30755
|
+
*
|
|
30756
|
+
* @internal
|
|
30757
|
+
*/
|
|
30758
|
+
function hasDeclaredNamespaces(tree) {
|
|
30759
|
+
if (!tree) return false;
|
|
30760
|
+
const declared = tree.namespaces;
|
|
30761
|
+
if (Array.isArray(declared) && declared.length > 0) return true;
|
|
30762
|
+
for (const elem of tree.getAllElements()) {
|
|
30763
|
+
const ns = elem.options.namespace;
|
|
30764
|
+
if (typeof ns === "string" && ns.length > 0) return true;
|
|
30765
|
+
}
|
|
30766
|
+
return false;
|
|
30767
|
+
}
|
|
30768
|
+
/**
|
|
30769
|
+
* Whether a PDF/UA-1 error code is already represented by a dedicated
|
|
30770
|
+
* UA-2 issue produced above (to avoid duplicate structure-tree / language
|
|
30771
|
+
* findings).
|
|
30772
|
+
*
|
|
30773
|
+
* @internal
|
|
30774
|
+
*/
|
|
30775
|
+
function isCoveredByUa2(ua1Code) {
|
|
30776
|
+
return ua1Code === "UA-STRUCT-001" || ua1Code === "UA-STRUCT-002" || ua1Code === "UA-META-001" || ua1Code === "UA-META-002" || ua1Code === "UA-META-003";
|
|
30777
|
+
}
|
|
30778
|
+
/**
|
|
30779
|
+
* Map a PDF/UA-1 (ISO 14289-1) clause reference onto the corresponding
|
|
30780
|
+
* PDF/UA-2 (ISO 14289-2) clause. The two standards share most clause
|
|
30781
|
+
* numbering; when a UA-1 clause is absent we fall back to the general
|
|
30782
|
+
* UA-2 conformance clause.
|
|
30783
|
+
*
|
|
30784
|
+
* @internal
|
|
30785
|
+
*/
|
|
30786
|
+
function mapUa1Clause(ua1Clause) {
|
|
30787
|
+
if (ua1Clause === void 0 || ua1Clause.length === 0) return "5";
|
|
30788
|
+
return ua1Clause;
|
|
30789
|
+
}
|
|
30790
|
+
//#endregion
|
|
30791
|
+
//#region src/accessibility/autoTag.ts
|
|
30792
|
+
/** Default heading-detection scale relative to body text size. */
|
|
30793
|
+
const DEFAULT_HEADING_SCALE = 1.2;
|
|
30794
|
+
/**
|
|
30795
|
+
* Maximum vertical gap (in page-space units, as a fraction of the run's
|
|
30796
|
+
* font size) within which two text runs are considered to belong to the
|
|
30797
|
+
* same visual line. Runs whose baselines are within this fraction of an
|
|
30798
|
+
* em are merged onto one line.
|
|
30799
|
+
*/
|
|
30800
|
+
const SAME_LINE_FRACTION = .5;
|
|
30801
|
+
/** Highest heading level tag supported. */
|
|
30802
|
+
const MAX_HEADING_LEVEL = 6;
|
|
30803
|
+
/**
|
|
30804
|
+
* Infer a coarse logical structure for a single untagged page and add the
|
|
30805
|
+
* inferred `H1`..`H6` and `P` elements to the document's structure tree.
|
|
30806
|
+
*
|
|
30807
|
+
* The procedure:
|
|
30808
|
+
* 1. Interpret the page into positioned text runs.
|
|
30809
|
+
* 2. Group runs into visual lines by their baseline y-position.
|
|
30810
|
+
* 3. Determine the body font size as the median run font size.
|
|
30811
|
+
* 4. Order lines top-to-bottom (reading order on a y-up page).
|
|
30812
|
+
* 5. Classify each line as a heading (font size `>= headingScale × body`)
|
|
30813
|
+
* or body text; map distinct heading sizes to `H1`..`H6`
|
|
30814
|
+
* (largest → `H1`).
|
|
30815
|
+
* 6. Emit one heading element per heading line and one `P` element per
|
|
30816
|
+
* contiguous block of body lines.
|
|
30817
|
+
*
|
|
30818
|
+
* Never throws on an empty (or text-free) page — it returns all-zero
|
|
30819
|
+
* counts and leaves the structure tree untouched.
|
|
30820
|
+
*
|
|
30821
|
+
* @param doc The document containing the page.
|
|
30822
|
+
* @param pageIndex Zero-based index of the page to tag.
|
|
30823
|
+
* @param options Optional heuristic tuning ({@link AutoTagOptions}).
|
|
30824
|
+
* @returns Counts of the elements that were added.
|
|
30825
|
+
*/
|
|
30826
|
+
function autoTagPage(doc, pageIndex, options) {
|
|
30827
|
+
const headingScale = options?.headingScale ?? DEFAULT_HEADING_SCALE;
|
|
30828
|
+
const textItems = collectTextItems(doc, pageIndex);
|
|
30829
|
+
if (textItems.length === 0) return {
|
|
30830
|
+
headings: 0,
|
|
30831
|
+
paragraphs: 0,
|
|
30832
|
+
elements: 0
|
|
30833
|
+
};
|
|
30834
|
+
const lines = groupIntoLines(textItems);
|
|
30835
|
+
if (lines.length === 0) return {
|
|
30836
|
+
headings: 0,
|
|
30837
|
+
paragraphs: 0,
|
|
30838
|
+
elements: 0
|
|
30839
|
+
};
|
|
30840
|
+
const bodySize = medianFontSize(textItems);
|
|
30841
|
+
const ordered = [...lines].sort((a, b) => b.y - a.y);
|
|
30842
|
+
const headingThreshold = bodySize * headingScale;
|
|
30843
|
+
const headingLevelForSize = buildHeadingLevelMap(ordered, headingThreshold);
|
|
30844
|
+
const tree = doc.createStructureTree();
|
|
30845
|
+
let headings = 0;
|
|
30846
|
+
let paragraphs = 0;
|
|
30847
|
+
let paragraphOpen = false;
|
|
30848
|
+
for (const line of ordered) if (line.fontSize >= headingThreshold) {
|
|
30849
|
+
paragraphOpen = false;
|
|
30850
|
+
const type = `H${headingLevelForSize.get(line.fontSize) ?? 1}`;
|
|
30851
|
+
tree.addElement(null, type, { title: line.text });
|
|
30852
|
+
headings++;
|
|
30853
|
+
} else if (!paragraphOpen) {
|
|
30854
|
+
tree.addElement(null, "P");
|
|
30855
|
+
paragraphs++;
|
|
30856
|
+
paragraphOpen = true;
|
|
30857
|
+
}
|
|
30858
|
+
return {
|
|
30859
|
+
headings,
|
|
30860
|
+
paragraphs,
|
|
30861
|
+
elements: headings + paragraphs
|
|
30862
|
+
};
|
|
30863
|
+
}
|
|
30864
|
+
/**
|
|
30865
|
+
* Interpret the page and return only its text runs. Returns an empty
|
|
30866
|
+
* array (never throws) if the page cannot be interpreted.
|
|
30867
|
+
*
|
|
30868
|
+
* @internal
|
|
30869
|
+
*/
|
|
30870
|
+
function collectTextItems(doc, pageIndex) {
|
|
30871
|
+
const pageCount = doc.getPageCount();
|
|
30872
|
+
if (pageIndex < 0 || pageIndex >= pageCount) return [];
|
|
30873
|
+
let items = [];
|
|
30874
|
+
try {
|
|
30875
|
+
items = interpretPage(doc.getPage(pageIndex)).items.filter((it) => it.type === "text" && it.text.trim().length > 0);
|
|
30876
|
+
} catch {
|
|
30877
|
+
return [];
|
|
30878
|
+
}
|
|
30879
|
+
return [...items];
|
|
30880
|
+
}
|
|
30881
|
+
/**
|
|
30882
|
+
* Group text runs into visual lines by their baseline y-position.
|
|
30883
|
+
*
|
|
30884
|
+
* The baseline is taken from the run's text-space → page-space transform
|
|
30885
|
+
* (`transform[5]`, the `f` translation component). Runs whose baselines
|
|
30886
|
+
* fall within {@link SAME_LINE_FRACTION} of an em of an existing line are
|
|
30887
|
+
* merged onto that line; the line's font size is the maximum run size.
|
|
30888
|
+
*
|
|
30889
|
+
* @internal
|
|
30890
|
+
*/
|
|
30891
|
+
function groupIntoLines(items) {
|
|
30892
|
+
const sorted = [...items].sort((a, b) => b.transform[5] - a.transform[5]);
|
|
30893
|
+
const lines = [];
|
|
30894
|
+
for (const item of sorted) {
|
|
30895
|
+
const y = item.transform[5];
|
|
30896
|
+
const size = item.fontSize > 0 ? item.fontSize : Math.abs(item.transform[3]) || 1;
|
|
30897
|
+
const last = lines[lines.length - 1];
|
|
30898
|
+
const tolerance = size * SAME_LINE_FRACTION;
|
|
30899
|
+
if (last && Math.abs(last.y - y) <= tolerance) {
|
|
30900
|
+
last.text = `${last.text} ${item.text}`.trim();
|
|
30901
|
+
if (size > last.fontSize) last.fontSize = size;
|
|
30902
|
+
} else lines.push({
|
|
30903
|
+
y,
|
|
30904
|
+
fontSize: size,
|
|
30905
|
+
text: item.text.trim()
|
|
30906
|
+
});
|
|
30907
|
+
}
|
|
30908
|
+
return lines.map((l) => ({
|
|
30909
|
+
y: l.y,
|
|
30910
|
+
fontSize: l.fontSize,
|
|
30911
|
+
text: l.text
|
|
30912
|
+
}));
|
|
30913
|
+
}
|
|
30914
|
+
/**
|
|
30915
|
+
* Compute the median font size across all text runs. The median is more
|
|
30916
|
+
* robust than the mean against a handful of oversized heading runs, so it
|
|
30917
|
+
* gives a reliable estimate of the body text size.
|
|
30918
|
+
*
|
|
30919
|
+
* @internal
|
|
30920
|
+
*/
|
|
30921
|
+
function medianFontSize(items) {
|
|
30922
|
+
const sizes = items.map((it) => it.fontSize > 0 ? it.fontSize : Math.abs(it.transform[3])).filter((s) => s > 0).sort((a, b) => a - b);
|
|
30923
|
+
if (sizes.length === 0) return 1;
|
|
30924
|
+
const mid = sizes.length >> 1;
|
|
30925
|
+
if (sizes.length % 2 === 1) return sizes[mid];
|
|
30926
|
+
return (sizes[mid - 1] + sizes[mid]) / 2;
|
|
30927
|
+
}
|
|
30928
|
+
/**
|
|
30929
|
+
* Build a map from each distinct heading font size to a heading level.
|
|
30930
|
+
* The largest heading size becomes `H1`, the next-largest `H2`, and so on,
|
|
30931
|
+
* capped at {@link MAX_HEADING_LEVEL} (`H6`).
|
|
30932
|
+
*
|
|
30933
|
+
* @internal
|
|
30934
|
+
*/
|
|
30935
|
+
function buildHeadingLevelMap(lines, headingThreshold) {
|
|
30936
|
+
const distinct = [...new Set(lines.filter((l) => l.fontSize >= headingThreshold).map((l) => l.fontSize))].sort((a, b) => b - a);
|
|
30937
|
+
const map = /* @__PURE__ */ new Map();
|
|
30938
|
+
distinct.forEach((size, index) => {
|
|
30939
|
+
map.set(size, Math.min(index + 1, MAX_HEADING_LEVEL));
|
|
30940
|
+
});
|
|
30941
|
+
return map;
|
|
30942
|
+
}
|
|
30943
|
+
//#endregion
|
|
30419
30944
|
//#region src/index.ts
|
|
30420
30945
|
/** Whether initWasm has already completed successfully. */
|
|
30421
30946
|
let wasmInitialized = false;
|
|
@@ -30569,6 +31094,12 @@ Object.defineProperty(exports, "InvalidPageSizeError", {
|
|
|
30569
31094
|
return InvalidPageSizeError;
|
|
30570
31095
|
}
|
|
30571
31096
|
});
|
|
31097
|
+
Object.defineProperty(exports, "LIST_NUMBERING_KEY", {
|
|
31098
|
+
enumerable: true,
|
|
31099
|
+
get: function() {
|
|
31100
|
+
return LIST_NUMBERING_KEY;
|
|
31101
|
+
}
|
|
31102
|
+
});
|
|
30572
31103
|
Object.defineProperty(exports, "MATHML_NAMESPACE", {
|
|
30573
31104
|
enumerable: true,
|
|
30574
31105
|
get: function() {
|
|
@@ -30893,6 +31424,12 @@ Object.defineProperty(exports, "attachOutputIntents", {
|
|
|
30893
31424
|
return attachOutputIntents;
|
|
30894
31425
|
}
|
|
30895
31426
|
});
|
|
31427
|
+
Object.defineProperty(exports, "autoTagPage", {
|
|
31428
|
+
enumerable: true,
|
|
31429
|
+
get: function() {
|
|
31430
|
+
return autoTagPage;
|
|
31431
|
+
}
|
|
31432
|
+
});
|
|
30896
31433
|
Object.defineProperty(exports, "batchFlatten", {
|
|
30897
31434
|
enumerable: true,
|
|
30898
31435
|
get: function() {
|
|
@@ -31055,6 +31592,12 @@ Object.defineProperty(exports, "buildPdfA4Xmp", {
|
|
|
31055
31592
|
return buildPdfA4Xmp;
|
|
31056
31593
|
}
|
|
31057
31594
|
});
|
|
31595
|
+
Object.defineProperty(exports, "buildPdfUa2Xmp", {
|
|
31596
|
+
enumerable: true,
|
|
31597
|
+
get: function() {
|
|
31598
|
+
return buildPdfUa2Xmp;
|
|
31599
|
+
}
|
|
31600
|
+
});
|
|
31058
31601
|
Object.defineProperty(exports, "buildPdfVtDParts", {
|
|
31059
31602
|
enumerable: true,
|
|
31060
31603
|
get: function() {
|
|
@@ -32255,6 +32798,66 @@ Object.defineProperty(exports, "tableToJson", {
|
|
|
32255
32798
|
return tableToJson;
|
|
32256
32799
|
}
|
|
32257
32800
|
});
|
|
32801
|
+
Object.defineProperty(exports, "tagFigure", {
|
|
32802
|
+
enumerable: true,
|
|
32803
|
+
get: function() {
|
|
32804
|
+
return tagFigure;
|
|
32805
|
+
}
|
|
32806
|
+
});
|
|
32807
|
+
Object.defineProperty(exports, "tagHeading", {
|
|
32808
|
+
enumerable: true,
|
|
32809
|
+
get: function() {
|
|
32810
|
+
return tagHeading;
|
|
32811
|
+
}
|
|
32812
|
+
});
|
|
32813
|
+
Object.defineProperty(exports, "tagLink", {
|
|
32814
|
+
enumerable: true,
|
|
32815
|
+
get: function() {
|
|
32816
|
+
return tagLink;
|
|
32817
|
+
}
|
|
32818
|
+
});
|
|
32819
|
+
Object.defineProperty(exports, "tagList", {
|
|
32820
|
+
enumerable: true,
|
|
32821
|
+
get: function() {
|
|
32822
|
+
return tagList;
|
|
32823
|
+
}
|
|
32824
|
+
});
|
|
32825
|
+
Object.defineProperty(exports, "tagListItem", {
|
|
32826
|
+
enumerable: true,
|
|
32827
|
+
get: function() {
|
|
32828
|
+
return tagListItem;
|
|
32829
|
+
}
|
|
32830
|
+
});
|
|
32831
|
+
Object.defineProperty(exports, "tagParagraph", {
|
|
32832
|
+
enumerable: true,
|
|
32833
|
+
get: function() {
|
|
32834
|
+
return tagParagraph;
|
|
32835
|
+
}
|
|
32836
|
+
});
|
|
32837
|
+
Object.defineProperty(exports, "tagTable", {
|
|
32838
|
+
enumerable: true,
|
|
32839
|
+
get: function() {
|
|
32840
|
+
return tagTable;
|
|
32841
|
+
}
|
|
32842
|
+
});
|
|
32843
|
+
Object.defineProperty(exports, "tagTableDataCell", {
|
|
32844
|
+
enumerable: true,
|
|
32845
|
+
get: function() {
|
|
32846
|
+
return tagTableDataCell;
|
|
32847
|
+
}
|
|
32848
|
+
});
|
|
32849
|
+
Object.defineProperty(exports, "tagTableHeaderCell", {
|
|
32850
|
+
enumerable: true,
|
|
32851
|
+
get: function() {
|
|
32852
|
+
return tagTableHeaderCell;
|
|
32853
|
+
}
|
|
32854
|
+
});
|
|
32855
|
+
Object.defineProperty(exports, "tagTableRow", {
|
|
32856
|
+
enumerable: true,
|
|
32857
|
+
get: function() {
|
|
32858
|
+
return tagTableRow;
|
|
32859
|
+
}
|
|
32860
|
+
});
|
|
32258
32861
|
Object.defineProperty(exports, "timestampPlugin", {
|
|
32259
32862
|
enumerable: true,
|
|
32260
32863
|
get: function() {
|
|
@@ -32357,6 +32960,12 @@ Object.defineProperty(exports, "validatePdfUa", {
|
|
|
32357
32960
|
return validatePdfUa;
|
|
32358
32961
|
}
|
|
32359
32962
|
});
|
|
32963
|
+
Object.defineProperty(exports, "validatePdfUa2", {
|
|
32964
|
+
enumerable: true,
|
|
32965
|
+
get: function() {
|
|
32966
|
+
return validatePdfUa2;
|
|
32967
|
+
}
|
|
32968
|
+
});
|
|
32360
32969
|
Object.defineProperty(exports, "validatePdfX", {
|
|
32361
32970
|
enumerable: true,
|
|
32362
32971
|
get: function() {
|
|
@@ -32412,4 +33021,4 @@ Object.defineProperty(exports, "wrapText", {
|
|
|
32412
33021
|
}
|
|
32413
33022
|
});
|
|
32414
33023
|
|
|
32415
|
-
//# sourceMappingURL=src-
|
|
33024
|
+
//# sourceMappingURL=src-VeeIdf7B.cjs.map
|
package/package.json
CHANGED