@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,776 @@
|
|
|
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 CCITTFaxDecode codec (ITU-T T.4 / T.6).
|
|
8
|
+
*
|
|
9
|
+
* Implements:
|
|
10
|
+
* - K < 0 : Group 4 (T.6), pure 2D coding.
|
|
11
|
+
* - K == 0 : Group 3 1D (T.4 baseline, white/black run Huffman).
|
|
12
|
+
* - K > 0 : Group 3 mixed 1D/2D (T.4 with K-row block, tag bit).
|
|
13
|
+
*
|
|
14
|
+
* Output is a packed bitstream of `Columns` bits per row, MSB-first
|
|
15
|
+
* (PDF convention). Polarity follows /BlackIs1 (default: white = 1).
|
|
16
|
+
*
|
|
17
|
+
* The encoder side is intentionally out of scope: the practical use
|
|
18
|
+
* case is reading legacy faxed image streams found in older PDFs.
|
|
19
|
+
*
|
|
20
|
+
* @module pdf/extra/ccitt-fax-decoder
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { pdfErrors } from '../errors.js';
|
|
24
|
+
|
|
25
|
+
export const pdfCcittFaxDecoder = {
|
|
26
|
+
name: 'pdfCcittFaxDecoder',
|
|
27
|
+
dependencies: ['pdfErrors'],
|
|
28
|
+
deps: [pdfErrors],
|
|
29
|
+
|
|
30
|
+
factory(errors) {
|
|
31
|
+
const { ParseError } = errors;
|
|
32
|
+
|
|
33
|
+
// ---------- Huffman tables (T.4 §4.1.4, T.6) ----------
|
|
34
|
+
//
|
|
35
|
+
// Format: Map keyed by bit-string string ('0', '01', ...).
|
|
36
|
+
// The decoder reads MSB-first, accumulating bits as a string,
|
|
37
|
+
// and looks up after each new bit. Codes are prefix-free so
|
|
38
|
+
// the first match wins. Tables are small (~ a few hundred
|
|
39
|
+
// entries) and built once per factory call.
|
|
40
|
+
|
|
41
|
+
// White terminating codes, run = 0..63
|
|
42
|
+
const WHITE_TERM = [
|
|
43
|
+
'00110101','000111','0111','1000','1011','1100','1110','1111',
|
|
44
|
+
'10011','10100','00111','01000','001000','000011','110100','110101',
|
|
45
|
+
'101010','101011','0100111','0001100','0001000','0010111','0000011','0000100',
|
|
46
|
+
'0101000','0101011','0010011','0100100','0011000','00000010','00000011','00011010',
|
|
47
|
+
'00011011','00010010','00010011','00010100','00010101','00010110','00010111','00101000',
|
|
48
|
+
'00101001','00101010','00101011','00101100','00101101','00000100','00000101','00001010',
|
|
49
|
+
'00001011','01010010','01010011','01010100','01010101','00100100','00100101','01011000',
|
|
50
|
+
'01011001','01011010','01011011','01001010','01001011','00110010','00110011','00110100'
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
// White make-up codes, run = 64,128,...,1728 (multiples of 64)
|
|
54
|
+
const WHITE_MAKEUP = {
|
|
55
|
+
64:'11011', 128:'10010', 192:'010111', 256:'0110111',
|
|
56
|
+
320:'00110110', 384:'00110111', 448:'01100100', 512:'01100101',
|
|
57
|
+
576:'01101000', 640:'01100111', 704:'011001100', 768:'011001101',
|
|
58
|
+
832:'011010010', 896:'011010011', 960:'011010100', 1024:'011010101',
|
|
59
|
+
1088:'011010110', 1152:'011010111', 1216:'011011000', 1280:'011011001',
|
|
60
|
+
1344:'011011010', 1408:'011011011', 1472:'010011000', 1536:'010011001',
|
|
61
|
+
1600:'010011010', 1664:'011000', 1728:'010011011'
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Black terminating codes, run = 0..63
|
|
65
|
+
const BLACK_TERM = [
|
|
66
|
+
'0000110111','010','11','10','011','0011','0010','00011',
|
|
67
|
+
'000101','000100','0000100','0000101','0000111','00000100','00000111','000011000',
|
|
68
|
+
'0000010111','0000011000','0000001000','00001100111','00001101000','00001101100','00000110111','00000101000',
|
|
69
|
+
'00000010111','00000011000','000011001010','000011001011','000011001100','000011001101','000001101000','000001101001',
|
|
70
|
+
'000001101010','000001101011','000011010010','000011010011','000011010100','000011010101','000011010110','000011010111',
|
|
71
|
+
'000001101100','000001101101','000011011010','000011011011','000001010100','000001010101','000001010110','000001010111',
|
|
72
|
+
'000001100100','000001100101','000001010010','000001010011','000000100100','000000110111','000000111000','000000100111',
|
|
73
|
+
'000000101000','000001011000','000001011001','000000101011','000000101100','000001011010','000001100110','000001100111'
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
// Black make-up codes, run = 64..1728
|
|
77
|
+
const BLACK_MAKEUP = {
|
|
78
|
+
64:'0000001111', 128:'000011001000', 192:'000011001001', 256:'000001011011',
|
|
79
|
+
320:'000000110011', 384:'000000110100', 448:'000000110101', 512:'0000001101100',
|
|
80
|
+
576:'0000001101101', 640:'0000001001010', 704:'0000001001011', 768:'0000001001100',
|
|
81
|
+
832:'0000001001101', 896:'0000001110010', 960:'0000001110011', 1024:'0000001110100',
|
|
82
|
+
1088:'0000001110101', 1152:'0000001110110', 1216:'0000001110111', 1280:'0000001010010',
|
|
83
|
+
1344:'0000001010011', 1408:'0000001010100', 1472:'0000001010101', 1536:'0000001011010',
|
|
84
|
+
1600:'0000001011011', 1664:'0000001100100', 1728:'0000001100101'
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Common extended make-up codes (T.4 §4.1.4, run > 1728)
|
|
88
|
+
// Encoded the same for both colours. Run values 1792, 1856, ...
|
|
89
|
+
const COMMON_MAKEUP = {
|
|
90
|
+
1792:'00000001000', 1856:'00000001100', 1920:'00000001101',
|
|
91
|
+
1984:'000000010010', 2048:'000000010011', 2112:'000000010100',
|
|
92
|
+
2176:'000000010101', 2240:'000000010110', 2304:'000000010111',
|
|
93
|
+
2368:'000000011100', 2432:'000000011101', 2496:'000000011110',
|
|
94
|
+
2560:'000000011111'
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// 2D mode codes (T.6, T.4 §4.2)
|
|
98
|
+
// Pass: '0001'
|
|
99
|
+
// Horizontal: '001'
|
|
100
|
+
// V0: '1'
|
|
101
|
+
// VR1: '011' VL1: '010'
|
|
102
|
+
// VR2: '000011' VL2: '000010'
|
|
103
|
+
// VR3: '0000011' VL3: '0000010'
|
|
104
|
+
// EXT2D: '0000001' (uncommon extension)
|
|
105
|
+
const MODE_CODES = {
|
|
106
|
+
'0001': 'P', // pass
|
|
107
|
+
'001': 'H', // horizontal
|
|
108
|
+
'1': 'V0',
|
|
109
|
+
'011': 'VR1', '010': 'VL1',
|
|
110
|
+
'000011':'VR2', '000010':'VL2',
|
|
111
|
+
'0000011':'VR3','0000010':'VL3',
|
|
112
|
+
'0000001':'EXT2D'
|
|
113
|
+
};
|
|
114
|
+
const MODE_MAX_LEN = 7;
|
|
115
|
+
|
|
116
|
+
// EOL = 000000000001 (12 bits). When followed by a tag bit
|
|
117
|
+
// we read it separately.
|
|
118
|
+
const EOL_BITS = '000000000001';
|
|
119
|
+
|
|
120
|
+
// Build reverse maps (string -> { kind, value })
|
|
121
|
+
function buildTermMap(arr, kind) {
|
|
122
|
+
const m = new Map();
|
|
123
|
+
for (let i = 0; i < arr.length; i++) {
|
|
124
|
+
m.set(arr[i], { kind, run: i, terminating: true });
|
|
125
|
+
}
|
|
126
|
+
return m;
|
|
127
|
+
}
|
|
128
|
+
function buildMakeupMap(obj, kind) {
|
|
129
|
+
const m = new Map();
|
|
130
|
+
for (const k of Object.keys(obj)) {
|
|
131
|
+
m.set(obj[k], { kind, run: k | 0, terminating: false });
|
|
132
|
+
}
|
|
133
|
+
return m;
|
|
134
|
+
}
|
|
135
|
+
const WHITE_MAP = new Map([
|
|
136
|
+
...buildTermMap(WHITE_TERM, 'W'),
|
|
137
|
+
...buildMakeupMap(WHITE_MAKEUP, 'W'),
|
|
138
|
+
...buildMakeupMap(COMMON_MAKEUP, 'W')
|
|
139
|
+
]);
|
|
140
|
+
const BLACK_MAP = new Map([
|
|
141
|
+
...buildTermMap(BLACK_TERM, 'B'),
|
|
142
|
+
...buildMakeupMap(BLACK_MAKEUP, 'B'),
|
|
143
|
+
...buildMakeupMap(COMMON_MAKEUP, 'B')
|
|
144
|
+
]);
|
|
145
|
+
// Maximum prefix length for run codes (cap by spec)
|
|
146
|
+
const MAX_RUN_BITS = 13;
|
|
147
|
+
|
|
148
|
+
// ---------- Bit reader (MSB-first) ----------
|
|
149
|
+
function makeBitReader(bytes) {
|
|
150
|
+
let bytePos = 0;
|
|
151
|
+
let bitPos = 0; // 0 = MSB of current byte
|
|
152
|
+
return {
|
|
153
|
+
readBit() {
|
|
154
|
+
if (bytePos >= bytes.length) return -1;
|
|
155
|
+
const b = bytes[bytePos];
|
|
156
|
+
const bit = (b >>> (7 - bitPos)) & 1;
|
|
157
|
+
bitPos++;
|
|
158
|
+
if (bitPos === 8) { bitPos = 0; bytePos++; }
|
|
159
|
+
return bit;
|
|
160
|
+
},
|
|
161
|
+
alignToByte() {
|
|
162
|
+
if (bitPos !== 0) { bitPos = 0; bytePos++; }
|
|
163
|
+
},
|
|
164
|
+
eof() { return bytePos >= bytes.length; },
|
|
165
|
+
pos() { return { bytePos, bitPos }; },
|
|
166
|
+
seek(p) { bytePos = p.bytePos; bitPos = p.bitPos; }
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Read a run code (sequence of white/black, possibly with
|
|
171
|
+
// make-up codes prepended). Returns total run length, or -1 on
|
|
172
|
+
// EOF. Throws on undecodable.
|
|
173
|
+
function readRun(reader, mapFor) {
|
|
174
|
+
let total = 0;
|
|
175
|
+
for (;;) {
|
|
176
|
+
let code = '';
|
|
177
|
+
let entry = null;
|
|
178
|
+
for (let i = 0; i < MAX_RUN_BITS; i++) {
|
|
179
|
+
const b = reader.readBit();
|
|
180
|
+
if (b < 0) return -1;
|
|
181
|
+
code += b;
|
|
182
|
+
entry = mapFor.get(code);
|
|
183
|
+
if (entry) break;
|
|
184
|
+
}
|
|
185
|
+
if (!entry) {
|
|
186
|
+
throw new ParseError('pdf/ccitt/bad-runcode',
|
|
187
|
+
'unrecognised CCITT run-length code',
|
|
188
|
+
{ context: { bits: code } });
|
|
189
|
+
}
|
|
190
|
+
total += entry.run;
|
|
191
|
+
if (entry.terminating) return total;
|
|
192
|
+
// make-up: continue reading additional codes
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Read a 2D mode code
|
|
197
|
+
function readMode(reader) {
|
|
198
|
+
let code = '';
|
|
199
|
+
for (let i = 0; i < MODE_MAX_LEN; i++) {
|
|
200
|
+
const b = reader.readBit();
|
|
201
|
+
if (b < 0) return null;
|
|
202
|
+
code += b;
|
|
203
|
+
const m = MODE_CODES[code];
|
|
204
|
+
if (m) return m;
|
|
205
|
+
}
|
|
206
|
+
throw new ParseError('pdf/ccitt/bad-2d-mode',
|
|
207
|
+
'unrecognised CCITT 2D mode code',
|
|
208
|
+
{ context: { bits: code } });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Try to read EOL (12 bits 0...01). Returns true if consumed.
|
|
212
|
+
function readEolMaybe(reader) {
|
|
213
|
+
const save = reader.pos();
|
|
214
|
+
let zeros = 0;
|
|
215
|
+
// Read up to 24 zero bits then a 1
|
|
216
|
+
for (let i = 0; i < 24; i++) {
|
|
217
|
+
const b = reader.readBit();
|
|
218
|
+
if (b < 0) { reader.seek(save); return false; }
|
|
219
|
+
if (b === 0) { zeros++; continue; }
|
|
220
|
+
if (b === 1 && zeros >= 11) return true;
|
|
221
|
+
reader.seek(save);
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
reader.seek(save);
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Peek N consecutive EOL codes without consuming. Returns true
|
|
229
|
+
// if N EOLs are present at the current position. Used to detect
|
|
230
|
+
// RTC (G3, 6×EOL) and EOFB (G4, 2×EOL) terminators. Note that
|
|
231
|
+
// readEolMaybe consumes the EOL when present, so we save/restore
|
|
232
|
+
// around the probe.
|
|
233
|
+
function peekEols(reader, n) {
|
|
234
|
+
const save = reader.pos();
|
|
235
|
+
let ok = true;
|
|
236
|
+
for (let k = 0; k < n; k++) {
|
|
237
|
+
if (!readEolMaybe(reader)) { ok = false; break; }
|
|
238
|
+
// For G3-2D / mixed, an EOL may be followed by a tag
|
|
239
|
+
// bit (1 for 1D / 0 for 2D). When probing for RTC we
|
|
240
|
+
// tolerate this since encoder emits tag=1 between RTC
|
|
241
|
+
// EOLs for K>0. We don't consume it here — peekEols is
|
|
242
|
+
// called only with K<0 (EOFB, 2×EOL, no tag) or as a
|
|
243
|
+
// raw pre-check before the per-row EOL reader runs.
|
|
244
|
+
}
|
|
245
|
+
reader.seek(save);
|
|
246
|
+
return ok;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Skip to next EOL (used for resync, optional)
|
|
250
|
+
function skipToEol(reader) {
|
|
251
|
+
// Naive: scan bit by bit for 11 zeros then a 1
|
|
252
|
+
let zeros = 0;
|
|
253
|
+
for (;;) {
|
|
254
|
+
const b = reader.readBit();
|
|
255
|
+
if (b < 0) return false;
|
|
256
|
+
if (b === 0) { zeros++; continue; }
|
|
257
|
+
if (b === 1 && zeros >= 11) return true;
|
|
258
|
+
zeros = 0;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ---------- Line decoders ----------
|
|
263
|
+
|
|
264
|
+
// Decode one 1D-coded line into a Uint8Array of length Columns
|
|
265
|
+
// (each cell 0 = white, 1 = black). Starts with WHITE run.
|
|
266
|
+
function decode1DLine(reader, columns) {
|
|
267
|
+
const line = new Uint8Array(columns);
|
|
268
|
+
let pos = 0;
|
|
269
|
+
let colour = 0; // 0=white, 1=black
|
|
270
|
+
while (pos < columns) {
|
|
271
|
+
const run = readRun(reader, colour === 0 ? WHITE_MAP : BLACK_MAP);
|
|
272
|
+
if (run < 0) return null;
|
|
273
|
+
const end = Math.min(pos + run, columns);
|
|
274
|
+
if (colour === 1) {
|
|
275
|
+
for (let i = pos; i < end; i++) line[i] = 1;
|
|
276
|
+
}
|
|
277
|
+
pos = end;
|
|
278
|
+
colour ^= 1;
|
|
279
|
+
}
|
|
280
|
+
return line;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Decode one 2D-coded line given the reference (previous) line.
|
|
284
|
+
// T.6 / T.4-2D state-machine.
|
|
285
|
+
function decode2DLine(reader, refLine, columns) {
|
|
286
|
+
const line = new Uint8Array(columns);
|
|
287
|
+
let a0 = -1; // position of last colour change (start at imaginary -1, white)
|
|
288
|
+
let a0Colour = 0; // 0 = white
|
|
289
|
+
while (a0 < columns) {
|
|
290
|
+
// b1 = first changing element on ref line right of a0 of
|
|
291
|
+
// opposite colour to a0
|
|
292
|
+
const b1 = findB1(refLine, a0, a0Colour, columns);
|
|
293
|
+
const b2 = findNextChange(refLine, b1, columns);
|
|
294
|
+
|
|
295
|
+
const mode = readMode(reader);
|
|
296
|
+
if (mode === null) return null;
|
|
297
|
+
|
|
298
|
+
if (mode === 'P') {
|
|
299
|
+
// Pass: paint from a0 (or 0) up to b2 with a0Colour
|
|
300
|
+
const start = a0 < 0 ? 0 : a0;
|
|
301
|
+
paint(line, start, b2, a0Colour);
|
|
302
|
+
a0 = b2;
|
|
303
|
+
// colour unchanged
|
|
304
|
+
} else if (mode === 'H') {
|
|
305
|
+
// Horizontal: two runs of a0Colour then opposite
|
|
306
|
+
const r1 = readRun(reader, a0Colour === 0 ? WHITE_MAP : BLACK_MAP);
|
|
307
|
+
const r2 = readRun(reader, a0Colour === 0 ? BLACK_MAP : WHITE_MAP);
|
|
308
|
+
if (r1 < 0 || r2 < 0) return null;
|
|
309
|
+
const start = a0 < 0 ? 0 : a0;
|
|
310
|
+
paint(line, start, Math.min(start + r1, columns), a0Colour);
|
|
311
|
+
paint(line, Math.min(start + r1, columns),
|
|
312
|
+
Math.min(start + r1 + r2, columns), a0Colour ^ 1);
|
|
313
|
+
a0 = start + r1 + r2;
|
|
314
|
+
// a0Colour unchanged (two flips)
|
|
315
|
+
} else {
|
|
316
|
+
// Vertical V0, VR1..3, VL1..3
|
|
317
|
+
let offset;
|
|
318
|
+
if (mode === 'V0') offset = 0;
|
|
319
|
+
else if (mode === 'VR1') offset = 1;
|
|
320
|
+
else if (mode === 'VR2') offset = 2;
|
|
321
|
+
else if (mode === 'VR3') offset = 3;
|
|
322
|
+
else if (mode === 'VL1') offset = -1;
|
|
323
|
+
else if (mode === 'VL2') offset = -2;
|
|
324
|
+
else if (mode === 'VL3') offset = -3;
|
|
325
|
+
else {
|
|
326
|
+
throw new ParseError('pdf/ccitt/unsupported-2d-mode',
|
|
327
|
+
'unsupported 2D mode',
|
|
328
|
+
{ context: { mode } });
|
|
329
|
+
}
|
|
330
|
+
const a1 = b1 + offset;
|
|
331
|
+
const start = a0 < 0 ? 0 : a0;
|
|
332
|
+
paint(line, start, Math.min(Math.max(a1, 0), columns), a0Colour);
|
|
333
|
+
a0 = a1;
|
|
334
|
+
a0Colour ^= 1;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return line;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function paint(line, from, to, colour) {
|
|
341
|
+
if (colour === 0) return;
|
|
342
|
+
const a = Math.max(0, from);
|
|
343
|
+
const b = Math.min(line.length, to);
|
|
344
|
+
for (let i = a; i < b; i++) line[i] = 1;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function colourAt(refLine, pos) {
|
|
348
|
+
if (pos < 0 || pos >= refLine.length) return 0;
|
|
349
|
+
return refLine[pos];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// b1 = first changing element to the right of a0 on the
|
|
353
|
+
// reference line whose colour is opposite to a0Colour.
|
|
354
|
+
function findB1(refLine, a0, a0Colour, columns) {
|
|
355
|
+
// Spec: a0 is imaginary at -1, treated as white (0).
|
|
356
|
+
// Scan from a0+1.
|
|
357
|
+
let start = a0 < 0 ? 0 : a0 + 1;
|
|
358
|
+
// Skip same-colour-as-a0Colour first
|
|
359
|
+
let prev = a0 < 0 ? 0 : colourAt(refLine, a0);
|
|
360
|
+
// Walk to first changing element of opposite colour to a0
|
|
361
|
+
for (let i = start; i < columns; i++) {
|
|
362
|
+
const c = refLine[i];
|
|
363
|
+
if (c !== prev) {
|
|
364
|
+
// changing element; if colour == opposite of a0Colour, this is b1
|
|
365
|
+
if (c !== a0Colour) return i;
|
|
366
|
+
prev = c;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return columns;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function findNextChange(refLine, from, columns) {
|
|
373
|
+
if (from >= columns) return columns;
|
|
374
|
+
const start = Math.max(0, from);
|
|
375
|
+
const cur = start < columns ? refLine[start] : 0;
|
|
376
|
+
for (let i = start + 1; i < columns; i++) {
|
|
377
|
+
if (refLine[i] !== cur) return i;
|
|
378
|
+
}
|
|
379
|
+
return columns;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ---------- Public decode ----------
|
|
383
|
+
|
|
384
|
+
function packBits(line, polarityInvert) {
|
|
385
|
+
// line[i] = 1 (black) or 0 (white)
|
|
386
|
+
// Output: MSB-first packed. By default PDF: 0=white, 1=black? No:
|
|
387
|
+
// CCITTFaxDecode default /BlackIs1 = false → 1 = white (per PDF spec).
|
|
388
|
+
// Our line uses 1=black. So output bit = polarityInvert ? sameAsLine : !line[i]
|
|
389
|
+
// If BlackIs1 is true → output bit = line[i] (black=1).
|
|
390
|
+
// If BlackIs1 is false → output bit = !line[i] (white=1).
|
|
391
|
+
const n = line.length;
|
|
392
|
+
const out = new Uint8Array((n + 7) >>> 3);
|
|
393
|
+
for (let i = 0; i < n; i++) {
|
|
394
|
+
const bit = polarityInvert ? line[i] : (line[i] ^ 1);
|
|
395
|
+
if (bit) out[i >>> 3] |= 1 << (7 - (i & 7));
|
|
396
|
+
}
|
|
397
|
+
return out;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function decode(bytes, parms) {
|
|
401
|
+
if (!(bytes instanceof Uint8Array)) {
|
|
402
|
+
throw new ParseError('pdf/ccitt/bad-input',
|
|
403
|
+
'CCITT input must be Uint8Array');
|
|
404
|
+
}
|
|
405
|
+
const columns = parms.Columns | 0;
|
|
406
|
+
if (columns <= 0 || columns > 65535) {
|
|
407
|
+
throw new ParseError('pdf/ccitt/bad-columns',
|
|
408
|
+
'CCITT /Columns out of range',
|
|
409
|
+
{ context: { columns } });
|
|
410
|
+
}
|
|
411
|
+
const rows = parms.Rows | 0; // 0 = unlimited
|
|
412
|
+
const K = parms.K | 0;
|
|
413
|
+
const reader = makeBitReader(bytes);
|
|
414
|
+
const lines = [];
|
|
415
|
+
// Reference line for 2D modes: all-white initial line
|
|
416
|
+
let refLine = new Uint8Array(columns);
|
|
417
|
+
|
|
418
|
+
// EOL handling helpers
|
|
419
|
+
function expectEolMaybe() {
|
|
420
|
+
if (parms.EndOfLine) {
|
|
421
|
+
// Must see EOL
|
|
422
|
+
if (!readEolMaybe(reader)) {
|
|
423
|
+
// try resync
|
|
424
|
+
if (!skipToEol(reader)) return false;
|
|
425
|
+
}
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
// Optional EOL: peek and consume if present
|
|
429
|
+
readEolMaybe(reader);
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function alignMaybe() {
|
|
434
|
+
if (parms.EncodedByteAlign) reader.alignToByte();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// For K>0 we cycle: each K-row block, the first row is 1D
|
|
438
|
+
// (after EOL+tag=1) and the next K-1 are 2D (EOL+tag=0).
|
|
439
|
+
// For K==0: all rows 1D, optional EOL between them.
|
|
440
|
+
// For K<0 (G4/T.6): all rows 2D, no per-line EOL inside the
|
|
441
|
+
// stream; only an optional RTC at the end.
|
|
442
|
+
|
|
443
|
+
let rowIndex = 0;
|
|
444
|
+
let kPhase = 0; // 0..K-1 within mixed block
|
|
445
|
+
|
|
446
|
+
try {
|
|
447
|
+
while (rows === 0 || rowIndex < rows) {
|
|
448
|
+
alignMaybe();
|
|
449
|
+
// RTC / EOFB detection: before reading the next row,
|
|
450
|
+
// peek for back-to-back EOL codes. 2 consecutive EOLs
|
|
451
|
+
// signal end-of-block (RTC head for G3, EOFB for G4).
|
|
452
|
+
if (K < 0) {
|
|
453
|
+
// G4 EOFB: 2 EOL codes. Probe with peekEols(2).
|
|
454
|
+
if (peekEols(reader, 2)) break;
|
|
455
|
+
} else if (K > 0 || parms.EndOfLine) {
|
|
456
|
+
// G3 1D/mixed RTC: pattern is EOL[+tag] x6.
|
|
457
|
+
// Detecting 2 stacked EOLs is sufficient and
|
|
458
|
+
// unambiguous: a normal row never produces a
|
|
459
|
+
// valid EOL bit pattern at its start. Only
|
|
460
|
+
// probe when EOLs are expected in the stream.
|
|
461
|
+
const sv = reader.pos();
|
|
462
|
+
if (readEolMaybe(reader)) {
|
|
463
|
+
// For K>0 a tag bit follows; skip it before
|
|
464
|
+
// probing the next EOL.
|
|
465
|
+
if (K > 0) {
|
|
466
|
+
const t = reader.readBit();
|
|
467
|
+
if (t < 0) { reader.seek(sv); break; }
|
|
468
|
+
}
|
|
469
|
+
const isRtc = readEolMaybe(reader);
|
|
470
|
+
reader.seek(sv);
|
|
471
|
+
if (isRtc) break;
|
|
472
|
+
} else {
|
|
473
|
+
reader.seek(sv);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
let is2D;
|
|
477
|
+
if (K < 0) {
|
|
478
|
+
is2D = true;
|
|
479
|
+
} else if (K === 0) {
|
|
480
|
+
is2D = false;
|
|
481
|
+
if (parms.EndOfLine) {
|
|
482
|
+
if (!expectEolMaybe()) break;
|
|
483
|
+
} else {
|
|
484
|
+
readEolMaybe(reader);
|
|
485
|
+
}
|
|
486
|
+
} else {
|
|
487
|
+
// K > 0 mixed
|
|
488
|
+
if (kPhase === 0) {
|
|
489
|
+
// Expect EOL + tag bit (1 → 1D)
|
|
490
|
+
if (parms.EndOfLine) {
|
|
491
|
+
if (!expectEolMaybe()) break;
|
|
492
|
+
const tag = reader.readBit();
|
|
493
|
+
if (tag < 0) break;
|
|
494
|
+
is2D = (tag === 0);
|
|
495
|
+
} else {
|
|
496
|
+
readEolMaybe(reader);
|
|
497
|
+
const tag = reader.readBit();
|
|
498
|
+
if (tag < 0) break;
|
|
499
|
+
is2D = (tag === 0);
|
|
500
|
+
}
|
|
501
|
+
} else {
|
|
502
|
+
if (parms.EndOfLine) {
|
|
503
|
+
if (!expectEolMaybe()) break;
|
|
504
|
+
const tag = reader.readBit();
|
|
505
|
+
if (tag < 0) break;
|
|
506
|
+
is2D = (tag === 0);
|
|
507
|
+
} else {
|
|
508
|
+
readEolMaybe(reader);
|
|
509
|
+
const tag = reader.readBit();
|
|
510
|
+
if (tag < 0) break;
|
|
511
|
+
is2D = (tag === 0);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
let line;
|
|
517
|
+
if (is2D) {
|
|
518
|
+
line = decode2DLine(reader, refLine, columns);
|
|
519
|
+
} else {
|
|
520
|
+
line = decode1DLine(reader, columns);
|
|
521
|
+
}
|
|
522
|
+
if (line === null) {
|
|
523
|
+
if (rows === 0) break;
|
|
524
|
+
if ((parms.DamagedRowsBeforeError | 0) > 0) {
|
|
525
|
+
// Tolerate: emit blank row
|
|
526
|
+
line = new Uint8Array(columns);
|
|
527
|
+
} else {
|
|
528
|
+
throw new ParseError('pdf/ccitt/truncated',
|
|
529
|
+
'CCITT stream truncated mid-line',
|
|
530
|
+
{ context: { row: rowIndex } });
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
lines.push(line);
|
|
534
|
+
refLine = line;
|
|
535
|
+
rowIndex++;
|
|
536
|
+
if (K > 0) kPhase = (kPhase + 1) % K;
|
|
537
|
+
if (reader.eof()) break;
|
|
538
|
+
}
|
|
539
|
+
} catch (e) {
|
|
540
|
+
if (e instanceof ParseError) throw e;
|
|
541
|
+
throw new ParseError('pdf/ccitt/decode-failed',
|
|
542
|
+
'CCITT decoder error: ' + e.message,
|
|
543
|
+
{ cause: e });
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Pack lines into a single Uint8Array. Each line is padded
|
|
547
|
+
// to byte boundary independently (standard PDF image data).
|
|
548
|
+
const polarityInvert = !!parms.BlackIs1;
|
|
549
|
+
const rowBytes = (columns + 7) >>> 3;
|
|
550
|
+
const out = new Uint8Array(rowBytes * lines.length);
|
|
551
|
+
for (let r = 0; r < lines.length; r++) {
|
|
552
|
+
const packed = packBits(lines[r], polarityInvert);
|
|
553
|
+
out.set(packed, r * rowBytes);
|
|
554
|
+
}
|
|
555
|
+
return out;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// ---------- Encode helpers (bit writer) ----------
|
|
559
|
+
// Production-grade T.4/T.6 encoder: greedy make-up choice,
|
|
560
|
+
// EncodedByteAlign padding, RTC (G3) / EOFB (G4), full 2D
|
|
561
|
+
// state machine.
|
|
562
|
+
function makeBitWriter() {
|
|
563
|
+
const bytes = [];
|
|
564
|
+
let cur = 0, n = 0;
|
|
565
|
+
function writeBit(b) {
|
|
566
|
+
cur = (cur << 1) | (b & 1);
|
|
567
|
+
n++;
|
|
568
|
+
if (n === 8) { bytes.push(cur & 0xFF); cur = 0; n = 0; }
|
|
569
|
+
}
|
|
570
|
+
function writeBits(s) {
|
|
571
|
+
for (let i = 0; i < s.length; i++) writeBit(s.charCodeAt(i) - 48);
|
|
572
|
+
}
|
|
573
|
+
function alignToByte() {
|
|
574
|
+
while (n !== 0) writeBit(0);
|
|
575
|
+
}
|
|
576
|
+
function flush() {
|
|
577
|
+
if (n > 0) {
|
|
578
|
+
cur <<= (8 - n);
|
|
579
|
+
bytes.push(cur & 0xFF);
|
|
580
|
+
cur = 0; n = 0;
|
|
581
|
+
}
|
|
582
|
+
return new Uint8Array(bytes);
|
|
583
|
+
}
|
|
584
|
+
function bitCount() { return bytes.length * 8 + n; }
|
|
585
|
+
return { writeBit, writeBits, alignToByte, flush, bitCount };
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Pre-sorted make-up run lengths (descending) for greedy lookup.
|
|
589
|
+
const WHITE_MAKEUP_KEYS = Object.keys(WHITE_MAKEUP)
|
|
590
|
+
.map((k) => k | 0)
|
|
591
|
+
.sort((a, b) => b - a);
|
|
592
|
+
const BLACK_MAKEUP_KEYS = Object.keys(BLACK_MAKEUP)
|
|
593
|
+
.map((k) => k | 0)
|
|
594
|
+
.sort((a, b) => b - a);
|
|
595
|
+
const COMMON_MAKEUP_KEYS = Object.keys(COMMON_MAKEUP)
|
|
596
|
+
.map((k) => k | 0)
|
|
597
|
+
.sort((a, b) => b - a);
|
|
598
|
+
const COMMON_MAKEUP_MAX = COMMON_MAKEUP_KEYS[0]; // 2560
|
|
599
|
+
|
|
600
|
+
// Lookup code for a run length / colour (returns string of bits).
|
|
601
|
+
// Greedy: at each step emit the largest make-up code <= remaining
|
|
602
|
+
// run. Cycles 2560 codes for very large runs.
|
|
603
|
+
function codeForRun(run, colour) {
|
|
604
|
+
const term = colour === 0 ? WHITE_TERM : BLACK_TERM;
|
|
605
|
+
const make = colour === 0 ? WHITE_MAKEUP : BLACK_MAKEUP;
|
|
606
|
+
const makeKeys = colour === 0 ? WHITE_MAKEUP_KEYS : BLACK_MAKEUP_KEYS;
|
|
607
|
+
let bits = '';
|
|
608
|
+
// Runs >= 2624: emit 2560 chunks first
|
|
609
|
+
while (run >= COMMON_MAKEUP_MAX + 64) {
|
|
610
|
+
bits += COMMON_MAKEUP[COMMON_MAKEUP_MAX];
|
|
611
|
+
run -= COMMON_MAKEUP_MAX;
|
|
612
|
+
}
|
|
613
|
+
// Runs in [1792, 2624): pick largest extended make-up <= run
|
|
614
|
+
if (run >= 1792) {
|
|
615
|
+
for (let i = 0; i < COMMON_MAKEUP_KEYS.length; i++) {
|
|
616
|
+
const v = COMMON_MAKEUP_KEYS[i];
|
|
617
|
+
if (v <= run) {
|
|
618
|
+
bits += COMMON_MAKEUP[v];
|
|
619
|
+
run -= v;
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
// Runs in [64, 1728]: pick largest colour make-up <= run
|
|
625
|
+
if (run >= 64) {
|
|
626
|
+
for (let i = 0; i < makeKeys.length; i++) {
|
|
627
|
+
const v = makeKeys[i];
|
|
628
|
+
if (v <= run) {
|
|
629
|
+
bits += make[v];
|
|
630
|
+
run -= v;
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
// Terminating
|
|
636
|
+
bits += term[run];
|
|
637
|
+
return bits;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Encode a single line in 1D coding (white run first)
|
|
641
|
+
function encode1DLine(line, columns, writer) {
|
|
642
|
+
let pos = 0;
|
|
643
|
+
let colour = 0;
|
|
644
|
+
while (pos < columns) {
|
|
645
|
+
let run = 0;
|
|
646
|
+
while (pos + run < columns && (line[pos + run] | 0) === colour) run++;
|
|
647
|
+
writer.writeBits(codeForRun(run, colour));
|
|
648
|
+
pos += run;
|
|
649
|
+
colour ^= 1;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Encode a line in 2D mode using simple algorithm
|
|
654
|
+
function encode2DLine(line, refLine, columns, writer) {
|
|
655
|
+
let a0 = -1;
|
|
656
|
+
let a0Colour = 0;
|
|
657
|
+
while (a0 < columns) {
|
|
658
|
+
// Find a1: next changing element on coding line right of a0
|
|
659
|
+
const a1 = findChangeFrom(line, a0, a0Colour, columns);
|
|
660
|
+
const b1 = findB1(refLine, a0, a0Colour, columns);
|
|
661
|
+
const b2 = findNextChange(refLine, b1, columns);
|
|
662
|
+
|
|
663
|
+
if (b2 < a1) {
|
|
664
|
+
// Pass mode
|
|
665
|
+
writer.writeBits('0001');
|
|
666
|
+
a0 = b2;
|
|
667
|
+
// colour unchanged
|
|
668
|
+
} else {
|
|
669
|
+
const diff = a1 - b1;
|
|
670
|
+
if (diff >= -3 && diff <= 3) {
|
|
671
|
+
// Vertical
|
|
672
|
+
if (diff === 0) writer.writeBits('1');
|
|
673
|
+
else if (diff === 1) writer.writeBits('011');
|
|
674
|
+
else if (diff === 2) writer.writeBits('000011');
|
|
675
|
+
else if (diff === 3) writer.writeBits('0000011');
|
|
676
|
+
else if (diff === -1) writer.writeBits('010');
|
|
677
|
+
else if (diff === -2) writer.writeBits('000010');
|
|
678
|
+
else if (diff === -3) writer.writeBits('0000010');
|
|
679
|
+
a0 = a1;
|
|
680
|
+
a0Colour ^= 1;
|
|
681
|
+
} else {
|
|
682
|
+
// Horizontal
|
|
683
|
+
const a2 = findChangeFrom(line, a1, a0Colour ^ 1, columns);
|
|
684
|
+
const r1 = a1 - (a0 < 0 ? 0 : a0);
|
|
685
|
+
const r2 = a2 - a1;
|
|
686
|
+
writer.writeBits('001');
|
|
687
|
+
writer.writeBits(codeForRun(r1, a0Colour));
|
|
688
|
+
writer.writeBits(codeForRun(r2, a0Colour ^ 1));
|
|
689
|
+
a0 = a2;
|
|
690
|
+
// colour unchanged
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function findChangeFrom(line, a0, a0Colour, columns) {
|
|
697
|
+
const start = a0 < 0 ? 0 : a0 + 1;
|
|
698
|
+
// We want the first position where colour changes to opposite of a0Colour
|
|
699
|
+
let prev = a0 < 0 ? 0 : (line[a0] | 0);
|
|
700
|
+
for (let i = start; i < columns; i++) {
|
|
701
|
+
const c = line[i] | 0;
|
|
702
|
+
if (c !== prev) {
|
|
703
|
+
if (c !== a0Colour) return i;
|
|
704
|
+
prev = c;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return columns;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function encode(lines, parms) {
|
|
711
|
+
const columns = (parms && parms.Columns) | 0 || 1728;
|
|
712
|
+
const K = parms && parms.K != null ? parms.K | 0 : 0;
|
|
713
|
+
const endOfLine = !!(parms && parms.EndOfLine);
|
|
714
|
+
const byteAlign = !!(parms && parms.EncodedByteAlign);
|
|
715
|
+
// EndOfBlock defaults to true per PDF spec.
|
|
716
|
+
const endOfBlock = !parms || parms.EndOfBlock !== false;
|
|
717
|
+
// G3 mixed (K>0) per T.4 always uses EOL between lines.
|
|
718
|
+
const writeEol = endOfLine || K > 0;
|
|
719
|
+
|
|
720
|
+
const writer = makeBitWriter();
|
|
721
|
+
let refLine = new Uint8Array(columns);
|
|
722
|
+
for (let r = 0; r < lines.length; r++) {
|
|
723
|
+
const line = lines[r];
|
|
724
|
+
if (byteAlign) writer.alignToByte();
|
|
725
|
+
if (writeEol) {
|
|
726
|
+
writer.writeBits(EOL_BITS);
|
|
727
|
+
if (K > 0) {
|
|
728
|
+
// tag bit: 1 = 1D, 0 = 2D — first row of each K-block is 1D
|
|
729
|
+
const is1D = (r % K) === 0;
|
|
730
|
+
writer.writeBit(is1D ? 1 : 0);
|
|
731
|
+
if (is1D) encode1DLine(line, columns, writer);
|
|
732
|
+
else encode2DLine(line, refLine, columns, writer);
|
|
733
|
+
} else if (K === 0) {
|
|
734
|
+
encode1DLine(line, columns, writer);
|
|
735
|
+
} else {
|
|
736
|
+
encode2DLine(line, refLine, columns, writer);
|
|
737
|
+
}
|
|
738
|
+
} else {
|
|
739
|
+
if (K < 0) encode2DLine(line, refLine, columns, writer);
|
|
740
|
+
else encode1DLine(line, columns, writer);
|
|
741
|
+
}
|
|
742
|
+
refLine = line;
|
|
743
|
+
}
|
|
744
|
+
// RTC / EOFB terminator
|
|
745
|
+
if (endOfBlock) {
|
|
746
|
+
if (K < 0) {
|
|
747
|
+
// G4 EOFB: two consecutive EOL codes (000000000001 000000000001)
|
|
748
|
+
if (byteAlign) writer.alignToByte();
|
|
749
|
+
writer.writeBits(EOL_BITS);
|
|
750
|
+
writer.writeBits(EOL_BITS);
|
|
751
|
+
} else if (writeEol) {
|
|
752
|
+
// G3 RTC: 6 consecutive EOL codes (only when EOL was used)
|
|
753
|
+
if (byteAlign) writer.alignToByte();
|
|
754
|
+
for (let i = 0; i < 6; i++) {
|
|
755
|
+
writer.writeBits(EOL_BITS);
|
|
756
|
+
if (K > 0) writer.writeBit(1); // tag bit must follow EOL in G3-2D
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return writer.flush();
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
return {
|
|
764
|
+
decode,
|
|
765
|
+
encode,
|
|
766
|
+
// exported for tests / introspection
|
|
767
|
+
_internals: {
|
|
768
|
+
WHITE_MAP, BLACK_MAP, MODE_CODES, EOL_BITS,
|
|
769
|
+
codeForRun, encode1DLine, encode2DLine,
|
|
770
|
+
decode1DLine, decode2DLine,
|
|
771
|
+
makeBitReader, makeBitWriter,
|
|
772
|
+
findB1, findNextChange, findChangeFrom
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
};
|