reamkit 1.5.0 → 1.7.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 +2 -2
- package/dist/esm/core/converter/facade.js +6 -1
- package/dist/esm/core/converter/ream.js +2 -1
- package/dist/esm/core/document-model/types.d.ts +9 -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/spreadsheet-model/index.d.ts +1 -1
- package/dist/esm/core/spreadsheet-model/types.d.ts +5 -0
- package/dist/esm/excel/column-bands.d.ts +7 -0
- package/dist/esm/excel/column-bands.js +87 -0
- package/dist/esm/excel/conditional-format.js +22 -0
- package/dist/esm/excel/print-model.js +33 -11
- package/dist/esm/excel/worksheet-parser.js +26 -0
- package/dist/esm/excel/xlsx-writer.js +88 -17
- package/dist/esm/html/html-writer.js +100 -5
- package/dist/esm/layout/styled-layout.js +88 -19
- package/dist/esm/pdf-reader/cmap.d.ts +5 -0
- package/dist/esm/pdf-reader/cmap.js +72 -0
- package/dist/esm/pdf-reader/content.d.ts +52 -0
- package/dist/esm/pdf-reader/content.js +420 -0
- 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 +199 -0
- package/dist/esm/pdf-reader/document.d.ts +30 -0
- package/dist/esm/pdf-reader/document.js +413 -0
- package/dist/esm/pdf-reader/flow-build.d.ts +19 -0
- package/dist/esm/pdf-reader/flow-build.js +126 -0
- package/dist/esm/pdf-reader/font.d.ts +4 -0
- package/dist/esm/pdf-reader/font.js +69 -0
- package/dist/esm/pdf-reader/image-decode.d.ts +15 -0
- package/dist/esm/pdf-reader/image-decode.js +442 -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 +3 -0
- package/dist/esm/pdf-reader/layout.js +103 -0
- package/dist/esm/pdf-reader/lexer.d.ts +43 -0
- package/dist/esm/pdf-reader/lexer.js +250 -0
- package/dist/esm/pdf-reader/parser.d.ts +10 -0
- package/dist/esm/pdf-reader/parser.js +86 -0
- 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 +4 -0
- package/dist/esm/pdf-reader/reader.js +50 -0
- package/dist/esm/pdf-reader/struct-tree.d.ts +14 -0
- package/dist/esm/pdf-reader/struct-tree.js +92 -0
- package/dist/esm/pdf-reader/tagged.d.ts +3 -0
- package/dist/esm/pdf-reader/tagged.js +141 -0
- package/dist/esm/pdf-reader/text.d.ts +3 -0
- package/dist/esm/pdf-reader/text.js +65 -0
- package/dist/esm/pdf-reader/vector.d.ts +12 -0
- package/dist/esm/pdf-reader/vector.js +54 -0
- package/dist/esm/word/docx-writer.js +96 -7
- package/dist/esm/word/omml-serializer.d.ts +2 -0
- package/dist/esm/word/omml-serializer.js +48 -0
- package/package.json +2 -2
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { pt } from "../core/ir/units.js";
|
|
2
|
+
import { ResourceStore } from "../core/ir/resources.js";
|
|
3
|
+
import { EMPTY_STYLE_SHEET, resolveBodyStyles } from "../core/style-cascade/resolver.js";
|
|
4
|
+
import "../core/style-cascade/index.js";
|
|
5
|
+
//#region src/pdf-reader/flow-build.ts
|
|
6
|
+
function paragraphBlock(text, outlineLevel) {
|
|
7
|
+
return {
|
|
8
|
+
kind: "paragraph",
|
|
9
|
+
paragraph: {
|
|
10
|
+
properties: outlineLevel !== void 0 ? { outlineLevel } : {},
|
|
11
|
+
runs: text.length > 0 ? [{
|
|
12
|
+
text,
|
|
13
|
+
properties: {}
|
|
14
|
+
}] : []
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
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
|
+
return {
|
|
96
|
+
kind: "shape",
|
|
97
|
+
shape: {
|
|
98
|
+
width: pt(w),
|
|
99
|
+
height: pt(h),
|
|
100
|
+
geometry: {
|
|
101
|
+
kind: "custom",
|
|
102
|
+
custom: {
|
|
103
|
+
pathWidth: w,
|
|
104
|
+
pathHeight: h,
|
|
105
|
+
commands
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
fill: {
|
|
109
|
+
kind: "solid",
|
|
110
|
+
colorHex: v.fillHex
|
|
111
|
+
},
|
|
112
|
+
paragraphProperties: {}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function buildFlowDoc(body, resources = new ResourceStore()) {
|
|
117
|
+
return {
|
|
118
|
+
kind: "flow",
|
|
119
|
+
body: resolveBodyStyles([...body], EMPTY_STYLE_SHEET),
|
|
120
|
+
sections: [],
|
|
121
|
+
styles: EMPTY_STYLE_SHEET,
|
|
122
|
+
resources
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
export { buildFlowDoc, dedupeLosses, imageBlock, paragraphBlock, paragraphFromRuns, shapeBlock };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { PDF_NULL, PdfName, PdfStream } from "../pdf/objects.js";
|
|
2
|
+
import { parseToUnicodeCMap } from "./cmap.js";
|
|
3
|
+
//#region src/pdf-reader/font.ts
|
|
4
|
+
function buildContentFont(file, fontDict) {
|
|
5
|
+
const isType0 = asName(file.resolve(fontDict.get("Subtype") ?? PDF_NULL)) === "Type0";
|
|
6
|
+
let toUnicode = /* @__PURE__ */ new Map();
|
|
7
|
+
let codeBytes = isType0 ? 2 : 1;
|
|
8
|
+
const tu = file.resolve(fontDict.get("ToUnicode") ?? PDF_NULL);
|
|
9
|
+
if (tu instanceof PdfStream) {
|
|
10
|
+
const parsed = parseToUnicodeCMap(file.streamData(tu));
|
|
11
|
+
toUnicode = parsed.map;
|
|
12
|
+
codeBytes = parsed.codeBytes;
|
|
13
|
+
}
|
|
14
|
+
const width = isType0 ? cidWidths(file, fontDict) : simpleWidths(file, fontDict);
|
|
15
|
+
const bytesPerCode = codeBytes;
|
|
16
|
+
return {
|
|
17
|
+
bytesPerCode,
|
|
18
|
+
decode: (codes) => codes.map((c) => toUnicode.get(c) ?? (bytesPerCode === 1 ? String.fromCharCode(c) : "")).join(""),
|
|
19
|
+
width
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function simpleWidths(file, fontDict) {
|
|
23
|
+
const first = asNumber(file.resolve(fontDict.get("FirstChar") ?? PDF_NULL), 0);
|
|
24
|
+
const widthsVal = file.resolve(fontDict.get("Widths") ?? PDF_NULL);
|
|
25
|
+
const widths = Array.isArray(widthsVal) ? widthsVal : [];
|
|
26
|
+
const descriptor = file.resolve(fontDict.get("FontDescriptor") ?? PDF_NULL);
|
|
27
|
+
const missing = descriptor instanceof Map ? asNumber(file.resolve(descriptor.get("MissingWidth") ?? PDF_NULL), 0) : 0;
|
|
28
|
+
return (code) => {
|
|
29
|
+
const w = widths[code - first];
|
|
30
|
+
return typeof w === "number" ? w : missing > 0 ? missing : 500;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function cidWidths(file, fontDict) {
|
|
34
|
+
const descFonts = file.resolve(fontDict.get("DescendantFonts") ?? PDF_NULL);
|
|
35
|
+
const desc0 = Array.isArray(descFonts) ? file.resolve(descFonts[0] ?? PDF_NULL) : PDF_NULL;
|
|
36
|
+
const cidFont = desc0 instanceof Map ? desc0 : /* @__PURE__ */ new Map();
|
|
37
|
+
const dw = asNumber(file.resolve(cidFont.get("DW") ?? PDF_NULL), 1e3);
|
|
38
|
+
const wMap = parseCidW(file, file.resolve(cidFont.get("W") ?? PDF_NULL));
|
|
39
|
+
return (cid) => wMap.get(cid) ?? (dw || 1e3);
|
|
40
|
+
}
|
|
41
|
+
function parseCidW(file, wVal) {
|
|
42
|
+
const out = /* @__PURE__ */ new Map();
|
|
43
|
+
if (!Array.isArray(wVal)) return out;
|
|
44
|
+
let i = 0;
|
|
45
|
+
while (i < wVal.length) {
|
|
46
|
+
const c = file.resolve(wVal[i++]);
|
|
47
|
+
if (typeof c !== "number") break;
|
|
48
|
+
const next = file.resolve(wVal[i] ?? PDF_NULL);
|
|
49
|
+
if (Array.isArray(next)) {
|
|
50
|
+
i++;
|
|
51
|
+
next.forEach((w, k) => {
|
|
52
|
+
if (typeof w === "number") out.set(c + k, w);
|
|
53
|
+
});
|
|
54
|
+
} else if (typeof next === "number") {
|
|
55
|
+
i++;
|
|
56
|
+
const w = file.resolve(wVal[i++] ?? PDF_NULL);
|
|
57
|
+
if (typeof w === "number") for (let cc = c; cc <= next && cc - c < 65536; cc++) out.set(cc, w);
|
|
58
|
+
} else break;
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
function asName(v) {
|
|
63
|
+
return v instanceof PdfName ? v.value : "";
|
|
64
|
+
}
|
|
65
|
+
function asNumber(v, dflt) {
|
|
66
|
+
return typeof v === "number" ? v : dflt;
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { buildContentFont };
|
|
@@ -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,442 @@
|
|
|
1
|
+
import { PDF_NULL, PdfHexString, PdfName, PdfStream } from "../pdf/objects.js";
|
|
2
|
+
import { reversePredictor } from "./predictor.js";
|
|
3
|
+
import { encodePng } from "./png-encode.js";
|
|
4
|
+
import { unzlibSync } from "fflate";
|
|
5
|
+
//#region src/pdf-reader/image-decode.ts
|
|
6
|
+
var MAX_PIXELS = 4e7;
|
|
7
|
+
function decodePdfImage(file, stream) {
|
|
8
|
+
const d = stream.dict;
|
|
9
|
+
const width = intOf(file.get(d, "Width")) || intOf(file.get(d, "W"));
|
|
10
|
+
const height = intOf(file.get(d, "Height")) || intOf(file.get(d, "H"));
|
|
11
|
+
if (width <= 0 || height <= 0) return fail("dropped", "image with no dimensions");
|
|
12
|
+
if (width * height > MAX_PIXELS) return fail("dropped", "image too large to decode");
|
|
13
|
+
if (boolOf(d.get("ImageMask")) || boolOf(d.get("IM"))) return fail("dropped", "stencil image mask not reconstructed");
|
|
14
|
+
const filters = filterNames(file, d);
|
|
15
|
+
const last = filters[filters.length - 1];
|
|
16
|
+
if (last === "DCTDecode" || last === "DCT") {
|
|
17
|
+
const degraded = hasSMask(file, d) ? "image transparency dropped (JPEG carries no alpha)" : void 0;
|
|
18
|
+
return {
|
|
19
|
+
ok: true,
|
|
20
|
+
bytes: applyChainExceptLast(filters, stream.data),
|
|
21
|
+
format: "jpeg",
|
|
22
|
+
widthPx: width,
|
|
23
|
+
heightPx: height,
|
|
24
|
+
...degraded ? { degraded } : {}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (last === "JPXDecode") return {
|
|
28
|
+
ok: true,
|
|
29
|
+
bytes: applyChainExceptLast(filters, stream.data),
|
|
30
|
+
format: "jpeg2000",
|
|
31
|
+
widthPx: width,
|
|
32
|
+
heightPx: height,
|
|
33
|
+
degraded: "JPEG 2000 image — limited viewer support"
|
|
34
|
+
};
|
|
35
|
+
if (last === "CCITTFaxDecode" || last === "CCF" || last === "JBIG2Decode") return fail("dropped", "fax-encoded (CCITT/JBIG2) image not decoded");
|
|
36
|
+
if (last === "LZWDecode" || last === "LZW") return fail("dropped", "LZW-encoded image not decoded");
|
|
37
|
+
const decoded = 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 resolveColorSpace(file, csVal) {
|
|
60
|
+
const cs = file.resolve(csVal ?? PDF_NULL);
|
|
61
|
+
if (cs instanceof PdfName) return namedColorSpace(cs.value);
|
|
62
|
+
if (!Array.isArray(cs) || cs.length === 0) return void 0;
|
|
63
|
+
const head = file.resolve(cs[0]);
|
|
64
|
+
const tag = head instanceof PdfName ? head.value : "";
|
|
65
|
+
if (tag === "ICCBased") {
|
|
66
|
+
const profile = file.resolve(cs[1]);
|
|
67
|
+
if (profile instanceof PdfStream) {
|
|
68
|
+
const n = intOf(file.get(profile.dict, "N"));
|
|
69
|
+
if (n === 1) return {
|
|
70
|
+
kind: "gray",
|
|
71
|
+
components: 1
|
|
72
|
+
};
|
|
73
|
+
if (n === 3) return {
|
|
74
|
+
kind: "rgb",
|
|
75
|
+
components: 3
|
|
76
|
+
};
|
|
77
|
+
if (n === 4) return {
|
|
78
|
+
kind: "cmyk",
|
|
79
|
+
components: 4
|
|
80
|
+
};
|
|
81
|
+
const alt = resolveColorSpace(file, profile.dict.get("Alternate"));
|
|
82
|
+
if (alt) return alt;
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (tag === "CalGray" || tag === "G") return {
|
|
87
|
+
kind: "gray",
|
|
88
|
+
components: 1
|
|
89
|
+
};
|
|
90
|
+
if (tag === "CalRGB" || tag === "RGB") return {
|
|
91
|
+
kind: "rgb",
|
|
92
|
+
components: 3
|
|
93
|
+
};
|
|
94
|
+
if (tag === "Indexed" || tag === "I") {
|
|
95
|
+
const base = resolveColorSpace(file, cs[1]);
|
|
96
|
+
const hival = intOf(file.resolve(cs[2] ?? PDF_NULL));
|
|
97
|
+
const lookup = valueToBytes(file, cs[3]);
|
|
98
|
+
if (!base || base.kind === "indexed" || !lookup) return void 0;
|
|
99
|
+
return {
|
|
100
|
+
kind: "indexed",
|
|
101
|
+
components: 1,
|
|
102
|
+
base,
|
|
103
|
+
hival,
|
|
104
|
+
lookup
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function namedColorSpace(name) {
|
|
109
|
+
if (name === "DeviceGray" || name === "G" || name === "CalGray") return {
|
|
110
|
+
kind: "gray",
|
|
111
|
+
components: 1
|
|
112
|
+
};
|
|
113
|
+
if (name === "DeviceRGB" || name === "RGB" || name === "CalRGB") return {
|
|
114
|
+
kind: "rgb",
|
|
115
|
+
components: 3
|
|
116
|
+
};
|
|
117
|
+
if (name === "DeviceCMYK" || name === "CMYK") return {
|
|
118
|
+
kind: "cmyk",
|
|
119
|
+
components: 4
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function toColor(cs, s, px, bpc, decode) {
|
|
123
|
+
const maxv = (1 << Math.min(bpc, 15)) * (bpc >= 16 ? 2 : 1) - 1;
|
|
124
|
+
const c01 = (v, comp) => {
|
|
125
|
+
const t = maxv > 0 ? v / maxv : 0;
|
|
126
|
+
if (decode && decode.length >= (comp + 1) * 2) {
|
|
127
|
+
const dmin = decode[comp * 2];
|
|
128
|
+
const dmax = decode[comp * 2 + 1];
|
|
129
|
+
return clamp01(dmin + t * (dmax - dmin));
|
|
130
|
+
}
|
|
131
|
+
return t;
|
|
132
|
+
};
|
|
133
|
+
const to8 = (v, comp) => Math.round(c01(v, comp) * 255);
|
|
134
|
+
if (cs.kind === "gray") {
|
|
135
|
+
const out = new Uint8Array(px);
|
|
136
|
+
for (let i = 0; i < px; i++) out[i] = to8(s[i], 0);
|
|
137
|
+
return {
|
|
138
|
+
color: "gray",
|
|
139
|
+
samples: out
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (cs.kind === "rgb") {
|
|
143
|
+
const out = new Uint8Array(px * 3);
|
|
144
|
+
for (let i = 0; i < px; i++) {
|
|
145
|
+
out[i * 3] = to8(s[i * 3], 0);
|
|
146
|
+
out[i * 3 + 1] = to8(s[i * 3 + 1], 1);
|
|
147
|
+
out[i * 3 + 2] = to8(s[i * 3 + 2], 2);
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
color: "rgb",
|
|
151
|
+
samples: out
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (cs.kind === "cmyk") {
|
|
155
|
+
const out = new Uint8Array(px * 3);
|
|
156
|
+
for (let i = 0; i < px; i++) {
|
|
157
|
+
const c = c01(s[i * 4], 0);
|
|
158
|
+
const m = c01(s[i * 4 + 1], 1);
|
|
159
|
+
const y = c01(s[i * 4 + 2], 2);
|
|
160
|
+
const k = c01(s[i * 4 + 3], 3);
|
|
161
|
+
out[i * 3] = Math.round(255 * (1 - c) * (1 - k));
|
|
162
|
+
out[i * 3 + 1] = Math.round(255 * (1 - m) * (1 - k));
|
|
163
|
+
out[i * 3 + 2] = Math.round(255 * (1 - y) * (1 - k));
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
color: "rgb",
|
|
167
|
+
samples: out
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const base = cs.base;
|
|
171
|
+
const lookup = cs.lookup;
|
|
172
|
+
const hival = cs.hival ?? 255;
|
|
173
|
+
const bn = base.components;
|
|
174
|
+
if (base.kind === "gray") {
|
|
175
|
+
const out = new Uint8Array(px);
|
|
176
|
+
for (let i = 0; i < px; i++) out[i] = lookup[Math.min(s[i], hival) * bn] ?? 0;
|
|
177
|
+
return {
|
|
178
|
+
color: "gray",
|
|
179
|
+
samples: out
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const out = new Uint8Array(px * 3);
|
|
183
|
+
for (let i = 0; i < px; i++) {
|
|
184
|
+
const [r, g, b] = paletteRgb(base, lookup, Math.min(s[i], hival) * bn);
|
|
185
|
+
out[i * 3] = r;
|
|
186
|
+
out[i * 3 + 1] = g;
|
|
187
|
+
out[i * 3 + 2] = b;
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
color: "rgb",
|
|
191
|
+
samples: out
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function paletteRgb(base, lookup, off) {
|
|
195
|
+
if (base.kind === "rgb") return [
|
|
196
|
+
lookup[off] ?? 0,
|
|
197
|
+
lookup[off + 1] ?? 0,
|
|
198
|
+
lookup[off + 2] ?? 0
|
|
199
|
+
];
|
|
200
|
+
if (base.kind === "cmyk") {
|
|
201
|
+
const c = (lookup[off] ?? 0) / 255;
|
|
202
|
+
const m = (lookup[off + 1] ?? 0) / 255;
|
|
203
|
+
const y = (lookup[off + 2] ?? 0) / 255;
|
|
204
|
+
const k = (lookup[off + 3] ?? 0) / 255;
|
|
205
|
+
return [
|
|
206
|
+
Math.round(255 * (1 - c) * (1 - k)),
|
|
207
|
+
Math.round(255 * (1 - m) * (1 - k)),
|
|
208
|
+
Math.round(255 * (1 - y) * (1 - k))
|
|
209
|
+
];
|
|
210
|
+
}
|
|
211
|
+
const g = lookup[off] ?? 0;
|
|
212
|
+
return [
|
|
213
|
+
g,
|
|
214
|
+
g,
|
|
215
|
+
g
|
|
216
|
+
];
|
|
217
|
+
}
|
|
218
|
+
function unpackSamples(raw, width, height, ncomp, bpc) {
|
|
219
|
+
const perRow = width * ncomp;
|
|
220
|
+
const out = new Uint16Array(perRow * height);
|
|
221
|
+
const rowBytes = Math.ceil(perRow * bpc / 8);
|
|
222
|
+
for (let y = 0; y < height; y++) {
|
|
223
|
+
const rowOff = y * rowBytes;
|
|
224
|
+
const base = y * perRow;
|
|
225
|
+
if (bpc === 8) for (let i = 0; i < perRow; i++) out[base + i] = raw[rowOff + i] ?? 0;
|
|
226
|
+
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);
|
|
227
|
+
else {
|
|
228
|
+
const mask = (1 << bpc) - 1;
|
|
229
|
+
let bit = 0;
|
|
230
|
+
for (let i = 0; i < perRow; i++) {
|
|
231
|
+
const bytePos = rowOff + (bit >> 3);
|
|
232
|
+
const shift = 8 - bpc - (bit & 7);
|
|
233
|
+
out[base + i] = ((raw[bytePos] ?? 0) >> shift & mask) >>> 0;
|
|
234
|
+
bit += bpc;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return out;
|
|
239
|
+
}
|
|
240
|
+
function decodeSMask(file, d, width, height) {
|
|
241
|
+
const sm = file.resolve(d.get("SMask") ?? PDF_NULL);
|
|
242
|
+
if (!(sm instanceof PdfStream)) return void 0;
|
|
243
|
+
const sw = intOf(file.get(sm.dict, "Width"));
|
|
244
|
+
const sh = intOf(file.get(sm.dict, "Height"));
|
|
245
|
+
if (sw <= 0 || sh <= 0) return void 0;
|
|
246
|
+
const decoded = decodeToSamples(file, sm, filterNames(file, sm.dict), sw, sh);
|
|
247
|
+
if (typeof decoded === "string") return void 0;
|
|
248
|
+
const ch = decoded.color === "rgb" ? 3 : 1;
|
|
249
|
+
const gray = new Uint8Array(sw * sh);
|
|
250
|
+
for (let i = 0; i < sw * sh; i++) gray[i] = decoded.samples[i * ch];
|
|
251
|
+
if (sw === width && sh === height) return { data: gray };
|
|
252
|
+
const out = new Uint8Array(width * height);
|
|
253
|
+
for (let y = 0; y < height; y++) {
|
|
254
|
+
const sy = Math.min(sh - 1, Math.floor(y * sh / height));
|
|
255
|
+
for (let x = 0; x < width; x++) {
|
|
256
|
+
const sx = Math.min(sw - 1, Math.floor(x * sw / width));
|
|
257
|
+
out[y * width + x] = gray[sy * sw + sx];
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return { data: out };
|
|
261
|
+
}
|
|
262
|
+
function combineAlpha(color, alpha) {
|
|
263
|
+
const px = alpha.data.length;
|
|
264
|
+
if (color.color === "gray") {
|
|
265
|
+
const out = new Uint8Array(px * 2);
|
|
266
|
+
for (let i = 0; i < px; i++) {
|
|
267
|
+
out[i * 2] = color.samples[i];
|
|
268
|
+
out[i * 2 + 1] = alpha.data[i];
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
color: "gray-alpha",
|
|
272
|
+
samples: out
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
const out = new Uint8Array(px * 4);
|
|
276
|
+
for (let i = 0; i < px; i++) {
|
|
277
|
+
out[i * 4] = color.samples[i * 3];
|
|
278
|
+
out[i * 4 + 1] = color.samples[i * 3 + 1];
|
|
279
|
+
out[i * 4 + 2] = color.samples[i * 3 + 2];
|
|
280
|
+
out[i * 4 + 3] = alpha.data[i];
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
color: "rgba",
|
|
284
|
+
samples: out
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function filterNames(file, d) {
|
|
288
|
+
const f = file.resolve(d.get("Filter") ?? PDF_NULL);
|
|
289
|
+
const arr = Array.isArray(f) ? f : [f];
|
|
290
|
+
const out = [];
|
|
291
|
+
for (const x of arr) {
|
|
292
|
+
const r = file.resolve(x);
|
|
293
|
+
if (r instanceof PdfName) out.push(r.value);
|
|
294
|
+
}
|
|
295
|
+
return out;
|
|
296
|
+
}
|
|
297
|
+
function decodeChain(file, stream, filters) {
|
|
298
|
+
let data = stream.data;
|
|
299
|
+
let flate = false;
|
|
300
|
+
for (const f of filters) if (f === "FlateDecode" || f === "Fl") try {
|
|
301
|
+
data = unzlibSync(data);
|
|
302
|
+
flate = true;
|
|
303
|
+
} catch {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
else if (f === "RunLengthDecode" || f === "RL") data = runLengthDecode(data);
|
|
307
|
+
else if (f === "ASCII85Decode" || f === "A85") data = ascii85Decode(data);
|
|
308
|
+
else if (f === "ASCIIHexDecode" || f === "AHx") data = asciiHexDecode(data);
|
|
309
|
+
else return;
|
|
310
|
+
return flate ? applyPredictor(file, stream.dict, data) : data;
|
|
311
|
+
}
|
|
312
|
+
function applyChainExceptLast(filters, raw) {
|
|
313
|
+
let data = raw;
|
|
314
|
+
for (let i = 0; i < filters.length - 1; i++) {
|
|
315
|
+
const f = filters[i];
|
|
316
|
+
if (f === "FlateDecode" || f === "Fl") try {
|
|
317
|
+
data = unzlibSync(data);
|
|
318
|
+
} catch {}
|
|
319
|
+
else if (f === "RunLengthDecode" || f === "RL") data = runLengthDecode(data);
|
|
320
|
+
else if (f === "ASCII85Decode" || f === "A85") data = ascii85Decode(data);
|
|
321
|
+
else if (f === "ASCIIHexDecode" || f === "AHx") data = asciiHexDecode(data);
|
|
322
|
+
}
|
|
323
|
+
return data;
|
|
324
|
+
}
|
|
325
|
+
function applyPredictor(file, d, data) {
|
|
326
|
+
const parms = decodeParmsOf(file, d);
|
|
327
|
+
if (!parms) return data;
|
|
328
|
+
const predictor = intOf(file.get(parms, "Predictor"));
|
|
329
|
+
if (predictor < 2) return data;
|
|
330
|
+
return reversePredictor(data, {
|
|
331
|
+
predictor,
|
|
332
|
+
colors: intOf(file.get(parms, "Colors")) || 1,
|
|
333
|
+
bitsPerComponent: intOf(file.get(parms, "BitsPerComponent")) || 8,
|
|
334
|
+
columns: intOf(file.get(parms, "Columns")) || 1
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
function runLengthDecode(data) {
|
|
338
|
+
const out = [];
|
|
339
|
+
let i = 0;
|
|
340
|
+
while (i < data.length) {
|
|
341
|
+
const len = data[i++];
|
|
342
|
+
if (len === 128) break;
|
|
343
|
+
if (len < 128) for (let j = 0; j <= len && i < data.length; j++) out.push(data[i++]);
|
|
344
|
+
else {
|
|
345
|
+
const b = data[i++] ?? 0;
|
|
346
|
+
for (let j = 0; j < 257 - len; j++) out.push(b);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return Uint8Array.from(out);
|
|
350
|
+
}
|
|
351
|
+
function ascii85Decode(data) {
|
|
352
|
+
const out = [];
|
|
353
|
+
let tuple = 0;
|
|
354
|
+
let count = 0;
|
|
355
|
+
for (const c of data) {
|
|
356
|
+
if (c === 126) break;
|
|
357
|
+
if (c <= 32) continue;
|
|
358
|
+
if (c === 122 && count === 0) {
|
|
359
|
+
out.push(0, 0, 0, 0);
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
if (c < 33 || c > 117) continue;
|
|
363
|
+
tuple = tuple * 85 + (c - 33);
|
|
364
|
+
if (++count === 5) {
|
|
365
|
+
out.push(tuple >>> 24 & 255, tuple >>> 16 & 255, tuple >>> 8 & 255, tuple & 255);
|
|
366
|
+
tuple = 0;
|
|
367
|
+
count = 0;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (count > 0) {
|
|
371
|
+
for (let i = count; i < 5; i++) tuple = tuple * 85 + 84;
|
|
372
|
+
for (let i = 0; i < count - 1; i++) out.push(tuple >>> 24 - i * 8 & 255);
|
|
373
|
+
}
|
|
374
|
+
return Uint8Array.from(out);
|
|
375
|
+
}
|
|
376
|
+
function asciiHexDecode(data) {
|
|
377
|
+
const out = [];
|
|
378
|
+
let hi = -1;
|
|
379
|
+
for (const c of data) {
|
|
380
|
+
if (c === 62) break;
|
|
381
|
+
const v = hexVal(c);
|
|
382
|
+
if (v < 0) continue;
|
|
383
|
+
if (hi < 0) hi = v;
|
|
384
|
+
else {
|
|
385
|
+
out.push(hi << 4 | v);
|
|
386
|
+
hi = -1;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (hi >= 0) out.push(hi << 4);
|
|
390
|
+
return Uint8Array.from(out);
|
|
391
|
+
}
|
|
392
|
+
function decodeParmsOf(file, d) {
|
|
393
|
+
const p = file.resolve(d.get("DecodeParms") ?? d.get("DP") ?? PDF_NULL);
|
|
394
|
+
if (p instanceof Map) return p;
|
|
395
|
+
if (Array.isArray(p)) for (const e of p) {
|
|
396
|
+
const r = file.resolve(e);
|
|
397
|
+
if (r instanceof Map) return r;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function decodeArrayOf(file, d) {
|
|
401
|
+
const v = file.resolve(d.get("Decode") ?? d.get("D") ?? PDF_NULL);
|
|
402
|
+
if (!Array.isArray(v)) return void 0;
|
|
403
|
+
const nums = v.map((x) => typeof x === "number" ? x : NaN);
|
|
404
|
+
return nums.some((n) => !Number.isFinite(n)) ? void 0 : nums;
|
|
405
|
+
}
|
|
406
|
+
function hasSMask(file, d) {
|
|
407
|
+
return file.resolve(d.get("SMask") ?? PDF_NULL) instanceof PdfStream;
|
|
408
|
+
}
|
|
409
|
+
function valueToBytes(file, v) {
|
|
410
|
+
const r = file.resolve(v ?? PDF_NULL);
|
|
411
|
+
if (r instanceof PdfHexString) return r.bytes;
|
|
412
|
+
if (typeof r === "string") {
|
|
413
|
+
const out = new Uint8Array(r.length);
|
|
414
|
+
for (let i = 0; i < r.length; i++) out[i] = r.charCodeAt(i) & 255;
|
|
415
|
+
return out;
|
|
416
|
+
}
|
|
417
|
+
if (r instanceof PdfStream) return file.streamData(r);
|
|
418
|
+
}
|
|
419
|
+
function intOf(v) {
|
|
420
|
+
return typeof v === "number" ? Math.round(v) : 0;
|
|
421
|
+
}
|
|
422
|
+
function boolOf(v) {
|
|
423
|
+
return v === true;
|
|
424
|
+
}
|
|
425
|
+
function clamp01(x) {
|
|
426
|
+
return x < 0 ? 0 : x > 1 ? 1 : x;
|
|
427
|
+
}
|
|
428
|
+
function hexVal(c) {
|
|
429
|
+
if (c >= 48 && c <= 57) return c - 48;
|
|
430
|
+
if (c >= 65 && c <= 70) return c - 65 + 10;
|
|
431
|
+
if (c >= 97 && c <= 102) return c - 97 + 10;
|
|
432
|
+
return -1;
|
|
433
|
+
}
|
|
434
|
+
function fail(severity, detail) {
|
|
435
|
+
return {
|
|
436
|
+
ok: false,
|
|
437
|
+
severity,
|
|
438
|
+
detail
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
//#endregion
|
|
442
|
+
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;
|