@awacloud/pdf 0.0.0-stage → 1.0.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/CHANGELOG.md +609 -0
- package/LICENSE +661 -0
- package/NOTICE +77 -0
- package/README.md +363 -2
- package/dist/build/index.js +21 -0
- package/dist/build/pdf-full-rw.js +10972 -0
- package/dist/build/pdf-full-rw.meta.json +105 -0
- package/dist/build/pdf-full-rw.min.js +53 -0
- package/dist/build/pdf-full.js +6078 -0
- package/dist/build/pdf-full.meta.json +90 -0
- package/dist/build/pdf-full.min.js +32 -0
- package/dist/build/pdf-large-rw.js +10367 -0
- package/dist/build/pdf-large-rw.meta.json +99 -0
- package/dist/build/pdf-large-rw.min.js +53 -0
- package/dist/build/pdf-large.js +5473 -0
- package/dist/build/pdf-large.meta.json +84 -0
- package/dist/build/pdf-large.min.js +32 -0
- package/dist/build/pdf-legacy-rw.js +12402 -0
- package/dist/build/pdf-legacy-rw.meta.json +110 -0
- package/dist/build/pdf-legacy-rw.min.js +53 -0
- package/dist/build/pdf-legacy.js +7508 -0
- package/dist/build/pdf-legacy.meta.json +95 -0
- package/dist/build/pdf-legacy.min.js +32 -0
- package/dist/build/pdf-rw.js +7578 -0
- package/dist/build/pdf-rw.meta.json +77 -0
- package/dist/build/pdf-rw.min.js +53 -0
- package/dist/build/pdf.js +2684 -0
- package/dist/build/pdf.meta.json +62 -0
- package/dist/build/pdf.min.js +32 -0
- package/dist/standalone/pdf-full-rw.js +16798 -0
- package/dist/standalone/pdf-full-rw.meta.json +78 -0
- package/dist/standalone/pdf-full-rw.min.js +56 -0
- package/dist/standalone/pdf-full.js +11904 -0
- package/dist/standalone/pdf-full.meta.json +63 -0
- package/dist/standalone/pdf-full.min.js +35 -0
- package/dist/standalone/pdf-large-rw.js +16193 -0
- package/dist/standalone/pdf-large-rw.meta.json +72 -0
- package/dist/standalone/pdf-large-rw.min.js +56 -0
- package/dist/standalone/pdf-large.js +11299 -0
- package/dist/standalone/pdf-large.meta.json +57 -0
- package/dist/standalone/pdf-large.min.js +35 -0
- package/dist/standalone/pdf-legacy-rw.js +18228 -0
- package/dist/standalone/pdf-legacy-rw.meta.json +83 -0
- package/dist/standalone/pdf-legacy-rw.min.js +56 -0
- package/dist/standalone/pdf-legacy.js +13334 -0
- package/dist/standalone/pdf-legacy.meta.json +68 -0
- package/dist/standalone/pdf-legacy.min.js +35 -0
- package/dist/standalone/pdf-rw.js +13404 -0
- package/dist/standalone/pdf-rw.meta.json +50 -0
- package/dist/standalone/pdf-rw.min.js +56 -0
- package/dist/standalone/pdf.js +8510 -0
- package/dist/standalone/pdf.meta.json +35 -0
- package/dist/standalone/pdf.min.js +35 -0
- package/docs/README.md +53 -0
- package/docs/api/README.md +38 -0
- package/docs/api/_shared/README.md +91 -0
- package/docs/api/action/README.md +29 -0
- package/docs/api/action/action.md +81 -0
- package/docs/api/action/goTo.md +66 -0
- package/docs/api/action/launch.md +58 -0
- package/docs/api/action/named.md +55 -0
- package/docs/api/action/uri.md +54 -0
- package/docs/api/annot/README.md +53 -0
- package/docs/api/annot/annot.md +114 -0
- package/docs/api/annot/fileAttach.md +53 -0
- package/docs/api/annot/freeText.md +68 -0
- package/docs/api/annot/ink.md +69 -0
- package/docs/api/annot/link.md +74 -0
- package/docs/api/annot/markup.md +83 -0
- package/docs/api/annot/popup.md +52 -0
- package/docs/api/annot/projection.md +56 -0
- package/docs/api/annot/redact.md +67 -0
- package/docs/api/annot/square.md +87 -0
- package/docs/api/annot/stamp.md +54 -0
- package/docs/api/annot/text.md +69 -0
- package/docs/api/annot/widget.md +69 -0
- package/docs/api/associatedFiles/README.md +9 -0
- package/docs/api/associatedFiles/associatedFiles.md +78 -0
- package/docs/api/bundles/README.md +68 -0
- package/docs/api/bundles/dist-matrix.md +165 -0
- package/docs/api/bundles/pdf-full.md +148 -0
- package/docs/api/bundles/pdf-large.md +144 -0
- package/docs/api/bundles/pdf-legacy.md +169 -0
- package/docs/api/content/README.md +29 -0
- package/docs/api/content/color.md +99 -0
- package/docs/api/content/graphics.md +114 -0
- package/docs/api/content/images.md +124 -0
- package/docs/api/content/ops.md +100 -0
- package/docs/api/content/stream.md +107 -0
- package/docs/api/content/text.md +98 -0
- package/docs/api/crypto/README.md +29 -0
- package/docs/api/crypto/aesGcm.md +72 -0
- package/docs/api/crypto/permissions.md +79 -0
- package/docs/api/crypto/security.md +98 -0
- package/docs/api/crypto/standardV4.md +104 -0
- package/docs/api/crypto/standardV5.md +84 -0
- package/docs/api/crypto/standardV6.md +93 -0
- package/docs/api/destination/README.md +9 -0
- package/docs/api/destination/destination.md +79 -0
- package/docs/api/document/README.md +29 -0
- package/docs/api/document/builder.md +281 -0
- package/docs/api/document/catalog.md +98 -0
- package/docs/api/document/document.md +187 -0
- package/docs/api/document/encryptedWriter.md +149 -0
- package/docs/api/document/incrementalWriter.md +148 -0
- package/docs/api/document/page.md +99 -0
- package/docs/api/document/pages.md +82 -0
- package/docs/api/document/resources.md +102 -0
- package/docs/api/document/writer.md +157 -0
- package/docs/api/document/xrefStreamWriter.md +122 -0
- package/docs/api/embedded/README.md +13 -0
- package/docs/api/embedded/collection.md +80 -0
- package/docs/api/embedded/embeddedFile.md +86 -0
- package/docs/api/embedded/fileSpec.md +87 -0
- package/docs/api/errors.md +110 -0
- package/docs/api/extra/3d-richmedia.md +76 -0
- package/docs/api/extra/README.md +99 -0
- package/docs/api/extra/annot-extended.md +71 -0
- package/docs/api/extra/associated-files.md +70 -0
- package/docs/api/extra/ccitt-fax-decoder.md +74 -0
- package/docs/api/extra/color-spaces-extended.md +72 -0
- package/docs/api/extra/content-ops-extended.md +82 -0
- package/docs/api/extra/document-parts.md +69 -0
- package/docs/api/extra/embedded-files-portfolio.md +87 -0
- package/docs/api/extra/font-cid-typed.md +77 -0
- package/docs/api/extra/font-color-tagging.md +76 -0
- package/docs/api/extra/form-actions-extended.md +75 -0
- package/docs/api/extra/info-dict-deprecated.md +72 -0
- package/docs/api/extra/jbig2-read.md +80 -0
- package/docs/api/extra/legacy-deprecated-annots.md +89 -0
- package/docs/api/extra/legacy-deprecated-filters.md +78 -0
- package/docs/api/extra/legacy-rc4-read.md +74 -0
- package/docs/api/extra/legacy-xfa-read.md +65 -0
- package/docs/api/extra/linearization-write.md +71 -0
- package/docs/api/extra/misc.md +93 -0
- package/docs/api/extra/optional-content-extended.md +83 -0
- package/docs/api/extra/pdf-a-output-intent.md +65 -0
- package/docs/api/extra/pdf-sandbox.md +76 -0
- package/docs/api/extra/pdf-ua-tagged.md +63 -0
- package/docs/api/extra/pdf-x-prepress.md +65 -0
- package/docs/api/extra/redaction-iso32005.md +65 -0
- package/docs/api/extra/shading-typed.md +73 -0
- package/docs/api/extra/sig-aes-gcm.md +69 -0
- package/docs/api/extra/sig-pades.md +103 -0
- package/docs/api/extra/tagged-pdf-typed.md +78 -0
- package/docs/api/extra/transparency-typed.md +74 -0
- package/docs/api/extra/well-tagged-pdf.md +61 -0
- package/docs/api/extra/xmp-extended.md +65 -0
- package/docs/api/font/README.md +25 -0
- package/docs/api/font/embed.md +157 -0
- package/docs/api/font/encoding.md +95 -0
- package/docs/api/font/font.md +97 -0
- package/docs/api/font/type3.md +89 -0
- package/docs/api/form/README.md +35 -0
- package/docs/api/form/acroform.md +88 -0
- package/docs/api/form/appearance.md +87 -0
- package/docs/api/form/button.md +97 -0
- package/docs/api/form/choice.md +96 -0
- package/docs/api/form/fieldTree.md +93 -0
- package/docs/api/form/signature.md +90 -0
- package/docs/api/form/text.md +88 -0
- package/docs/api/linearization/README.md +11 -0
- package/docs/api/linearization/linearization.md +81 -0
- package/docs/api/main.md +116 -0
- package/docs/api/metadata/README.md +10 -0
- package/docs/api/metadata/info.md +70 -0
- package/docs/api/metadata/xmp.md +62 -0
- package/docs/api/ocg/README.md +23 -0
- package/docs/api/ocg/config.md +95 -0
- package/docs/api/ocg/ocg.md +77 -0
- package/docs/api/outline/README.md +11 -0
- package/docs/api/outline/outline.md +107 -0
- package/docs/api/pdf.md +152 -0
- package/docs/api/prepress/README.md +10 -0
- package/docs/api/prepress/outputIntent.md +79 -0
- package/docs/api/prepress/pageBoundary.md +75 -0
- package/docs/api/sig/README.md +32 -0
- package/docs/api/sig/byteRange.md +120 -0
- package/docs/api/sig/certChain.md +84 -0
- package/docs/api/sig/dss.md +111 -0
- package/docs/api/sig/oids.md +76 -0
- package/docs/api/sig/sha1.md +72 -0
- package/docs/api/sig/sign.md +317 -0
- package/docs/api/sig/signature.md +178 -0
- package/docs/api/sig/timestamp.md +84 -0
- package/docs/api/syntax/README.md +29 -0
- package/docs/api/syntax/crossRefStream.md +115 -0
- package/docs/api/syntax/filters/README.md +50 -0
- package/docs/api/syntax/filters/ascii85.md +76 -0
- package/docs/api/syntax/filters/asciiHex.md +73 -0
- package/docs/api/syntax/filters/dispatch.md +125 -0
- package/docs/api/syntax/filters/flate.md +134 -0
- package/docs/api/syntax/filters/runLength.md +78 -0
- package/docs/api/syntax/objStream.md +88 -0
- package/docs/api/syntax/parser-obj.md +97 -0
- package/docs/api/syntax/parser.md +151 -0
- package/docs/api/syntax/serializer.md +109 -0
- package/docs/api/syntax/tokenizer.md +104 -0
- package/docs/api/syntax/trailer.md +85 -0
- package/docs/api/syntax/xref.md +139 -0
- package/docs/api/tagged/README.md +25 -0
- package/docs/api/tagged/classMap.md +67 -0
- package/docs/api/tagged/markedContent.md +62 -0
- package/docs/api/tagged/parentTree.md +67 -0
- package/docs/api/tagged/roleMap.md +67 -0
- package/docs/api/tagged/structElement.md +76 -0
- package/docs/api/tagged/structTree.md +75 -0
- package/docs/guide/coverage.md +113 -0
- package/docs/guide/crypto.md +121 -0
- package/docs/guide/extending.md +76 -0
- package/docs/guide/getting-started.md +75 -0
- package/docs/guide/legacy-1.7.md +42 -0
- package/docs/guide/pades-integration.md +579 -0
- package/docs/guide/read-pdf.md +89 -0
- package/package.json +97 -4
- package/src/_shared/index.js +179 -0
- package/src/action/action.js +119 -0
- package/src/action/goTo.js +89 -0
- package/src/action/launch.js +61 -0
- package/src/action/named.js +54 -0
- package/src/action/uri.js +51 -0
- package/src/annot/annot.js +212 -0
- package/src/annot/fileAttach.js +55 -0
- package/src/annot/freeText.js +82 -0
- package/src/annot/ink.js +77 -0
- package/src/annot/link.js +77 -0
- package/src/annot/markup.js +91 -0
- package/src/annot/popup.js +53 -0
- package/src/annot/projection.js +52 -0
- package/src/annot/redact.js +87 -0
- package/src/annot/square.js +132 -0
- package/src/annot/stamp.js +48 -0
- package/src/annot/text.js +54 -0
- package/src/annot/widget.js +61 -0
- package/src/associatedFiles/associatedFiles.js +86 -0
- package/src/bundles/pdf-full.js +91 -0
- package/src/bundles/pdf-large.js +81 -0
- package/src/bundles/pdf-legacy.js +107 -0
- package/src/content/color.js +114 -0
- package/src/content/graphics.js +192 -0
- package/src/content/images.js +160 -0
- package/src/content/ops.js +137 -0
- package/src/content/stream.js +154 -0
- package/src/content/text.js +125 -0
- package/src/crypto/aesGcm.js +123 -0
- package/src/crypto/permissions.js +112 -0
- package/src/crypto/security.js +327 -0
- package/src/crypto/standardV4.js +443 -0
- package/src/crypto/standardV5.js +306 -0
- package/src/crypto/standardV6.js +334 -0
- package/src/destination/destination.js +183 -0
- package/src/document/builder.js +618 -0
- package/src/document/catalog.js +100 -0
- package/src/document/document.js +472 -0
- package/src/document/encryptedWriter.js +554 -0
- package/src/document/incrementalWriter.js +514 -0
- package/src/document/page.js +131 -0
- package/src/document/pages.js +103 -0
- package/src/document/resources.js +146 -0
- package/src/document/writer.js +211 -0
- package/src/document/xrefStreamWriter.js +353 -0
- package/src/embedded/collection.js +102 -0
- package/src/embedded/embeddedFile.js +99 -0
- package/src/embedded/fileSpec.js +137 -0
- package/src/errors.js +78 -0
- package/src/extra/3d-richmedia.js +171 -0
- package/src/extra/annot-extended.js +200 -0
- package/src/extra/associated-files.js +131 -0
- package/src/extra/ccitt-fax-decoder.js +776 -0
- package/src/extra/color-spaces-extended.js +196 -0
- package/src/extra/content-ops-extended.js +153 -0
- package/src/extra/document-parts.js +149 -0
- package/src/extra/embedded-files-portfolio.js +234 -0
- package/src/extra/font-cid-typed.js +185 -0
- package/src/extra/font-color-tagging.js +144 -0
- package/src/extra/form-actions-extended.js +196 -0
- package/src/extra/info-dict-deprecated.js +137 -0
- package/src/extra/jbig2-read.js +169 -0
- package/src/extra/legacy-deprecated-annots.js +198 -0
- package/src/extra/legacy-deprecated-filters.js +167 -0
- package/src/extra/legacy-rc4-read.js +235 -0
- package/src/extra/legacy-xfa-read.js +104 -0
- package/src/extra/linearization-write.js +97 -0
- package/src/extra/misc.js +217 -0
- package/src/extra/optional-content-extended.js +142 -0
- package/src/extra/pdf-a-output-intent.js +112 -0
- package/src/extra/pdf-sandbox.js +88 -0
- package/src/extra/pdf-ua-tagged.js +116 -0
- package/src/extra/pdf-x-prepress.js +114 -0
- package/src/extra/redaction-iso32005.js +136 -0
- package/src/extra/shading-typed.js +222 -0
- package/src/extra/sig-aes-gcm.js +135 -0
- package/src/extra/sig-pades.js +242 -0
- package/src/extra/tagged-pdf-typed.js +203 -0
- package/src/extra/transparency-typed.js +135 -0
- package/src/extra/well-tagged-pdf.js +138 -0
- package/src/extra/xmp-extended.js +190 -0
- package/src/font/embed.js +480 -0
- package/src/font/encoding.js +92 -0
- package/src/font/font.js +101 -0
- package/src/font/type3.js +75 -0
- package/src/form/acroform.js +94 -0
- package/src/form/appearance.js +90 -0
- package/src/form/button.js +105 -0
- package/src/form/choice.js +152 -0
- package/src/form/fieldTree.js +120 -0
- package/src/form/signature.js +100 -0
- package/src/form/text.js +101 -0
- package/src/linearization/linearization.js +107 -0
- package/src/main.js +411 -0
- package/src/metadata/info.js +87 -0
- package/src/metadata/xmp.js +62 -0
- package/src/ocg/config.js +156 -0
- package/src/ocg/ocg.js +124 -0
- package/src/outline/outline.js +157 -0
- package/src/pdf.js +133 -0
- package/src/prepress/outputIntent.js +118 -0
- package/src/prepress/pageBoundary.js +108 -0
- package/src/sig/byteRange.js +306 -0
- package/src/sig/certChain.js +247 -0
- package/src/sig/dss.js +317 -0
- package/src/sig/oids.js +157 -0
- package/src/sig/sha1.js +142 -0
- package/src/sig/sign.js +1899 -0
- package/src/sig/signature.js +1441 -0
- package/src/sig/timestamp.js +236 -0
- package/src/syntax/crossRefStream.js +133 -0
- package/src/syntax/filters/ascii85.js +122 -0
- package/src/syntax/filters/asciiHex.js +83 -0
- package/src/syntax/filters/dispatch.js +176 -0
- package/src/syntax/filters/flate.js +316 -0
- package/src/syntax/filters/runLength.js +96 -0
- package/src/syntax/objStream.js +99 -0
- package/src/syntax/parser-obj.js +52 -0
- package/src/syntax/parser.js +321 -0
- package/src/syntax/serializer.js +221 -0
- package/src/syntax/tokenizer.js +290 -0
- package/src/syntax/trailer.js +76 -0
- package/src/syntax/xref.js +341 -0
- package/src/tagged/classMap.js +81 -0
- package/src/tagged/markedContent.js +123 -0
- package/src/tagged/parentTree.js +126 -0
- package/src/tagged/roleMap.js +107 -0
- package/src/tagged/structElement.js +138 -0
- package/src/tagged/structTree.js +94 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Copyright (c) 2026 AwaCloud SAS
|
|
2
|
+
// Author: Matthieu Bouilloux
|
|
3
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
4
|
+
// Dual-licensed; see the NOTICE file for licensing and any additional terms.
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @fileoverview Page-tree walker per ISO 32000-2:2020 §7.7.3.
|
|
8
|
+
*
|
|
9
|
+
* The page tree is a balanced tree of `/Type /Pages` nodes whose leaves
|
|
10
|
+
* are `/Type /Page` dicts. Internal nodes carry `/Kids` (an array of
|
|
11
|
+
* refs) and `/Count` (number of leaves in the subtree). The catalog
|
|
12
|
+
* holds a reference to the root.
|
|
13
|
+
*
|
|
14
|
+
* This module walks an already-resolved tree (the caller passes a
|
|
15
|
+
* `resolveRef(ref) → obj` function) and produces an ordered list of
|
|
16
|
+
* page refs.
|
|
17
|
+
*
|
|
18
|
+
* @module pdf/document/pages
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Module factory — worker-safe, self-contained.
|
|
23
|
+
*/
|
|
24
|
+
import { pdfErrors } from '../errors.js';
|
|
25
|
+
import { pdfParser } from '../syntax/parser.js';
|
|
26
|
+
|
|
27
|
+
export const pdfPages = {
|
|
28
|
+
name: 'pdfPages',
|
|
29
|
+
dependencies: ['pdfErrors', 'pdfParser'],
|
|
30
|
+
deps: [pdfErrors, pdfParser],
|
|
31
|
+
factory(errors, parserMod) {
|
|
32
|
+
const { ParseError } = errors;
|
|
33
|
+
const isType = (parserMod && parserMod.isType)
|
|
34
|
+
|| ((v, kind) => !!(v && v.type === kind));
|
|
35
|
+
|
|
36
|
+
function walkPageTree(rootRef, resolveRef, opts) {
|
|
37
|
+
const maxDepth = (opts && opts.maxDepth) || 64;
|
|
38
|
+
const maxPages = (opts && opts.maxPages) || 200000;
|
|
39
|
+
const out = [];
|
|
40
|
+
const visited = new Set();
|
|
41
|
+
walk(rootRef, 0);
|
|
42
|
+
return out;
|
|
43
|
+
|
|
44
|
+
function walk(ref, depth) {
|
|
45
|
+
if (depth > maxDepth) {
|
|
46
|
+
throw new ParseError('pdf/pages/max-depth',
|
|
47
|
+
'page tree depth limit exceeded',
|
|
48
|
+
{ context: { depth, maxDepth } });
|
|
49
|
+
}
|
|
50
|
+
const key = ref.num + ':' + ref.gen;
|
|
51
|
+
if (visited.has(key)) {
|
|
52
|
+
throw new ParseError('pdf/pages/cycle',
|
|
53
|
+
'cycle detected in page tree',
|
|
54
|
+
{ context: { ref } });
|
|
55
|
+
}
|
|
56
|
+
visited.add(key);
|
|
57
|
+
|
|
58
|
+
const node = resolveRef({ type: 'ref', num: ref.num, gen: ref.gen });
|
|
59
|
+
if (!isType(node, 'dict')) {
|
|
60
|
+
throw new ParseError('pdf/pages/not-dict',
|
|
61
|
+
'page tree node is not a dict',
|
|
62
|
+
{ context: { ref } });
|
|
63
|
+
}
|
|
64
|
+
const t = node.entries.Type;
|
|
65
|
+
const isPage = (t && t.type === 'name' && t.value === 'Page')
|
|
66
|
+
|| (!node.entries.Kids && !t);
|
|
67
|
+
if (isPage) {
|
|
68
|
+
out.push({ num: ref.num, gen: ref.gen });
|
|
69
|
+
if (out.length > maxPages) {
|
|
70
|
+
throw new ParseError('pdf/pages/too-many',
|
|
71
|
+
'page count limit exceeded',
|
|
72
|
+
{ context: { maxPages } });
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const kids = node.entries.Kids;
|
|
78
|
+
if (!kids || kids.type !== 'array') {
|
|
79
|
+
throw new ParseError('pdf/pages/missing-kids',
|
|
80
|
+
'intermediate page tree node missing /Kids array',
|
|
81
|
+
{ context: { ref, hasKids: !!kids } });
|
|
82
|
+
}
|
|
83
|
+
for (const k of kids.items) {
|
|
84
|
+
if (k.type !== 'ref') {
|
|
85
|
+
throw new ParseError('pdf/pages/non-ref-kid',
|
|
86
|
+
'page tree /Kids entries must be indirect refs',
|
|
87
|
+
{ context: { kind: k.type } });
|
|
88
|
+
}
|
|
89
|
+
walk({ num: k.num, gen: k.gen }, depth + 1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function readPageCount(node) {
|
|
95
|
+
if (!isType(node, 'dict')) return null;
|
|
96
|
+
const c = node.entries.Count;
|
|
97
|
+
if (!c || (c.type !== 'int' && c.type !== 'real')) return null;
|
|
98
|
+
return c.value | 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { walkPageTree, readPageCount };
|
|
102
|
+
}
|
|
103
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// Copyright (c) 2026 AwaCloud SAS
|
|
2
|
+
// Author: Matthieu Bouilloux
|
|
3
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
4
|
+
// Dual-licensed; see the NOTICE file for licensing and any additional terms.
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @fileoverview Resource dictionary resolution per ISO 32000-2:2020
|
|
8
|
+
* §7.8.3.
|
|
9
|
+
*
|
|
10
|
+
* A Page (or Form XObject) carries a `/Resources` dict with six
|
|
11
|
+
* named sub-dicts that map local names (used in content streams) to
|
|
12
|
+
* indirect references:
|
|
13
|
+
*
|
|
14
|
+
* | Sub-dict | Maps to |
|
|
15
|
+
* |---------------|----------------------------------|
|
|
16
|
+
* | `/Font` | Font dicts |
|
|
17
|
+
* | `/XObject` | Image / Form XObjects |
|
|
18
|
+
* | `/ColorSpace` | ColorSpace arrays / refs |
|
|
19
|
+
* | `/ExtGState` | ExtGState dicts |
|
|
20
|
+
* | `/Pattern` | Pattern dicts |
|
|
21
|
+
* | `/Shading` | Shading dicts |
|
|
22
|
+
*
|
|
23
|
+
* A `/ProcSet` array exists for legacy PDFs and is read but never
|
|
24
|
+
* required at L2 (the spec deprecates it in PDF 2.0).
|
|
25
|
+
*
|
|
26
|
+
* Resources are **inheritable**: a Page that lacks `/Resources` of its
|
|
27
|
+
* own inherits from its `/Parent` chain in the page tree (§7.7.3.4).
|
|
28
|
+
*
|
|
29
|
+
* @module pdf/document/resources
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Module factory — worker-safe, self-contained.
|
|
34
|
+
*/
|
|
35
|
+
import { pdfErrors } from '../errors.js';
|
|
36
|
+
import { pdfParser } from '../syntax/parser.js';
|
|
37
|
+
|
|
38
|
+
export const pdfResources = {
|
|
39
|
+
name: 'pdfResources',
|
|
40
|
+
dependencies: ['pdfErrors', 'pdfParser'],
|
|
41
|
+
deps: [pdfErrors, pdfParser],
|
|
42
|
+
factory(errors, parserObjMod) {
|
|
43
|
+
const { ParseError } = errors;
|
|
44
|
+
const isType = (parserObjMod && parserObjMod.isType)
|
|
45
|
+
|| ((v, kind) => !!(v && v.type === kind));
|
|
46
|
+
|
|
47
|
+
const CATEGORIES = ['Font', 'XObject', 'ColorSpace', 'ExtGState', 'Pattern', 'Shading'];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Type a `/Resources` dictionary into its six category maps.
|
|
51
|
+
*
|
|
52
|
+
* A category value (`/Font`, `/ExtGState`, …) may itself be an
|
|
53
|
+
* indirect reference (ISO 32000-2 §7.3.10 — any object may be
|
|
54
|
+
* indirect); when `resolveRef` is supplied such a reference is
|
|
55
|
+
* resolved through it (the document's own resolver, which reaches
|
|
56
|
+
* objects inside object streams). Without a resolver a referenced
|
|
57
|
+
* category is rejected as before (`pdf/resources/bad-subdict`).
|
|
58
|
+
*
|
|
59
|
+
* @param {object|null} dict Typed `/Resources` dict, or `null`.
|
|
60
|
+
* @param {(ref: object) => object} [resolveRef] Indirect-ref resolver.
|
|
61
|
+
* @returns {object} The typed `Resources` shape.
|
|
62
|
+
*/
|
|
63
|
+
function typeResources(dict, resolveRef) {
|
|
64
|
+
const out = {
|
|
65
|
+
Font: {}, XObject: {}, ColorSpace: {},
|
|
66
|
+
ExtGState: {}, Pattern: {}, Shading: {},
|
|
67
|
+
ProcSet: null,
|
|
68
|
+
raw: dict || null
|
|
69
|
+
};
|
|
70
|
+
if (!dict) return out;
|
|
71
|
+
if (!isType(dict, 'dict')) {
|
|
72
|
+
throw new ParseError('pdf/resources/not-dict',
|
|
73
|
+
'/Resources must be a dictionary',
|
|
74
|
+
{ context: { type: dict.type } });
|
|
75
|
+
}
|
|
76
|
+
for (const cat of CATEGORIES) {
|
|
77
|
+
let sub = dict.entries[cat];
|
|
78
|
+
if (!sub) continue;
|
|
79
|
+
if (sub.type === 'ref' && typeof resolveRef === 'function') {
|
|
80
|
+
sub = resolveRef(sub);
|
|
81
|
+
if (!sub || sub.type === 'null') continue;
|
|
82
|
+
}
|
|
83
|
+
if (sub.type !== 'dict') {
|
|
84
|
+
throw new ParseError('pdf/resources/bad-subdict',
|
|
85
|
+
`/${cat} entry must be a dictionary`,
|
|
86
|
+
{ context: { category: cat, type: sub.type } });
|
|
87
|
+
}
|
|
88
|
+
out[cat] = { ...sub.entries };
|
|
89
|
+
}
|
|
90
|
+
if (dict.entries.ProcSet && dict.entries.ProcSet.type === 'array') {
|
|
91
|
+
out.ProcSet = dict.entries.ProcSet.items
|
|
92
|
+
.filter(it => it.type === 'name')
|
|
93
|
+
.map(it => it.value);
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function resolvePageResources(pageDict, resolveRef) {
|
|
99
|
+
let node = pageDict;
|
|
100
|
+
const visited = new Set();
|
|
101
|
+
for (let safety = 0; safety < 64; safety++) {
|
|
102
|
+
if (!isType(node, 'dict')) break;
|
|
103
|
+
if (node.entries.Resources) {
|
|
104
|
+
const r = node.entries.Resources;
|
|
105
|
+
if (r.type === 'ref') return typeResources(resolveRef(r), resolveRef);
|
|
106
|
+
if (r.type === 'dict') return typeResources(r, resolveRef);
|
|
107
|
+
throw new ParseError('pdf/resources/bad-shape',
|
|
108
|
+
'/Resources must be a dict or indirect ref',
|
|
109
|
+
{ context: { type: r.type } });
|
|
110
|
+
}
|
|
111
|
+
const parent = node.entries.Parent;
|
|
112
|
+
if (!parent || parent.type !== 'ref') break;
|
|
113
|
+
const key = parent.num + ':' + parent.gen;
|
|
114
|
+
if (visited.has(key)) {
|
|
115
|
+
throw new ParseError('pdf/resources/cycle',
|
|
116
|
+
'cycle in page-tree parent chain',
|
|
117
|
+
{ context: { ref: parent } });
|
|
118
|
+
}
|
|
119
|
+
visited.add(key);
|
|
120
|
+
node = resolveRef(parent);
|
|
121
|
+
}
|
|
122
|
+
return typeResources(null);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function lookupResource(resources, category, name, resolveRef) {
|
|
126
|
+
const cat = resources[category];
|
|
127
|
+
if (!cat) {
|
|
128
|
+
throw new ParseError('pdf/resources/unknown-category',
|
|
129
|
+
`unknown resource category "${category}"`,
|
|
130
|
+
{ context: { category } });
|
|
131
|
+
}
|
|
132
|
+
const entry = cat[name];
|
|
133
|
+
if (!entry) {
|
|
134
|
+
throw new ParseError('pdf/resources/missing-name',
|
|
135
|
+
`resource /${category} /${name} not found`,
|
|
136
|
+
{ context: { category, name, available: Object.keys(cat) } });
|
|
137
|
+
}
|
|
138
|
+
if (entry.type === 'ref' && typeof resolveRef === 'function') {
|
|
139
|
+
return resolveRef(entry);
|
|
140
|
+
}
|
|
141
|
+
return entry;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { typeResources, resolvePageResources, lookupResource };
|
|
145
|
+
}
|
|
146
|
+
};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// Copyright (c) 2026 AwaCloud SAS
|
|
2
|
+
// Author: Matthieu Bouilloux
|
|
3
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
4
|
+
// Dual-licensed; see the NOTICE file for licensing and any additional terms.
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @fileoverview Document writer — turn a model into PDF bytes (`%PDF-2.0` unless `opts.version` says otherwise).
|
|
8
|
+
*
|
|
9
|
+
* Strategy:
|
|
10
|
+
*
|
|
11
|
+
* 1. Emit the header (`%PDF-<version>\n`) and the binary comment line (bytes `0x25 0xE2 0xE3 0xCF 0xD3 0x0A`).
|
|
12
|
+
* 2. For each indirect object, serialize via `serializeIndirect` and
|
|
13
|
+
* record its byte offset.
|
|
14
|
+
* 3. Emit a classical xref table (`xref` keyword + subsection
|
|
15
|
+
* `0 N` + 20-byte entries — object 0 is the head of the free list
|
|
16
|
+
* with `0000000000 65535 f `).
|
|
17
|
+
* 4. Emit the trailer dict + `startxref` pointer + `%%EOF`.
|
|
18
|
+
*
|
|
19
|
+
* @module pdf/document/writer
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Module factory — worker-safe, self-contained.
|
|
24
|
+
*/
|
|
25
|
+
import { pdfErrors } from '../errors.js';
|
|
26
|
+
import { pdfSerializer } from '../syntax/serializer.js';
|
|
27
|
+
|
|
28
|
+
export const pdfWriter = {
|
|
29
|
+
name: 'pdfWriter',
|
|
30
|
+
dependencies: ['pdfErrors', 'pdfSerializer'],
|
|
31
|
+
deps: [pdfErrors, pdfSerializer],
|
|
32
|
+
factory(errors, serializerMod) {
|
|
33
|
+
const { RenderError } = errors;
|
|
34
|
+
const serializeIndirect = serializerMod.serializeIndirect;
|
|
35
|
+
|
|
36
|
+
const te = new TextEncoder();
|
|
37
|
+
|
|
38
|
+
function pad10(n) { return String(n).padStart(10, '0'); }
|
|
39
|
+
|
|
40
|
+
function hexLit(bytes) {
|
|
41
|
+
const H = '0123456789ABCDEF';
|
|
42
|
+
let s = '<';
|
|
43
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
44
|
+
s += H[bytes[i] >> 4] + H[bytes[i] & 0xF];
|
|
45
|
+
}
|
|
46
|
+
return s + '>';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function concat(arrays) {
|
|
50
|
+
let n = 0;
|
|
51
|
+
for (const a of arrays) n += a.length;
|
|
52
|
+
const out = new Uint8Array(n);
|
|
53
|
+
let o = 0;
|
|
54
|
+
for (const a of arrays) { out.set(a, o); o += a.length; }
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function validateIndirects(list) {
|
|
59
|
+
let prev = -1;
|
|
60
|
+
for (const it of list) {
|
|
61
|
+
if (!it || !Number.isFinite(it.num) || it.num < 1) {
|
|
62
|
+
throw new RenderError('pdf/writer/bad-indirect',
|
|
63
|
+
'each indirect requires num >= 1',
|
|
64
|
+
{ context: { item: it } });
|
|
65
|
+
}
|
|
66
|
+
if (it.num === prev) {
|
|
67
|
+
throw new RenderError('pdf/writer/duplicate-num',
|
|
68
|
+
'duplicate object number',
|
|
69
|
+
{ context: { num: it.num } });
|
|
70
|
+
}
|
|
71
|
+
prev = it.num;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function buildXref(maxNum, offsets) {
|
|
76
|
+
let s = `xref\n0 ${maxNum + 1}\n`;
|
|
77
|
+
s += '0000000000 65535 f \n';
|
|
78
|
+
for (let n = 1; n <= maxNum; n++) {
|
|
79
|
+
if (offsets.has(n)) {
|
|
80
|
+
s += pad10(offsets.get(n)) + ' 00000 n \n';
|
|
81
|
+
} else {
|
|
82
|
+
s += '0000000000 00000 f \n';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return te.encode(s);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function buildTrailer(opts) {
|
|
89
|
+
const parts = [`trailer\n<< /Size ${opts.size}`];
|
|
90
|
+
parts.push(` /Root ${opts.root.num} ${opts.root.gen | 0} R`);
|
|
91
|
+
if (opts.info) parts.push(` /Info ${opts.info.num} ${opts.info.gen | 0} R`);
|
|
92
|
+
if (opts.id) {
|
|
93
|
+
parts.push(' /ID [');
|
|
94
|
+
parts.push(hexLit(opts.id[0]));
|
|
95
|
+
parts.push(hexLit(opts.id[1]));
|
|
96
|
+
parts.push(']');
|
|
97
|
+
}
|
|
98
|
+
parts.push(' >>\n');
|
|
99
|
+
parts.push(`startxref\n${opts.xrefOffset}\n%%EOF\n`);
|
|
100
|
+
return te.encode(parts.join(''));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function writeDocument(opts) {
|
|
104
|
+
if (!opts || !Array.isArray(opts.indirects)) {
|
|
105
|
+
throw new RenderError('pdf/writer/bad-input',
|
|
106
|
+
'writeDocument expects { indirects: [...] }');
|
|
107
|
+
}
|
|
108
|
+
if (!opts.root || !Number.isFinite(opts.root.num)) {
|
|
109
|
+
throw new RenderError('pdf/writer/no-root',
|
|
110
|
+
'writeDocument requires opts.root');
|
|
111
|
+
}
|
|
112
|
+
const version = opts.version || '2.0';
|
|
113
|
+
if (!/^\d\.\d$/.test(version)) {
|
|
114
|
+
throw new RenderError('pdf/writer/bad-version',
|
|
115
|
+
'version must look like "x.y"',
|
|
116
|
+
{ context: { version } });
|
|
117
|
+
}
|
|
118
|
+
const indirects = opts.indirects.slice().sort((a, b) => a.num - b.num);
|
|
119
|
+
validateIndirects(indirects);
|
|
120
|
+
|
|
121
|
+
const parts = [];
|
|
122
|
+
let cursor = 0;
|
|
123
|
+
const offsets = new Map();
|
|
124
|
+
|
|
125
|
+
function push(u8) {
|
|
126
|
+
parts.push(u8);
|
|
127
|
+
cursor += u8.length;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
push(te.encode(`%PDF-${version}\n`));
|
|
131
|
+
push(new Uint8Array([0x25, 0xE2, 0xE3, 0xCF, 0xD3, 0x0A]));
|
|
132
|
+
|
|
133
|
+
const maxNum = indirects.length > 0 ? indirects[indirects.length - 1].num : 0;
|
|
134
|
+
for (const { num, gen, value } of indirects) {
|
|
135
|
+
offsets.set(num, cursor);
|
|
136
|
+
push(serializeIndirect(num, gen, value));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const xrefOffset = cursor;
|
|
140
|
+
push(buildXref(maxNum, offsets));
|
|
141
|
+
|
|
142
|
+
const size = maxNum + 1;
|
|
143
|
+
push(buildTrailer({
|
|
144
|
+
size,
|
|
145
|
+
root: opts.root,
|
|
146
|
+
info: opts.info,
|
|
147
|
+
id: opts.id,
|
|
148
|
+
xrefOffset
|
|
149
|
+
}));
|
|
150
|
+
|
|
151
|
+
return concat(parts);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Snapshot a `pdf.read()` model into an ordered `{ num, gen, value }`
|
|
156
|
+
* list ready for `writeDocument`. Forces full resolution first; an
|
|
157
|
+
* in-use xref entry that cannot be resolved is NOT written (its
|
|
158
|
+
* indirect never reaches the cache), and the loss is reported rather
|
|
159
|
+
* than silent.
|
|
160
|
+
*
|
|
161
|
+
* Lenient by default: the list is still returned, with the skipped
|
|
162
|
+
* entries on its non-enumerable `skippedObjects` property
|
|
163
|
+
* (`Array<{ num, gen, code }>`, empty when nothing was skipped;
|
|
164
|
+
* `code` is the caught error's code when it is a typed pdf error,
|
|
165
|
+
* else `'unknown'`). With `opts.strict === true` the call throws a
|
|
166
|
+
* `RenderError` coded `pdf/writer/unresolvable-objects` instead,
|
|
167
|
+
* carrying the same list in `context.objects`.
|
|
168
|
+
*
|
|
169
|
+
* @param {object} model A model produced by `pdf.read()`.
|
|
170
|
+
* @param {{ strict?: boolean }} [opts]
|
|
171
|
+
* @returns {Array<{num:number, gen:number, value:object}>}
|
|
172
|
+
*/
|
|
173
|
+
function assembleIndirects(model, opts) {
|
|
174
|
+
if (!model || !model._raw || typeof model._raw.resolve !== 'function') {
|
|
175
|
+
throw new RenderError('pdf/writer/bad-model',
|
|
176
|
+
'assembleIndirects requires a model produced by pdf.read()');
|
|
177
|
+
}
|
|
178
|
+
const skipped = [];
|
|
179
|
+
for (const k of Object.keys(model.xref.entries)) {
|
|
180
|
+
const e = model.xref.entries[k];
|
|
181
|
+
if (e.free) continue;
|
|
182
|
+
try {
|
|
183
|
+
model._raw.resolve({ type: 'ref', num: Number(k), gen: e.gen });
|
|
184
|
+
} catch (err) {
|
|
185
|
+
skipped.push({
|
|
186
|
+
num: Number(k),
|
|
187
|
+
gen: e.gen,
|
|
188
|
+
code: errors.isPdfError(err) ? err.code : 'unknown'
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (skipped.length > 0 && opts && opts.strict === true) {
|
|
193
|
+
throw new RenderError('pdf/writer/unresolvable-objects',
|
|
194
|
+
`${skipped.length} object${skipped.length === 1 ? '' : 's'} could not be resolved and would be dropped`,
|
|
195
|
+
{ context: { objects: skipped } });
|
|
196
|
+
}
|
|
197
|
+
const out = [];
|
|
198
|
+
for (const [key, rec] of model._raw.indirects.entries()) {
|
|
199
|
+
const [n, g] = key.split(':').map(Number);
|
|
200
|
+
out.push({ num: n, gen: g, value: rec.value });
|
|
201
|
+
}
|
|
202
|
+
out.sort((a, b) => a.num - b.num);
|
|
203
|
+
Object.defineProperty(out, 'skippedObjects', {
|
|
204
|
+
value: skipped, enumerable: false, writable: true, configurable: true
|
|
205
|
+
});
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return { writeDocument, assembleIndirects };
|
|
210
|
+
}
|
|
211
|
+
};
|