@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,137 @@
|
|
|
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 Catalogue exhaustif des opérateurs de content stream
|
|
8
|
+
* PDF per ISO 32000-2:2020 §8.2 / §9.4.
|
|
9
|
+
*
|
|
10
|
+
* Chaque entrée décrit :
|
|
11
|
+
* - `op` — le mnémonique exact (string).
|
|
12
|
+
* - `category` — `'gstate'|'path'|'clip'|'paint'|'color'|'text'|'shading'|'xobject'|'marked'|'image'`.
|
|
13
|
+
* - `arity` — nombre d'opérandes attendus, ou `'var'` pour TJ.
|
|
14
|
+
* - `desc` — courte description (anglais, mots-clés OK).
|
|
15
|
+
*
|
|
16
|
+
* @module pdf/content/ops
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Module factory — worker-safe, self-contained.
|
|
21
|
+
*/
|
|
22
|
+
export const pdfContentOps = {
|
|
23
|
+
name: 'pdfContentOps',
|
|
24
|
+
dependencies: [],
|
|
25
|
+
factory() {
|
|
26
|
+
const OPS = Object.freeze({
|
|
27
|
+
// --- Graphics state (§8.4) ---
|
|
28
|
+
q: { op: 'q', category: 'gstate', arity: 0, desc: 'push graphics state' },
|
|
29
|
+
Q: { op: 'Q', category: 'gstate', arity: 0, desc: 'pop graphics state' },
|
|
30
|
+
cm: { op: 'cm', category: 'gstate', arity: 6, desc: 'concatenate CTM (a b c d e f)' },
|
|
31
|
+
w: { op: 'w', category: 'gstate', arity: 1, desc: 'set line width' },
|
|
32
|
+
J: { op: 'J', category: 'gstate', arity: 1, desc: 'set line cap style' },
|
|
33
|
+
j: { op: 'j', category: 'gstate', arity: 1, desc: 'set line join style' },
|
|
34
|
+
M: { op: 'M', category: 'gstate', arity: 1, desc: 'set miter limit' },
|
|
35
|
+
d: { op: 'd', category: 'gstate', arity: 2, desc: 'set dash pattern' },
|
|
36
|
+
ri: { op: 'ri', category: 'gstate', arity: 1, desc: 'set rendering intent' },
|
|
37
|
+
i: { op: 'i', category: 'gstate', arity: 1, desc: 'set flatness tolerance' },
|
|
38
|
+
gs: { op: 'gs', category: 'gstate', arity: 1, desc: 'set parameters from /ExtGState' },
|
|
39
|
+
|
|
40
|
+
// --- Path construction (§8.5.2) ---
|
|
41
|
+
m: { op: 'm', category: 'path', arity: 2, desc: 'moveto' },
|
|
42
|
+
l: { op: 'l', category: 'path', arity: 2, desc: 'lineto' },
|
|
43
|
+
c: { op: 'c', category: 'path', arity: 6, desc: 'cubic Bézier (x1 y1 x2 y2 x3 y3)' },
|
|
44
|
+
v: { op: 'v', category: 'path', arity: 4, desc: 'cubic Bézier (current to x2 y2 x3 y3)' },
|
|
45
|
+
y: { op: 'y', category: 'path', arity: 4, desc: 'cubic Bézier (x1 y1 to x3 y3)' },
|
|
46
|
+
h: { op: 'h', category: 'path', arity: 0, desc: 'close subpath' },
|
|
47
|
+
re: { op: 're', category: 'path', arity: 4, desc: 'append rectangle' },
|
|
48
|
+
|
|
49
|
+
// --- Path painting (§8.5.3) ---
|
|
50
|
+
S: { op: 'S', category: 'paint', arity: 0, desc: 'stroke' },
|
|
51
|
+
s: { op: 's', category: 'paint', arity: 0, desc: 'close + stroke' },
|
|
52
|
+
f: { op: 'f', category: 'paint', arity: 0, desc: 'fill (non-zero)' },
|
|
53
|
+
F: { op: 'F', category: 'paint', arity: 0, desc: 'fill (non-zero, deprecated alias)' },
|
|
54
|
+
'f*': { op: 'f*', category: 'paint', arity: 0, desc: 'fill (even-odd)' },
|
|
55
|
+
B: { op: 'B', category: 'paint', arity: 0, desc: 'fill + stroke (non-zero)' },
|
|
56
|
+
'B*': { op: 'B*', category: 'paint', arity: 0, desc: 'fill + stroke (even-odd)' },
|
|
57
|
+
b: { op: 'b', category: 'paint', arity: 0, desc: 'close + fill + stroke (non-zero)' },
|
|
58
|
+
'b*': { op: 'b*', category: 'paint', arity: 0, desc: 'close + fill + stroke (even-odd)' },
|
|
59
|
+
n: { op: 'n', category: 'paint', arity: 0, desc: 'end path (no paint, no clip)' },
|
|
60
|
+
|
|
61
|
+
// --- Clipping paths (§8.5.4) ---
|
|
62
|
+
W: { op: 'W', category: 'clip', arity: 0, desc: 'modify clipping (non-zero)' },
|
|
63
|
+
'W*': { op: 'W*', category: 'clip', arity: 0, desc: 'modify clipping (even-odd)' },
|
|
64
|
+
|
|
65
|
+
// --- Color (§8.6) ---
|
|
66
|
+
CS: { op: 'CS', category: 'color', arity: 1, desc: 'set stroking color space' },
|
|
67
|
+
cs: { op: 'cs', category: 'color', arity: 1, desc: 'set non-stroking color space' },
|
|
68
|
+
SC: { op: 'SC', category: 'color', arity: 'var', desc: 'set stroking color (no name)' },
|
|
69
|
+
sc: { op: 'sc', category: 'color', arity: 'var', desc: 'set non-stroking color (no name)' },
|
|
70
|
+
SCN: { op: 'SCN', category: 'color', arity: 'var', desc: 'set stroking color (with name for pattern/separation)' },
|
|
71
|
+
scn: { op: 'scn', category: 'color', arity: 'var', desc: 'set non-stroking color (with name)' },
|
|
72
|
+
G: { op: 'G', category: 'color', arity: 1, desc: 'set stroking DeviceGray' },
|
|
73
|
+
g: { op: 'g', category: 'color', arity: 1, desc: 'set non-stroking DeviceGray' },
|
|
74
|
+
RG: { op: 'RG', category: 'color', arity: 3, desc: 'set stroking DeviceRGB' },
|
|
75
|
+
rg: { op: 'rg', category: 'color', arity: 3, desc: 'set non-stroking DeviceRGB' },
|
|
76
|
+
K: { op: 'K', category: 'color', arity: 4, desc: 'set stroking DeviceCMYK' },
|
|
77
|
+
k: { op: 'k', category: 'color', arity: 4, desc: 'set non-stroking DeviceCMYK' },
|
|
78
|
+
|
|
79
|
+
// --- Shading (§8.7) ---
|
|
80
|
+
sh: { op: 'sh', category: 'shading', arity: 1, desc: 'paint shading pattern' },
|
|
81
|
+
|
|
82
|
+
// --- Inline images (§8.9.7) ---
|
|
83
|
+
BI: { op: 'BI', category: 'image', arity: 0, desc: 'begin inline image' },
|
|
84
|
+
ID: { op: 'ID', category: 'image', arity: 0, desc: 'inline image data marker' },
|
|
85
|
+
EI: { op: 'EI', category: 'image', arity: 0, desc: 'end inline image' },
|
|
86
|
+
|
|
87
|
+
// --- XObject (§8.10) ---
|
|
88
|
+
Do: { op: 'Do', category: 'xobject', arity: 1, desc: 'invoke XObject' },
|
|
89
|
+
|
|
90
|
+
// --- Marked content (§14.6) ---
|
|
91
|
+
MP: { op: 'MP', category: 'marked', arity: 1, desc: 'marked content point' },
|
|
92
|
+
DP: { op: 'DP', category: 'marked', arity: 2, desc: 'marked content point with property list' },
|
|
93
|
+
BMC: { op: 'BMC', category: 'marked', arity: 1, desc: 'begin marked content' },
|
|
94
|
+
BDC: { op: 'BDC', category: 'marked', arity: 2, desc: 'begin marked content with property list' },
|
|
95
|
+
EMC: { op: 'EMC', category: 'marked', arity: 0, desc: 'end marked content' },
|
|
96
|
+
|
|
97
|
+
// --- Text objects + state (§9.3 / §9.4) ---
|
|
98
|
+
BT: { op: 'BT', category: 'text', arity: 0, desc: 'begin text object' },
|
|
99
|
+
ET: { op: 'ET', category: 'text', arity: 0, desc: 'end text object' },
|
|
100
|
+
Tc: { op: 'Tc', category: 'text', arity: 1, desc: 'set character spacing' },
|
|
101
|
+
Tw: { op: 'Tw', category: 'text', arity: 1, desc: 'set word spacing' },
|
|
102
|
+
Tz: { op: 'Tz', category: 'text', arity: 1, desc: 'set horizontal scaling (percent)' },
|
|
103
|
+
TL: { op: 'TL', category: 'text', arity: 1, desc: 'set leading' },
|
|
104
|
+
Tf: { op: 'Tf', category: 'text', arity: 2, desc: 'set text font + size' },
|
|
105
|
+
Tr: { op: 'Tr', category: 'text', arity: 1, desc: 'set text rendering mode' },
|
|
106
|
+
Ts: { op: 'Ts', category: 'text', arity: 1, desc: 'set text rise' },
|
|
107
|
+
|
|
108
|
+
// --- Text positioning ---
|
|
109
|
+
Td: { op: 'Td', category: 'text', arity: 2, desc: 'move text position' },
|
|
110
|
+
TD: { op: 'TD', category: 'text', arity: 2, desc: 'move + set leading' },
|
|
111
|
+
Tm: { op: 'Tm', category: 'text', arity: 6, desc: 'set text matrix' },
|
|
112
|
+
'T*': { op: 'T*', category: 'text', arity: 0, desc: 'move to next line' },
|
|
113
|
+
|
|
114
|
+
// --- Text showing ---
|
|
115
|
+
Tj: { op: 'Tj', category: 'text', arity: 1, desc: 'show text' },
|
|
116
|
+
"'": { op: "'", category: 'text', arity: 1, desc: 'next-line + show' },
|
|
117
|
+
'"': { op: '"', category: 'text', arity: 3, desc: 'aw ac string : set spacing + next line + show' },
|
|
118
|
+
TJ: { op: 'TJ', category: 'text', arity: 1, desc: 'show text with positioning array' },
|
|
119
|
+
|
|
120
|
+
// --- Type3 font glyph metrics ---
|
|
121
|
+
d0: { op: 'd0', category: 'gstate', arity: 2, desc: 'Type3: set glyph width (no bbox)' },
|
|
122
|
+
d1: { op: 'd1', category: 'gstate', arity: 6, desc: 'Type3: set glyph width + bbox' }
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const OP_NAMES = Object.freeze(Object.keys(OPS).sort());
|
|
126
|
+
|
|
127
|
+
function lookupOp(mnemonic) {
|
|
128
|
+
return OPS[mnemonic];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isOp(mnemonic) {
|
|
132
|
+
return Object.prototype.hasOwnProperty.call(OPS, mnemonic);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return { OPS, OP_NAMES, lookupOp, isOp };
|
|
136
|
+
}
|
|
137
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
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 Content stream parser per ISO 32000-2:2020 §7.8 + §8.2.
|
|
8
|
+
*
|
|
9
|
+
* Reads a `Uint8Array` of content-stream bytes (the result of decoding
|
|
10
|
+
* any `/Filter` chain on a page's `/Contents` stream) and produces an
|
|
11
|
+
* ordered list of operations.
|
|
12
|
+
*
|
|
13
|
+
* Inline images (`BI ... ID ... EI`) are captured as one synthetic
|
|
14
|
+
* operation `{ op: 'BI', args: [{type:'dict', entries}], data: Uint8Array }`.
|
|
15
|
+
*
|
|
16
|
+
* @module pdf/content/stream
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Module factory — worker-safe, self-contained.
|
|
21
|
+
*
|
|
22
|
+
* Note: `pdfParser` and `pdfContentOps` are declared as dependencies for
|
|
23
|
+
* forward-looking DI; the current shim materialises a tokenizer+parser
|
|
24
|
+
* directly. When called via the runtime with empty-stub deps, the shim
|
|
25
|
+
* fallbacks are used.
|
|
26
|
+
*/
|
|
27
|
+
import { pdfErrors } from '../errors.js';
|
|
28
|
+
import { pdfParser } from '../syntax/parser.js';
|
|
29
|
+
import { pdfContentOps } from './ops.js';
|
|
30
|
+
|
|
31
|
+
export const pdfContentStream = {
|
|
32
|
+
name: 'pdfContentStream',
|
|
33
|
+
dependencies: ['pdfErrors', 'pdfParser', 'pdfContentOps'],
|
|
34
|
+
deps: [pdfErrors, pdfParser, pdfContentOps],
|
|
35
|
+
factory(errors, parserMod, opsMod) {
|
|
36
|
+
const { ParseError } = errors;
|
|
37
|
+
|
|
38
|
+
const SP = 0x20, HT = 0x09, LF = 0x0A, CR = 0x0D;
|
|
39
|
+
const E = 0x45, I = 0x49;
|
|
40
|
+
|
|
41
|
+
// `parserMod` must expose `tokenize` + `parseObject` (the shim
|
|
42
|
+
// below materialises a compound object that satisfies this).
|
|
43
|
+
// `opsMod` must expose `isOp`.
|
|
44
|
+
const tokenize = parserMod && parserMod.tokenize;
|
|
45
|
+
const parseObject = parserMod && parserMod.parseObject;
|
|
46
|
+
const isOp = opsMod && opsMod.isOp;
|
|
47
|
+
|
|
48
|
+
function isWsByte(b) {
|
|
49
|
+
return b === SP || b === HT || b === LF || b === CR || b === 0x0C || b === 0x00;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function findInlineImageEnd(bytes, from) {
|
|
53
|
+
for (let i = from; i < bytes.length - 1; i++) {
|
|
54
|
+
if (bytes[i] === E && bytes[i + 1] === I) {
|
|
55
|
+
const prev = i > 0 ? bytes[i - 1] : SP;
|
|
56
|
+
const next = i + 2 < bytes.length ? bytes[i + 2] : SP;
|
|
57
|
+
if (isWsByte(prev) && (isWsByte(next) || i + 2 === bytes.length)) {
|
|
58
|
+
let end = i;
|
|
59
|
+
if (end > from && isWsByte(bytes[end - 1])) end--;
|
|
60
|
+
return { dataEnd: end, eiEnd: i + 2 };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function readInlineImage(tok, bytes) {
|
|
68
|
+
const entries = {};
|
|
69
|
+
for (;;) {
|
|
70
|
+
const t = tok.peek();
|
|
71
|
+
if (!t) {
|
|
72
|
+
throw new ParseError('pdf/content/inline-image/no-ID',
|
|
73
|
+
'inline image missing ID marker');
|
|
74
|
+
}
|
|
75
|
+
if (t.kind === 'kw' && t.value === 'ID') {
|
|
76
|
+
tok.next();
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
if (t.kind !== 'name') {
|
|
80
|
+
throw new ParseError('pdf/content/inline-image/bad-key',
|
|
81
|
+
'inline image dict keys must be names',
|
|
82
|
+
{ context: { offset: t.offset, kind: t.kind } });
|
|
83
|
+
}
|
|
84
|
+
const key = tok.next().value;
|
|
85
|
+
entries[key] = parseObject(tok);
|
|
86
|
+
}
|
|
87
|
+
let p = tok.pos();
|
|
88
|
+
if (p < bytes.length && (bytes[p] === SP || bytes[p] === HT
|
|
89
|
+
|| bytes[p] === LF || bytes[p] === CR)) {
|
|
90
|
+
p++;
|
|
91
|
+
}
|
|
92
|
+
const dataStart = p;
|
|
93
|
+
const located = findInlineImageEnd(bytes, dataStart);
|
|
94
|
+
if (!located) {
|
|
95
|
+
throw new ParseError('pdf/content/inline-image/no-EI',
|
|
96
|
+
'inline image missing EI marker',
|
|
97
|
+
{ context: { dataStart } });
|
|
98
|
+
}
|
|
99
|
+
tok.seek(located.eiEnd);
|
|
100
|
+
return {
|
|
101
|
+
op: 'BI',
|
|
102
|
+
args: [{ type: 'dict', entries }],
|
|
103
|
+
data: bytes.subarray(dataStart, located.dataEnd)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function parseContentStream(bytes) {
|
|
108
|
+
if (!(bytes instanceof Uint8Array)) {
|
|
109
|
+
throw new ParseError('pdf/content/bad-input',
|
|
110
|
+
'parseContentStream expects Uint8Array',
|
|
111
|
+
{ context: { typeof: typeof bytes } });
|
|
112
|
+
}
|
|
113
|
+
const tok = tokenize(bytes);
|
|
114
|
+
const out = [];
|
|
115
|
+
let argStack = [];
|
|
116
|
+
|
|
117
|
+
for (;;) {
|
|
118
|
+
const t = tok.peek();
|
|
119
|
+
if (!t) break;
|
|
120
|
+
if (t.kind === 'kw') {
|
|
121
|
+
if (t.value === 'true' || t.value === 'false' || t.value === 'null') {
|
|
122
|
+
argStack.push(parseObject(tok));
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (t.value === 'BI') {
|
|
126
|
+
tok.next();
|
|
127
|
+
const inline = readInlineImage(tok, bytes);
|
|
128
|
+
argStack = [];
|
|
129
|
+
out.push(inline);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (!isOp(t.value)) {
|
|
133
|
+
throw new ParseError('pdf/content/unknown-op',
|
|
134
|
+
`unknown content-stream operator "${t.value}"`,
|
|
135
|
+
{ context: { offset: t.offset, op: t.value } });
|
|
136
|
+
}
|
|
137
|
+
tok.next();
|
|
138
|
+
out.push({ op: t.value, args: argStack });
|
|
139
|
+
argStack = [];
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
argStack.push(parseObject(tok));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (argStack.length > 0) {
|
|
146
|
+
out.push({ op: '__trailing__', args: argStack });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { parseContentStream };
|
|
153
|
+
}
|
|
154
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
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 Text state + positioning per ISO 32000-2:2020 §9.3 / §9.4.
|
|
8
|
+
*
|
|
9
|
+
* Provides operators that mutate `gstate.text` and helpers for the
|
|
10
|
+
* text-object matrices Tm + Tlm (text matrix + text line matrix).
|
|
11
|
+
*
|
|
12
|
+
* Text-showing operators (`Tj`, `TJ`, `'`, `"`) **do not** decode the
|
|
13
|
+
* glyph stream into Unicode here — that requires the font's ToUnicode
|
|
14
|
+
* CMap, which lives in `@awacloud/fonts/cmap-to-unicode`. The high-level
|
|
15
|
+
* `extractText` helper takes a resolved `Font` + a ToUnicode mapping
|
|
16
|
+
* function and produces a `string`.
|
|
17
|
+
*
|
|
18
|
+
* @module pdf/content/text
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Module factory — worker-safe, self-contained.
|
|
23
|
+
*/
|
|
24
|
+
import { pdfErrors } from '../errors.js';
|
|
25
|
+
|
|
26
|
+
export const pdfText = {
|
|
27
|
+
name: 'pdfText',
|
|
28
|
+
dependencies: ['pdfErrors'],
|
|
29
|
+
deps: [pdfErrors],
|
|
30
|
+
factory(errors) {
|
|
31
|
+
const { ContractError } = errors;
|
|
32
|
+
|
|
33
|
+
function mul(a, b) {
|
|
34
|
+
return [
|
|
35
|
+
a[0] * b[0] + a[1] * b[2],
|
|
36
|
+
a[0] * b[1] + a[1] * b[3],
|
|
37
|
+
a[2] * b[0] + a[3] * b[2],
|
|
38
|
+
a[2] * b[1] + a[3] * b[3],
|
|
39
|
+
a[4] * b[0] + a[5] * b[2] + b[4],
|
|
40
|
+
a[4] * b[1] + a[5] * b[3] + b[5]
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function setFont(gstate, fontResource, size) {
|
|
45
|
+
gstate.text.font = fontResource;
|
|
46
|
+
gstate.text.fontSize = size;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function beginTextObject() {
|
|
50
|
+
return {
|
|
51
|
+
Tm: [1, 0, 0, 1, 0, 0],
|
|
52
|
+
Tlm: [1, 0, 0, 1, 0, 0]
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function td(state, tx, ty) {
|
|
57
|
+
state.Tlm = mul([1, 0, 0, 1, tx, ty], state.Tlm);
|
|
58
|
+
state.Tm = state.Tlm.slice();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function tdSetLeading(gstate, state, tx, ty) {
|
|
62
|
+
gstate.text.leading = -ty;
|
|
63
|
+
td(state, tx, ty);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function setTextMatrix(state, m) {
|
|
67
|
+
if (!Array.isArray(m) || m.length !== 6) {
|
|
68
|
+
throw new ContractError('pdf/text/bad-matrix',
|
|
69
|
+
'Tm matrix must have 6 elements');
|
|
70
|
+
}
|
|
71
|
+
state.Tm = m.slice();
|
|
72
|
+
state.Tlm = m.slice();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function nextLine(gstate, state) {
|
|
76
|
+
td(state, 0, -gstate.text.leading);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function rawStringBytes(stringObj) {
|
|
80
|
+
if (!stringObj || stringObj.type !== 'string') {
|
|
81
|
+
throw new ContractError('pdf/text/bad-string',
|
|
82
|
+
'expected typed string operand',
|
|
83
|
+
{ context: { type: stringObj && stringObj.type } });
|
|
84
|
+
}
|
|
85
|
+
return stringObj.value;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function extractText(op, font, cidToUnicode) {
|
|
89
|
+
const cidBytes = (font && font.subtype === 'Type0') ? 2 : 1;
|
|
90
|
+
let text = '';
|
|
91
|
+
const collect = (strObj) => {
|
|
92
|
+
const bytes = rawStringBytes(strObj);
|
|
93
|
+
for (let i = 0; i + cidBytes <= bytes.length; i += cidBytes) {
|
|
94
|
+
let cid = 0;
|
|
95
|
+
for (let k = 0; k < cidBytes; k++) cid = (cid << 8) | bytes[i + k];
|
|
96
|
+
const u = cidToUnicode(cid);
|
|
97
|
+
text += (typeof u === 'string') ? u : '�';
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
if (op.op === 'Tj' || op.op === "'") {
|
|
101
|
+
collect(op.args[0]);
|
|
102
|
+
} else if (op.op === '"') {
|
|
103
|
+
collect(op.args[2]);
|
|
104
|
+
} else if (op.op === 'TJ') {
|
|
105
|
+
const arr = op.args[0];
|
|
106
|
+
if (!arr || arr.type !== 'array') return '';
|
|
107
|
+
for (const it of arr.items) {
|
|
108
|
+
if (it.type === 'string') collect(it);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return text;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
setFont,
|
|
116
|
+
beginTextObject,
|
|
117
|
+
td,
|
|
118
|
+
tdSetLeading,
|
|
119
|
+
setTextMatrix,
|
|
120
|
+
nextLine,
|
|
121
|
+
rawStringBytes,
|
|
122
|
+
extractText
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
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 ISO/TS 32003 AES-GCM wrapper for PDF object encryption.
|
|
8
|
+
*
|
|
9
|
+
* TS 32003 amends ISO 32000-2 to permit AES-GCM (CFM = `AESV4`) as an
|
|
10
|
+
* alternative cipher to AES-CBC in the Standard Security Handler v6.
|
|
11
|
+
* Wire format:
|
|
12
|
+
* IV (12 bytes) ‖ ciphertext (n bytes) ‖ tag (16 bytes)
|
|
13
|
+
*
|
|
14
|
+
* @module pdf/crypto/aesGcm
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Module factory — worker-safe, self-contained.
|
|
19
|
+
*/
|
|
20
|
+
import { pdfErrors } from '../errors.js';
|
|
21
|
+
import { aes } from '@awacloud/fw/crypto/cipher/aes.js';
|
|
22
|
+
import { gcm } from '@awacloud/fw/crypto/mode/gcm.js';
|
|
23
|
+
import { bitArray } from '@awacloud/fw/crypto/utils/bitArray.js';
|
|
24
|
+
|
|
25
|
+
export const pdfAesGcm = {
|
|
26
|
+
name: 'pdfAesGcm',
|
|
27
|
+
dependencies: ['pdfErrors', 'aes', 'gcm', 'bitArray'],
|
|
28
|
+
deps: [pdfErrors, aes, gcm, bitArray],
|
|
29
|
+
factory(errors, aes, gcm, bitArray) {
|
|
30
|
+
const { EncryptionError } = errors;
|
|
31
|
+
|
|
32
|
+
function bytesToWords(b, off, len) {
|
|
33
|
+
const n = len >>> 2;
|
|
34
|
+
const out = new Array(n);
|
|
35
|
+
for (let i = 0; i < n; i++) {
|
|
36
|
+
const j = off + (i << 2);
|
|
37
|
+
out[i] = ((b[j] << 24) | (b[j + 1] << 16) | (b[j + 2] << 8) | b[j + 3]) | 0;
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!aes || !gcm || !bitArray) {
|
|
43
|
+
throw new EncryptionError('pdf/crypto/gcm/missing-fw',
|
|
44
|
+
'pdfAesGcm requires aes, gcm, bitArray fw modules');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function buildCipher(fek) {
|
|
48
|
+
if (!(fek instanceof Uint8Array) || fek.length !== 32) {
|
|
49
|
+
throw new EncryptionError('pdf/crypto/gcm/bad-fek',
|
|
50
|
+
'File Encryption Key must be 32 bytes (AES-256)',
|
|
51
|
+
{ context: { length: fek && fek.length } });
|
|
52
|
+
}
|
|
53
|
+
const c = aes.fn(bytesToWords(fek, 0, 32), false);
|
|
54
|
+
if (c === false) {
|
|
55
|
+
throw new EncryptionError('pdf/crypto/gcm/aes-schedule-failed',
|
|
56
|
+
'AES-256 key schedule failed');
|
|
57
|
+
}
|
|
58
|
+
return c;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function decryptObjectGcm(fek, framed) {
|
|
62
|
+
if (!(framed instanceof Uint8Array) || framed.length < 12 + 16) {
|
|
63
|
+
throw new EncryptionError('pdf/crypto/gcm/bad-input',
|
|
64
|
+
'AES-GCM framed input must be at least 28 bytes (IV + tag)');
|
|
65
|
+
}
|
|
66
|
+
const cipher = buildCipher(fek);
|
|
67
|
+
const iv = framed.subarray(0, 12);
|
|
68
|
+
const tag = framed.subarray(framed.length - 16);
|
|
69
|
+
const ct = framed.subarray(12, framed.length - 16);
|
|
70
|
+
const pt = gcm.decrypt(
|
|
71
|
+
cipher,
|
|
72
|
+
bitArray.ui8_to_ba(ct),
|
|
73
|
+
bitArray.ui8_to_ba(iv),
|
|
74
|
+
[],
|
|
75
|
+
bitArray.ui8_to_ba(tag),
|
|
76
|
+
128
|
|
77
|
+
);
|
|
78
|
+
if (pt === false) {
|
|
79
|
+
throw new EncryptionError('pdf/crypto/gcm/tag-mismatch',
|
|
80
|
+
'AES-GCM authentication tag mismatch');
|
|
81
|
+
}
|
|
82
|
+
return bitArray.ba_to_ui8(pt);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function encryptObjectGcm(fek, plaintext, ivProvider) {
|
|
86
|
+
if (!(plaintext instanceof Uint8Array)) {
|
|
87
|
+
throw new EncryptionError('pdf/crypto/gcm/bad-plaintext',
|
|
88
|
+
'plaintext must be Uint8Array');
|
|
89
|
+
}
|
|
90
|
+
if (typeof ivProvider !== 'function') {
|
|
91
|
+
throw new EncryptionError('pdf/crypto/gcm/bad-iv-provider',
|
|
92
|
+
'ivProvider must be a function returning 12 bytes');
|
|
93
|
+
}
|
|
94
|
+
const iv = ivProvider();
|
|
95
|
+
if (!(iv instanceof Uint8Array) || iv.length !== 12) {
|
|
96
|
+
throw new EncryptionError('pdf/crypto/gcm/bad-iv',
|
|
97
|
+
'ivProvider must return exactly 12 bytes (96-bit nonce)',
|
|
98
|
+
{ context: { length: iv && iv.length } });
|
|
99
|
+
}
|
|
100
|
+
const cipher = buildCipher(fek);
|
|
101
|
+
const res = gcm.encrypt(
|
|
102
|
+
cipher,
|
|
103
|
+
bitArray.ui8_to_ba(plaintext),
|
|
104
|
+
bitArray.ui8_to_ba(iv),
|
|
105
|
+
[],
|
|
106
|
+
128
|
|
107
|
+
);
|
|
108
|
+
if (res === false) {
|
|
109
|
+
throw new EncryptionError('pdf/crypto/gcm/encrypt-failed',
|
|
110
|
+
'AES-GCM encryption failed');
|
|
111
|
+
}
|
|
112
|
+
const ct = bitArray.ba_to_ui8(res.ct);
|
|
113
|
+
const tag = bitArray.ba_to_ui8(res.tag);
|
|
114
|
+
const out = new Uint8Array(12 + ct.length + 16);
|
|
115
|
+
out.set(iv, 0);
|
|
116
|
+
out.set(ct, 12);
|
|
117
|
+
out.set(tag, 12 + ct.length);
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return { encryptObjectGcm, decryptObjectGcm };
|
|
122
|
+
}
|
|
123
|
+
};
|
|
@@ -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 PDF permission flag decoding + /Perms field validation.
|
|
8
|
+
*
|
|
9
|
+
* Per ISO 32000-2:2020 Table 24, the /P value in the /Encrypt dictionary is
|
|
10
|
+
* a signed 32-bit integer whose individual bits convey user access
|
|
11
|
+
* permissions. For Security Handler v5/v6 the /Perms field (16 bytes,
|
|
12
|
+
* AES-256-ECB single block, no IV) is decrypted with the File Encryption
|
|
13
|
+
* Key and validated against /P (Algorithm 13, ISO 32000-2 §7.6.4.5).
|
|
14
|
+
*
|
|
15
|
+
* @module pdf/crypto/permissions
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Module factory — worker-safe, self-contained.
|
|
20
|
+
*/
|
|
21
|
+
import { pdfErrors } from '../errors.js';
|
|
22
|
+
import { aes } from '@awacloud/fw/crypto/cipher/aes.js';
|
|
23
|
+
|
|
24
|
+
export const pdfPermissions = {
|
|
25
|
+
name: 'pdfPermissions',
|
|
26
|
+
dependencies: ['pdfErrors', 'aes'],
|
|
27
|
+
deps: [pdfErrors, aes],
|
|
28
|
+
factory(errors, aesFw) {
|
|
29
|
+
const { EncryptionError } = errors;
|
|
30
|
+
|
|
31
|
+
function bytesToWords(b, off, len) {
|
|
32
|
+
const n = len >>> 2;
|
|
33
|
+
const out = new Array(n);
|
|
34
|
+
for (let i = 0; i < n; i++) {
|
|
35
|
+
const j = off + (i << 2);
|
|
36
|
+
out[i] = ((b[j] << 24) | (b[j + 1] << 16) | (b[j + 2] << 8) | b[j + 3]) | 0;
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function wordsToBytes(w, into, off) {
|
|
42
|
+
for (let i = 0; i < w.length; i++) {
|
|
43
|
+
const j = off + (i << 2);
|
|
44
|
+
into[j] = (w[i] >>> 24) & 0xff;
|
|
45
|
+
into[j + 1] = (w[i] >>> 16) & 0xff;
|
|
46
|
+
into[j + 2] = (w[i] >>> 8) & 0xff;
|
|
47
|
+
into[j + 3] = w[i] & 0xff;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function decodePermissions(p) {
|
|
52
|
+
if (typeof p !== 'number' || !Number.isInteger(p)) {
|
|
53
|
+
throw new EncryptionError('pdf/crypto/permissions/bad-input',
|
|
54
|
+
'/P must be a 32-bit integer',
|
|
55
|
+
{ context: { p } });
|
|
56
|
+
}
|
|
57
|
+
const v = p | 0;
|
|
58
|
+
return {
|
|
59
|
+
raw: v,
|
|
60
|
+
print: (v & 0x004) !== 0,
|
|
61
|
+
modify: (v & 0x008) !== 0,
|
|
62
|
+
copy: (v & 0x010) !== 0,
|
|
63
|
+
annot: (v & 0x020) !== 0,
|
|
64
|
+
formFill: (v & 0x100) !== 0,
|
|
65
|
+
accessible: (v & 0x200) !== 0,
|
|
66
|
+
assemble: (v & 0x400) !== 0,
|
|
67
|
+
printHighQuality: (v & 0x800) !== 0
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!aesFw || typeof aesFw.fn !== 'function') {
|
|
72
|
+
throw new EncryptionError('pdf/crypto/permissions/missing-fw',
|
|
73
|
+
'pdfPermissions requires the fw aes factory');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function verifyPermsField(typedEncrypt, fek) {
|
|
77
|
+
if (!typedEncrypt || !typedEncrypt.Perms) {
|
|
78
|
+
throw new EncryptionError('pdf/crypto/permissions/missing-perms',
|
|
79
|
+
'/Perms entry absent in /Encrypt dictionary');
|
|
80
|
+
}
|
|
81
|
+
const perms = typedEncrypt.Perms;
|
|
82
|
+
if (!(perms instanceof Uint8Array) || perms.length !== 16) {
|
|
83
|
+
throw new EncryptionError('pdf/crypto/permissions/bad-perms-length',
|
|
84
|
+
'/Perms must be a 16-byte string',
|
|
85
|
+
{ context: { length: perms && perms.length } });
|
|
86
|
+
}
|
|
87
|
+
if (!(fek instanceof Uint8Array) || fek.length !== 32) {
|
|
88
|
+
throw new EncryptionError('pdf/crypto/permissions/bad-fek',
|
|
89
|
+
'File Encryption Key must be 32 bytes for AES-256',
|
|
90
|
+
{ context: { length: fek && fek.length } });
|
|
91
|
+
}
|
|
92
|
+
const cipher = aesFw.fn(bytesToWords(fek, 0, 32), true);
|
|
93
|
+
if (cipher === false || typeof cipher.decrypt !== 'function') {
|
|
94
|
+
throw new EncryptionError('pdf/crypto/permissions/aes-schedule-failed',
|
|
95
|
+
'AES-256 key schedule failed for /Perms verification');
|
|
96
|
+
}
|
|
97
|
+
const blk = cipher.decrypt(bytesToWords(perms, 0, 16));
|
|
98
|
+
if (blk === false) {
|
|
99
|
+
throw new EncryptionError('pdf/crypto/permissions/aes-decrypt-failed',
|
|
100
|
+
'AES-256 block decryption failed for /Perms');
|
|
101
|
+
}
|
|
102
|
+
const out = new Uint8Array(16);
|
|
103
|
+
wordsToBytes(blk, out, 0);
|
|
104
|
+
const ok = out[9] === 0x61 && out[10] === 0x64 && out[11] === 0x62;
|
|
105
|
+
const p = ((out[0]) | (out[1] << 8) | (out[2] << 16) | (out[3] << 24)) | 0;
|
|
106
|
+
const encryptMetadata = out[8] === 0x54;
|
|
107
|
+
return { ok, p, encryptMetadata };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { decodePermissions, verifyPermsField };
|
|
111
|
+
}
|
|
112
|
+
};
|