@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,104 @@
|
|
|
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 Opt-in: LEGACY read-only /XFA reader.
|
|
8
|
+
*
|
|
9
|
+
* PDF 2.0 forbids XFA on write, but legacy documents still need to be
|
|
10
|
+
* parsed. The /XFA entry on the AcroForm dict comes in two shapes:
|
|
11
|
+
*
|
|
12
|
+
* (a) a single stream containing a complete XDP document, or
|
|
13
|
+
* (b) an array alternating string-keys and streams, e.g.
|
|
14
|
+
* ['preamble', stream, 'config', stream, 'template', stream, …].
|
|
15
|
+
*
|
|
16
|
+
* This extra surfaces the packets as opaque XML byte buffers on a
|
|
17
|
+
* `_legacy.xfa` object — no further interpretation is attempted.
|
|
18
|
+
*
|
|
19
|
+
* @module pdf/extra/legacy-xfa-read
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { pdfErrors } from '../errors.js';
|
|
23
|
+
|
|
24
|
+
export const pdfLegacyXfaRead = {
|
|
25
|
+
name: 'pdfLegacyXfaRead',
|
|
26
|
+
dependencies: ['pdfErrors'],
|
|
27
|
+
deps: [pdfErrors],
|
|
28
|
+
|
|
29
|
+
factory(errors) {
|
|
30
|
+
const { ParseError } = errors;
|
|
31
|
+
const KNOWN_PACKETS = new Set([
|
|
32
|
+
'xdp', 'preamble', 'config', 'template', 'localeSet',
|
|
33
|
+
'datasets', 'form', 'connectionSet', 'sourceSet',
|
|
34
|
+
'stylesheet', 'xmpmeta', 'signature', 'postamble', 'pdf'
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
function isStream(v) { return v && v.type === 'stream'; }
|
|
38
|
+
function isStr(v) { return v && v.type === 'string'; }
|
|
39
|
+
function isArr(v) { return v && v.type === 'array'; }
|
|
40
|
+
|
|
41
|
+
function streamBytes(s) {
|
|
42
|
+
if (!isStream(s)) {
|
|
43
|
+
throw new ParseError('pdf/xfa/bad-packet',
|
|
44
|
+
'XFA packet must be a stream',
|
|
45
|
+
{ context: { type: s && s.type } });
|
|
46
|
+
}
|
|
47
|
+
return s.raw;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function decodeString(v) {
|
|
51
|
+
if (!isStr(v)) {
|
|
52
|
+
throw new ParseError('pdf/xfa/bad-key',
|
|
53
|
+
'XFA array key must be a PDF string',
|
|
54
|
+
{ context: { type: v && v.type } });
|
|
55
|
+
}
|
|
56
|
+
const b = v.value;
|
|
57
|
+
if (b instanceof Uint8Array) return new TextDecoder('latin1').decode(b);
|
|
58
|
+
return String(b);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readXfa(xfa) {
|
|
62
|
+
if (isStream(xfa)) {
|
|
63
|
+
return {
|
|
64
|
+
_legacy: { xfa: { shape: 'stream', xdp: streamBytes(xfa) } }
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (isArr(xfa)) {
|
|
68
|
+
if (xfa.items.length % 2 !== 0) {
|
|
69
|
+
throw new ParseError('pdf/xfa/odd-array',
|
|
70
|
+
'XFA array must have even item count (key/stream pairs)',
|
|
71
|
+
{ context: { length: xfa.items.length } });
|
|
72
|
+
}
|
|
73
|
+
const packets = {};
|
|
74
|
+
const order = [];
|
|
75
|
+
const unknown = [];
|
|
76
|
+
for (let i = 0; i < xfa.items.length; i += 2) {
|
|
77
|
+
const key = decodeString(xfa.items[i]);
|
|
78
|
+
const bytes = streamBytes(xfa.items[i + 1]);
|
|
79
|
+
packets[key] = bytes;
|
|
80
|
+
order.push(key);
|
|
81
|
+
if (!KNOWN_PACKETS.has(key)) unknown.push(key);
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
_legacy: {
|
|
85
|
+
xfa: { shape: 'array', packets, order, unknownPackets: unknown }
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
throw new ParseError('pdf/xfa/bad-shape',
|
|
90
|
+
'/XFA must be a stream or array',
|
|
91
|
+
{ context: { type: xfa && xfa.type } });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isKnownPacket(name) {
|
|
95
|
+
return KNOWN_PACKETS.has(String(name));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
readXfa,
|
|
100
|
+
isKnownPacket,
|
|
101
|
+
KNOWN_PACKETS
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
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 Opt-in: Linearization Parameter Dictionary emitter
|
|
8
|
+
* (builds and validates the dictionary; the hint stream is an empty stub).
|
|
9
|
+
*
|
|
10
|
+
* @module pdf/extra/linearization-write
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Module factory.
|
|
15
|
+
*/
|
|
16
|
+
import { pdfErrors } from '../errors.js';
|
|
17
|
+
import { pdfParser } from '../syntax/parser.js';
|
|
18
|
+
|
|
19
|
+
export const pdfLinearizationWrite = {
|
|
20
|
+
name: 'pdfLinearizationWrite',
|
|
21
|
+
dependencies: ['pdfErrors', 'pdfParser'],
|
|
22
|
+
deps: [pdfErrors, pdfParser],
|
|
23
|
+
factory(errors, parser) {
|
|
24
|
+
const { RenderError } = errors;
|
|
25
|
+
const { obj } = parser;
|
|
26
|
+
|
|
27
|
+
/** Frozen list of required entries (§F.2 Table F.1). */
|
|
28
|
+
const LINEARIZED_KEYS = Object.freeze([
|
|
29
|
+
'Linearized', 'L', 'H', 'O', 'E', 'N', 'T'
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
function buildLinearizedDict(p) {
|
|
33
|
+
if (!p || typeof p !== 'object') {
|
|
34
|
+
throw new RenderError('pdf/extra/linwrite/bad-params',
|
|
35
|
+
'buildLinearizedDict requires a parameters object',
|
|
36
|
+
{ context: { type: typeof p } });
|
|
37
|
+
}
|
|
38
|
+
for (const k of ['fileLength', 'firstPageObj', 'firstPageEnd',
|
|
39
|
+
'pageCount', 'mainXrefOffset']) {
|
|
40
|
+
if (!Number.isFinite(p[k])) {
|
|
41
|
+
throw new RenderError('pdf/extra/linwrite/missing-' + k,
|
|
42
|
+
'parameter ' + k + ' is required and must be a number',
|
|
43
|
+
{ context: { key: k, type: typeof p[k] } });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (!Number.isFinite(p.hintOffset) || !Number.isFinite(p.hintLength)) {
|
|
47
|
+
throw new RenderError('pdf/extra/linwrite/missing-hint',
|
|
48
|
+
'hintOffset and hintLength are required',
|
|
49
|
+
{ context: { hintOffset: p.hintOffset, hintLength: p.hintLength } });
|
|
50
|
+
}
|
|
51
|
+
const entries = {
|
|
52
|
+
Linearized: obj.real(p.version != null ? p.version : 1.0),
|
|
53
|
+
L: obj.int(p.fileLength),
|
|
54
|
+
H: obj.array([obj.int(p.hintOffset), obj.int(p.hintLength)]),
|
|
55
|
+
O: obj.int(p.firstPageObj),
|
|
56
|
+
E: obj.int(p.firstPageEnd),
|
|
57
|
+
N: obj.int(p.pageCount),
|
|
58
|
+
T: obj.int(p.mainXrefOffset)
|
|
59
|
+
};
|
|
60
|
+
if (p.firstPage != null) entries.P = obj.int(p.firstPage);
|
|
61
|
+
return obj.dict(entries);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function buildHintStreamStub() {
|
|
65
|
+
const dict = obj.dict({
|
|
66
|
+
Length: obj.int(0),
|
|
67
|
+
S: obj.int(0)
|
|
68
|
+
});
|
|
69
|
+
return obj.stream(dict, new Uint8Array(0));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function validateLinearizedDict(dict) {
|
|
73
|
+
if (!dict || dict.type !== 'dict') {
|
|
74
|
+
throw new RenderError('pdf/extra/linwrite/validate/not-dict',
|
|
75
|
+
'expected a dict',
|
|
76
|
+
{ context: { type: dict && dict.type } });
|
|
77
|
+
}
|
|
78
|
+
const errs = [];
|
|
79
|
+
for (const k of LINEARIZED_KEYS) {
|
|
80
|
+
if (!dict.entries[k]) errs.push('missing /' + k);
|
|
81
|
+
}
|
|
82
|
+
const h = dict.entries.H;
|
|
83
|
+
if (h && (h.type !== 'array' || h.items.length < 2)) {
|
|
84
|
+
errs.push('/H must be an array of at least 2 integers');
|
|
85
|
+
}
|
|
86
|
+
return { pass: errs.length === 0, errors: errs, warnings: [] };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
buildLinearizedDict,
|
|
91
|
+
buildHintStreamStub,
|
|
92
|
+
validateLinearizedDict,
|
|
93
|
+
LINEARIZED_KEYS
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
@@ -0,0 +1,217 @@
|
|
|
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 Opt-in: miscellaneous tail typing.
|
|
8
|
+
*
|
|
9
|
+
* Covers the long tail of Catalog-level entries that don't warrant
|
|
10
|
+
* their own L1 module:
|
|
11
|
+
*
|
|
12
|
+
* - /SpiderInfo — Web-Capture provenance (ISO 32000-2 §14.10.2).
|
|
13
|
+
* - /Threads — array of article-thread dicts (§12.4.3).
|
|
14
|
+
* - /Legal — legal-attestation dict (§12.8.5).
|
|
15
|
+
* - /Requirements — array of requirement dicts (§12.10).
|
|
16
|
+
* - /Perms — extra permission flavours beyond /UR3, notably
|
|
17
|
+
* /DocMDP transformation parameters (§12.8.2.2).
|
|
18
|
+
* - /NeedsRendering — boolean indicator for XFA-bearing forms.
|
|
19
|
+
*
|
|
20
|
+
* @module pdf/extra/misc
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { pdfErrors } from '../errors.js';
|
|
24
|
+
|
|
25
|
+
export const pdfMisc = {
|
|
26
|
+
name: 'pdfMisc',
|
|
27
|
+
dependencies: ['pdfErrors'],
|
|
28
|
+
deps: [pdfErrors],
|
|
29
|
+
|
|
30
|
+
factory(errors) {
|
|
31
|
+
const { ParseError } = errors;
|
|
32
|
+
const REQUIREMENT_S_VALUES = new Set([
|
|
33
|
+
'EnableJavaScripts'
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
function isDict(v) { return v && v.type === 'dict'; }
|
|
37
|
+
function isName(v) { return v && v.type === 'name'; }
|
|
38
|
+
function isArr(v) { return v && v.type === 'array'; }
|
|
39
|
+
function isBool(v) { return v && v.type === 'bool'; }
|
|
40
|
+
function isInt(v) { return v && v.type === 'int'; }
|
|
41
|
+
|
|
42
|
+
function typeSpiderInfo(dict) {
|
|
43
|
+
if (!isDict(dict)) {
|
|
44
|
+
throw new ParseError('pdf/misc/spider-not-dict',
|
|
45
|
+
'/SpiderInfo must be a dict');
|
|
46
|
+
}
|
|
47
|
+
const e = dict.entries;
|
|
48
|
+
const out = { raw: dict, _extras: {} };
|
|
49
|
+
if (e.V) {
|
|
50
|
+
if (!isInt(e.V) && !(e.V && e.V.type === 'real')) {
|
|
51
|
+
throw new ParseError('pdf/misc/spider-bad-V',
|
|
52
|
+
'/SpiderInfo /V must be numeric');
|
|
53
|
+
}
|
|
54
|
+
out.version = e.V.value;
|
|
55
|
+
}
|
|
56
|
+
if (e.C) {
|
|
57
|
+
if (!isArr(e.C)) {
|
|
58
|
+
throw new ParseError('pdf/misc/spider-bad-C',
|
|
59
|
+
'/SpiderInfo /C must be an array');
|
|
60
|
+
}
|
|
61
|
+
out.commands = e.C.items;
|
|
62
|
+
}
|
|
63
|
+
const KNOWN = new Set(['V', 'C']);
|
|
64
|
+
for (const k of Object.keys(e)) {
|
|
65
|
+
if (!KNOWN.has(k)) out._extras[k] = e[k];
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function typeThreads(arr) {
|
|
71
|
+
if (!isArr(arr)) {
|
|
72
|
+
throw new ParseError('pdf/misc/threads-not-array',
|
|
73
|
+
'/Threads must be an array');
|
|
74
|
+
}
|
|
75
|
+
const out = [];
|
|
76
|
+
for (let i = 0; i < arr.items.length; i++) {
|
|
77
|
+
const it = arr.items[i];
|
|
78
|
+
if (it.type !== 'ref' && !isDict(it)) {
|
|
79
|
+
throw new ParseError('pdf/misc/thread-bad-entry',
|
|
80
|
+
'/Threads entry must be a dict or ref',
|
|
81
|
+
{ context: { index: i } });
|
|
82
|
+
}
|
|
83
|
+
out.push(it);
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function typeLegal(dict) {
|
|
89
|
+
if (!isDict(dict)) {
|
|
90
|
+
throw new ParseError('pdf/misc/legal-not-dict',
|
|
91
|
+
'/Legal must be a dict');
|
|
92
|
+
}
|
|
93
|
+
const e = dict.entries;
|
|
94
|
+
const out = { raw: dict, flags: {}, _extras: {} };
|
|
95
|
+
const KNOWN_FLAGS = new Set([
|
|
96
|
+
'JavaScriptActions', 'LaunchActions', 'URIActions',
|
|
97
|
+
'MovieActions', 'SoundActions', 'HiddenAnnotations',
|
|
98
|
+
'NonEmbeddedFonts', 'DevDepGS_OP', 'DevDepGS_HT',
|
|
99
|
+
'DevDepGS_TR', 'DevDepGS_UCR', 'DevDepGS_FL', 'DevDepGS_BG',
|
|
100
|
+
'Annotations', 'ExternalRefXobjects', 'ExternalOPIdicts',
|
|
101
|
+
'ExternalStreams', 'TrueTypeFonts', 'AlternateImages'
|
|
102
|
+
]);
|
|
103
|
+
for (const [k, v] of Object.entries(e)) {
|
|
104
|
+
if (KNOWN_FLAGS.has(k)) {
|
|
105
|
+
if (!isInt(v)) {
|
|
106
|
+
throw new ParseError('pdf/misc/legal-bad-flag',
|
|
107
|
+
`/Legal /${k} must be an integer count`,
|
|
108
|
+
{ context: { key: k } });
|
|
109
|
+
}
|
|
110
|
+
out.flags[k] = v.value;
|
|
111
|
+
} else if (k === 'Attestation') {
|
|
112
|
+
if (v.type !== 'string') {
|
|
113
|
+
throw new ParseError('pdf/misc/legal-bad-attestation',
|
|
114
|
+
'/Attestation must be a string');
|
|
115
|
+
}
|
|
116
|
+
out.attestation = v.value;
|
|
117
|
+
} else {
|
|
118
|
+
out._extras[k] = v;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function typeRequirements(arr) {
|
|
125
|
+
if (!isArr(arr)) {
|
|
126
|
+
throw new ParseError('pdf/misc/req-not-array',
|
|
127
|
+
'/Requirements must be an array');
|
|
128
|
+
}
|
|
129
|
+
const out = [];
|
|
130
|
+
for (let i = 0; i < arr.items.length; i++) {
|
|
131
|
+
const it = arr.items[i];
|
|
132
|
+
if (!isDict(it)) {
|
|
133
|
+
throw new ParseError('pdf/misc/req-bad-entry',
|
|
134
|
+
'/Requirements entry must be a dict',
|
|
135
|
+
{ context: { index: i } });
|
|
136
|
+
}
|
|
137
|
+
const e = it.entries;
|
|
138
|
+
if (!e.S || !isName(e.S)) {
|
|
139
|
+
throw new ParseError('pdf/misc/req-missing-S',
|
|
140
|
+
'/Requirements entry needs /S name',
|
|
141
|
+
{ context: { index: i } });
|
|
142
|
+
}
|
|
143
|
+
out.push({
|
|
144
|
+
raw: it,
|
|
145
|
+
s: e.S.value,
|
|
146
|
+
standard: REQUIREMENT_S_VALUES.has(e.S.value),
|
|
147
|
+
rh: e.RH
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function typeDocMdpParams(dict) {
|
|
154
|
+
if (!isDict(dict)) {
|
|
155
|
+
throw new ParseError('pdf/misc/docmdp-not-dict',
|
|
156
|
+
'/DocMDP transform params must be a dict');
|
|
157
|
+
}
|
|
158
|
+
const e = dict.entries;
|
|
159
|
+
const out = { raw: dict, _extras: {} };
|
|
160
|
+
if (e.P) {
|
|
161
|
+
if (!isInt(e.P) || e.P.value < 1 || e.P.value > 3) {
|
|
162
|
+
throw new ParseError('pdf/misc/docmdp-bad-P',
|
|
163
|
+
'/DocMDP /P must be 1, 2, or 3');
|
|
164
|
+
}
|
|
165
|
+
out.permission = e.P.value;
|
|
166
|
+
}
|
|
167
|
+
if (e.V) {
|
|
168
|
+
if (!isName(e.V)) {
|
|
169
|
+
throw new ParseError('pdf/misc/docmdp-bad-V',
|
|
170
|
+
'/DocMDP /V must be a name');
|
|
171
|
+
}
|
|
172
|
+
out.version = e.V.value;
|
|
173
|
+
}
|
|
174
|
+
const KNOWN = new Set(['Type', 'P', 'V']);
|
|
175
|
+
for (const k of Object.keys(e)) {
|
|
176
|
+
if (!KNOWN.has(k)) out._extras[k] = e[k];
|
|
177
|
+
}
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function typePerms(dict) {
|
|
182
|
+
if (!isDict(dict)) {
|
|
183
|
+
throw new ParseError('pdf/misc/perms-not-dict',
|
|
184
|
+
'/Perms must be a dict');
|
|
185
|
+
}
|
|
186
|
+
const e = dict.entries;
|
|
187
|
+
const out = { raw: dict, _extras: {} };
|
|
188
|
+
if (e.DocMDP) out.docMDP = e.DocMDP;
|
|
189
|
+
if (e.UR3) out.ur3 = e.UR3;
|
|
190
|
+
if (e.UR) out.ur = e.UR;
|
|
191
|
+
const KNOWN = new Set(['DocMDP', 'UR3', 'UR']);
|
|
192
|
+
for (const k of Object.keys(e)) {
|
|
193
|
+
if (!KNOWN.has(k)) out._extras[k] = e[k];
|
|
194
|
+
}
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function typeNeedsRendering(v) {
|
|
199
|
+
if (!isBool(v)) {
|
|
200
|
+
throw new ParseError('pdf/misc/needsrendering-bad',
|
|
201
|
+
'/NeedsRendering must be boolean');
|
|
202
|
+
}
|
|
203
|
+
return v.value;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
typeSpiderInfo,
|
|
208
|
+
typeThreads,
|
|
209
|
+
typeLegal,
|
|
210
|
+
typeRequirements,
|
|
211
|
+
typeDocMdpParams,
|
|
212
|
+
typePerms,
|
|
213
|
+
typeNeedsRendering,
|
|
214
|
+
REQUIREMENT_S_VALUES
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
};
|
|
@@ -0,0 +1,142 @@
|
|
|
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 Opt-in: Extended OCG/OCMD support.
|
|
8
|
+
*
|
|
9
|
+
* ISO 32000-2 §8.11.2.4 defines OCMD `/VE` (Visibility Expression) as a
|
|
10
|
+
* nested array `[op, operand, ...]` with ops `And`, `Or`, `Not`. This
|
|
11
|
+
* module evaluates such expressions, types `/Order` trees with embedded
|
|
12
|
+
* sub-headings (string leaves), and surfaces RBGroups + `/Intent`
|
|
13
|
+
* routing (View vs Design).
|
|
14
|
+
*
|
|
15
|
+
* @module pdf/extra/optional-content-extended
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Module factory.
|
|
20
|
+
*/
|
|
21
|
+
import { pdfErrors } from '../errors.js';
|
|
22
|
+
import { pdfParser } from '../syntax/parser.js';
|
|
23
|
+
|
|
24
|
+
export const pdfOptionalContentExtended = {
|
|
25
|
+
name: 'pdfOptionalContentExtended',
|
|
26
|
+
dependencies: ['pdfErrors', 'pdfParser'],
|
|
27
|
+
deps: [pdfErrors, pdfParser],
|
|
28
|
+
factory(errors, parser) {
|
|
29
|
+
const { ParseError } = errors;
|
|
30
|
+
const { isType } = parser;
|
|
31
|
+
|
|
32
|
+
const VE_OPERATORS = Object.freeze(['And', 'Or', 'Not']);
|
|
33
|
+
|
|
34
|
+
function evaluateVE(ve, on) {
|
|
35
|
+
if (!ve) {
|
|
36
|
+
throw new ParseError('pdf/extra/ocgx/ve/null',
|
|
37
|
+
'VE expression is null');
|
|
38
|
+
}
|
|
39
|
+
if (!(on instanceof Set)) {
|
|
40
|
+
throw new ParseError('pdf/extra/ocgx/ve/bad-state',
|
|
41
|
+
'evaluateVE requires a Set of on refs',
|
|
42
|
+
{ context: { type: typeof on } });
|
|
43
|
+
}
|
|
44
|
+
if (ve.type === 'ref') {
|
|
45
|
+
return on.has(ve.num + ' ' + ve.gen);
|
|
46
|
+
}
|
|
47
|
+
if (ve.type !== 'array' || ve.items.length === 0) {
|
|
48
|
+
throw new ParseError('pdf/extra/ocgx/ve/bad-shape',
|
|
49
|
+
'VE must be an array starting with an operator',
|
|
50
|
+
{ context: { type: ve.type } });
|
|
51
|
+
}
|
|
52
|
+
const op = ve.items[0];
|
|
53
|
+
if (!isType(op, 'name') || !VE_OPERATORS.includes(op.value)) {
|
|
54
|
+
throw new ParseError('pdf/extra/ocgx/ve/bad-op',
|
|
55
|
+
'VE operator must be /And, /Or or /Not',
|
|
56
|
+
{ context: { actual: op && op.value } });
|
|
57
|
+
}
|
|
58
|
+
const operands = ve.items.slice(1);
|
|
59
|
+
if (op.value === 'Not') {
|
|
60
|
+
if (operands.length !== 1) {
|
|
61
|
+
throw new ParseError('pdf/extra/ocgx/ve/not-arity',
|
|
62
|
+
'/Not requires exactly one operand');
|
|
63
|
+
}
|
|
64
|
+
return !evaluateVE(operands[0], on);
|
|
65
|
+
}
|
|
66
|
+
if (op.value === 'And') return operands.every(o => evaluateVE(o, on));
|
|
67
|
+
return operands.some(o => evaluateVE(o, on));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function typeOrderTree(arr) {
|
|
71
|
+
if (!arr || arr.type !== 'array') {
|
|
72
|
+
throw new ParseError('pdf/extra/ocgx/order/not-array',
|
|
73
|
+
'/Order must be an array',
|
|
74
|
+
{ context: { type: arr && arr.type } });
|
|
75
|
+
}
|
|
76
|
+
const out = [];
|
|
77
|
+
for (const it of arr.items) {
|
|
78
|
+
if (!it) continue;
|
|
79
|
+
if (it.type === 'string') {
|
|
80
|
+
out.push({ kind: 'heading', text: it.value });
|
|
81
|
+
} else if (it.type === 'array') {
|
|
82
|
+
out.push({ kind: 'group', children: typeOrderTree(it) });
|
|
83
|
+
} else if (it.type === 'ref') {
|
|
84
|
+
out.push({ kind: 'ocg', ref: { num: it.num, gen: it.gen } });
|
|
85
|
+
} else {
|
|
86
|
+
out.push({ kind: 'other', value: it });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function typeRBGroups(arr) {
|
|
93
|
+
if (!arr || arr.type !== 'array') {
|
|
94
|
+
throw new ParseError('pdf/extra/ocgx/rbg/not-array',
|
|
95
|
+
'/RBGroups must be an array',
|
|
96
|
+
{ context: { type: arr && arr.type } });
|
|
97
|
+
}
|
|
98
|
+
const out = [];
|
|
99
|
+
for (const grp of arr.items) {
|
|
100
|
+
if (!grp || grp.type !== 'array') {
|
|
101
|
+
throw new ParseError('pdf/extra/ocgx/rbg/bad-group',
|
|
102
|
+
'/RBGroups entry must be an array',
|
|
103
|
+
{ context: { type: grp && grp.type } });
|
|
104
|
+
}
|
|
105
|
+
const refs = [];
|
|
106
|
+
for (const r of grp.items) {
|
|
107
|
+
if (!r || r.type !== 'ref') continue;
|
|
108
|
+
refs.push({ num: r.num, gen: r.gen });
|
|
109
|
+
}
|
|
110
|
+
out.push(refs);
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function classifyOcgIntent(v) {
|
|
116
|
+
if (!v) return { view: false, design: false, names: [] };
|
|
117
|
+
const names = [];
|
|
118
|
+
if (v.type === 'name') names.push(v.value);
|
|
119
|
+
else if (v.type === 'array') {
|
|
120
|
+
for (const it of v.items) if (isType(it, 'name')) names.push(it.value);
|
|
121
|
+
} else {
|
|
122
|
+
throw new ParseError('pdf/extra/ocgx/intent/bad',
|
|
123
|
+
'/Intent must be name or array of names',
|
|
124
|
+
{ context: { type: v.type } });
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
view: names.includes('View'),
|
|
128
|
+
design: names.includes('Design'),
|
|
129
|
+
names
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
evaluateVE,
|
|
135
|
+
typeOrderTree,
|
|
136
|
+
typeRBGroups,
|
|
137
|
+
classifyOcgIntent,
|
|
138
|
+
VE_OPERATORS
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
@@ -0,0 +1,112 @@
|
|
|
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 Opt-in: PDF/A validation helpers.
|
|
8
|
+
*
|
|
9
|
+
* Recognises an `/OutputIntent` whose `/S` is `GTS_PDFA1` (or later
|
|
10
|
+
* profile names introduced by ISO 19005-{2..4}), extracts the
|
|
11
|
+
* conformance level letter (a/b/u/e/f) and maps the
|
|
12
|
+
* `/OutputConditionIdentifier` to a known registry profile.
|
|
13
|
+
*
|
|
14
|
+
* @module pdf/extra/pdf-a-output-intent
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Module factory.
|
|
19
|
+
*/
|
|
20
|
+
import { pdfErrors } from '../errors.js';
|
|
21
|
+
import { pdfParser } from '../syntax/parser.js';
|
|
22
|
+
|
|
23
|
+
export const pdfAOutputIntent = {
|
|
24
|
+
name: 'pdfAOutputIntent',
|
|
25
|
+
dependencies: ['pdfErrors', 'pdfParser'],
|
|
26
|
+
deps: [pdfErrors, pdfParser],
|
|
27
|
+
factory(errors, parser) {
|
|
28
|
+
const { ParseError } = errors;
|
|
29
|
+
const { isType } = parser;
|
|
30
|
+
|
|
31
|
+
const PDFA_PROFILES = Object.freeze({
|
|
32
|
+
'1a': { part: 1, level: 'a', iso: 'ISO 19005-1:2005', notes: 'accessible' },
|
|
33
|
+
'1b': { part: 1, level: 'b', iso: 'ISO 19005-1:2005', notes: 'basic' },
|
|
34
|
+
'2a': { part: 2, level: 'a', iso: 'ISO 19005-2:2011', notes: 'accessible' },
|
|
35
|
+
'2b': { part: 2, level: 'b', iso: 'ISO 19005-2:2011', notes: 'basic' },
|
|
36
|
+
'2u': { part: 2, level: 'u', iso: 'ISO 19005-2:2011', notes: 'unicode' },
|
|
37
|
+
'3a': { part: 3, level: 'a', iso: 'ISO 19005-3:2012', notes: 'accessible + embed' },
|
|
38
|
+
'3b': { part: 3, level: 'b', iso: 'ISO 19005-3:2012', notes: 'basic + embed' },
|
|
39
|
+
'3u': { part: 3, level: 'u', iso: 'ISO 19005-3:2012', notes: 'unicode + embed' },
|
|
40
|
+
'4': { part: 4, level: null, iso: 'ISO 19005-4:2020', notes: 'PDF 2.0 base' },
|
|
41
|
+
'4e': { part: 4, level: 'e', iso: 'ISO 19005-4:2020', notes: 'engineering' },
|
|
42
|
+
'4f': { part: 4, level: 'f', iso: 'ISO 19005-4:2020', notes: 'embedded files allowed' }
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const VALID_LEVELS = new Set(['1a', '1b', '2a', '2b', '2u',
|
|
46
|
+
'3a', '3b', '3u', '4', '4e', '4f']);
|
|
47
|
+
|
|
48
|
+
function detectPdfAProfile(dict) {
|
|
49
|
+
if (!isType(dict, 'dict')) {
|
|
50
|
+
throw new ParseError('pdf/extra/pdfa/not-dict',
|
|
51
|
+
'OutputIntent must be a dictionary',
|
|
52
|
+
{ context: { type: dict && dict.type } });
|
|
53
|
+
}
|
|
54
|
+
const e = dict.entries;
|
|
55
|
+
const s = isType(e.S, 'name') ? e.S.value : null;
|
|
56
|
+
const ident = isType(e.OutputConditionIdentifier, 'string')
|
|
57
|
+
? new TextDecoder('latin1').decode(e.OutputConditionIdentifier.value)
|
|
58
|
+
: null;
|
|
59
|
+
const isPdfA = s === 'GTS_PDFA1';
|
|
60
|
+
const profile = profileFromIdentifier(ident);
|
|
61
|
+
return {
|
|
62
|
+
isPdfA,
|
|
63
|
+
subtype: s,
|
|
64
|
+
identifier: ident,
|
|
65
|
+
profile,
|
|
66
|
+
part: profile ? PDFA_PROFILES[profile].part : null,
|
|
67
|
+
level: profile ? PDFA_PROFILES[profile].level : null,
|
|
68
|
+
catalog: profile ? PDFA_PROFILES[profile] : null,
|
|
69
|
+
raw: dict
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function profileFromIdentifier(s) {
|
|
74
|
+
if (!s) return null;
|
|
75
|
+
const m = s.match(/PDF\/A[-\s]?(1[ab]|2[abu]|3[abu]|4[ef]?)/i);
|
|
76
|
+
if (!m) return null;
|
|
77
|
+
const tag = m[1].toLowerCase();
|
|
78
|
+
return VALID_LEVELS.has(tag) ? tag : null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function validatePdfABasics(typedDoc) {
|
|
82
|
+
if (!typedDoc || typeof typedDoc !== 'object') {
|
|
83
|
+
throw new ParseError('pdf/extra/pdfa/lint/bad-doc',
|
|
84
|
+
'validatePdfABasics requires a typed document object',
|
|
85
|
+
{ context: { type: typeof typedDoc } });
|
|
86
|
+
}
|
|
87
|
+
const errors = [];
|
|
88
|
+
const warnings = [];
|
|
89
|
+
const cat = typedDoc.catalog && typedDoc.catalog.entries;
|
|
90
|
+
if (typedDoc.encrypted) errors.push('PDF/A forbids encryption');
|
|
91
|
+
if (!cat) {
|
|
92
|
+
errors.push('catalog missing');
|
|
93
|
+
return { pass: false, errors, warnings };
|
|
94
|
+
}
|
|
95
|
+
if (!cat.Metadata) errors.push('catalog requires /Metadata XMP stream');
|
|
96
|
+
if (cat.MarkInfo && (!isType(cat.MarkInfo, 'dict'))) {
|
|
97
|
+
errors.push('/MarkInfo must be a dictionary');
|
|
98
|
+
}
|
|
99
|
+
if (!cat.OutputIntents) {
|
|
100
|
+
warnings.push('catalog has no /OutputIntents array');
|
|
101
|
+
}
|
|
102
|
+
return { pass: errors.length === 0, errors, warnings };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
detectPdfAProfile,
|
|
107
|
+
validatePdfABasics,
|
|
108
|
+
PDFA_PROFILES
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|