@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,480 @@
|
|
|
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 Adapter between PDF Font objects and `@awacloud/fonts/embed-pdf`.
|
|
8
|
+
*
|
|
9
|
+
* This is the only file in `@awacloud/pdf` that knows about
|
|
10
|
+
* `@awacloud/fonts/embed-pdf`. It takes a parsed font (the output of
|
|
11
|
+
* `fonts.read(bytes)`) and a set of code points to embed, and produces the
|
|
12
|
+
* PDF dicts needed for a simple (`/TrueType` + `/WinAnsiEncoding`) or a
|
|
13
|
+
* composite (`/Type0` + `/Identity-H` + `/CIDFontType2`) embedding.
|
|
14
|
+
*
|
|
15
|
+
* **Re-derived against the REAL `@awacloud/fonts` contract.**
|
|
16
|
+
* The `@awacloud/fonts/embed-pdf` `subsetForPdf(font, codePoints, opts?)` result
|
|
17
|
+
* carries exactly these eight keys:
|
|
18
|
+
*
|
|
19
|
+
* ```js
|
|
20
|
+
* {
|
|
21
|
+
* subsetBytes: Uint8Array, // packed TrueType SFNT
|
|
22
|
+
* gidMap: Map<oldGid, newGid>,
|
|
23
|
+
* glyphMap: Map<codePoint, newGid>,
|
|
24
|
+
* encoding: null, // Identity-H → cmap-driven
|
|
25
|
+
* widths: number[], // per NEW gid, in FONT UNITS
|
|
26
|
+
* toUnicodeCmap: string, // ready-made CMap stream text
|
|
27
|
+
* postScriptName: string, // 'ABCDEF+Family'
|
|
28
|
+
* fontDescriptor: object // POJO, carries FontFile2 bytes
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* It does NOT return `font`, `subtype`, `baseFont`, `firstChar`, `lastChar`
|
|
33
|
+
* nor `fontFile` — the keys the previous revision of this adapter read,
|
|
34
|
+
* which made every call on a real `Font` throw `fonts/fd-no-font`.
|
|
35
|
+
*
|
|
36
|
+
* Two contracts this adapter owns:
|
|
37
|
+
*
|
|
38
|
+
* 1. **Widths are rescaled here.** `subset.widths[newGid]` is in font units
|
|
39
|
+
* (`hmtx.advanceWidth`); PDF wants 1000/em. Every width this module
|
|
40
|
+
* emits is `round(w * 1000 / parsedFont.unitsPerEm)`.
|
|
41
|
+
* 2. **The descriptor leaves without its font program.** `FontFile2` /
|
|
42
|
+
* `FontFile3` is LIFTED out of the descriptor POJO into the returned
|
|
43
|
+
* `fontFile` (+ `fontFileKey`); the returned `descriptor` dict carries no
|
|
44
|
+
* font-program key. The consumer allocates the stream indirect
|
|
45
|
+
* (`/Length1` = `fontFile.length`) and injects the ref. Same for
|
|
46
|
+
* `toUnicodeStream`, which the consumer must also allocate as an indirect
|
|
47
|
+
* and reference (the serializer refuses an inline stream).
|
|
48
|
+
*
|
|
49
|
+
* 3. **`/ToUnicode` is keyed by character code, so its source is
|
|
50
|
+
* route-dependent.** `embedCid` uses `subset.toUnicodeCmap` as-is (under
|
|
51
|
+
* `Identity-H` the code IS the CID = the subset's new gid, exactly how
|
|
52
|
+
* the subsetter keys it); `embedSimple` builds its own byte-keyed map
|
|
53
|
+
* through `embedBuildToUnicode` (with `{ codeBytes: 1 }`: a one-byte
|
|
54
|
+
* `<00> <FF>` codespace), because a WinAnsi font's codes are bytes, not
|
|
55
|
+
* gids. Never both for one route.
|
|
56
|
+
*
|
|
57
|
+
* @module pdf/font/embed
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Module factory — worker-safe, self-contained.
|
|
62
|
+
*/
|
|
63
|
+
import { pdfErrors } from '../errors.js';
|
|
64
|
+
import { embedSubsetForPdf } from '@awacloud/fonts/embed-pdf/subsetForPdf.js';
|
|
65
|
+
import { embedFontDescriptor } from '@awacloud/fonts/embed-pdf/fontDescriptor.js';
|
|
66
|
+
import { embedCidSystemInfo } from '@awacloud/fonts/embed-pdf/cidSystemInfo.js';
|
|
67
|
+
import { embedToUnicodeBuilder } from '@awacloud/fonts/embed-pdf/toUnicodeBuilder.js';
|
|
68
|
+
|
|
69
|
+
export const pdfFontEmbed = {
|
|
70
|
+
name: 'pdfFontEmbed',
|
|
71
|
+
dependencies: [
|
|
72
|
+
'pdfErrors',
|
|
73
|
+
'embedSubsetForPdf', 'embedFontDescriptor',
|
|
74
|
+
'embedCidSystemInfo', 'embedToUnicodeBuilder'
|
|
75
|
+
],
|
|
76
|
+
deps: [pdfErrors, embedSubsetForPdf, embedFontDescriptor, embedCidSystemInfo, embedToUnicodeBuilder],
|
|
77
|
+
factory(errors, subsetMod, fontDescriptorMod, cidSystemInfoMod, toUnicodeBuilderMod) {
|
|
78
|
+
const { ContractError } = errors;
|
|
79
|
+
const fontsEmbedPdf = {
|
|
80
|
+
subsetForPdf: subsetMod.subsetForPdf,
|
|
81
|
+
buildFontDescriptor: fontDescriptorMod.buildFontDescriptor,
|
|
82
|
+
buildCidSystemInfo: cidSystemInfoMod.buildCidSystemInfo,
|
|
83
|
+
embedBuildToUnicode: toUnicodeBuilderMod.embedBuildToUnicode
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Minimal `obj.*` helpers — duplicated rather than depending on
|
|
87
|
+
// pdfParserObj to keep this adapter slim (it's only used from
|
|
88
|
+
// writers that already wired pdfParserObj independently).
|
|
89
|
+
const obj = {
|
|
90
|
+
name: (v) => ({ type: 'name', value: String(v) }),
|
|
91
|
+
int: (v) => ({ type: 'int', value: v | 0 }),
|
|
92
|
+
real: (v) => ({ type: 'real', value: +v }),
|
|
93
|
+
bool: (v) => ({ type: 'bool', value: !!v }),
|
|
94
|
+
string: (v) => ({ type: 'string', value: v }),
|
|
95
|
+
array: (items) => ({ type: 'array', items }),
|
|
96
|
+
dict: (entries) => ({ type: 'dict', entries }),
|
|
97
|
+
stream: (dict, raw) => ({ type: 'stream', dict, raw }),
|
|
98
|
+
nul: () => ({ type: 'null' })
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// --- WinAnsiEncoding (CP1252), ISO 32000-2 Annex D.2 -------------
|
|
102
|
+
// Byte → code point for the 0x80..0x9F block; 0x20..0x7E and
|
|
103
|
+
// 0xA0..0xFF are Latin-1 identity. Built inside the factory so the
|
|
104
|
+
// descriptor stays capture-free (`fw/no-factory-capture`).
|
|
105
|
+
const WIN_ANSI_HIGH = [
|
|
106
|
+
0x20AC, null, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021,
|
|
107
|
+
0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, null, 0x017D, null,
|
|
108
|
+
null, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
|
|
109
|
+
0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, null, 0x017E, 0x0178
|
|
110
|
+
];
|
|
111
|
+
const WIN_ANSI_BYTE = new Map(); // codePoint → byte
|
|
112
|
+
for (let b = 0x20; b <= 0x7E; b++) WIN_ANSI_BYTE.set(b, b);
|
|
113
|
+
for (let i = 0; i < WIN_ANSI_HIGH.length; i++) {
|
|
114
|
+
if (WIN_ANSI_HIGH[i] != null) WIN_ANSI_BYTE.set(WIN_ANSI_HIGH[i], 0x80 + i);
|
|
115
|
+
}
|
|
116
|
+
for (let b = 0xA0; b <= 0xFF; b++) WIN_ANSI_BYTE.set(b, b);
|
|
117
|
+
|
|
118
|
+
function coerce(v) {
|
|
119
|
+
if (v && typeof v === 'object' && typeof v.type === 'string') return v;
|
|
120
|
+
if (typeof v === 'number') {
|
|
121
|
+
return Number.isInteger(v) ? obj.int(v) : obj.real(v);
|
|
122
|
+
}
|
|
123
|
+
if (typeof v === 'boolean') return obj.bool(v);
|
|
124
|
+
if (typeof v === 'string') {
|
|
125
|
+
if (/^[A-Za-z][\w.-]{0,63}$/.test(v)) return obj.name(v);
|
|
126
|
+
return obj.string(new TextEncoder().encode(v));
|
|
127
|
+
}
|
|
128
|
+
if (Array.isArray(v)) return obj.array(v.map(coerce));
|
|
129
|
+
return obj.nul();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Split a `buildFontDescriptor` POJO into the dict entries and the
|
|
134
|
+
* raw font program. `FontName` is forced to a PDF name: a subset
|
|
135
|
+
* name (`ABCDEF+Family`) is rejected by `coerce`'s name regex
|
|
136
|
+
* because of the `+`, and would otherwise serialize as a string.
|
|
137
|
+
*/
|
|
138
|
+
function splitDescriptor(desc) {
|
|
139
|
+
const src = (desc && typeof desc === 'object') ? desc : {};
|
|
140
|
+
const entries = {};
|
|
141
|
+
let fontFile = null;
|
|
142
|
+
let fontFileKey = 'FontFile2';
|
|
143
|
+
for (const k of Object.keys(src)) {
|
|
144
|
+
if (k === 'FontFile' || k === 'FontFile2' || k === 'FontFile3') {
|
|
145
|
+
fontFileKey = k;
|
|
146
|
+
fontFile = src[k];
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
entries[k] = (k === 'FontName') ? obj.name(src[k]) : coerce(src[k]);
|
|
150
|
+
}
|
|
151
|
+
if (!entries.Type) entries.Type = obj.name('FontDescriptor');
|
|
152
|
+
return { descriptor: obj.dict(entries), fontFile, fontFileKey };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function wrapCidSysInfo(info) {
|
|
156
|
+
if (!info || typeof info !== 'object') return obj.dict({});
|
|
157
|
+
return obj.dict({
|
|
158
|
+
Registry: coerce(info.Registry || 'Adobe'),
|
|
159
|
+
Ordering: coerce(info.Ordering || 'Identity'),
|
|
160
|
+
Supplement: coerce(info.Supplement != null ? info.Supplement : 0)
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function assertFont(parsedFont) {
|
|
165
|
+
if (!parsedFont
|
|
166
|
+
|| typeof parsedFont !== 'object'
|
|
167
|
+
|| !parsedFont.unicodeMap
|
|
168
|
+
|| typeof parsedFont.glyphIndexForCodePoint !== 'function'
|
|
169
|
+
|| typeof parsedFont.advanceWidth !== 'function'
|
|
170
|
+
|| !Number.isFinite(parsedFont.unitsPerEm)) {
|
|
171
|
+
throw new ContractError('pdf/embed/bad-font',
|
|
172
|
+
'embed requires a parsed Font (unicodeMap, glyphIndexForCodePoint, advanceWidth, unitsPerEm)',
|
|
173
|
+
{ context: { keys: parsedFont && typeof parsedFont === 'object'
|
|
174
|
+
? Object.keys(parsedFont) : typeof parsedFont } });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** De-duplicate + sort, rejecting anything that is not a code point. */
|
|
179
|
+
function normaliseCodePoints(codePoints) {
|
|
180
|
+
if (codePoints == null || typeof codePoints[Symbol.iterator] !== 'function') {
|
|
181
|
+
throw new ContractError('pdf/embed/bad-codepoints',
|
|
182
|
+
'codePoints must be an iterable of integer code points',
|
|
183
|
+
{ context: { got: typeof codePoints } });
|
|
184
|
+
}
|
|
185
|
+
const seen = new Set();
|
|
186
|
+
for (const cp of codePoints) {
|
|
187
|
+
if (!Number.isInteger(cp) || cp < 0 || cp > 0x10FFFF) {
|
|
188
|
+
throw new ContractError('pdf/embed/bad-codepoints',
|
|
189
|
+
'codePoints must contain integer code points in [0, 0x10FFFF]',
|
|
190
|
+
{ context: { cp } });
|
|
191
|
+
}
|
|
192
|
+
seen.add(cp);
|
|
193
|
+
}
|
|
194
|
+
if (seen.size === 0) {
|
|
195
|
+
throw new ContractError('pdf/embed/bad-codepoints',
|
|
196
|
+
'codePoints must not be empty', { context: { size: 0 } });
|
|
197
|
+
}
|
|
198
|
+
return [...seen].sort((a, b) => a - b);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** `subset.widths` is per NEW gid in font units — rescale to 1000/em. */
|
|
202
|
+
function scaledWidthByCp(parsedFont, subset, cps) {
|
|
203
|
+
const upem = parsedFont.unitsPerEm || 1000;
|
|
204
|
+
const glyphMap = (subset.glyphMap instanceof Map) ? subset.glyphMap : new Map();
|
|
205
|
+
const widths = Array.isArray(subset.widths) ? subset.widths : null;
|
|
206
|
+
const out = new Map();
|
|
207
|
+
for (const cp of cps) {
|
|
208
|
+
const ng = glyphMap.get(cp);
|
|
209
|
+
let raw;
|
|
210
|
+
if (widths && ng != null && Number.isFinite(widths[ng])) {
|
|
211
|
+
raw = widths[ng];
|
|
212
|
+
} else {
|
|
213
|
+
raw = parsedFont.advanceWidth(parsedFont.glyphIndexForCodePoint(cp));
|
|
214
|
+
}
|
|
215
|
+
out.set(cp, Math.round((raw * 1000) / upem));
|
|
216
|
+
}
|
|
217
|
+
return out;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Per NEW gid, in 1000/em — the source of the `/W` array. */
|
|
221
|
+
function scaledWidthByGid(parsedFont, subset) {
|
|
222
|
+
const upem = parsedFont.unitsPerEm || 1000;
|
|
223
|
+
const out = new Map();
|
|
224
|
+
if (Array.isArray(subset.widths)) {
|
|
225
|
+
for (let g = 0; g < subset.widths.length; g++) {
|
|
226
|
+
if (Number.isFinite(subset.widths[g])) {
|
|
227
|
+
out.set(g, Math.round((subset.widths[g] * 1000) / upem));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
232
|
+
const glyphMap = (subset.glyphMap instanceof Map) ? subset.glyphMap : new Map();
|
|
233
|
+
for (const [cp, ng] of glyphMap) {
|
|
234
|
+
const raw = parsedFont.advanceWidth(parsedFont.glyphIndexForCodePoint(cp));
|
|
235
|
+
out.set(ng, Math.round((raw * 1000) / upem));
|
|
236
|
+
}
|
|
237
|
+
return out;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** `[ c [w …] c [w …] … ]` — consecutive CIDs grouped into one run. */
|
|
241
|
+
function compactWArray(byGid) {
|
|
242
|
+
const gids = [...byGid.keys()].sort((a, b) => a - b);
|
|
243
|
+
const items = [];
|
|
244
|
+
let i = 0;
|
|
245
|
+
while (i < gids.length) {
|
|
246
|
+
const start = gids[i];
|
|
247
|
+
const run = [byGid.get(gids[i])];
|
|
248
|
+
let j = i + 1;
|
|
249
|
+
while (j < gids.length && gids[j] === gids[j - 1] + 1) {
|
|
250
|
+
run.push(byGid.get(gids[j]));
|
|
251
|
+
j++;
|
|
252
|
+
}
|
|
253
|
+
items.push(obj.int(start));
|
|
254
|
+
items.push(obj.array(run.map(w => obj.int(w))));
|
|
255
|
+
i = j;
|
|
256
|
+
}
|
|
257
|
+
return obj.array(items);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function streamOfCmap(cmap) {
|
|
261
|
+
const raw = (cmap instanceof Uint8Array)
|
|
262
|
+
? cmap
|
|
263
|
+
: new TextEncoder().encode(String(cmap));
|
|
264
|
+
return obj.stream(obj.dict({}), raw);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* A `/ToUnicode` CMap is keyed by the font's CHARACTER CODES, so the
|
|
269
|
+
* two routes need different sources:
|
|
270
|
+
*
|
|
271
|
+
* - composite (`Identity-H`): the code IS the CID, i.e. the subset's
|
|
272
|
+
* new gid, which is exactly what `subset.toUnicodeCmap` is keyed by
|
|
273
|
+
* (`subsetForPdf` inverts `glyphMap` into `newGid → String.fromCodePoint(cp)`).
|
|
274
|
+
* Used as-is; the local builder is only the fallback for a subsetter that omits it.
|
|
275
|
+
* - simple (`WinAnsiEncoding`): the codes are WinAnsi BYTES, so the
|
|
276
|
+
* subset's gid-keyed CMap would be plain wrong. The adapter builds
|
|
277
|
+
* the byte-keyed map and hands it to `embedBuildToUnicode`.
|
|
278
|
+
*
|
|
279
|
+
* Never both for one route — two CMaps over the same glyph set can
|
|
280
|
+
* only diverge.
|
|
281
|
+
*/
|
|
282
|
+
function toUnicodeStreamForCid(deps, subset) {
|
|
283
|
+
if (subset.toUnicodeCmap != null) return streamOfCmap(subset.toUnicodeCmap);
|
|
284
|
+
const gidToUnicode = new Map();
|
|
285
|
+
const glyphMap = (subset.glyphMap instanceof Map) ? subset.glyphMap : new Map();
|
|
286
|
+
for (const [cp, ng] of glyphMap) gidToUnicode.set(ng, String.fromCodePoint(cp));
|
|
287
|
+
return streamOfCmap(deps.embedBuildToUnicode(gidToUnicode, {}));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function baseFontName(subset) {
|
|
291
|
+
const n = subset && subset.postScriptName;
|
|
292
|
+
return (typeof n === 'string' && n.length) ? n : 'Embedded';
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function createEmbed(deps) {
|
|
296
|
+
if (!deps
|
|
297
|
+
|| typeof deps.subsetForPdf !== 'function'
|
|
298
|
+
|| typeof deps.buildFontDescriptor !== 'function'
|
|
299
|
+
|| typeof deps.buildCidSystemInfo !== 'function'
|
|
300
|
+
|| typeof deps.embedBuildToUnicode !== 'function') {
|
|
301
|
+
throw new ContractError('pdf/embed/missing-fonts-embed',
|
|
302
|
+
'createEmbed requires the @awacloud/fonts/embed-pdf factory output',
|
|
303
|
+
{ context: { keys: deps && Object.keys(deps) } });
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* The subset's descriptor already carries the font program; only
|
|
308
|
+
* rebuild it when the subsetter omitted it entirely.
|
|
309
|
+
*/
|
|
310
|
+
function descriptorOf(parsedFont, subset) {
|
|
311
|
+
const raw = (subset.fontDescriptor && typeof subset.fontDescriptor === 'object')
|
|
312
|
+
? subset.fontDescriptor
|
|
313
|
+
: deps.buildFontDescriptor(parsedFont, { subsetBytes: subset.subsetBytes });
|
|
314
|
+
const split = splitDescriptor(raw);
|
|
315
|
+
if (!split.fontFile && subset.subsetBytes instanceof Uint8Array) {
|
|
316
|
+
split.fontFile = subset.subsetBytes;
|
|
317
|
+
}
|
|
318
|
+
return split;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Simple (non-composite) TrueType embedding with
|
|
323
|
+
* `/WinAnsiEncoding`. Every code point must be representable in
|
|
324
|
+
* CP1252 — a Unicode caller picks {@link embedCid} instead.
|
|
325
|
+
*
|
|
326
|
+
* The `/ToUnicode` CMap is built with a one-byte codespace (`<00> <FF>`),
|
|
327
|
+
* matching the single-byte WinAnsi codes.
|
|
328
|
+
*
|
|
329
|
+
* @param {object} parsedFont Output of `fonts.read(bytes)`.
|
|
330
|
+
* @param {Iterable<number>} codePoints
|
|
331
|
+
* @param {object} [opts] Forwarded to `subsetForPdf`.
|
|
332
|
+
*/
|
|
333
|
+
function embedSimple(parsedFont, codePoints, opts) {
|
|
334
|
+
assertFont(parsedFont);
|
|
335
|
+
const cps = normaliseCodePoints(codePoints);
|
|
336
|
+
for (const cp of cps) {
|
|
337
|
+
if (!WIN_ANSI_BYTE.has(cp)) {
|
|
338
|
+
throw new ContractError('pdf/embed/not-winansi',
|
|
339
|
+
'embedSimple requires WinAnsi (CP1252)-representable code points; use embedCid',
|
|
340
|
+
{ context: { cp } });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const subset = deps.subsetForPdf(parsedFont, cps, opts);
|
|
345
|
+
const { descriptor, fontFile, fontFileKey } = descriptorOf(parsedFont, subset);
|
|
346
|
+
|
|
347
|
+
const byteToUnicode = new Map();
|
|
348
|
+
for (const cp of cps) byteToUnicode.set(WIN_ANSI_BYTE.get(cp), String.fromCodePoint(cp));
|
|
349
|
+
const toUnicodeStream = streamOfCmap(deps.embedBuildToUnicode(byteToUnicode, { codeBytes: 1 }));
|
|
350
|
+
|
|
351
|
+
const byCp = scaledWidthByCp(parsedFont, subset, cps);
|
|
352
|
+
const bytes = cps.map(cp => WIN_ANSI_BYTE.get(cp));
|
|
353
|
+
const firstChar = Math.min(...bytes);
|
|
354
|
+
const lastChar = Math.max(...bytes);
|
|
355
|
+
const byByte = new Map();
|
|
356
|
+
for (const cp of cps) byByte.set(WIN_ANSI_BYTE.get(cp), byCp.get(cp));
|
|
357
|
+
const widths = [];
|
|
358
|
+
for (let b = firstChar; b <= lastChar; b++) {
|
|
359
|
+
widths.push(obj.int(byByte.has(b) ? byByte.get(b) : 0));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const cpSet = new Set(cps);
|
|
363
|
+
function encode(text) {
|
|
364
|
+
const out = [];
|
|
365
|
+
for (const ch of String(text)) {
|
|
366
|
+
const cp = ch.codePointAt(0);
|
|
367
|
+
if (!cpSet.has(cp) || !WIN_ANSI_BYTE.has(cp)) {
|
|
368
|
+
throw new ContractError('pdf/embed/not-winansi',
|
|
369
|
+
'code point is not part of this WinAnsi embedding',
|
|
370
|
+
{ context: { cp } });
|
|
371
|
+
}
|
|
372
|
+
out.push(WIN_ANSI_BYTE.get(cp));
|
|
373
|
+
}
|
|
374
|
+
return Uint8Array.from(out);
|
|
375
|
+
}
|
|
376
|
+
function widthOf(cp) {
|
|
377
|
+
return byCp.has(cp) ? byCp.get(cp) : 0;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const fontDict = obj.dict({
|
|
381
|
+
Type: obj.name('Font'),
|
|
382
|
+
Subtype: obj.name('TrueType'),
|
|
383
|
+
BaseFont: obj.name(baseFontName(subset)),
|
|
384
|
+
Encoding: obj.name('WinAnsiEncoding'),
|
|
385
|
+
FirstChar: obj.int(firstChar),
|
|
386
|
+
LastChar: obj.int(lastChar),
|
|
387
|
+
Widths: obj.array(widths),
|
|
388
|
+
FontDescriptor: descriptor,
|
|
389
|
+
ToUnicode: toUnicodeStream
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
subtype: 'TrueType',
|
|
394
|
+
fontDict,
|
|
395
|
+
descriptor,
|
|
396
|
+
toUnicodeStream,
|
|
397
|
+
fontFile,
|
|
398
|
+
fontFileKey,
|
|
399
|
+
encode,
|
|
400
|
+
widthOf,
|
|
401
|
+
codePoints: cps
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Composite `/Type0` + `/Identity-H` embedding. The subset is
|
|
407
|
+
* renumbered, so CID === new gid and `/CIDToGIDMap` is
|
|
408
|
+
* `/Identity`.
|
|
409
|
+
*
|
|
410
|
+
* @param {object} parsedFont Output of `fonts.read(bytes)`.
|
|
411
|
+
* @param {Iterable<number>} codePoints
|
|
412
|
+
* @param {object} [opts] Forwarded to `subsetForPdf`.
|
|
413
|
+
*/
|
|
414
|
+
function embedCid(parsedFont, codePoints, opts) {
|
|
415
|
+
assertFont(parsedFont);
|
|
416
|
+
const cps = normaliseCodePoints(codePoints);
|
|
417
|
+
|
|
418
|
+
const subset = deps.subsetForPdf(parsedFont, cps, { ...(opts || {}), cid: true });
|
|
419
|
+
const { descriptor, fontFile, fontFileKey } = descriptorOf(parsedFont, subset);
|
|
420
|
+
const toUnicodeStream = toUnicodeStreamForCid(deps, subset);
|
|
421
|
+
const cidSys = wrapCidSysInfo(deps.buildCidSystemInfo(parsedFont));
|
|
422
|
+
|
|
423
|
+
const byCp = scaledWidthByCp(parsedFont, subset, cps);
|
|
424
|
+
const byGid = scaledWidthByGid(parsedFont, subset);
|
|
425
|
+
const glyphMap = (subset.glyphMap instanceof Map) ? subset.glyphMap : new Map();
|
|
426
|
+
|
|
427
|
+
function encode(text) {
|
|
428
|
+
const out = [];
|
|
429
|
+
for (const ch of String(text)) {
|
|
430
|
+
const cp = ch.codePointAt(0);
|
|
431
|
+
let gid = glyphMap.get(cp);
|
|
432
|
+
if (gid == null) { gid = 0; encode.missing++; }
|
|
433
|
+
out.push((gid >> 8) & 0xFF, gid & 0xFF);
|
|
434
|
+
}
|
|
435
|
+
return Uint8Array.from(out);
|
|
436
|
+
}
|
|
437
|
+
encode.missing = 0;
|
|
438
|
+
function widthOf(cp) {
|
|
439
|
+
return byCp.has(cp) ? byCp.get(cp) : 0;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const baseFont = baseFontName(subset);
|
|
443
|
+
const cidFontDict = obj.dict({
|
|
444
|
+
Type: obj.name('Font'),
|
|
445
|
+
Subtype: obj.name('CIDFontType2'),
|
|
446
|
+
BaseFont: obj.name(baseFont),
|
|
447
|
+
CIDSystemInfo: cidSys,
|
|
448
|
+
FontDescriptor: descriptor,
|
|
449
|
+
DW: obj.int(1000),
|
|
450
|
+
W: compactWArray(byGid),
|
|
451
|
+
CIDToGIDMap: obj.name('Identity')
|
|
452
|
+
});
|
|
453
|
+
const type0Dict = obj.dict({
|
|
454
|
+
Type: obj.name('Font'),
|
|
455
|
+
Subtype: obj.name('Type0'),
|
|
456
|
+
BaseFont: obj.name(baseFont),
|
|
457
|
+
Encoding: obj.name('Identity-H'),
|
|
458
|
+
DescendantFonts: obj.array([cidFontDict]),
|
|
459
|
+
ToUnicode: toUnicodeStream
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
return {
|
|
463
|
+
type0Dict,
|
|
464
|
+
cidFontDict,
|
|
465
|
+
descriptor,
|
|
466
|
+
toUnicodeStream,
|
|
467
|
+
fontFile,
|
|
468
|
+
fontFileKey,
|
|
469
|
+
encode,
|
|
470
|
+
widthOf,
|
|
471
|
+
codePoints: cps
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return { embedSimple, embedCid };
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return createEmbed(fontsEmbedPdf);
|
|
479
|
+
}
|
|
480
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
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 Font encoding resolution per ISO 32000-2:2020 §9.6.5.
|
|
8
|
+
*
|
|
9
|
+
* A simple Font's `/Encoding` entry is either a name or a dict with
|
|
10
|
+
* `{ BaseEncoding?: name, Differences?: array }`.
|
|
11
|
+
*
|
|
12
|
+
* `resolveEncoding(entry, lookupNamed)` returns a 256-entry array of
|
|
13
|
+
* glyph names (or `null` for unmapped slots).
|
|
14
|
+
*
|
|
15
|
+
* @module pdf/font/encoding
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Module factory — worker-safe, self-contained.
|
|
20
|
+
*/
|
|
21
|
+
import { pdfErrors } from '../errors.js';
|
|
22
|
+
|
|
23
|
+
export const pdfFontEncoding = {
|
|
24
|
+
name: 'pdfFontEncoding',
|
|
25
|
+
dependencies: ['pdfErrors'],
|
|
26
|
+
deps: [pdfErrors],
|
|
27
|
+
factory(errors) {
|
|
28
|
+
const { ParseError } = errors;
|
|
29
|
+
|
|
30
|
+
function isType(v, kind) { return !!(v && v.type === kind); }
|
|
31
|
+
|
|
32
|
+
function applyDifferences(diffsArr, table) {
|
|
33
|
+
if (diffsArr.type !== 'array') {
|
|
34
|
+
throw new ParseError('pdf/encoding/bad-differences',
|
|
35
|
+
'/Differences must be an array');
|
|
36
|
+
}
|
|
37
|
+
let cur = 0;
|
|
38
|
+
for (const it of diffsArr.items) {
|
|
39
|
+
if (it.type === 'int') cur = it.value;
|
|
40
|
+
else if (it.type === 'name') {
|
|
41
|
+
if (cur >= 0 && cur < 256) table[cur] = it.value;
|
|
42
|
+
cur++;
|
|
43
|
+
} else {
|
|
44
|
+
throw new ParseError('pdf/encoding/bad-differences-entry',
|
|
45
|
+
'/Differences entries must be ints or names',
|
|
46
|
+
{ context: { kind: it.type } });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveEncoding(entry, lookupNamed) {
|
|
52
|
+
const table = new Array(256).fill(null);
|
|
53
|
+
let baseName;
|
|
54
|
+
|
|
55
|
+
if (!entry) baseName = 'StandardEncoding';
|
|
56
|
+
else if (entry.type === 'name') baseName = entry.value;
|
|
57
|
+
else if (entry.type === 'dict') {
|
|
58
|
+
const be = entry.entries.BaseEncoding;
|
|
59
|
+
if (be) {
|
|
60
|
+
if (be.type !== 'name') {
|
|
61
|
+
throw new ParseError('pdf/encoding/bad-base',
|
|
62
|
+
'BaseEncoding must be a name',
|
|
63
|
+
{ context: { type: be.type } });
|
|
64
|
+
}
|
|
65
|
+
baseName = be.value;
|
|
66
|
+
} else {
|
|
67
|
+
baseName = 'StandardEncoding';
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
throw new ParseError('pdf/encoding/bad-shape',
|
|
71
|
+
'/Encoding must be a name or a dict',
|
|
72
|
+
{ context: { type: entry.type } });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (typeof lookupNamed === 'function') {
|
|
76
|
+
const named = lookupNamed(baseName);
|
|
77
|
+
if (named) {
|
|
78
|
+
for (let i = 0; i < 256 && i < named.length; i++) {
|
|
79
|
+
table[i] = named[i] || null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (isType(entry, 'dict') && entry.entries.Differences) {
|
|
85
|
+
applyDifferences(entry.entries.Differences, table);
|
|
86
|
+
}
|
|
87
|
+
return table;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { resolveEncoding };
|
|
91
|
+
}
|
|
92
|
+
};
|
package/src/font/font.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
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 Font dict typing per ISO 32000-2:2020 §9.6 / §9.7.
|
|
8
|
+
*
|
|
9
|
+
* This module **does not parse font files**. All actual font byte
|
|
10
|
+
* parsing (Type 1 PFB/PFA, TrueType/OpenType, CFF, CIDFont) is
|
|
11
|
+
* delegated to `@awacloud/fonts`. Here we only type the Font dict itself
|
|
12
|
+
* (`/Type /Font`, `/Subtype`, `/BaseFont`, `/Encoding`, ...).
|
|
13
|
+
*
|
|
14
|
+
* @module pdf/font/font
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Module factory — worker-safe, self-contained.
|
|
19
|
+
*/
|
|
20
|
+
import { pdfErrors } from '../errors.js';
|
|
21
|
+
import { pdfParser } from '../syntax/parser.js';
|
|
22
|
+
|
|
23
|
+
export const pdfFont = {
|
|
24
|
+
name: 'pdfFont',
|
|
25
|
+
dependencies: ['pdfErrors', 'pdfParser'],
|
|
26
|
+
deps: [pdfErrors, pdfParser],
|
|
27
|
+
factory(errors, parserMod) {
|
|
28
|
+
const { ParseError } = errors;
|
|
29
|
+
const isType = (parserMod && parserMod.isType)
|
|
30
|
+
|| ((v, kind) => !!(v && v.type === kind));
|
|
31
|
+
|
|
32
|
+
const KNOWN_SUBTYPES = new Set([
|
|
33
|
+
'Type0', 'Type1', 'MMType1', 'Type3', 'TrueType',
|
|
34
|
+
'CIDFontType0', 'CIDFontType2'
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
function typeFont(dict, opts) {
|
|
38
|
+
if (!isType(dict, 'dict')) {
|
|
39
|
+
throw new ParseError('pdf/font/not-dict',
|
|
40
|
+
'Font must be a dictionary',
|
|
41
|
+
{ context: { type: dict && dict.type } });
|
|
42
|
+
}
|
|
43
|
+
const e = dict.entries;
|
|
44
|
+
if (e.Type && (e.Type.type !== 'name' || e.Type.value !== 'Font')) {
|
|
45
|
+
throw new ParseError('pdf/font/bad-type',
|
|
46
|
+
'/Type must be /Font when present',
|
|
47
|
+
{ context: { actual: e.Type.value } });
|
|
48
|
+
}
|
|
49
|
+
if (!e.Subtype || e.Subtype.type !== 'name') {
|
|
50
|
+
throw new ParseError('pdf/font/missing-subtype',
|
|
51
|
+
'Font dict missing required /Subtype');
|
|
52
|
+
}
|
|
53
|
+
if (!KNOWN_SUBTYPES.has(e.Subtype.value)) {
|
|
54
|
+
throw new ParseError('pdf/font/unknown-subtype',
|
|
55
|
+
`unknown font subtype "${e.Subtype.value}"`,
|
|
56
|
+
{ context: { subtype: e.Subtype.value } });
|
|
57
|
+
}
|
|
58
|
+
const baseFont = e.BaseFont && e.BaseFont.type === 'name'
|
|
59
|
+
? e.BaseFont.value : null;
|
|
60
|
+
|
|
61
|
+
const out = {
|
|
62
|
+
subtype: e.Subtype.value,
|
|
63
|
+
baseFont,
|
|
64
|
+
encoding: e.Encoding || null,
|
|
65
|
+
firstChar: e.FirstChar && e.FirstChar.type === 'int' ? e.FirstChar.value : null,
|
|
66
|
+
lastChar: e.LastChar && e.LastChar.type === 'int' ? e.LastChar.value : null,
|
|
67
|
+
widths: e.Widths && e.Widths.type === 'array'
|
|
68
|
+
? e.Widths.items.filter(it => it.type === 'int' || it.type === 'real')
|
|
69
|
+
.map(it => it.value)
|
|
70
|
+
: null,
|
|
71
|
+
fontDescriptor: e.FontDescriptor || null,
|
|
72
|
+
toUnicode: e.ToUnicode || null,
|
|
73
|
+
descendantFonts: e.DescendantFonts && e.DescendantFonts.type === 'array'
|
|
74
|
+
? e.DescendantFonts.items.slice() : null,
|
|
75
|
+
standard14: null,
|
|
76
|
+
raw: dict
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
if ((out.subtype === 'Type1' || out.subtype === 'MMType1')
|
|
80
|
+
&& !out.fontDescriptor
|
|
81
|
+
&& opts && opts.standard14
|
|
82
|
+
&& baseFont
|
|
83
|
+
&& opts.standard14.isStandard14(baseFont)) {
|
|
84
|
+
out.standard14 = opts.standard14.lookupStandard14(baseFont);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function resolveDescendant(type0Font, resolveRef) {
|
|
91
|
+
if (!type0Font || type0Font.subtype !== 'Type0') return null;
|
|
92
|
+
const descs = type0Font.descendantFonts;
|
|
93
|
+
if (!descs || descs.length === 0) return null;
|
|
94
|
+
const d = descs[0];
|
|
95
|
+
const dict = d.type === 'ref' ? resolveRef(d) : d;
|
|
96
|
+
return typeFont(dict);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { typeFont, resolveDescendant };
|
|
100
|
+
}
|
|
101
|
+
};
|