reamkit 1.6.0 → 1.9.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/README.md +13 -1
- package/dist/esm/core/converter/ream.d.ts +1 -0
- package/dist/esm/core/converter/ream.js +1 -1
- package/dist/esm/core/document-model/types.d.ts +3 -1
- package/dist/esm/core/drawingml/chart-serializer.d.ts +2 -0
- package/dist/esm/core/drawingml/chart-serializer.js +53 -0
- package/dist/esm/core/drawingml/shape-render.d.ts +3 -1
- package/dist/esm/core/drawingml/shape-render.js +27 -1
- package/dist/esm/core/vector.d.ts +10 -0
- package/dist/esm/excel/print-model.js +6 -3
- package/dist/esm/excel/xlsx-writer.js +81 -17
- package/dist/esm/html/html-writer.js +4 -2
- package/dist/esm/layout/styled-layout.js +5 -2
- package/dist/esm/pdf/shading.d.ts +12 -0
- package/dist/esm/pdf/shading.js +152 -0
- package/dist/esm/pdf/styled-page-emitter.js +29 -4
- package/dist/esm/pdf/vector-graphics.d.ts +1 -1
- package/dist/esm/pdf/vector-graphics.js +5 -3
- package/dist/esm/pdf-reader/ccitt.d.ts +15 -0
- package/dist/esm/pdf-reader/ccitt.js +394 -0
- package/dist/esm/pdf-reader/content.d.ts +43 -1
- package/dist/esm/pdf-reader/content.js +173 -4
- package/dist/esm/pdf-reader/crypto.d.ts +7 -0
- package/dist/esm/pdf-reader/crypto.js +609 -0
- package/dist/esm/pdf-reader/decrypt.d.ts +5 -0
- package/dist/esm/pdf-reader/decrypt.js +211 -0
- package/dist/esm/pdf-reader/document.d.ts +8 -1
- package/dist/esm/pdf-reader/document.js +210 -27
- package/dist/esm/pdf-reader/flow-build.d.ts +16 -1
- package/dist/esm/pdf-reader/flow-build.js +113 -3
- package/dist/esm/pdf-reader/image-decode.d.ts +15 -0
- package/dist/esm/pdf-reader/image-decode.js +550 -0
- package/dist/esm/pdf-reader/images.d.ts +16 -0
- package/dist/esm/pdf-reader/images.js +90 -0
- package/dist/esm/pdf-reader/layout.d.ts +2 -2
- package/dist/esm/pdf-reader/layout.js +86 -14
- package/dist/esm/pdf-reader/png-encode.d.ts +2 -0
- package/dist/esm/pdf-reader/png-encode.js +98 -0
- package/dist/esm/pdf-reader/predictor.d.ts +7 -0
- package/dist/esm/pdf-reader/predictor.js +61 -0
- package/dist/esm/pdf-reader/reader.d.ts +1 -1
- package/dist/esm/pdf-reader/reader.js +14 -7
- package/dist/esm/pdf-reader/shading.d.ts +3 -0
- package/dist/esm/pdf-reader/shading.js +147 -0
- package/dist/esm/pdf-reader/tagged.d.ts +2 -2
- package/dist/esm/pdf-reader/tagged.js +60 -7
- package/dist/esm/pdf-reader/text.js +91 -10
- package/dist/esm/pdf-reader/vector.d.ts +16 -0
- package/dist/esm/pdf-reader/vector.js +63 -0
- package/dist/esm/svg/svg-writer.js +12 -5
- package/dist/esm/word/docx-writer.js +104 -8
- package/dist/esm/word/drawing-parser.js +28 -17
- package/dist/esm/word/omml-serializer.d.ts +2 -0
- package/dist/esm/word/omml-serializer.js +48 -0
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { pt } from "../core/ir/units.js";
|
|
1
2
|
import { ResourceStore } from "../core/ir/resources.js";
|
|
2
3
|
import { EMPTY_STYLE_SHEET, resolveBodyStyles } from "../core/style-cascade/resolver.js";
|
|
3
4
|
import "../core/style-cascade/index.js";
|
|
@@ -14,14 +15,123 @@ function paragraphBlock(text, outlineLevel) {
|
|
|
14
15
|
}
|
|
15
16
|
};
|
|
16
17
|
}
|
|
17
|
-
function
|
|
18
|
+
function paragraphFromRuns(spans, outlineLevel) {
|
|
19
|
+
const merged = [];
|
|
20
|
+
for (const s of spans) {
|
|
21
|
+
const last = merged[merged.length - 1];
|
|
22
|
+
if (last && last.href === s.href) last.text += s.text;
|
|
23
|
+
else if (s.href !== void 0) merged.push({
|
|
24
|
+
text: s.text,
|
|
25
|
+
href: s.href
|
|
26
|
+
});
|
|
27
|
+
else merged.push({ text: s.text });
|
|
28
|
+
}
|
|
29
|
+
const runs = merged.map((m) => ({
|
|
30
|
+
text: m.text.replace(/\s+/g, " "),
|
|
31
|
+
href: m.href
|
|
32
|
+
})).filter((m) => m.text.length > 0);
|
|
33
|
+
if (runs.length > 0) {
|
|
34
|
+
runs[0].text = runs[0].text.replace(/^ /, "");
|
|
35
|
+
runs[runs.length - 1].text = runs[runs.length - 1].text.replace(/ $/, "");
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
kind: "paragraph",
|
|
39
|
+
paragraph: {
|
|
40
|
+
properties: outlineLevel !== void 0 ? { outlineLevel } : {},
|
|
41
|
+
runs: runs.filter((r) => r.text.length > 0).map((r) => ({
|
|
42
|
+
text: r.text,
|
|
43
|
+
properties: {},
|
|
44
|
+
...r.href ? { href: r.href } : {}
|
|
45
|
+
}))
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function imageBlock(image, resources, alt) {
|
|
50
|
+
return {
|
|
51
|
+
kind: "image",
|
|
52
|
+
image: {
|
|
53
|
+
resource: resources.put(image.bytes),
|
|
54
|
+
width: pt(image.widthPt),
|
|
55
|
+
height: pt(image.heightPt),
|
|
56
|
+
paragraphProperties: {},
|
|
57
|
+
...alt ? { altText: alt } : {}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function dedupeLosses(losses) {
|
|
62
|
+
const byDetail = /* @__PURE__ */ new Map();
|
|
63
|
+
for (const loss of losses) if (!byDetail.has(loss.detail)) byDetail.set(loss.detail, loss);
|
|
64
|
+
return [...byDetail.values()];
|
|
65
|
+
}
|
|
66
|
+
function shapeBlock(v) {
|
|
67
|
+
const w = v.maxX - v.minX;
|
|
68
|
+
const h = v.maxY - v.minY;
|
|
69
|
+
const fx = (x) => x - v.minX;
|
|
70
|
+
const fy = (y) => v.maxY - y;
|
|
71
|
+
const commands = v.segs.map((s) => {
|
|
72
|
+
switch (s.op) {
|
|
73
|
+
case "move": return {
|
|
74
|
+
cmd: "move",
|
|
75
|
+
x: fx(s.x),
|
|
76
|
+
y: fy(s.y)
|
|
77
|
+
};
|
|
78
|
+
case "line": return {
|
|
79
|
+
cmd: "line",
|
|
80
|
+
x: fx(s.x),
|
|
81
|
+
y: fy(s.y)
|
|
82
|
+
};
|
|
83
|
+
case "cubic": return {
|
|
84
|
+
cmd: "cubic",
|
|
85
|
+
x1: fx(s.x1),
|
|
86
|
+
y1: fy(s.y1),
|
|
87
|
+
x2: fx(s.x2),
|
|
88
|
+
y2: fy(s.y2),
|
|
89
|
+
x: fx(s.x),
|
|
90
|
+
y: fy(s.y)
|
|
91
|
+
};
|
|
92
|
+
case "close": return { cmd: "close" };
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
const thick = v.strokeHex !== void 0 ? Math.max(v.lineWidth ?? .75, .5) : 0;
|
|
96
|
+
const fill = v.gradient !== void 0 ? {
|
|
97
|
+
kind: "gradient",
|
|
98
|
+
gradient: v.gradient
|
|
99
|
+
} : v.fillHex !== void 0 ? {
|
|
100
|
+
kind: "solid",
|
|
101
|
+
colorHex: v.fillHex
|
|
102
|
+
} : { kind: "none" };
|
|
103
|
+
const line = v.strokeHex !== void 0 ? {
|
|
104
|
+
width: pt(thick),
|
|
105
|
+
colorHex: v.strokeHex,
|
|
106
|
+
fill: "solid"
|
|
107
|
+
} : void 0;
|
|
108
|
+
return {
|
|
109
|
+
kind: "shape",
|
|
110
|
+
shape: {
|
|
111
|
+
width: pt(Math.max(w, thick)),
|
|
112
|
+
height: pt(Math.max(h, thick)),
|
|
113
|
+
geometry: {
|
|
114
|
+
kind: "custom",
|
|
115
|
+
custom: {
|
|
116
|
+
pathWidth: w,
|
|
117
|
+
pathHeight: h,
|
|
118
|
+
commands
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
fill,
|
|
122
|
+
...line ? { line } : {},
|
|
123
|
+
paragraphProperties: {}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function buildFlowDoc(body, resources = new ResourceStore()) {
|
|
18
128
|
return {
|
|
19
129
|
kind: "flow",
|
|
20
130
|
body: resolveBodyStyles([...body], EMPTY_STYLE_SHEET),
|
|
21
131
|
sections: [],
|
|
22
132
|
styles: EMPTY_STYLE_SHEET,
|
|
23
|
-
resources
|
|
133
|
+
resources
|
|
24
134
|
};
|
|
25
135
|
}
|
|
26
136
|
//#endregion
|
|
27
|
-
export { buildFlowDoc, paragraphBlock };
|
|
137
|
+
export { buildFlowDoc, dedupeLosses, imageBlock, paragraphBlock, paragraphFromRuns, shapeBlock };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PdfFile } from './document.js';
|
|
2
|
+
import { PdfStream } from '../pdf/objects.js';
|
|
3
|
+
export type DecodedImage = {
|
|
4
|
+
readonly ok: true;
|
|
5
|
+
readonly bytes: Uint8Array;
|
|
6
|
+
readonly format: 'png' | 'jpeg' | 'jpeg2000';
|
|
7
|
+
readonly widthPx: number;
|
|
8
|
+
readonly heightPx: number;
|
|
9
|
+
readonly degraded?: string;
|
|
10
|
+
} | {
|
|
11
|
+
readonly ok: false;
|
|
12
|
+
readonly severity: 'dropped' | 'degraded';
|
|
13
|
+
readonly detail: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function decodePdfImage(file: PdfFile, stream: PdfStream): DecodedImage;
|
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
import { PDF_NULL, PdfHexString, PdfName, PdfStream } from "../pdf/objects.js";
|
|
2
|
+
import { reversePredictor } from "./predictor.js";
|
|
3
|
+
import { decodeCcitt } from "./ccitt.js";
|
|
4
|
+
import { encodePng } from "./png-encode.js";
|
|
5
|
+
import { unzlibSync } from "fflate";
|
|
6
|
+
//#region src/pdf-reader/image-decode.ts
|
|
7
|
+
var MAX_PIXELS = 4e7;
|
|
8
|
+
function decodePdfImage(file, stream) {
|
|
9
|
+
const d = stream.dict;
|
|
10
|
+
const width = intOf(file.get(d, "Width")) || intOf(file.get(d, "W"));
|
|
11
|
+
const height = intOf(file.get(d, "Height")) || intOf(file.get(d, "H"));
|
|
12
|
+
if (width <= 0 || height <= 0) return fail("dropped", "image with no dimensions");
|
|
13
|
+
if (width * height > MAX_PIXELS) return fail("dropped", "image too large to decode");
|
|
14
|
+
if (boolOf(d.get("ImageMask")) || boolOf(d.get("IM"))) return fail("dropped", "stencil image mask not reconstructed");
|
|
15
|
+
const filters = filterNames(file, d);
|
|
16
|
+
const last = filters[filters.length - 1];
|
|
17
|
+
if (last === "DCTDecode" || last === "DCT") {
|
|
18
|
+
const degraded = hasSMask(file, d) ? "image transparency dropped (JPEG carries no alpha)" : void 0;
|
|
19
|
+
return {
|
|
20
|
+
ok: true,
|
|
21
|
+
bytes: applyChainExceptLast(filters, stream.data),
|
|
22
|
+
format: "jpeg",
|
|
23
|
+
widthPx: width,
|
|
24
|
+
heightPx: height,
|
|
25
|
+
...degraded ? { degraded } : {}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (last === "JPXDecode") return {
|
|
29
|
+
ok: true,
|
|
30
|
+
bytes: applyChainExceptLast(filters, stream.data),
|
|
31
|
+
format: "jpeg2000",
|
|
32
|
+
widthPx: width,
|
|
33
|
+
heightPx: height,
|
|
34
|
+
degraded: "JPEG 2000 image — limited viewer support"
|
|
35
|
+
};
|
|
36
|
+
if (last === "JBIG2Decode") return fail("dropped", "JBIG2-encoded image not decoded");
|
|
37
|
+
const decoded = last === "CCITTFaxDecode" || last === "CCF" ? decodeCcittImage(file, stream, filters, width, height) : decodeToSamples(file, stream, filters, width, height);
|
|
38
|
+
if (typeof decoded === "string") return fail("dropped", decoded);
|
|
39
|
+
const alpha = decodeSMask(file, d, width, height);
|
|
40
|
+
const { color, samples } = alpha ? combineAlpha(decoded, alpha) : decoded;
|
|
41
|
+
return {
|
|
42
|
+
ok: true,
|
|
43
|
+
bytes: encodePng(width, height, color, samples),
|
|
44
|
+
format: "png",
|
|
45
|
+
widthPx: width,
|
|
46
|
+
heightPx: height
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function decodeToSamples(file, stream, filters, width, height) {
|
|
50
|
+
const d = stream.dict;
|
|
51
|
+
const raw = decodeChain(file, stream, filters);
|
|
52
|
+
if (!raw) return "undecodable image stream";
|
|
53
|
+
const cs = resolveColorSpace(file, d.get("ColorSpace") ?? d.get("CS"));
|
|
54
|
+
if (!cs) return "unsupported image colour space";
|
|
55
|
+
const bpc = intOf(file.get(d, "BitsPerComponent")) || intOf(file.get(d, "BPC")) || 8;
|
|
56
|
+
const decodeArr = decodeArrayOf(file, d);
|
|
57
|
+
return toColor(cs, unpackSamples(raw, width, height, cs.components, bpc), width * height, bpc, decodeArr);
|
|
58
|
+
}
|
|
59
|
+
function decodeCcittImage(file, stream, filters, width, height) {
|
|
60
|
+
const parms = decodeParmsOf(file, stream.dict);
|
|
61
|
+
const columns = (parms ? intOf(file.get(parms, "Columns")) : 0) || 1728;
|
|
62
|
+
const packed = decodeCcitt(applyChainExceptLast(filters, stream.data), {
|
|
63
|
+
k: parms ? intOf(file.get(parms, "K")) : 0,
|
|
64
|
+
columns,
|
|
65
|
+
rows: height,
|
|
66
|
+
byteAlign: parms ? boolOf(file.get(parms, "EncodedByteAlign")) : false
|
|
67
|
+
});
|
|
68
|
+
if (!packed) return "CCITT fax image not decoded (Group 3 2-D or malformed)";
|
|
69
|
+
const rowBytes = columns + 7 >> 3;
|
|
70
|
+
const samples = new Uint8Array(width * height);
|
|
71
|
+
for (let y = 0; y < height; y++) {
|
|
72
|
+
const rowOff = y * rowBytes;
|
|
73
|
+
for (let x = 0; x < width; x++) {
|
|
74
|
+
const black = x < columns ? packed[rowOff + (x >> 3)] >> 7 - (x & 7) & 1 : 0;
|
|
75
|
+
samples[y * width + x] = black ? 0 : 255;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
color: "gray",
|
|
80
|
+
samples
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function resolveColorSpace(file, csVal) {
|
|
84
|
+
const cs = file.resolve(csVal ?? PDF_NULL);
|
|
85
|
+
if (cs instanceof PdfName) return namedColorSpace(cs.value);
|
|
86
|
+
if (!Array.isArray(cs) || cs.length === 0) return void 0;
|
|
87
|
+
const head = file.resolve(cs[0]);
|
|
88
|
+
const tag = head instanceof PdfName ? head.value : "";
|
|
89
|
+
if (tag === "ICCBased") {
|
|
90
|
+
const profile = file.resolve(cs[1]);
|
|
91
|
+
if (profile instanceof PdfStream) {
|
|
92
|
+
const n = intOf(file.get(profile.dict, "N"));
|
|
93
|
+
if (n === 1) return {
|
|
94
|
+
kind: "gray",
|
|
95
|
+
components: 1
|
|
96
|
+
};
|
|
97
|
+
if (n === 3) return {
|
|
98
|
+
kind: "rgb",
|
|
99
|
+
components: 3
|
|
100
|
+
};
|
|
101
|
+
if (n === 4) return {
|
|
102
|
+
kind: "cmyk",
|
|
103
|
+
components: 4
|
|
104
|
+
};
|
|
105
|
+
const alt = resolveColorSpace(file, profile.dict.get("Alternate"));
|
|
106
|
+
if (alt) return alt;
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (tag === "CalGray" || tag === "G") return {
|
|
111
|
+
kind: "gray",
|
|
112
|
+
components: 1
|
|
113
|
+
};
|
|
114
|
+
if (tag === "CalRGB" || tag === "RGB") return {
|
|
115
|
+
kind: "rgb",
|
|
116
|
+
components: 3
|
|
117
|
+
};
|
|
118
|
+
if (tag === "Indexed" || tag === "I") {
|
|
119
|
+
const base = resolveColorSpace(file, cs[1]);
|
|
120
|
+
const hival = intOf(file.resolve(cs[2] ?? PDF_NULL));
|
|
121
|
+
const lookup = valueToBytes(file, cs[3]);
|
|
122
|
+
if (!base || base.kind === "indexed" || !lookup) return void 0;
|
|
123
|
+
return {
|
|
124
|
+
kind: "indexed",
|
|
125
|
+
components: 1,
|
|
126
|
+
base,
|
|
127
|
+
hival,
|
|
128
|
+
lookup
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function namedColorSpace(name) {
|
|
133
|
+
if (name === "DeviceGray" || name === "G" || name === "CalGray") return {
|
|
134
|
+
kind: "gray",
|
|
135
|
+
components: 1
|
|
136
|
+
};
|
|
137
|
+
if (name === "DeviceRGB" || name === "RGB" || name === "CalRGB") return {
|
|
138
|
+
kind: "rgb",
|
|
139
|
+
components: 3
|
|
140
|
+
};
|
|
141
|
+
if (name === "DeviceCMYK" || name === "CMYK") return {
|
|
142
|
+
kind: "cmyk",
|
|
143
|
+
components: 4
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function toColor(cs, s, px, bpc, decode) {
|
|
147
|
+
const maxv = (1 << Math.min(bpc, 15)) * (bpc >= 16 ? 2 : 1) - 1;
|
|
148
|
+
const c01 = (v, comp) => {
|
|
149
|
+
const t = maxv > 0 ? v / maxv : 0;
|
|
150
|
+
if (decode && decode.length >= (comp + 1) * 2) {
|
|
151
|
+
const dmin = decode[comp * 2];
|
|
152
|
+
const dmax = decode[comp * 2 + 1];
|
|
153
|
+
return clamp01(dmin + t * (dmax - dmin));
|
|
154
|
+
}
|
|
155
|
+
return t;
|
|
156
|
+
};
|
|
157
|
+
const to8 = (v, comp) => Math.round(c01(v, comp) * 255);
|
|
158
|
+
if (cs.kind === "gray") {
|
|
159
|
+
const out = new Uint8Array(px);
|
|
160
|
+
for (let i = 0; i < px; i++) out[i] = to8(s[i], 0);
|
|
161
|
+
return {
|
|
162
|
+
color: "gray",
|
|
163
|
+
samples: out
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (cs.kind === "rgb") {
|
|
167
|
+
const out = new Uint8Array(px * 3);
|
|
168
|
+
for (let i = 0; i < px; i++) {
|
|
169
|
+
out[i * 3] = to8(s[i * 3], 0);
|
|
170
|
+
out[i * 3 + 1] = to8(s[i * 3 + 1], 1);
|
|
171
|
+
out[i * 3 + 2] = to8(s[i * 3 + 2], 2);
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
color: "rgb",
|
|
175
|
+
samples: out
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
if (cs.kind === "cmyk") {
|
|
179
|
+
const out = new Uint8Array(px * 3);
|
|
180
|
+
for (let i = 0; i < px; i++) {
|
|
181
|
+
const c = c01(s[i * 4], 0);
|
|
182
|
+
const m = c01(s[i * 4 + 1], 1);
|
|
183
|
+
const y = c01(s[i * 4 + 2], 2);
|
|
184
|
+
const k = c01(s[i * 4 + 3], 3);
|
|
185
|
+
out[i * 3] = Math.round(255 * (1 - c) * (1 - k));
|
|
186
|
+
out[i * 3 + 1] = Math.round(255 * (1 - m) * (1 - k));
|
|
187
|
+
out[i * 3 + 2] = Math.round(255 * (1 - y) * (1 - k));
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
color: "rgb",
|
|
191
|
+
samples: out
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const base = cs.base;
|
|
195
|
+
const lookup = cs.lookup;
|
|
196
|
+
const hival = cs.hival ?? 255;
|
|
197
|
+
const bn = base.components;
|
|
198
|
+
if (base.kind === "gray") {
|
|
199
|
+
const out = new Uint8Array(px);
|
|
200
|
+
for (let i = 0; i < px; i++) out[i] = lookup[Math.min(s[i], hival) * bn] ?? 0;
|
|
201
|
+
return {
|
|
202
|
+
color: "gray",
|
|
203
|
+
samples: out
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
const out = new Uint8Array(px * 3);
|
|
207
|
+
for (let i = 0; i < px; i++) {
|
|
208
|
+
const [r, g, b] = paletteRgb(base, lookup, Math.min(s[i], hival) * bn);
|
|
209
|
+
out[i * 3] = r;
|
|
210
|
+
out[i * 3 + 1] = g;
|
|
211
|
+
out[i * 3 + 2] = b;
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
color: "rgb",
|
|
215
|
+
samples: out
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function paletteRgb(base, lookup, off) {
|
|
219
|
+
if (base.kind === "rgb") return [
|
|
220
|
+
lookup[off] ?? 0,
|
|
221
|
+
lookup[off + 1] ?? 0,
|
|
222
|
+
lookup[off + 2] ?? 0
|
|
223
|
+
];
|
|
224
|
+
if (base.kind === "cmyk") {
|
|
225
|
+
const c = (lookup[off] ?? 0) / 255;
|
|
226
|
+
const m = (lookup[off + 1] ?? 0) / 255;
|
|
227
|
+
const y = (lookup[off + 2] ?? 0) / 255;
|
|
228
|
+
const k = (lookup[off + 3] ?? 0) / 255;
|
|
229
|
+
return [
|
|
230
|
+
Math.round(255 * (1 - c) * (1 - k)),
|
|
231
|
+
Math.round(255 * (1 - m) * (1 - k)),
|
|
232
|
+
Math.round(255 * (1 - y) * (1 - k))
|
|
233
|
+
];
|
|
234
|
+
}
|
|
235
|
+
const g = lookup[off] ?? 0;
|
|
236
|
+
return [
|
|
237
|
+
g,
|
|
238
|
+
g,
|
|
239
|
+
g
|
|
240
|
+
];
|
|
241
|
+
}
|
|
242
|
+
function unpackSamples(raw, width, height, ncomp, bpc) {
|
|
243
|
+
const perRow = width * ncomp;
|
|
244
|
+
const out = new Uint16Array(perRow * height);
|
|
245
|
+
const rowBytes = Math.ceil(perRow * bpc / 8);
|
|
246
|
+
for (let y = 0; y < height; y++) {
|
|
247
|
+
const rowOff = y * rowBytes;
|
|
248
|
+
const base = y * perRow;
|
|
249
|
+
if (bpc === 8) for (let i = 0; i < perRow; i++) out[base + i] = raw[rowOff + i] ?? 0;
|
|
250
|
+
else if (bpc === 16) for (let i = 0; i < perRow; i++) out[base + i] = (raw[rowOff + 2 * i] ?? 0) << 8 | (raw[rowOff + 2 * i + 1] ?? 0);
|
|
251
|
+
else {
|
|
252
|
+
const mask = (1 << bpc) - 1;
|
|
253
|
+
let bit = 0;
|
|
254
|
+
for (let i = 0; i < perRow; i++) {
|
|
255
|
+
const bytePos = rowOff + (bit >> 3);
|
|
256
|
+
const shift = 8 - bpc - (bit & 7);
|
|
257
|
+
out[base + i] = ((raw[bytePos] ?? 0) >> shift & mask) >>> 0;
|
|
258
|
+
bit += bpc;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return out;
|
|
263
|
+
}
|
|
264
|
+
function decodeSMask(file, d, width, height) {
|
|
265
|
+
const sm = file.resolve(d.get("SMask") ?? PDF_NULL);
|
|
266
|
+
if (!(sm instanceof PdfStream)) return void 0;
|
|
267
|
+
const sw = intOf(file.get(sm.dict, "Width"));
|
|
268
|
+
const sh = intOf(file.get(sm.dict, "Height"));
|
|
269
|
+
if (sw <= 0 || sh <= 0) return void 0;
|
|
270
|
+
const decoded = decodeToSamples(file, sm, filterNames(file, sm.dict), sw, sh);
|
|
271
|
+
if (typeof decoded === "string") return void 0;
|
|
272
|
+
const ch = decoded.color === "rgb" ? 3 : 1;
|
|
273
|
+
const gray = new Uint8Array(sw * sh);
|
|
274
|
+
for (let i = 0; i < sw * sh; i++) gray[i] = decoded.samples[i * ch];
|
|
275
|
+
if (sw === width && sh === height) return { data: gray };
|
|
276
|
+
const out = new Uint8Array(width * height);
|
|
277
|
+
for (let y = 0; y < height; y++) {
|
|
278
|
+
const sy = Math.min(sh - 1, Math.floor(y * sh / height));
|
|
279
|
+
for (let x = 0; x < width; x++) {
|
|
280
|
+
const sx = Math.min(sw - 1, Math.floor(x * sw / width));
|
|
281
|
+
out[y * width + x] = gray[sy * sw + sx];
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return { data: out };
|
|
285
|
+
}
|
|
286
|
+
function combineAlpha(color, alpha) {
|
|
287
|
+
const px = alpha.data.length;
|
|
288
|
+
if (color.color === "gray") {
|
|
289
|
+
const out = new Uint8Array(px * 2);
|
|
290
|
+
for (let i = 0; i < px; i++) {
|
|
291
|
+
out[i * 2] = color.samples[i];
|
|
292
|
+
out[i * 2 + 1] = alpha.data[i];
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
color: "gray-alpha",
|
|
296
|
+
samples: out
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const out = new Uint8Array(px * 4);
|
|
300
|
+
for (let i = 0; i < px; i++) {
|
|
301
|
+
out[i * 4] = color.samples[i * 3];
|
|
302
|
+
out[i * 4 + 1] = color.samples[i * 3 + 1];
|
|
303
|
+
out[i * 4 + 2] = color.samples[i * 3 + 2];
|
|
304
|
+
out[i * 4 + 3] = alpha.data[i];
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
color: "rgba",
|
|
308
|
+
samples: out
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function filterNames(file, d) {
|
|
312
|
+
const f = file.resolve(d.get("Filter") ?? PDF_NULL);
|
|
313
|
+
const arr = Array.isArray(f) ? f : [f];
|
|
314
|
+
const out = [];
|
|
315
|
+
for (const x of arr) {
|
|
316
|
+
const r = file.resolve(x);
|
|
317
|
+
if (r instanceof PdfName) out.push(r.value);
|
|
318
|
+
}
|
|
319
|
+
return out;
|
|
320
|
+
}
|
|
321
|
+
function decodeChain(file, stream, filters) {
|
|
322
|
+
let data = stream.data;
|
|
323
|
+
let mayPredict = false;
|
|
324
|
+
for (const f of filters) if (f === "FlateDecode" || f === "Fl") try {
|
|
325
|
+
data = unzlibSync(data);
|
|
326
|
+
mayPredict = true;
|
|
327
|
+
} catch {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
else if (f === "LZWDecode" || f === "LZW") {
|
|
331
|
+
const dec = lzwDecode(data, lzwEarlyChange(file, stream.dict));
|
|
332
|
+
if (!dec) return void 0;
|
|
333
|
+
data = dec;
|
|
334
|
+
mayPredict = true;
|
|
335
|
+
} else if (f === "RunLengthDecode" || f === "RL") data = runLengthDecode(data);
|
|
336
|
+
else if (f === "ASCII85Decode" || f === "A85") data = ascii85Decode(data);
|
|
337
|
+
else if (f === "ASCIIHexDecode" || f === "AHx") data = asciiHexDecode(data);
|
|
338
|
+
else return;
|
|
339
|
+
return mayPredict ? applyPredictor(file, stream.dict, data) : data;
|
|
340
|
+
}
|
|
341
|
+
function applyChainExceptLast(filters, raw) {
|
|
342
|
+
let data = raw;
|
|
343
|
+
for (let i = 0; i < filters.length - 1; i++) {
|
|
344
|
+
const f = filters[i];
|
|
345
|
+
if (f === "FlateDecode" || f === "Fl") try {
|
|
346
|
+
data = unzlibSync(data);
|
|
347
|
+
} catch {}
|
|
348
|
+
else if (f === "LZWDecode" || f === "LZW") data = lzwDecode(data, 1) ?? data;
|
|
349
|
+
else if (f === "RunLengthDecode" || f === "RL") data = runLengthDecode(data);
|
|
350
|
+
else if (f === "ASCII85Decode" || f === "A85") data = ascii85Decode(data);
|
|
351
|
+
else if (f === "ASCIIHexDecode" || f === "AHx") data = asciiHexDecode(data);
|
|
352
|
+
}
|
|
353
|
+
return data;
|
|
354
|
+
}
|
|
355
|
+
function applyPredictor(file, d, data) {
|
|
356
|
+
const parms = decodeParmsOf(file, d);
|
|
357
|
+
if (!parms) return data;
|
|
358
|
+
const predictor = intOf(file.get(parms, "Predictor"));
|
|
359
|
+
if (predictor < 2) return data;
|
|
360
|
+
return reversePredictor(data, {
|
|
361
|
+
predictor,
|
|
362
|
+
colors: intOf(file.get(parms, "Colors")) || 1,
|
|
363
|
+
bitsPerComponent: intOf(file.get(parms, "BitsPerComponent")) || 8,
|
|
364
|
+
columns: intOf(file.get(parms, "Columns")) || 1
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
var MAX_LZW_OUT = MAX_PIXELS * 4;
|
|
368
|
+
function lzwDecode(data, earlyChange) {
|
|
369
|
+
let out = new Uint8Array(4096);
|
|
370
|
+
let outLen = 0;
|
|
371
|
+
const emit = (e) => {
|
|
372
|
+
if (outLen + e.length > MAX_LZW_OUT) return false;
|
|
373
|
+
if (outLen + e.length > out.length) {
|
|
374
|
+
let cap = out.length * 2;
|
|
375
|
+
while (cap < outLen + e.length) cap *= 2;
|
|
376
|
+
const grown = new Uint8Array(cap);
|
|
377
|
+
grown.set(out.subarray(0, outLen));
|
|
378
|
+
out = grown;
|
|
379
|
+
}
|
|
380
|
+
out.set(e, outLen);
|
|
381
|
+
outLen += e.length;
|
|
382
|
+
return true;
|
|
383
|
+
};
|
|
384
|
+
let bitBuffer = 0;
|
|
385
|
+
let bitCount = 0;
|
|
386
|
+
let pos = 0;
|
|
387
|
+
let codeLength = 9;
|
|
388
|
+
const readCode = () => {
|
|
389
|
+
while (bitCount < codeLength) {
|
|
390
|
+
if (pos >= data.length) return -1;
|
|
391
|
+
bitBuffer = (bitBuffer << 8 | data[pos++]) >>> 0;
|
|
392
|
+
bitCount += 8;
|
|
393
|
+
}
|
|
394
|
+
bitCount -= codeLength;
|
|
395
|
+
return bitBuffer >>> bitCount & (1 << codeLength) - 1;
|
|
396
|
+
};
|
|
397
|
+
const dict = new Array(4096);
|
|
398
|
+
let nextCode = 258;
|
|
399
|
+
let prev = -1;
|
|
400
|
+
const reset = () => {
|
|
401
|
+
for (let i = 0; i < 256; i++) dict[i] = Uint8Array.of(i);
|
|
402
|
+
nextCode = 258;
|
|
403
|
+
codeLength = 9;
|
|
404
|
+
prev = -1;
|
|
405
|
+
};
|
|
406
|
+
reset();
|
|
407
|
+
for (;;) {
|
|
408
|
+
const code = readCode();
|
|
409
|
+
if (code < 0 || code === 257) break;
|
|
410
|
+
if (code === 256) {
|
|
411
|
+
reset();
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
if (prev < 0) {
|
|
415
|
+
const first = dict[code];
|
|
416
|
+
if (!first || !emit(first)) break;
|
|
417
|
+
prev = code;
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
const prevEntry = dict[prev];
|
|
421
|
+
let entry = code < nextCode ? dict[code] : void 0;
|
|
422
|
+
if (!entry) {
|
|
423
|
+
entry = new Uint8Array(prevEntry.length + 1);
|
|
424
|
+
entry.set(prevEntry);
|
|
425
|
+
entry[prevEntry.length] = prevEntry[0];
|
|
426
|
+
}
|
|
427
|
+
if (!emit(entry)) break;
|
|
428
|
+
if (nextCode < 4096) {
|
|
429
|
+
const added = new Uint8Array(prevEntry.length + 1);
|
|
430
|
+
added.set(prevEntry);
|
|
431
|
+
added[prevEntry.length] = entry[0];
|
|
432
|
+
dict[nextCode++] = added;
|
|
433
|
+
if (nextCode + earlyChange === 512) codeLength = 10;
|
|
434
|
+
else if (nextCode + earlyChange === 1024) codeLength = 11;
|
|
435
|
+
else if (nextCode + earlyChange === 2048) codeLength = 12;
|
|
436
|
+
}
|
|
437
|
+
prev = code;
|
|
438
|
+
}
|
|
439
|
+
return out.subarray(0, outLen);
|
|
440
|
+
}
|
|
441
|
+
function lzwEarlyChange(file, d) {
|
|
442
|
+
const ec = decodeParmsOf(file, d)?.get("EarlyChange");
|
|
443
|
+
return ec !== void 0 && file.resolve(ec) === 0 ? 0 : 1;
|
|
444
|
+
}
|
|
445
|
+
function runLengthDecode(data) {
|
|
446
|
+
const out = [];
|
|
447
|
+
let i = 0;
|
|
448
|
+
while (i < data.length) {
|
|
449
|
+
const len = data[i++];
|
|
450
|
+
if (len === 128) break;
|
|
451
|
+
if (len < 128) for (let j = 0; j <= len && i < data.length; j++) out.push(data[i++]);
|
|
452
|
+
else {
|
|
453
|
+
const b = data[i++] ?? 0;
|
|
454
|
+
for (let j = 0; j < 257 - len; j++) out.push(b);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return Uint8Array.from(out);
|
|
458
|
+
}
|
|
459
|
+
function ascii85Decode(data) {
|
|
460
|
+
const out = [];
|
|
461
|
+
let tuple = 0;
|
|
462
|
+
let count = 0;
|
|
463
|
+
for (const c of data) {
|
|
464
|
+
if (c === 126) break;
|
|
465
|
+
if (c <= 32) continue;
|
|
466
|
+
if (c === 122 && count === 0) {
|
|
467
|
+
out.push(0, 0, 0, 0);
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
if (c < 33 || c > 117) continue;
|
|
471
|
+
tuple = tuple * 85 + (c - 33);
|
|
472
|
+
if (++count === 5) {
|
|
473
|
+
out.push(tuple >>> 24 & 255, tuple >>> 16 & 255, tuple >>> 8 & 255, tuple & 255);
|
|
474
|
+
tuple = 0;
|
|
475
|
+
count = 0;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (count > 0) {
|
|
479
|
+
for (let i = count; i < 5; i++) tuple = tuple * 85 + 84;
|
|
480
|
+
for (let i = 0; i < count - 1; i++) out.push(tuple >>> 24 - i * 8 & 255);
|
|
481
|
+
}
|
|
482
|
+
return Uint8Array.from(out);
|
|
483
|
+
}
|
|
484
|
+
function asciiHexDecode(data) {
|
|
485
|
+
const out = [];
|
|
486
|
+
let hi = -1;
|
|
487
|
+
for (const c of data) {
|
|
488
|
+
if (c === 62) break;
|
|
489
|
+
const v = hexVal(c);
|
|
490
|
+
if (v < 0) continue;
|
|
491
|
+
if (hi < 0) hi = v;
|
|
492
|
+
else {
|
|
493
|
+
out.push(hi << 4 | v);
|
|
494
|
+
hi = -1;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (hi >= 0) out.push(hi << 4);
|
|
498
|
+
return Uint8Array.from(out);
|
|
499
|
+
}
|
|
500
|
+
function decodeParmsOf(file, d) {
|
|
501
|
+
const p = file.resolve(d.get("DecodeParms") ?? d.get("DP") ?? PDF_NULL);
|
|
502
|
+
if (p instanceof Map) return p;
|
|
503
|
+
if (Array.isArray(p)) for (const e of p) {
|
|
504
|
+
const r = file.resolve(e);
|
|
505
|
+
if (r instanceof Map) return r;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
function decodeArrayOf(file, d) {
|
|
509
|
+
const v = file.resolve(d.get("Decode") ?? d.get("D") ?? PDF_NULL);
|
|
510
|
+
if (!Array.isArray(v)) return void 0;
|
|
511
|
+
const nums = v.map((x) => typeof x === "number" ? x : NaN);
|
|
512
|
+
return nums.some((n) => !Number.isFinite(n)) ? void 0 : nums;
|
|
513
|
+
}
|
|
514
|
+
function hasSMask(file, d) {
|
|
515
|
+
return file.resolve(d.get("SMask") ?? PDF_NULL) instanceof PdfStream;
|
|
516
|
+
}
|
|
517
|
+
function valueToBytes(file, v) {
|
|
518
|
+
const r = file.resolve(v ?? PDF_NULL);
|
|
519
|
+
if (r instanceof PdfHexString) return r.bytes;
|
|
520
|
+
if (typeof r === "string") {
|
|
521
|
+
const out = new Uint8Array(r.length);
|
|
522
|
+
for (let i = 0; i < r.length; i++) out[i] = r.charCodeAt(i) & 255;
|
|
523
|
+
return out;
|
|
524
|
+
}
|
|
525
|
+
if (r instanceof PdfStream) return file.streamData(r);
|
|
526
|
+
}
|
|
527
|
+
function intOf(v) {
|
|
528
|
+
return typeof v === "number" ? Math.round(v) : 0;
|
|
529
|
+
}
|
|
530
|
+
function boolOf(v) {
|
|
531
|
+
return v === true;
|
|
532
|
+
}
|
|
533
|
+
function clamp01(x) {
|
|
534
|
+
return x < 0 ? 0 : x > 1 ? 1 : x;
|
|
535
|
+
}
|
|
536
|
+
function hexVal(c) {
|
|
537
|
+
if (c >= 48 && c <= 57) return c - 48;
|
|
538
|
+
if (c >= 65 && c <= 70) return c - 65 + 10;
|
|
539
|
+
if (c >= 97 && c <= 102) return c - 97 + 10;
|
|
540
|
+
return -1;
|
|
541
|
+
}
|
|
542
|
+
function fail(severity, detail) {
|
|
543
|
+
return {
|
|
544
|
+
ok: false,
|
|
545
|
+
severity,
|
|
546
|
+
detail
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
//#endregion
|
|
550
|
+
export { decodePdfImage };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Loss } from '../core/ir/index.js';
|
|
2
|
+
import { PdfFile, PdfPage } from './document.js';
|
|
3
|
+
export interface PdfImage {
|
|
4
|
+
readonly bytes: Uint8Array;
|
|
5
|
+
readonly format: 'png' | 'jpeg' | 'jpeg2000';
|
|
6
|
+
readonly widthPt: number;
|
|
7
|
+
readonly heightPt: number;
|
|
8
|
+
readonly x: number;
|
|
9
|
+
readonly y: number;
|
|
10
|
+
readonly mcid?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface PageImages {
|
|
13
|
+
readonly images: Array<PdfImage>;
|
|
14
|
+
readonly losses: Array<Loss>;
|
|
15
|
+
}
|
|
16
|
+
export declare function collectPageImages(file: PdfFile, page: PdfPage): PageImages;
|