@jasy/pdf 1.0.0-alpha.1 → 1.0.0-alpha.11
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/LICENSE +21 -0
- package/README.md +35 -21
- package/dist/api/args.d.ts +1 -1
- package/dist/api/args.js +2 -5
- package/dist/api/color.d.ts +4 -4
- package/dist/api/color.js +11 -17
- package/dist/api/content.d.ts +20 -13
- package/dist/api/content.js +44 -29
- package/dist/api/descriptor.d.ts +2 -2
- package/dist/api/descriptor.js +121 -31
- package/dist/api/dimension.d.ts +76 -0
- package/dist/api/dimension.js +43 -0
- package/dist/api/font-url.d.ts +28 -0
- package/dist/api/font-url.js +134 -0
- package/dist/api/forms.d.ts +347 -0
- package/dist/api/forms.js +189 -0
- package/dist/api/gradient.d.ts +53 -0
- package/dist/api/gradient.js +97 -0
- package/dist/api/index.d.ts +13 -8
- package/dist/api/index.js +13 -24
- package/dist/api/insets.d.ts +19 -12
- package/dist/api/insets.js +19 -12
- package/dist/api/layout.d.ts +149 -25
- package/dist/api/layout.js +182 -51
- package/dist/api/page-builder.d.ts +40 -0
- package/dist/api/page-builder.js +44 -0
- package/dist/api/structure.d.ts +107 -13
- package/dist/api/structure.js +187 -88
- package/dist/api/table.d.ts +5 -5
- package/dist/api/table.js +38 -26
- package/dist/api/text.d.ts +90 -4
- package/dist/api/text.js +116 -31
- package/dist/assets/font-data.d.ts +2 -0
- package/dist/assets/font-data.js +21 -0
- package/dist/assets/font-data.ts +37 -0
- package/dist/common/color.js +1 -5
- package/dist/constants/page-sizes.js +3 -6
- package/dist/constants/pdf-parts.js +1 -4
- package/dist/crypto/legacy-handler.d.ts +43 -0
- package/dist/crypto/legacy-handler.js +145 -0
- package/dist/crypto/security-handler.d.ts +62 -0
- package/dist/crypto/security-handler.js +155 -0
- package/dist/crypto/webcrypto.d.ts +11 -0
- package/dist/crypto/webcrypto.js +62 -0
- package/dist/edit/acroform-reader.d.ts +70 -0
- package/dist/edit/acroform-reader.js +126 -0
- package/dist/edit/appearance.d.ts +47 -0
- package/dist/edit/appearance.js +171 -0
- package/dist/edit/document.d.ts +145 -0
- package/dist/edit/document.js +642 -0
- package/dist/edit/fill.d.ts +65 -0
- package/dist/edit/fill.js +424 -0
- package/dist/edit/flatten.d.ts +54 -0
- package/dist/edit/flatten.js +462 -0
- package/dist/edit/index.d.ts +20 -0
- package/dist/edit/index.js +18 -0
- package/dist/edit/lexer.d.ts +30 -0
- package/dist/edit/lexer.js +285 -0
- package/dist/edit/objects.d.ts +68 -0
- package/dist/edit/objects.js +83 -0
- package/dist/edit/writer.d.ts +60 -0
- package/dist/edit/writer.js +283 -0
- package/dist/elements/container-element.d.ts +50 -5
- package/dist/elements/container-element.js +100 -39
- package/dist/elements/forms/checkbox-element.d.ts +51 -0
- package/dist/elements/forms/checkbox-element.js +48 -0
- package/dist/elements/forms/choice-element.d.ts +53 -0
- package/dist/elements/forms/choice-element.js +59 -0
- package/dist/elements/forms/push-button-element.d.ts +56 -0
- package/dist/elements/forms/push-button-element.js +49 -0
- package/dist/elements/forms/radio-element.d.ts +52 -0
- package/dist/elements/forms/radio-element.js +49 -0
- package/dist/elements/forms/signature-element.d.ts +54 -0
- package/dist/elements/forms/signature-element.js +47 -0
- package/dist/elements/forms/text-field-element.d.ts +61 -0
- package/dist/elements/forms/text-field-element.js +54 -0
- package/dist/elements/image-element.d.ts +58 -6
- package/dist/elements/image-element.js +145 -111
- package/dist/elements/index.d.ts +26 -10
- package/dist/elements/index.js +26 -28
- package/dist/elements/layout/anchor-element.d.ts +23 -0
- package/dist/elements/layout/anchor-element.js +27 -0
- package/dist/elements/layout/bookmark-element.d.ts +27 -0
- package/dist/elements/layout/bookmark-element.js +30 -0
- package/dist/elements/layout/default-text-style-element.d.ts +30 -0
- package/dist/elements/layout/default-text-style-element.js +47 -0
- package/dist/elements/layout/deferred-element.d.ts +3 -3
- package/dist/elements/layout/deferred-element.js +4 -8
- package/dist/elements/layout/expanded-element.d.ts +6 -4
- package/dist/elements/layout/expanded-element.js +22 -17
- package/dist/elements/layout/keep-together-element.d.ts +42 -0
- package/dist/elements/layout/keep-together-element.js +85 -0
- package/dist/elements/layout/link-element.d.ts +33 -0
- package/dist/elements/layout/link-element.js +39 -0
- package/dist/elements/layout/padding-element.d.ts +10 -5
- package/dist/elements/layout/padding-element.js +25 -16
- package/dist/elements/layout/page-break-element.d.ts +17 -0
- package/dist/elements/layout/page-break-element.js +24 -0
- package/dist/elements/layout/page-builder-element.d.ts +32 -0
- package/dist/elements/layout/page-builder-element.js +45 -0
- package/dist/elements/layout/positioned-element.d.ts +47 -0
- package/dist/elements/layout/positioned-element.js +73 -0
- package/dist/elements/layout/repeating-header-element.d.ts +3 -3
- package/dist/elements/layout/repeating-header-element.js +8 -12
- package/dist/elements/layout/rotated-box-element.d.ts +35 -0
- package/dist/elements/layout/rotated-box-element.js +54 -0
- package/dist/elements/layout/rotated-element.d.ts +32 -0
- package/dist/elements/layout/rotated-element.js +41 -0
- package/dist/elements/layout/sized-container-element.d.ts +2 -2
- package/dist/elements/layout/sized-container-element.js +6 -11
- package/dist/elements/layout/struct-group.d.ts +28 -0
- package/dist/elements/layout/struct-group.js +42 -0
- package/dist/elements/line-element.d.ts +3 -3
- package/dist/elements/line-element.js +5 -10
- package/dist/elements/page-element.d.ts +26 -6
- package/dist/elements/page-element.js +72 -27
- package/dist/elements/pdf-document-element.d.ts +10 -4
- package/dist/elements/pdf-document-element.js +11 -10
- package/dist/elements/pdf-element.d.ts +150 -4
- package/dist/elements/pdf-element.js +138 -20
- package/dist/elements/rectangle-element.d.ts +52 -11
- package/dist/elements/rectangle-element.js +129 -36
- package/dist/elements/row-element.d.ts +45 -4
- package/dist/elements/row-element.js +66 -18
- package/dist/elements/text-element.d.ts +139 -11
- package/dist/elements/text-element.js +234 -57
- package/dist/forms/acroform.d.ts +63 -0
- package/dist/forms/acroform.js +427 -0
- package/dist/forms/appearance.d.ts +58 -0
- package/dist/forms/appearance.js +204 -0
- package/dist/forms/field.d.ts +164 -0
- package/dist/forms/field.js +1 -0
- package/dist/forms/pdf.d.ts +19 -0
- package/dist/forms/pdf.js +28 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -19
- package/dist/ir/display-list.d.ts +197 -8
- package/dist/ir/display-list.js +8 -2
- package/dist/layout/alignment.d.ts +10 -0
- package/dist/layout/alignment.js +6 -0
- package/dist/layout/box-constraints.d.ts +61 -0
- package/dist/layout/box-constraints.js +88 -6
- package/dist/layout/collect-images.d.ts +9 -0
- package/dist/layout/collect-images.js +34 -0
- package/dist/layout/fragmentation.d.ts +20 -1
- package/dist/layout/fragmentation.js +74 -18
- package/dist/layout/insets.d.ts +28 -0
- package/dist/layout/insets.js +20 -0
- package/dist/platform/browser-fs.d.ts +2 -0
- package/dist/platform/browser-fs.js +9 -0
- package/dist/platform/browser-image.d.ts +5 -0
- package/dist/platform/browser-image.js +13 -0
- package/dist/platform/node-fs.d.ts +2 -0
- package/dist/platform/node-fs.js +10 -0
- package/dist/platform/node-image.d.ts +5 -0
- package/dist/platform/node-image.js +9 -0
- package/dist/renderer/anchor-renderer.d.ts +6 -0
- package/dist/renderer/anchor-renderer.js +11 -0
- package/dist/renderer/bookmark-renderer.d.ts +6 -0
- package/dist/renderer/bookmark-renderer.js +11 -0
- package/dist/renderer/container-renderer.d.ts +3 -3
- package/dist/renderer/container-renderer.js +11 -28
- package/dist/renderer/default-text-style-renderer.d.ts +6 -0
- package/dist/renderer/default-text-style-renderer.js +10 -0
- package/dist/renderer/deferred-renderer.d.ts +3 -3
- package/dist/renderer/deferred-renderer.js +8 -23
- package/dist/renderer/emoji-image.d.ts +9 -0
- package/dist/renderer/emoji-image.js +68 -0
- package/dist/renderer/expanded-renderer.d.ts +3 -3
- package/dist/renderer/expanded-renderer.js +6 -21
- package/dist/renderer/form-field-renderer.d.ts +20 -0
- package/dist/renderer/form-field-renderer.js +12 -0
- package/dist/renderer/image-renderer.d.ts +4 -4
- package/dist/renderer/image-renderer.js +86 -75
- package/dist/renderer/index.d.ts +10 -10
- package/dist/renderer/index.js +10 -26
- package/dist/renderer/keep-together-renderer.d.ts +10 -0
- package/dist/renderer/keep-together-renderer.js +12 -0
- package/dist/renderer/line-renderer.d.ts +3 -3
- package/dist/renderer/line-renderer.js +13 -28
- package/dist/renderer/link-renderer.d.ts +6 -0
- package/dist/renderer/link-renderer.js +11 -0
- package/dist/renderer/padding-renderer.d.ts +3 -3
- package/dist/renderer/padding-renderer.js +6 -21
- package/dist/renderer/page-break-renderer.d.ts +14 -0
- package/dist/renderer/page-break-renderer.js +15 -0
- package/dist/renderer/page-builder-renderer.d.ts +6 -0
- package/dist/renderer/page-builder-renderer.js +11 -0
- package/dist/renderer/page-renderer.d.ts +2 -2
- package/dist/renderer/page-renderer.js +115 -77
- package/dist/renderer/pdf-backend.d.ts +31 -4
- package/dist/renderer/pdf-backend.js +287 -49
- package/dist/renderer/pdf-config.js +4 -7
- package/dist/renderer/pdf-document-class.d.ts +5 -5
- package/dist/renderer/pdf-document-class.js +27 -41
- package/dist/renderer/pdf-document-renderer.d.ts +19 -10
- package/dist/renderer/pdf-document-renderer.js +135 -91
- package/dist/renderer/pdf-renderer.d.ts +2 -2
- package/dist/renderer/pdf-renderer.js +165 -90
- package/dist/renderer/positioned-renderer.d.ts +6 -0
- package/dist/renderer/positioned-renderer.js +10 -0
- package/dist/renderer/rectangle-renderer.d.ts +3 -3
- package/dist/renderer/rectangle-renderer.js +49 -44
- package/dist/renderer/repeating-header-renderer.d.ts +3 -3
- package/dist/renderer/repeating-header-renderer.js +10 -26
- package/dist/renderer/rotated-renderer.d.ts +7 -0
- package/dist/renderer/rotated-renderer.js +29 -0
- package/dist/renderer/row-renderer.d.ts +3 -3
- package/dist/renderer/row-renderer.js +11 -28
- package/dist/renderer/struct-group-renderer.d.ts +10 -0
- package/dist/renderer/struct-group-renderer.js +24 -0
- package/dist/renderer/text-renderer.d.ts +9 -5
- package/dist/renderer/text-renderer.js +437 -89
- package/dist/text/advance.d.ts +34 -0
- package/dist/text/advance.js +45 -0
- package/dist/text/arabic.d.ts +20 -0
- package/dist/text/arabic.js +101 -0
- package/dist/text/bidi.d.ts +33 -0
- package/dist/text/bidi.js +94 -0
- package/dist/text/emoji-codepoints.d.ts +1 -0
- package/dist/text/emoji-codepoints.js +29 -0
- package/dist/text/font-fallback.d.ts +25 -0
- package/dist/text/font-fallback.js +38 -0
- package/dist/text/glyph-coverage.d.ts +15 -0
- package/dist/text/glyph-coverage.js +77 -0
- package/dist/text/line-breaker.d.ts +50 -12
- package/dist/text/line-breaker.js +200 -54
- package/dist/text/line-metrics.d.ts +49 -0
- package/dist/text/line-metrics.js +57 -0
- package/dist/text/orphans-widows.d.ts +27 -0
- package/dist/text/orphans-widows.js +53 -0
- package/dist/text/shape.d.ts +29 -0
- package/dist/text/shape.js +67 -0
- package/dist/text/text-decoration.d.ts +50 -0
- package/dist/text/text-decoration.js +62 -0
- package/dist/text/text-style.d.ts +60 -0
- package/dist/text/text-style.js +79 -0
- package/dist/text/whitespace.d.ts +4 -0
- package/dist/text/whitespace.js +43 -0
- package/dist/utils/afm-parser.d.ts +34 -0
- package/dist/utils/afm-parser.js +76 -17
- package/dist/utils/bytes.d.ts +24 -0
- package/dist/utils/bytes.js +76 -0
- package/dist/utils/dest-registry.d.ts +22 -0
- package/dist/utils/dest-registry.js +32 -0
- package/dist/utils/flex-layout.d.ts +36 -9
- package/dist/utils/flex-layout.js +207 -30
- package/dist/utils/font-metrics.d.ts +26 -1
- package/dist/utils/font-metrics.js +1 -2
- package/dist/utils/font-path.js +3 -6
- package/dist/utils/gsub.d.ts +36 -0
- package/dist/utils/gsub.js +178 -0
- package/dist/utils/image-helper.d.ts +6 -5
- package/dist/utils/image-helper.js +101 -111
- package/dist/utils/inflate.d.ts +16 -0
- package/dist/utils/inflate.js +52 -0
- package/dist/utils/md5.d.ts +4 -0
- package/dist/utils/md5.js +79 -0
- package/dist/utils/outline.d.ts +21 -0
- package/dist/utils/outline.js +79 -0
- package/dist/utils/pdf-object-manager.d.ts +127 -8
- package/dist/utils/pdf-object-manager.js +615 -141
- package/dist/utils/renderer-registry.d.ts +13 -1
- package/dist/utils/renderer-registry.js +23 -6
- package/dist/utils/struct-tree.d.ts +44 -0
- package/dist/utils/struct-tree.js +129 -0
- package/dist/utils/ttf-parser.d.ts +134 -2
- package/dist/utils/ttf-parser.js +873 -50
- package/dist/utils/ttf-subsetter.d.ts +1 -1
- package/dist/utils/ttf-subsetter.js +40 -42
- package/dist/utils/ua-xmp.d.ts +9 -0
- package/dist/utils/ua-xmp.js +32 -0
- package/dist/utils/utf8-to-windows1252-encoder.d.ts +6 -0
- package/dist/utils/utf8-to-windows1252-encoder.js +11 -4
- package/dist/utils/woff.d.ts +10 -0
- package/dist/utils/woff.js +116 -0
- package/dist/validators/element-validator.d.ts +2 -2
- package/dist/validators/element-validator.js +31 -26
- package/package.json +22 -2
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { u16, u32, i16, latin1FromBytes } from "./bytes.js";
|
|
2
|
+
const EXTENSION = 7;
|
|
3
|
+
/** A coverage table may not expand past this: a glyph id is a uint16, so no honest one can. */
|
|
4
|
+
const MAX_COVERAGE_ENTRIES = 65536;
|
|
5
|
+
export class GsubTable {
|
|
6
|
+
constructor(data, base) {
|
|
7
|
+
this.data = data;
|
|
8
|
+
this.base = base;
|
|
9
|
+
this.lookupCache = new Map();
|
|
10
|
+
this.coverageCache = new Map();
|
|
11
|
+
this.scriptList = base + u16(data, base + 4);
|
|
12
|
+
this.featureList = base + u16(data, base + 6);
|
|
13
|
+
this.lookupList = base + u16(data, base + 8);
|
|
14
|
+
}
|
|
15
|
+
/** Whether the font carries this script at all - `arab` is the question the shaper asks. */
|
|
16
|
+
hasScript(script) {
|
|
17
|
+
return this.scriptOffset(script) !== undefined;
|
|
18
|
+
}
|
|
19
|
+
/** A feature's lookup indices, under the script's DEFAULT language system - Arabic shaping features
|
|
20
|
+
* are script-wide, so a language-specific override would only add typographic niceties. */
|
|
21
|
+
lookups(script, feature) {
|
|
22
|
+
const scriptOff = this.scriptOffset(script);
|
|
23
|
+
if (scriptOff === undefined)
|
|
24
|
+
return [];
|
|
25
|
+
// The default LangSys is optional; fall back to the first named one - a font declaring only
|
|
26
|
+
// `ARA ` still shapes Arabic.
|
|
27
|
+
const defaultOff = u16(this.data, scriptOff);
|
|
28
|
+
const langSys = defaultOff !== 0
|
|
29
|
+
? scriptOff + defaultOff
|
|
30
|
+
: u16(this.data, scriptOff + 2) > 0
|
|
31
|
+
? // The first LangSysRecord is at +4 and is tag(4) + offset(2), so its offset is at +8.
|
|
32
|
+
scriptOff + u16(this.data, scriptOff + 8)
|
|
33
|
+
: undefined;
|
|
34
|
+
if (langSys === undefined)
|
|
35
|
+
return [];
|
|
36
|
+
const out = [];
|
|
37
|
+
const count = u16(this.data, langSys + 4);
|
|
38
|
+
for (let i = 0; i < count; i++) {
|
|
39
|
+
const featureIndex = u16(this.data, langSys + 6 + i * 2);
|
|
40
|
+
const record = this.featureList + 2 + featureIndex * 6;
|
|
41
|
+
if (latin1FromBytes(this.data.subarray(record, record + 4)) !== feature)
|
|
42
|
+
continue;
|
|
43
|
+
const featureOff = this.featureList + u16(this.data, record + 4);
|
|
44
|
+
const lookupCount = u16(this.data, featureOff + 2);
|
|
45
|
+
for (let j = 0; j < lookupCount; j++)
|
|
46
|
+
out.push(u16(this.data, featureOff + 4 + j * 2));
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/** Type 1: substitute one glyph. `null` when no subtable covers it - the feature does not apply. */
|
|
51
|
+
substituteSingle(lookupIndex, glyph) {
|
|
52
|
+
const lookup = this.lookup(lookupIndex);
|
|
53
|
+
if (!lookup || lookup.type !== 1)
|
|
54
|
+
return null;
|
|
55
|
+
for (const sub of lookup.subtables) {
|
|
56
|
+
const coverage = this.coverage(sub + u16(this.data, sub + 2));
|
|
57
|
+
const index = coverage.get(glyph);
|
|
58
|
+
if (index === undefined)
|
|
59
|
+
continue;
|
|
60
|
+
// Format 1 shifts every covered glyph by one delta; format 2 lists a replacement per glyph.
|
|
61
|
+
return u16(this.data, sub) === 1
|
|
62
|
+
? (glyph + i16(this.data, sub + 4)) & 0xffff
|
|
63
|
+
: u16(this.data, sub + 6 + index * 2);
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Type 4: collapse a run of glyphs into one, starting at `at`. Longest match wins, as the spec
|
|
69
|
+
* requires - a three-glyph ligature beats the two-glyph one that starts the same way.
|
|
70
|
+
*/
|
|
71
|
+
substituteLigature(lookupIndex, glyphs, at) {
|
|
72
|
+
const lookup = this.lookup(lookupIndex);
|
|
73
|
+
if (!lookup || lookup.type !== 4)
|
|
74
|
+
return null;
|
|
75
|
+
let best = null;
|
|
76
|
+
for (const sub of lookup.subtables) {
|
|
77
|
+
const coverage = this.coverage(sub + u16(this.data, sub + 2));
|
|
78
|
+
const setIndex = coverage.get(glyphs[at]);
|
|
79
|
+
if (setIndex === undefined)
|
|
80
|
+
continue;
|
|
81
|
+
const set = sub + u16(this.data, sub + 6 + setIndex * 2);
|
|
82
|
+
const ligatureCount = u16(this.data, set);
|
|
83
|
+
for (let i = 0; i < ligatureCount; i++) {
|
|
84
|
+
const lig = set + u16(this.data, set + 2 + i * 2);
|
|
85
|
+
const components = u16(this.data, lig + 2); // includes the glyph coverage already matched
|
|
86
|
+
if (at + components > glyphs.length)
|
|
87
|
+
continue;
|
|
88
|
+
let matches = true;
|
|
89
|
+
for (let c = 1; c < components && matches; c++) {
|
|
90
|
+
matches = glyphs[at + c] === u16(this.data, lig + 2 + c * 2);
|
|
91
|
+
}
|
|
92
|
+
if (matches && (best === null || components > best.consumed)) {
|
|
93
|
+
best = { glyph: u16(this.data, lig), consumed: components };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return best;
|
|
98
|
+
}
|
|
99
|
+
/** The type of a lookup, for reporting what a font needs that we do not do. */
|
|
100
|
+
lookupType(lookupIndex) {
|
|
101
|
+
return this.lookup(lookupIndex)?.type;
|
|
102
|
+
}
|
|
103
|
+
scriptOffset(script) {
|
|
104
|
+
const count = u16(this.data, this.scriptList);
|
|
105
|
+
for (let i = 0; i < count; i++) {
|
|
106
|
+
const record = this.scriptList + 2 + i * 6;
|
|
107
|
+
if (latin1FromBytes(this.data.subarray(record, record + 4)) === script) {
|
|
108
|
+
return this.scriptList + u16(this.data, record + 4);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Resolve a lookup, unwrapping type 7 (extension) - which exists only because the 16-bit offsets
|
|
115
|
+
* elsewhere cannot reach far enough in a big font. Callers never learn it was wrapped.
|
|
116
|
+
*/
|
|
117
|
+
lookup(index) {
|
|
118
|
+
const cached = this.lookupCache.get(index);
|
|
119
|
+
if (cached !== undefined)
|
|
120
|
+
return cached;
|
|
121
|
+
let result = null;
|
|
122
|
+
if (index < u16(this.data, this.lookupList)) {
|
|
123
|
+
const off = this.lookupList + u16(this.data, this.lookupList + 2 + index * 2);
|
|
124
|
+
const type = u16(this.data, off);
|
|
125
|
+
const count = u16(this.data, off + 4);
|
|
126
|
+
const subtables = [];
|
|
127
|
+
let realType = type;
|
|
128
|
+
for (let i = 0; i < count; i++) {
|
|
129
|
+
const sub = off + u16(this.data, off + 6 + i * 2);
|
|
130
|
+
if (type === EXTENSION) {
|
|
131
|
+
realType = u16(this.data, sub + 2);
|
|
132
|
+
subtables.push(sub + u32(this.data, sub + 4));
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
subtables.push(sub);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
result = { type: realType, subtables };
|
|
139
|
+
}
|
|
140
|
+
this.lookupCache.set(index, result);
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
/** Coverage table -> glyph id to its INDEX in the table, which is how subtables address glyphs. */
|
|
144
|
+
coverage(off) {
|
|
145
|
+
const cached = this.coverageCache.get(off);
|
|
146
|
+
if (cached)
|
|
147
|
+
return cached;
|
|
148
|
+
const map = new Map();
|
|
149
|
+
const count = u16(this.data, off + 2);
|
|
150
|
+
if (u16(this.data, off) === 1) {
|
|
151
|
+
for (let i = 0; i < count; i++)
|
|
152
|
+
map.set(u16(this.data, off + 4 + i * 2), i);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
// Format 2 is RANGES, and a malformed file can claim far more glyphs than any font holds. Such
|
|
156
|
+
// a table is refused whole rather than expanded - the feature then simply does not apply.
|
|
157
|
+
let total = 0;
|
|
158
|
+
for (let i = 0; i < count; i++) {
|
|
159
|
+
const record = off + 4 + i * 6;
|
|
160
|
+
const start = u16(this.data, record);
|
|
161
|
+
const end = u16(this.data, record + 2);
|
|
162
|
+
if (end < start)
|
|
163
|
+
continue; // an inverted range covers nothing
|
|
164
|
+
total += end - start + 1;
|
|
165
|
+
if (total > MAX_COVERAGE_ENTRIES)
|
|
166
|
+
return this.cacheCoverage(off, new Map());
|
|
167
|
+
const startIndex = u16(this.data, record + 4);
|
|
168
|
+
for (let g = start; g <= end; g++)
|
|
169
|
+
map.set(g, startIndex + (g - start));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return this.cacheCoverage(off, map);
|
|
173
|
+
}
|
|
174
|
+
cacheCoverage(off, map) {
|
|
175
|
+
this.coverageCache.set(off, map);
|
|
176
|
+
return map;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -7,7 +7,7 @@ interface ImageDimensions {
|
|
|
7
7
|
width: number;
|
|
8
8
|
height: number;
|
|
9
9
|
}
|
|
10
|
-
export declare function getImageDimensions(buffer:
|
|
10
|
+
export declare function getImageDimensions(buffer: Uint8Array): Promise<ImageDimensions>;
|
|
11
11
|
/**
|
|
12
12
|
* Converts the given image to grayscale and returns its binary data.
|
|
13
13
|
* @param imagePath Path to the input image file.
|
|
@@ -16,15 +16,16 @@ export declare function getImageDimensions(buffer: Buffer): Promise<ImageDimensi
|
|
|
16
16
|
/**
|
|
17
17
|
* Decodes a PNG into raw DeviceRGB samples, Flate-compressed, ready to embed as a PDF
|
|
18
18
|
* image XObject. A PNG file is NOT a valid `/FlateDecode` stream (it's a signature plus
|
|
19
|
-
* chunks of filtered, zlib-compressed scanlines), so it must be decoded first.
|
|
20
|
-
* no alpha
|
|
19
|
+
* chunks of filtered, zlib-compressed scanlines), so it must be decoded first. DeviceRGB has
|
|
20
|
+
* no alpha itself, so any transparency rides alongside as a DeviceGray `/SMask` (the return's `smask`).
|
|
21
21
|
*/
|
|
22
|
-
export declare function decodePngToRgbFlate(pngBuffer:
|
|
22
|
+
export declare function decodePngToRgbFlate(pngBuffer: Uint8Array): Promise<{
|
|
23
23
|
data: string;
|
|
24
|
+
smask?: string;
|
|
24
25
|
width: number;
|
|
25
26
|
height: number;
|
|
26
27
|
}>;
|
|
27
|
-
export declare function convertImageToGrayscaleBuffer(imagePath: string): Promise<
|
|
28
|
+
export declare function convertImageToGrayscaleBuffer(imagePath: string): Promise<Uint8Array>;
|
|
28
29
|
export interface FitResult {
|
|
29
30
|
width: number;
|
|
30
31
|
height: number;
|
|
@@ -1,23 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.getImageDimensions = getImageDimensions;
|
|
13
|
-
exports.decodePngToRgbFlate = decodePngToRgbFlate;
|
|
14
|
-
exports.convertImageToGrayscaleBuffer = convertImageToGrayscaleBuffer;
|
|
15
|
-
exports.applyContainFit = applyContainFit;
|
|
16
|
-
exports.applyCoverFit = applyCoverFit;
|
|
17
|
-
exports.applyFillFit = applyFillFit;
|
|
18
|
-
exports.applyFitNone = applyFitNone;
|
|
19
|
-
const jimp_1 = require("jimp");
|
|
20
|
-
const zlib_1 = require("zlib");
|
|
1
|
+
import { zlibSync } from "fflate";
|
|
2
|
+
import { latin1FromBytes } from "./bytes.js";
|
|
3
|
+
import { pngToRgba } from "../platform/node-image.js";
|
|
21
4
|
// Implement the method to handle 24-bit integers
|
|
22
5
|
DataView.prototype.getUint24 = function (byteOffset, littleEndian = false) {
|
|
23
6
|
const b1 = this.getUint8(byteOffset);
|
|
@@ -25,61 +8,59 @@ DataView.prototype.getUint24 = function (byteOffset, littleEndian = false) {
|
|
|
25
8
|
const b3 = this.getUint8(byteOffset + 2);
|
|
26
9
|
return littleEndian ? (b3 << 16) | (b2 << 8) | b1 : (b1 << 16) | (b2 << 8) | b3;
|
|
27
10
|
};
|
|
28
|
-
function getImageDimensions(buffer) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
offset += dataView.getUint16(offset, false);
|
|
46
|
-
}
|
|
11
|
+
export async function getImageDimensions(buffer) {
|
|
12
|
+
const dataView = new DataView(buffer.buffer);
|
|
13
|
+
// Check for JPEG (0xFFD8 is the start of JPEG file)
|
|
14
|
+
if (dataView.getUint16(0) === 0xffd8) {
|
|
15
|
+
let offset = 2;
|
|
16
|
+
while (offset < buffer.byteLength) {
|
|
17
|
+
const marker = dataView.getUint16(offset, false);
|
|
18
|
+
offset += 2;
|
|
19
|
+
if (marker === 0xffc0 || marker === 0xffc2) {
|
|
20
|
+
// SOF0 or SOF2
|
|
21
|
+
return {
|
|
22
|
+
height: dataView.getUint16(offset + 3, false),
|
|
23
|
+
width: dataView.getUint16(offset + 5, false),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
offset += dataView.getUint16(offset, false);
|
|
47
28
|
}
|
|
48
29
|
}
|
|
49
|
-
|
|
50
|
-
|
|
30
|
+
}
|
|
31
|
+
// Check for PNG (0x89504E47 is the PNG signature)
|
|
32
|
+
if (dataView.getUint32(0) === 0x89504e47) {
|
|
33
|
+
return {
|
|
34
|
+
width: dataView.getUint32(16, false),
|
|
35
|
+
height: dataView.getUint32(20, false),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// Check for BMP (0x424D is the BMP signature)
|
|
39
|
+
if (dataView.getUint16(0) === 0x424d) {
|
|
40
|
+
return {
|
|
41
|
+
width: dataView.getUint32(18, true),
|
|
42
|
+
height: dataView.getUint32(22, true),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// Check for WebP (0x52494646 is the WebP signature 'RIFF')
|
|
46
|
+
if (dataView.getUint32(0) === 0x52494646 && dataView.getUint32(8) === 0x57454250) {
|
|
47
|
+
// 'WEBP'
|
|
48
|
+
if (dataView.getUint32(12) === 0x56503820) {
|
|
49
|
+
// 'VP8 '
|
|
51
50
|
return {
|
|
52
|
-
width: dataView.
|
|
53
|
-
height: dataView.
|
|
51
|
+
width: dataView.getUint16(26, true),
|
|
52
|
+
height: dataView.getUint16(28, true),
|
|
54
53
|
};
|
|
55
54
|
}
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
else if (dataView.getUint32(12) === 0x56503858) {
|
|
56
|
+
// 'VP8X'
|
|
58
57
|
return {
|
|
59
|
-
width: dataView.
|
|
60
|
-
height: dataView.
|
|
58
|
+
width: dataView.getUint24(24, true) + 1,
|
|
59
|
+
height: dataView.getUint24(27, true) + 1,
|
|
61
60
|
};
|
|
62
61
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// 'WEBP'
|
|
66
|
-
if (dataView.getUint32(12) === 0x56503820) {
|
|
67
|
-
// 'VP8 '
|
|
68
|
-
return {
|
|
69
|
-
width: dataView.getUint16(26, true),
|
|
70
|
-
height: dataView.getUint16(28, true),
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
else if (dataView.getUint32(12) === 0x56503858) {
|
|
74
|
-
// 'VP8X'
|
|
75
|
-
return {
|
|
76
|
-
width: dataView.getUint24(24, true) + 1,
|
|
77
|
-
height: dataView.getUint24(27, true) + 1,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
throw new Error("Unsupported image format");
|
|
82
|
-
});
|
|
62
|
+
}
|
|
63
|
+
throw new Error("Unsupported image format");
|
|
83
64
|
}
|
|
84
65
|
/**
|
|
85
66
|
* Converts the given image to grayscale and returns its binary data.
|
|
@@ -89,50 +70,59 @@ function getImageDimensions(buffer) {
|
|
|
89
70
|
/**
|
|
90
71
|
* Decodes a PNG into raw DeviceRGB samples, Flate-compressed, ready to embed as a PDF
|
|
91
72
|
* image XObject. A PNG file is NOT a valid `/FlateDecode` stream (it's a signature plus
|
|
92
|
-
* chunks of filtered, zlib-compressed scanlines), so it must be decoded first.
|
|
93
|
-
* no alpha
|
|
73
|
+
* chunks of filtered, zlib-compressed scanlines), so it must be decoded first. DeviceRGB has
|
|
74
|
+
* no alpha itself, so any transparency rides alongside as a DeviceGray `/SMask` (the return's `smask`).
|
|
94
75
|
*/
|
|
95
|
-
function decodePngToRgbFlate(pngBuffer) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
76
|
+
export async function decodePngToRgbFlate(pngBuffer) {
|
|
77
|
+
// The decode (PNG bytes → RGBA) is platform-specific: jimp on Node, an OffscreenCanvas in the browser
|
|
78
|
+
// (`platform/{node,browser}-image.ts`, swapped by the package `browser` field). The split below is shared.
|
|
79
|
+
const { width, height, rgba } = await pngToRgba(pngBuffer);
|
|
80
|
+
// Raw DeviceRGB samples + (only when the PNG actually has transparency) a DeviceGray alpha channel that
|
|
81
|
+
// rides as the XObject's /SMask. An opaque PNG yields the same raw RGB as before and no SMask, so it stays
|
|
82
|
+
// byte-identical; a transparent one now composites correctly over whatever sits behind it.
|
|
83
|
+
const rgb = new Uint8Array(width * height * 3);
|
|
84
|
+
const alpha = new Uint8Array(width * height);
|
|
85
|
+
let hasAlpha = false;
|
|
86
|
+
for (let i = 0, j = 0, k = 0; i < rgba.length; i += 4, j += 3, k++) {
|
|
87
|
+
rgb[j] = rgba[i];
|
|
88
|
+
rgb[j + 1] = rgba[i + 1];
|
|
89
|
+
rgb[j + 2] = rgba[i + 2];
|
|
90
|
+
alpha[k] = rgba[i + 3];
|
|
91
|
+
if (rgba[i + 3] !== 255)
|
|
92
|
+
hasAlpha = true;
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
data: latin1FromBytes(zlibSync(rgb)),
|
|
96
|
+
smask: hasAlpha ? latin1FromBytes(zlibSync(alpha)) : undefined,
|
|
97
|
+
width,
|
|
98
|
+
height,
|
|
99
|
+
};
|
|
109
100
|
}
|
|
110
|
-
function convertImageToGrayscaleBuffer(imagePath) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
});
|
|
101
|
+
export async function convertImageToGrayscaleBuffer(imagePath) {
|
|
102
|
+
// We get the image from the buffer (jimp lazily loaded - see decodePngToRgbFlate).
|
|
103
|
+
const { Jimp, JimpMime } = await import("jimp");
|
|
104
|
+
const image = await Jimp.read(imagePath);
|
|
105
|
+
// Get MIME type. If emtpy throw error
|
|
106
|
+
const mime = image.mime;
|
|
107
|
+
if (!mime)
|
|
108
|
+
throw new Error("Cannot read MIME type");
|
|
109
|
+
// We need to check the MIME type (the exact union `getBuffer` accepts).
|
|
110
|
+
let mimeType;
|
|
111
|
+
switch (mime) {
|
|
112
|
+
case JimpMime.png:
|
|
113
|
+
case JimpMime.jpeg:
|
|
114
|
+
case JimpMime.bmp:
|
|
115
|
+
mimeType = mime;
|
|
116
|
+
break;
|
|
117
|
+
default:
|
|
118
|
+
throw new Error("Unsupported MIME type");
|
|
119
|
+
}
|
|
120
|
+
image.greyscale();
|
|
121
|
+
// Convert the image back to buffer with current MIME type
|
|
122
|
+
const grayscaleBuffer = await image.getBuffer(mimeType);
|
|
123
|
+
return grayscaleBuffer;
|
|
134
124
|
}
|
|
135
|
-
function applyContainFit(imageWidth, imageHeight, containerWidth, containerHeight) {
|
|
125
|
+
export function applyContainFit(imageWidth, imageHeight, containerWidth, containerHeight) {
|
|
136
126
|
const imageAspectRatio = imageWidth / imageHeight;
|
|
137
127
|
const containerAspectRatio = containerWidth / containerHeight;
|
|
138
128
|
let width, height, offsetX, offsetY;
|
|
@@ -159,7 +149,7 @@ function applyContainFit(imageWidth, imageHeight, containerWidth, containerHeigh
|
|
|
159
149
|
offsetY,
|
|
160
150
|
};
|
|
161
151
|
}
|
|
162
|
-
function applyCoverFit(imageWidth, imageHeight, containerWidth, containerHeight) {
|
|
152
|
+
export function applyCoverFit(imageWidth, imageHeight, containerWidth, containerHeight) {
|
|
163
153
|
const imageAspectRatio = imageWidth / imageHeight;
|
|
164
154
|
const containerAspectRatio = containerWidth / containerHeight;
|
|
165
155
|
let width, height, offsetX, offsetY;
|
|
@@ -186,7 +176,7 @@ function applyCoverFit(imageWidth, imageHeight, containerWidth, containerHeight)
|
|
|
186
176
|
offsetY,
|
|
187
177
|
};
|
|
188
178
|
}
|
|
189
|
-
function applyFillFit(containerWidth, containerHeight) {
|
|
179
|
+
export function applyFillFit(containerWidth, containerHeight) {
|
|
190
180
|
return {
|
|
191
181
|
width: containerWidth,
|
|
192
182
|
height: containerHeight,
|
|
@@ -194,7 +184,7 @@ function applyFillFit(containerWidth, containerHeight) {
|
|
|
194
184
|
offsetY: 0,
|
|
195
185
|
};
|
|
196
186
|
}
|
|
197
|
-
function applyFitNone(imageWidth, imageHeight, containerWidth, containerHeight) {
|
|
187
|
+
export function applyFitNone(imageWidth, imageHeight, containerWidth, containerHeight) {
|
|
198
188
|
const offsetX = (containerWidth - imageWidth) / 2; // Center horizontally
|
|
199
189
|
const offsetY = (containerHeight - imageHeight) / 2; // Center vertically
|
|
200
190
|
return {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inflating with a ceiling, so a hostile or mistaken input cannot exhaust memory.
|
|
3
|
+
*
|
|
4
|
+
* Used by BOTH sides: the reader inflating a PDF stream, and the font path unpacking a WOFF table.
|
|
5
|
+
* It lives in `utils/` for exactly that reason - the generate path must not import from `edit/`.
|
|
6
|
+
*
|
|
7
|
+
* fflate's `unzlibSync(data, { out })` is NOT usable for this: it truncates silently instead of failing.
|
|
8
|
+
* Hence the streamed form - input in slices, so the total can be checked while it grows.
|
|
9
|
+
*/
|
|
10
|
+
/** Thrown when a stream expands past the ceiling; distinct from a stream that simply will not decode. */
|
|
11
|
+
export declare class PdfStreamTooLargeError extends Error {
|
|
12
|
+
readonly limit: number;
|
|
13
|
+
constructor(limit: number, message: string);
|
|
14
|
+
}
|
|
15
|
+
export declare const DEFAULT_MAX_STREAM_SIZE: number;
|
|
16
|
+
export declare function inflateBounded(data: Uint8Array, limit: number): Uint8Array;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Unzlib, unzlibSync } from "fflate";
|
|
2
|
+
/**
|
|
3
|
+
* Inflating with a ceiling, so a hostile or mistaken input cannot exhaust memory.
|
|
4
|
+
*
|
|
5
|
+
* Used by BOTH sides: the reader inflating a PDF stream, and the font path unpacking a WOFF table.
|
|
6
|
+
* It lives in `utils/` for exactly that reason - the generate path must not import from `edit/`.
|
|
7
|
+
*
|
|
8
|
+
* fflate's `unzlibSync(data, { out })` is NOT usable for this: it truncates silently instead of failing.
|
|
9
|
+
* Hence the streamed form - input in slices, so the total can be checked while it grows.
|
|
10
|
+
*/
|
|
11
|
+
/** Thrown when a stream expands past the ceiling; distinct from a stream that simply will not decode. */
|
|
12
|
+
export class PdfStreamTooLargeError extends Error {
|
|
13
|
+
constructor(limit, message) {
|
|
14
|
+
super(`@jasy/pdf: ${message}`);
|
|
15
|
+
this.limit = limit;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export const DEFAULT_MAX_STREAM_SIZE = 64 * 1024 * 1024;
|
|
19
|
+
/** Only the granularity of the check: fflate buffers internally, so the abort lands one block past. */
|
|
20
|
+
const INPUT_SLICE = 16 * 1024;
|
|
21
|
+
/** DEFLATE's maximum expansion (a 258-byte match in its shortest encoding). A format property. */
|
|
22
|
+
const MAX_DEFLATE_RATIO = 1032;
|
|
23
|
+
export function inflateBounded(data, limit) {
|
|
24
|
+
// Too small to reach the ceiling even at maximum compression, so checking as it grows is pointless.
|
|
25
|
+
// Nearly every stream in a real PDF lands here, which is why the guard costs nothing.
|
|
26
|
+
if (data.length <= limit / MAX_DEFLATE_RATIO)
|
|
27
|
+
return unzlibSync(data);
|
|
28
|
+
const parts = [];
|
|
29
|
+
let total = 0;
|
|
30
|
+
const inflater = new Unzlib();
|
|
31
|
+
inflater.ondata = (chunk) => {
|
|
32
|
+
total += chunk.length;
|
|
33
|
+
if (total > limit) {
|
|
34
|
+
throw new PdfStreamTooLargeError(limit, `a stream in this PDF expands past the ${limit} byte limit for a single stream; if the file is ` +
|
|
35
|
+
`genuinely that large, raise it with { maxStreamSize }`);
|
|
36
|
+
}
|
|
37
|
+
parts.push(chunk);
|
|
38
|
+
};
|
|
39
|
+
for (let at = 0; at < data.length; at += INPUT_SLICE) {
|
|
40
|
+
const end = Math.min(at + INPUT_SLICE, data.length);
|
|
41
|
+
inflater.push(data.subarray(at, end), end >= data.length);
|
|
42
|
+
}
|
|
43
|
+
if (parts.length === 1)
|
|
44
|
+
return parts[0];
|
|
45
|
+
const out = new Uint8Array(total);
|
|
46
|
+
let at = 0;
|
|
47
|
+
for (const part of parts) {
|
|
48
|
+
out.set(part, at);
|
|
49
|
+
at += part.length;
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// MD5 (RFC 1321), vendored + isomorphic - so the font subset tag and the documentId /ID hash work in the
|
|
2
|
+
// browser too: Node `crypto` is absent there, and Web Crypto has no MD5 and is async. Verified byte-for-byte
|
|
3
|
+
// identical to Node's `createHash("md5")`. Operates on bytes; callers UTF-8-encode strings (TextEncoder),
|
|
4
|
+
// matching `hash.update(string)`'s default encoding.
|
|
5
|
+
// Per-round left-rotate amounts.
|
|
6
|
+
const S = [
|
|
7
|
+
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14,
|
|
8
|
+
20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6,
|
|
9
|
+
10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
|
|
10
|
+
];
|
|
11
|
+
// Per-round constants K[i] = floor(abs(sin(i+1)) * 2^32).
|
|
12
|
+
const K = new Int32Array(64);
|
|
13
|
+
for (let i = 0; i < 64; i++)
|
|
14
|
+
K[i] = Math.floor(Math.abs(Math.sin(i + 1)) * 2 ** 32) | 0;
|
|
15
|
+
/** The raw 16-byte MD5 digest of `input`. */
|
|
16
|
+
export function md5(input) {
|
|
17
|
+
const len = input.length;
|
|
18
|
+
// Pad to a multiple of 64: message + 0x80 + zeros + 64-bit little-endian bit length.
|
|
19
|
+
const total = (((len + 8) >> 6) + 1) << 6;
|
|
20
|
+
const b = new Uint8Array(total);
|
|
21
|
+
b.set(input);
|
|
22
|
+
b[len] = 0x80;
|
|
23
|
+
const bits = len * 8;
|
|
24
|
+
b[total - 8] = bits & 0xff;
|
|
25
|
+
b[total - 7] = (bits >>> 8) & 0xff;
|
|
26
|
+
b[total - 6] = (bits >>> 16) & 0xff;
|
|
27
|
+
b[total - 5] = (bits >>> 24) & 0xff;
|
|
28
|
+
// High 32 bits of the length stay 0 (inputs are well under 512 MB).
|
|
29
|
+
let a0 = 0x67452301, b0 = 0xefcdab89, c0 = 0x98badcfe, d0 = 0x10325476;
|
|
30
|
+
const M = new Int32Array(16);
|
|
31
|
+
for (let off = 0; off < total; off += 64) {
|
|
32
|
+
for (let i = 0; i < 16; i++) {
|
|
33
|
+
const j = off + i * 4;
|
|
34
|
+
M[i] = b[j] | (b[j + 1] << 8) | (b[j + 2] << 16) | (b[j + 3] << 24);
|
|
35
|
+
}
|
|
36
|
+
let A = a0, B = b0, C = c0, D = d0;
|
|
37
|
+
for (let i = 0; i < 64; i++) {
|
|
38
|
+
let f, g;
|
|
39
|
+
if (i < 16) {
|
|
40
|
+
f = (B & C) | (~B & D);
|
|
41
|
+
g = i;
|
|
42
|
+
}
|
|
43
|
+
else if (i < 32) {
|
|
44
|
+
f = (D & B) | (~D & C);
|
|
45
|
+
g = (5 * i + 1) % 16;
|
|
46
|
+
}
|
|
47
|
+
else if (i < 48) {
|
|
48
|
+
f = B ^ C ^ D;
|
|
49
|
+
g = (3 * i + 5) % 16;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
f = C ^ (B | ~D);
|
|
53
|
+
g = (7 * i) % 16;
|
|
54
|
+
}
|
|
55
|
+
f = (f + A + K[i] + M[g]) | 0;
|
|
56
|
+
A = D;
|
|
57
|
+
D = C;
|
|
58
|
+
C = B;
|
|
59
|
+
B = (B + ((f << S[i]) | (f >>> (32 - S[i])) | 0)) | 0;
|
|
60
|
+
}
|
|
61
|
+
a0 = (a0 + A) | 0;
|
|
62
|
+
b0 = (b0 + B) | 0;
|
|
63
|
+
c0 = (c0 + C) | 0;
|
|
64
|
+
d0 = (d0 + D) | 0;
|
|
65
|
+
}
|
|
66
|
+
const out = new Uint8Array(16);
|
|
67
|
+
const w = [a0, b0, c0, d0];
|
|
68
|
+
for (let i = 0; i < 4; i++) {
|
|
69
|
+
out[i * 4] = w[i] & 0xff;
|
|
70
|
+
out[i * 4 + 1] = (w[i] >>> 8) & 0xff;
|
|
71
|
+
out[i * 4 + 2] = (w[i] >>> 16) & 0xff;
|
|
72
|
+
out[i * 4 + 3] = (w[i] >>> 24) & 0xff;
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
/** Lowercase hex of the MD5 digest (matches `digest("hex")`). */
|
|
77
|
+
export function md5Hex(input) {
|
|
78
|
+
return [...md5(input)].map((x) => x.toString(16).padStart(2, "0")).join("");
|
|
79
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PDFObjectManager } from "./pdf-object-manager.js";
|
|
2
|
+
interface Entry {
|
|
3
|
+
title: string;
|
|
4
|
+
level: number;
|
|
5
|
+
pageObjNum: number;
|
|
6
|
+
top: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Builds the PDF document outline (`/Outlines`) - the viewer's bookmark panel. Entries are collected
|
|
10
|
+
* flat in reading order with a 1-based `level`; `finalize` nests each entry under the nearest preceding
|
|
11
|
+
* entry of a smaller level (so level 2s hang under the last level 1), emits the outline objects and
|
|
12
|
+
* returns the catalog addition `/Outlines N 0 R`. A no-op returning "" when nothing was added, so a
|
|
13
|
+
* document without bookmarks stays byte-identical.
|
|
14
|
+
*/
|
|
15
|
+
export declare class OutlineBuilder {
|
|
16
|
+
private entries;
|
|
17
|
+
add(entry: Entry): void;
|
|
18
|
+
get isEmpty(): boolean;
|
|
19
|
+
finalize(om: PDFObjectManager): string;
|
|
20
|
+
}
|
|
21
|
+
export {};
|