reamkit 1.16.0 → 1.18.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/dist/esm/core/bidi/segments.js +1 -2
- package/dist/esm/core/converter/project.js +5 -1
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +526 -8
- package/dist/esm/core/drawingml/chart-geometry.d.ts +8 -12
- package/dist/esm/core/drawingml/chart-geometry.js +58 -9
- package/dist/esm/core/drawingml/chart-parser.js +44 -5
- package/dist/esm/core/drawingml/colors.d.ts +1 -1
- package/dist/esm/core/drawingml/colors.js +27 -1
- package/dist/esm/core/drawingml/preset-geometry.js +233 -0
- package/dist/esm/core/drawingml/shape-render.d.ts +12 -0
- package/dist/esm/core/drawingml/shape-render.js +116 -3
- package/dist/esm/core/drawingml/theme-parser.d.ts +10 -0
- package/dist/esm/core/drawingml/theme-parser.js +19 -1
- package/dist/esm/core/font/measure.d.ts +5 -0
- package/dist/esm/core/font/measure.js +21 -1
- package/dist/esm/core/images.d.ts +2 -2
- package/dist/esm/core/images.js +219 -0
- package/dist/esm/core/ir/flow.d.ts +23 -1
- package/dist/esm/core/lzw.d.ts +17 -0
- package/dist/esm/core/lzw.js +83 -0
- package/dist/esm/core/metafile/emf.d.ts +16 -0
- package/dist/esm/core/metafile/emf.js +635 -0
- package/dist/esm/core/metafile/picture.d.ts +106 -0
- package/dist/esm/core/metafile/picture.js +55 -0
- package/dist/esm/core/metafile/wmf.d.ts +9 -0
- package/dist/esm/core/metafile/wmf.js +371 -0
- package/dist/esm/core/numbering/apply.d.ts +5 -3
- package/dist/esm/core/numbering/apply.js +42 -11
- package/dist/esm/core/numbering/index.js +2 -0
- package/dist/esm/core/numbering/state.d.ts +13 -1
- package/dist/esm/core/numbering/state.js +256 -15
- package/dist/esm/core/po-helpers.js +16 -1
- package/dist/esm/core/style-cascade/resolver.js +66 -11
- package/dist/esm/core/style-cascade/table.js +35 -11
- package/dist/esm/core/style-cascade/types.d.ts +42 -1
- package/dist/esm/core/style-cascade/types.js +4 -0
- package/dist/esm/core/tiff.d.ts +20 -0
- package/dist/esm/core/tiff.js +339 -0
- package/dist/esm/core/vector.d.ts +22 -0
- package/dist/esm/excel/print-model.js +4 -2
- package/dist/esm/excel/sheet-to-flow.js +10 -8
- package/dist/esm/excel/worksheet-parser.js +1 -1
- package/dist/esm/html/html-writer.js +20 -5
- package/dist/esm/layout/page-doc.d.ts +107 -4
- package/dist/esm/layout/styled-layout.d.ts +44 -2
- package/dist/esm/layout/styled-layout.js +2330 -283
- package/dist/esm/pdf/shading.js +76 -6
- package/dist/esm/pdf/styled-page-emitter.js +294 -45
- package/dist/esm/pdf/vector-graphics.d.ts +29 -2
- package/dist/esm/pdf/vector-graphics.js +96 -12
- package/dist/esm/pdf-reader/image-decode.js +7 -75
- package/dist/esm/pptx/pptx-reader.js +1 -0
- package/dist/esm/pptx/slide-parser.d.ts +21 -4
- package/dist/esm/pptx/slide-parser.js +86 -23
- package/dist/esm/svg/svg-writer.js +2 -2
- package/dist/esm/word/document-parser.d.ts +96 -7
- package/dist/esm/word/document-parser.js +540 -116
- package/dist/esm/word/docx-reader.js +135 -43
- package/dist/esm/word/docx-writer.js +6 -1
- package/dist/esm/word/drawing-parser.d.ts +58 -5
- package/dist/esm/word/drawing-parser.js +1632 -63
- package/dist/esm/word/index.d.ts +4 -4
- package/dist/esm/word/numbering-parser.d.ts +2 -1
- package/dist/esm/word/numbering-parser.js +107 -21
- package/dist/esm/word/paragraph-properties.d.ts +11 -1
- package/dist/esm/word/paragraph-properties.js +219 -10
- package/dist/esm/word/run-properties.js +45 -1
- package/dist/esm/word/settings-parser.d.ts +24 -0
- package/dist/esm/word/settings-parser.js +31 -5
- package/dist/esm/word/shading.d.ts +11 -0
- package/dist/esm/word/shading.js +64 -0
- package/dist/esm/word/styles-parser.js +8 -2
- package/dist/esm/word/table-parser.d.ts +9 -1
- package/dist/esm/word/table-parser.js +182 -30
- package/package.json +1 -1
|
@@ -60,12 +60,125 @@ function gradientToSolid(gradient) {
|
|
|
60
60
|
function gradientSvgDef(id, g) {
|
|
61
61
|
const n = (x) => String(Math.round(x * 1e4) / 1e4);
|
|
62
62
|
const stops = g.stops.map((s) => `<stop offset="${n(s.offset)}" stop-color="#${s.colorHex}"/>`).join("");
|
|
63
|
-
if (g.kind === "radial")
|
|
63
|
+
if (g.kind === "radial") {
|
|
64
|
+
const c = g.center;
|
|
65
|
+
if (!c) return `<radialGradient id="${id}">${stops}</radialGradient>`;
|
|
66
|
+
const r = Math.SQRT2 / 2;
|
|
67
|
+
return `<radialGradient id="${id}" cx="${n(c.x)}" cy="${n(c.y)}" r="${n(r)}">${stops}</radialGradient>`;
|
|
68
|
+
}
|
|
64
69
|
const rad = -(g.angle ?? 0) * Math.PI / 180;
|
|
65
70
|
const dx = Math.cos(rad) / 2;
|
|
66
71
|
const dy = Math.sin(rad) / 2;
|
|
67
72
|
return `<linearGradient id="${id}" x1="${n(.5 - dx)}" y1="${n(.5 - dy)}" x2="${n(.5 + dx)}" y2="${n(.5 + dy)}">${stops}</linearGradient>`;
|
|
68
73
|
}
|
|
74
|
+
var LINE_END_STEPS = {
|
|
75
|
+
sm: 3,
|
|
76
|
+
med: 4.5,
|
|
77
|
+
lg: 6
|
|
78
|
+
};
|
|
79
|
+
var MIN_LINE_END_PEN_PT = 1;
|
|
80
|
+
/**
|
|
81
|
+
* §20.1.8.24 / §20.1.8.42 — the arrowheads a line asks for at its ends, as
|
|
82
|
+
* filled paths in the same local frame as the shape's own geometry. The head
|
|
83
|
+
* rides the first point of the first subpath and the tail the last point of
|
|
84
|
+
* the last one, each turned along the segment that reaches it.
|
|
85
|
+
*
|
|
86
|
+
* @param paths The shape's geometry paths.
|
|
87
|
+
* @param line The parsed line (its `headEnd`/`tailEnd` are read).
|
|
88
|
+
* @param strokeWidthPt The pen width the decorations are sized against.
|
|
89
|
+
* @returns The decoration paths (empty when the line asks for none).
|
|
90
|
+
*/
|
|
91
|
+
function lineEndPaths(paths, line, strokeWidthPt) {
|
|
92
|
+
if (!line || !line.headEnd && !line.tailEnd || line.fill === "none") return [];
|
|
93
|
+
const pts = pathPoints(paths);
|
|
94
|
+
if (pts.length < 2) return [];
|
|
95
|
+
const out = [];
|
|
96
|
+
const pen = Math.max(strokeWidthPt, MIN_LINE_END_PEN_PT);
|
|
97
|
+
if (line.headEnd) {
|
|
98
|
+
const p = pts[0];
|
|
99
|
+
const q = pts[1];
|
|
100
|
+
const path = endPath(line.headEnd, p, q, pen);
|
|
101
|
+
if (path) out.push(path);
|
|
102
|
+
}
|
|
103
|
+
if (line.tailEnd) {
|
|
104
|
+
const p = pts[pts.length - 1];
|
|
105
|
+
const q = pts[pts.length - 2];
|
|
106
|
+
const path = endPath(line.tailEnd, p, q, pen);
|
|
107
|
+
if (path) out.push(path);
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
function pathPoints(paths) {
|
|
112
|
+
const out = [];
|
|
113
|
+
for (const path of paths) for (const seg of path.segments) {
|
|
114
|
+
if (seg.op === "close") continue;
|
|
115
|
+
out.push({
|
|
116
|
+
x: seg.x,
|
|
117
|
+
y: seg.y
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
function endPath(end, tip, from, pen) {
|
|
123
|
+
const dx = tip.x - from.x;
|
|
124
|
+
const dy = tip.y - from.y;
|
|
125
|
+
const len = Math.hypot(dx, dy);
|
|
126
|
+
if (len < 1e-6) return void 0;
|
|
127
|
+
const ux = dx / len;
|
|
128
|
+
const uy = dy / len;
|
|
129
|
+
const nx = -uy;
|
|
130
|
+
const ny = ux;
|
|
131
|
+
const L = LINE_END_STEPS[end.length ?? "med"] * pen;
|
|
132
|
+
const W = LINE_END_STEPS[end.width ?? "med"] * pen;
|
|
133
|
+
const at = (along, across) => ({
|
|
134
|
+
x: tip.x - ux * along + nx * across,
|
|
135
|
+
y: tip.y - uy * along + ny * across
|
|
136
|
+
});
|
|
137
|
+
const segments = [];
|
|
138
|
+
const move = (p) => {
|
|
139
|
+
segments.push({
|
|
140
|
+
op: "move",
|
|
141
|
+
x: p.x,
|
|
142
|
+
y: p.y
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
const line2 = (p) => {
|
|
146
|
+
segments.push({
|
|
147
|
+
op: "line",
|
|
148
|
+
x: p.x,
|
|
149
|
+
y: p.y
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
if (end.type === "diamond") {
|
|
153
|
+
move(at(0, 0));
|
|
154
|
+
line2(at(L / 2, W / 2));
|
|
155
|
+
line2(at(L, 0));
|
|
156
|
+
line2(at(L / 2, -W / 2));
|
|
157
|
+
} else if (end.type === "oval") {
|
|
158
|
+
const r = W / 2;
|
|
159
|
+
const c = at(L / 2, 0);
|
|
160
|
+
for (let i = 0; i < 8; i++) {
|
|
161
|
+
const a = i / 8 * 2 * Math.PI;
|
|
162
|
+
const p = {
|
|
163
|
+
x: c.x + r * Math.cos(a),
|
|
164
|
+
y: c.y + r * Math.sin(a)
|
|
165
|
+
};
|
|
166
|
+
if (i === 0) move(p);
|
|
167
|
+
else line2(p);
|
|
168
|
+
}
|
|
169
|
+
} else if (end.type === "stealth") {
|
|
170
|
+
move(at(0, 0));
|
|
171
|
+
line2(at(L, W / 2));
|
|
172
|
+
line2(at(L * .6, 0));
|
|
173
|
+
line2(at(L, -W / 2));
|
|
174
|
+
} else {
|
|
175
|
+
move(at(0, 0));
|
|
176
|
+
line2(at(L, W / 2));
|
|
177
|
+
line2(at(L, -W / 2));
|
|
178
|
+
}
|
|
179
|
+
segments.push({ op: "close" });
|
|
180
|
+
return { segments };
|
|
181
|
+
}
|
|
69
182
|
/**
|
|
70
183
|
* Build a {@link StrokeStyle} from a shape's `a:ln` line, resolving the default
|
|
71
184
|
* width (0.75pt), dash pattern and line cap. Returns `undefined` for no line or
|
|
@@ -77,7 +190,7 @@ function gradientSvgDef(id, g) {
|
|
|
77
190
|
function buildStroke(line) {
|
|
78
191
|
if (!line || line.fill === "none") return void 0;
|
|
79
192
|
const widthPt = line.width ?? DEFAULT_LINE_WIDTH_EMU / 12700;
|
|
80
|
-
const dash = line.dash && line.dash !== "solid" ? dashPattern(line.dash, widthPt) : void 0;
|
|
193
|
+
const dash = line.customDash?.map((n) => Math.max(.01, n * widthPt)) ?? (line.dash && line.dash !== "solid" ? dashPattern(line.dash, widthPt) : void 0);
|
|
81
194
|
const cap = line.cap === "flat" ? "butt" : line.cap;
|
|
82
195
|
return {
|
|
83
196
|
colorHex: line.colorHex ?? "000000",
|
|
@@ -148,4 +261,4 @@ function buildShapeTransform(pageX, pageY, widthPt, heightPt, rotation60k, flipH
|
|
|
148
261
|
];
|
|
149
262
|
}
|
|
150
263
|
//#endregion
|
|
151
|
-
export { DEFAULT_INSET_LR_PT, DEFAULT_INSET_TB_PT, buildShapePaths, buildShapeTransform, buildStroke, gradientSvgDef, gradientToSolid };
|
|
264
|
+
export { DEFAULT_INSET_LR_PT, DEFAULT_INSET_TB_PT, buildShapePaths, buildShapeTransform, buildStroke, gradientSvgDef, gradientToSolid, lineEndPaths };
|
|
@@ -41,6 +41,16 @@ export declare function parseThemeLineWidths(themeXml: Uint8Array): Array<number
|
|
|
41
41
|
* declares no `a:fmtScheme`.
|
|
42
42
|
*/
|
|
43
43
|
export declare function parseThemeFillStyles(themeXml: Uint8Array): Array<PoNode>;
|
|
44
|
+
/**
|
|
45
|
+
* Parse a theme's BACKGROUND fill styles (§20.1.4.1.7 `a:bgFillStyleLst`), as
|
|
46
|
+
* the raw nodes. An `<a:fillRef>` reaches them with an index past 1000 — slot
|
|
47
|
+
* 1001 is the first — and that is where the page-sized backdrops Word's cover
|
|
48
|
+
* pages are built from live (fdo78957.docx).
|
|
49
|
+
*
|
|
50
|
+
* @param themeXml The raw theme part bytes, UTF-8.
|
|
51
|
+
* @returns The fill children in list order; empty when the theme declares none.
|
|
52
|
+
*/
|
|
53
|
+
export declare function parseThemeBgFillStyles(themeXml: Uint8Array): Array<PoNode>;
|
|
44
54
|
/**
|
|
45
55
|
* Parse a theme's effect styles (§20.1.4.1.15 `a:effectStyleLst`), as the raw
|
|
46
56
|
* nodes. `<a:effectRef idx="N">` is a 1-based index into this list, exactly as
|
|
@@ -108,6 +108,24 @@ function parseThemeFillStyles(themeXml) {
|
|
|
108
108
|
return list ? [...poChildren(list)] : [];
|
|
109
109
|
}
|
|
110
110
|
/**
|
|
111
|
+
* Parse a theme's BACKGROUND fill styles (§20.1.4.1.7 `a:bgFillStyleLst`), as
|
|
112
|
+
* the raw nodes. An `<a:fillRef>` reaches them with an index past 1000 — slot
|
|
113
|
+
* 1001 is the first — and that is where the page-sized backdrops Word's cover
|
|
114
|
+
* pages are built from live (fdo78957.docx).
|
|
115
|
+
*
|
|
116
|
+
* @param themeXml The raw theme part bytes, UTF-8.
|
|
117
|
+
* @returns The fill children in list order; empty when the theme declares none.
|
|
118
|
+
*/
|
|
119
|
+
function parseThemeBgFillStyles(themeXml) {
|
|
120
|
+
const list = poFindByPath(parser.parse(decoder.decode(themeXml)), [
|
|
121
|
+
"a:theme",
|
|
122
|
+
"a:themeElements",
|
|
123
|
+
"a:fmtScheme",
|
|
124
|
+
"a:bgFillStyleLst"
|
|
125
|
+
]);
|
|
126
|
+
return list ? [...poChildren(list)] : [];
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
111
129
|
* Parse a theme's effect styles (§20.1.4.1.15 `a:effectStyleLst`), as the raw
|
|
112
130
|
* nodes. `<a:effectRef idx="N">` is a 1-based index into this list, exactly as
|
|
113
131
|
* the fill and line references index theirs.
|
|
@@ -136,4 +154,4 @@ function colorOf(slot) {
|
|
|
136
154
|
}
|
|
137
155
|
}
|
|
138
156
|
//#endregion
|
|
139
|
-
export { parseTheme, parseThemeEffectStyles, parseThemeFillStyles, parseThemeLineWidths };
|
|
157
|
+
export { parseTheme, parseThemeBgFillStyles, parseThemeEffectStyles, parseThemeFillStyles, parseThemeLineWidths };
|
|
@@ -12,6 +12,11 @@ export interface FontMeasure {
|
|
|
12
12
|
readonly textWidthPt: (text: string, fontSize: number) => number;
|
|
13
13
|
/** Encode `text` as a hex string of 4-digit glyph ids (Identity-H addressing). */
|
|
14
14
|
readonly encodeTextAsCidHex: (text: string) => string;
|
|
15
|
+
/**
|
|
16
|
+
* The complete PDF text-showing operator for `text` — `<hex> Tj`, or a
|
|
17
|
+
* `[…] TJ` array when shaping moved a glyph off its own advance.
|
|
18
|
+
*/
|
|
19
|
+
readonly showText: (text: string) => string;
|
|
15
20
|
}
|
|
16
21
|
/**
|
|
17
22
|
* Build a {@link FontMeasure} over a parsed font.
|
|
@@ -33,10 +33,30 @@ function createFontMeasure(parsed, kern = true) {
|
|
|
33
33
|
for (const gid of shaped.gids) out += gid.toString(16).padStart(4, "0").toUpperCase();
|
|
34
34
|
return out;
|
|
35
35
|
};
|
|
36
|
+
const showText = (text) => {
|
|
37
|
+
const shaped = shapeText(text, parsed.glyphForCodepoint, parsed.advanceWidths, parsed.ligatures, kerning, parsed.joiningForms);
|
|
38
|
+
const parts = [];
|
|
39
|
+
let hex = "";
|
|
40
|
+
let kerned = false;
|
|
41
|
+
for (let i = 0; i < shaped.gids.length; i++) {
|
|
42
|
+
const gid = shaped.gids[i];
|
|
43
|
+
hex += gid.toString(16).padStart(4, "0").toUpperCase();
|
|
44
|
+
const delta = (shaped.advances[i] ?? 0) - (parsed.advanceWidths[gid] ?? 0);
|
|
45
|
+
if (delta === 0) continue;
|
|
46
|
+
const adj = -delta * 1e3 / parsed.unitsPerEm;
|
|
47
|
+
parts.push(`<${hex}>`, String(Number(adj.toFixed(2))));
|
|
48
|
+
hex = "";
|
|
49
|
+
kerned = true;
|
|
50
|
+
}
|
|
51
|
+
if (!kerned) return `<${hex}> Tj`;
|
|
52
|
+
if (hex !== "") parts.push(`<${hex}>`);
|
|
53
|
+
return `[${parts.join(" ")}] TJ`;
|
|
54
|
+
};
|
|
36
55
|
return {
|
|
37
56
|
pdfWidthForGid,
|
|
38
57
|
textWidthPt,
|
|
39
|
-
encodeTextAsCidHex
|
|
58
|
+
encodeTextAsCidHex,
|
|
59
|
+
showText
|
|
40
60
|
};
|
|
41
61
|
}
|
|
42
62
|
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** The raster formats this module recognizes and can prepare for embedding. */
|
|
2
|
-
export type ImageFormat = 'jpeg' | 'png' | 'jpeg2000';
|
|
2
|
+
export type ImageFormat = 'jpeg' | 'png' | 'jpeg2000' | 'gif' | 'tiff';
|
|
3
3
|
/**
|
|
4
4
|
* Sniff the raster format from a file's leading magic bytes (JPEG SOI, the PNG
|
|
5
5
|
* signature, or a JP2 box / raw JPEG 2000 codestream). Returns the
|
|
@@ -24,7 +24,7 @@ export interface EmbedImageOptions {
|
|
|
24
24
|
*/
|
|
25
25
|
export interface PreparedImage {
|
|
26
26
|
readonly format: ImageFormat;
|
|
27
|
-
readonly mimeType: 'image/jp2' | 'image/jpeg' | 'image/png';
|
|
27
|
+
readonly mimeType: 'image/jp2' | 'image/jpeg' | 'image/png' | 'image/gif' | 'image/tiff';
|
|
28
28
|
readonly widthPx: number;
|
|
29
29
|
readonly heightPx: number;
|
|
30
30
|
/**
|
package/dist/esm/core/images.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { decodeTiff, isTiff } from "./tiff.js";
|
|
1
2
|
import { unzlibSync, zlibSync } from "fflate";
|
|
2
3
|
//#region src/core/images.ts
|
|
3
4
|
/**
|
|
@@ -10,6 +11,8 @@ function detectImageFormat(bytes) {
|
|
|
10
11
|
if (bytes.length >= 8 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71 && bytes[4] === 13 && bytes[5] === 10 && bytes[6] === 26 && bytes[7] === 10) return "png";
|
|
11
12
|
if (bytes.length >= 12 && bytes[0] === 0 && bytes[1] === 0 && bytes[2] === 0 && bytes[3] === 12 && bytes[4] === 106 && bytes[5] === 80 && bytes[6] === 32 && bytes[7] === 32 && bytes[8] === 13 && bytes[9] === 10 && bytes[10] === 135 && bytes[11] === 10) return "jpeg2000";
|
|
12
13
|
if (bytes.length >= 4 && bytes[0] === 255 && bytes[1] === 79 && bytes[2] === 255 && bytes[3] === 81) return "jpeg2000";
|
|
14
|
+
if (bytes.length >= 6 && bytes[0] === 71 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 56 && (bytes[4] === 55 || bytes[4] === 57) && bytes[5] === 97) return "gif";
|
|
15
|
+
if (isTiff(bytes)) return "tiff";
|
|
13
16
|
return null;
|
|
14
17
|
}
|
|
15
18
|
/**
|
|
@@ -24,6 +27,8 @@ function prepareImage(bytes, options = {}) {
|
|
|
24
27
|
if (format === "jpeg") return prepareJpeg(bytes);
|
|
25
28
|
if (format === "png") return preparePng(bytes, options);
|
|
26
29
|
if (format === "jpeg2000") return prepareJpeg2000(bytes);
|
|
30
|
+
if (format === "gif") return prepareGif(bytes, options);
|
|
31
|
+
if (format === "tiff") return prepareTiff(bytes, options);
|
|
27
32
|
throw new Error("Unsupported image format");
|
|
28
33
|
}
|
|
29
34
|
function prepareJpeg2000(bytes) {
|
|
@@ -125,6 +130,220 @@ function readJpegInfo(bytes) {
|
|
|
125
130
|
}
|
|
126
131
|
throw new Error("JPEG SOFn marker not found");
|
|
127
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Decode a GIF (87a/89a) into a {@link PreparedImage}: the first frame's
|
|
135
|
+
* palette indices expanded to RGB and re-compressed, with a transparent index
|
|
136
|
+
* becoming a soft mask. PDF has no GIF filter, so the pixels have to be handed
|
|
137
|
+
* over as raw ones — dml-picture-in-textframe.docx (and seventeen other corpus
|
|
138
|
+
* documents) draw nothing at all without this.
|
|
139
|
+
*
|
|
140
|
+
* @param bytes The GIF file.
|
|
141
|
+
* @param options Emit options (PDF/A-1 forbids the soft mask).
|
|
142
|
+
* @returns The prepared image.
|
|
143
|
+
* @throws Error when the file is truncated or holds no image.
|
|
144
|
+
*/
|
|
145
|
+
function prepareGif(bytes, options = {}) {
|
|
146
|
+
const frame = decodeGif(bytes);
|
|
147
|
+
const rgb = new Uint8Array(frame.width * frame.height * 3);
|
|
148
|
+
const alpha = frame.transparentIndex !== void 0 ? new Uint8Array(frame.width * frame.height) : void 0;
|
|
149
|
+
for (let i = 0; i < frame.indices.length; i++) {
|
|
150
|
+
const idx = frame.indices[i];
|
|
151
|
+
const p = idx * 3;
|
|
152
|
+
rgb[i * 3] = frame.palette[p] ?? 0;
|
|
153
|
+
rgb[i * 3 + 1] = frame.palette[p + 1] ?? 0;
|
|
154
|
+
rgb[i * 3 + 2] = frame.palette[p + 2] ?? 0;
|
|
155
|
+
if (alpha) alpha[i] = idx === frame.transparentIndex ? 0 : 255;
|
|
156
|
+
}
|
|
157
|
+
if (alpha && options.flattenAlpha) {
|
|
158
|
+
for (let i = 0; i < alpha.length; i++) if (alpha[i] === 0) {
|
|
159
|
+
rgb[i * 3] = 255;
|
|
160
|
+
rgb[i * 3 + 1] = 255;
|
|
161
|
+
rgb[i * 3 + 2] = 255;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
format: "gif",
|
|
166
|
+
mimeType: "image/gif",
|
|
167
|
+
widthPx: frame.width,
|
|
168
|
+
heightPx: frame.height,
|
|
169
|
+
colorSpace: "DeviceRGB",
|
|
170
|
+
bitsPerComponent: 8,
|
|
171
|
+
filter: "FlateDecode",
|
|
172
|
+
data: zlibSync(rgb),
|
|
173
|
+
...alpha && !options.flattenAlpha ? { smaskData: zlibSync(alpha) } : {}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function prepareTiff(bytes, options = {}) {
|
|
177
|
+
const img = decodeTiff(bytes);
|
|
178
|
+
const channels = img.channels === 4 ? 3 : img.channels;
|
|
179
|
+
const data = img.channels === 4 ? cmykToRgb(img.data) : img.data.slice();
|
|
180
|
+
const alpha = img.alpha;
|
|
181
|
+
if (alpha && options.flattenAlpha) for (let i = 0; i < alpha.length; i++) {
|
|
182
|
+
if (alpha[i] === 255) continue;
|
|
183
|
+
const a = alpha[i] / 255;
|
|
184
|
+
for (let c = 0; c < channels; c++) {
|
|
185
|
+
const at = i * channels + c;
|
|
186
|
+
data[at] = Math.round(data[at] * a + 255 * (1 - a));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
format: "tiff",
|
|
191
|
+
mimeType: "image/tiff",
|
|
192
|
+
widthPx: img.width,
|
|
193
|
+
heightPx: img.height,
|
|
194
|
+
colorSpace: channels === 3 ? "DeviceRGB" : "DeviceGray",
|
|
195
|
+
bitsPerComponent: 8,
|
|
196
|
+
filter: "FlateDecode",
|
|
197
|
+
data: zlibSync(data),
|
|
198
|
+
...alpha && !options.flattenAlpha ? { smaskData: zlibSync(alpha.slice()) } : {}
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function cmykToRgb(cmyk) {
|
|
202
|
+
const out = new Uint8Array(cmyk.length / 4 * 3);
|
|
203
|
+
for (let i = 0; i * 4 + 3 < cmyk.length; i++) {
|
|
204
|
+
const k = 255 - cmyk[i * 4 + 3];
|
|
205
|
+
out[i * 3] = (255 - cmyk[i * 4]) * k / 255;
|
|
206
|
+
out[i * 3 + 1] = (255 - cmyk[i * 4 + 1]) * k / 255;
|
|
207
|
+
out[i * 3 + 2] = (255 - cmyk[i * 4 + 2]) * k / 255;
|
|
208
|
+
}
|
|
209
|
+
return out;
|
|
210
|
+
}
|
|
211
|
+
function decodeGif(bytes) {
|
|
212
|
+
const u16 = (i) => (bytes[i] ?? 0) | (bytes[i + 1] ?? 0) << 8;
|
|
213
|
+
const screenW = u16(6);
|
|
214
|
+
const screenH = u16(8);
|
|
215
|
+
const packed = bytes[10] ?? 0;
|
|
216
|
+
let pos = 13;
|
|
217
|
+
let globalPalette = new Uint8Array(0);
|
|
218
|
+
if ((packed & 128) !== 0) {
|
|
219
|
+
const size = 3 * (1 << (packed & 7) + 1);
|
|
220
|
+
globalPalette = bytes.subarray(pos, pos + size);
|
|
221
|
+
pos += size;
|
|
222
|
+
}
|
|
223
|
+
let transparentIndex;
|
|
224
|
+
while (pos < bytes.length) {
|
|
225
|
+
const block = bytes[pos];
|
|
226
|
+
if (block === 33) {
|
|
227
|
+
const label = bytes[pos + 1];
|
|
228
|
+
pos += 2;
|
|
229
|
+
if (label === 249 && (bytes[pos + 1] & 1) !== 0) transparentIndex = bytes[pos + 4];
|
|
230
|
+
while (pos < bytes.length && bytes[pos] !== 0) pos += bytes[pos] + 1;
|
|
231
|
+
pos++;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (block !== 44) break;
|
|
235
|
+
const width = u16(pos + 5) || screenW;
|
|
236
|
+
const height = u16(pos + 7) || screenH;
|
|
237
|
+
const flags = bytes[pos + 9];
|
|
238
|
+
pos += 10;
|
|
239
|
+
let palette = globalPalette;
|
|
240
|
+
if ((flags & 128) !== 0) {
|
|
241
|
+
const size = 3 * (1 << (flags & 7) + 1);
|
|
242
|
+
palette = bytes.subarray(pos, pos + size);
|
|
243
|
+
pos += size;
|
|
244
|
+
}
|
|
245
|
+
const minCodeSize = bytes[pos];
|
|
246
|
+
pos++;
|
|
247
|
+
const chunks = [];
|
|
248
|
+
while (pos < bytes.length && bytes[pos] !== 0) {
|
|
249
|
+
const len = bytes[pos];
|
|
250
|
+
chunks.push(bytes.subarray(pos + 1, pos + 1 + len));
|
|
251
|
+
pos += len + 1;
|
|
252
|
+
}
|
|
253
|
+
const indices = lzwDecode(concatBytes(chunks), minCodeSize, width * height);
|
|
254
|
+
return {
|
|
255
|
+
width,
|
|
256
|
+
height,
|
|
257
|
+
indices: (flags & 64) !== 0 ? deinterlace(indices, width, height) : indices,
|
|
258
|
+
palette,
|
|
259
|
+
...transparentIndex !== void 0 ? { transparentIndex } : {}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
throw new Error("GIF holds no image");
|
|
263
|
+
}
|
|
264
|
+
function lzwDecode(data, minCodeSize, pixelCount) {
|
|
265
|
+
const clear = 1 << minCodeSize;
|
|
266
|
+
const end = clear + 1;
|
|
267
|
+
const out = new Uint8Array(pixelCount);
|
|
268
|
+
let outPos = 0;
|
|
269
|
+
let prefix = new Int32Array(4096);
|
|
270
|
+
let suffix = new Uint8Array(4096);
|
|
271
|
+
let length = new Int32Array(4096);
|
|
272
|
+
const reset = () => {
|
|
273
|
+
prefix = new Int32Array(4096);
|
|
274
|
+
suffix = new Uint8Array(4096);
|
|
275
|
+
length = new Int32Array(4096);
|
|
276
|
+
for (let i = 0; i < clear; i++) {
|
|
277
|
+
prefix[i] = -1;
|
|
278
|
+
suffix[i] = i;
|
|
279
|
+
length[i] = 1;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
reset();
|
|
283
|
+
let next = end + 1;
|
|
284
|
+
let codeSize = minCodeSize + 1;
|
|
285
|
+
let prev = -1;
|
|
286
|
+
let bitBuf = 0;
|
|
287
|
+
let bitCount = 0;
|
|
288
|
+
const stack = new Uint8Array(4096);
|
|
289
|
+
for (let i = 0; i < data.length || bitCount >= codeSize;) {
|
|
290
|
+
while (bitCount < codeSize && i < data.length) {
|
|
291
|
+
bitBuf |= (data[i] & 255) << bitCount;
|
|
292
|
+
bitCount += 8;
|
|
293
|
+
i++;
|
|
294
|
+
}
|
|
295
|
+
if (bitCount < codeSize) break;
|
|
296
|
+
const code = bitBuf & (1 << codeSize) - 1;
|
|
297
|
+
bitBuf >>>= codeSize;
|
|
298
|
+
bitCount -= codeSize;
|
|
299
|
+
if (code === clear) {
|
|
300
|
+
reset();
|
|
301
|
+
next = end + 1;
|
|
302
|
+
codeSize = minCodeSize + 1;
|
|
303
|
+
prev = -1;
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (code === end) break;
|
|
307
|
+
let cur = code;
|
|
308
|
+
if (code >= next) {
|
|
309
|
+
if (prev < 0) break;
|
|
310
|
+
cur = prev;
|
|
311
|
+
}
|
|
312
|
+
let depth = 0;
|
|
313
|
+
while (cur >= 0 && depth < 4096) {
|
|
314
|
+
stack[depth] = suffix[cur];
|
|
315
|
+
depth++;
|
|
316
|
+
cur = prefix[cur];
|
|
317
|
+
}
|
|
318
|
+
if (code >= next) stack[depth++] = stack[depth - 2] ?? 0;
|
|
319
|
+
for (let d = depth - 1; d >= 0 && outPos < out.length; d--) out[outPos++] = stack[d];
|
|
320
|
+
if (prev >= 0 && next < 4096) {
|
|
321
|
+
prefix[next] = prev;
|
|
322
|
+
suffix[next] = stack[depth - 1];
|
|
323
|
+
length[next] = (length[prev] ?? 1) + 1;
|
|
324
|
+
next++;
|
|
325
|
+
if (next === 1 << codeSize && codeSize < 12) codeSize++;
|
|
326
|
+
}
|
|
327
|
+
prev = code < next ? code : prev;
|
|
328
|
+
if (outPos >= out.length) break;
|
|
329
|
+
}
|
|
330
|
+
return out;
|
|
331
|
+
}
|
|
332
|
+
function deinterlace(src, width, height) {
|
|
333
|
+
const out = new Uint8Array(src.length);
|
|
334
|
+
const passes = [
|
|
335
|
+
[0, 8],
|
|
336
|
+
[4, 8],
|
|
337
|
+
[2, 4],
|
|
338
|
+
[1, 2]
|
|
339
|
+
];
|
|
340
|
+
let row = 0;
|
|
341
|
+
for (const [start, step] of passes) for (let y = start; y < height; y += step) {
|
|
342
|
+
out.set(src.subarray(row * width, (row + 1) * width), y * width);
|
|
343
|
+
row++;
|
|
344
|
+
}
|
|
345
|
+
return out;
|
|
346
|
+
}
|
|
128
347
|
function preparePng(bytes, options = {}) {
|
|
129
348
|
let decoded = decodePng(bytes);
|
|
130
349
|
if (options.flattenAlpha && decoded.smaskRaw) decoded = flattenAlphaOnWhite(decoded);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BodyElement, Chart, Comment, DocumentInfo, Numbering, Section, SectionProperties, StyleSheet } from '../document-model/index.js';
|
|
1
|
+
import { BodyElement, Chart, Comment, DocumentInfo, Numbering, Section, SectionProperties, ShapeFill, StyleSheet } from '../document-model/index.js';
|
|
2
2
|
import { FontRegistry } from '../font/index.js';
|
|
3
3
|
import { ResourceStore } from './resources.js';
|
|
4
4
|
/**
|
|
@@ -46,4 +46,26 @@ export interface FlowDoc {
|
|
|
46
46
|
readonly info?: DocumentInfo;
|
|
47
47
|
/** Document natural language hint (BCP-47), e.g. for tagged-PDF /Lang. */
|
|
48
48
|
readonly language?: string;
|
|
49
|
+
/**
|
|
50
|
+
* ECMA-376 §17.15.1.35 `w:doNotExpandShiftReturn` — a justified line that
|
|
51
|
+
* ends at a soft line break keeps its natural width.
|
|
52
|
+
*/
|
|
53
|
+
readonly doNotExpandShiftReturn?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* ECMA-376 §17.2.1 `w:background` — the colour every page is painted, when
|
|
56
|
+
* the document asks for one AND §17.15.1.28 `w:displayBackgroundShape` says
|
|
57
|
+
* to draw it.
|
|
58
|
+
*/
|
|
59
|
+
readonly pageBackgroundColorHex?: string;
|
|
60
|
+
/**
|
|
61
|
+
* ECMA-376 §17.2.1 — the same background as the FILL it is, when that is more
|
|
62
|
+
* than a flat colour: the `v:background`'s gradient or picture. The colour
|
|
63
|
+
* above stays the flat fallback, for writers that paint only colours.
|
|
64
|
+
*/
|
|
65
|
+
readonly pageBackgroundFill?: ShapeFill;
|
|
66
|
+
/**
|
|
67
|
+
* ECMA-376 §17.15.1.38 `w:gutterAtTop` — the binding space `w:pgMar
|
|
68
|
+
* @w:gutter` reserves belongs to the TOP margin rather than the left.
|
|
69
|
+
*/
|
|
70
|
+
readonly gutterAtTop?: boolean;
|
|
49
71
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** How to run {@link lzwDecodeMsb}: the early-change flag and an output cap. */
|
|
2
|
+
export interface LzwOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Widen the code one step BEFORE the table fills (PDF `/EarlyChange`,
|
|
5
|
+
* TIFF's own convention). 1 by default, matching both specifications' norm.
|
|
6
|
+
*/
|
|
7
|
+
readonly earlyChange?: number;
|
|
8
|
+
/** Stop after this many output bytes — the guard against a decompression bomb. */
|
|
9
|
+
readonly limit: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Decode an MSB-first LZW stream (TIFF 6.0 §13 / ISO 32000-1 §7.4.4.2). The
|
|
13
|
+
* KwKwK case (a code not yet in the table) repeats the previous string plus its
|
|
14
|
+
* own first byte. Truncated input ends the stream where it stops rather than
|
|
15
|
+
* throwing: a partly-decoded strip still prints.
|
|
16
|
+
*/
|
|
17
|
+
export declare function lzwDecodeMsb(data: Uint8Array, options: LzwOptions): Uint8Array;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
//#region src/core/lzw.ts
|
|
2
|
+
/**
|
|
3
|
+
* Decode an MSB-first LZW stream (TIFF 6.0 §13 / ISO 32000-1 §7.4.4.2). The
|
|
4
|
+
* KwKwK case (a code not yet in the table) repeats the previous string plus its
|
|
5
|
+
* own first byte. Truncated input ends the stream where it stops rather than
|
|
6
|
+
* throwing: a partly-decoded strip still prints.
|
|
7
|
+
*/
|
|
8
|
+
function lzwDecodeMsb(data, options) {
|
|
9
|
+
const earlyChange = options.earlyChange ?? 1;
|
|
10
|
+
let out = new Uint8Array(4096);
|
|
11
|
+
let outLen = 0;
|
|
12
|
+
const emit = (e) => {
|
|
13
|
+
if (outLen + e.length > options.limit) return false;
|
|
14
|
+
if (outLen + e.length > out.length) {
|
|
15
|
+
let cap = out.length * 2;
|
|
16
|
+
while (cap < outLen + e.length) cap *= 2;
|
|
17
|
+
const grown = new Uint8Array(cap);
|
|
18
|
+
grown.set(out.subarray(0, outLen));
|
|
19
|
+
out = grown;
|
|
20
|
+
}
|
|
21
|
+
out.set(e, outLen);
|
|
22
|
+
outLen += e.length;
|
|
23
|
+
return true;
|
|
24
|
+
};
|
|
25
|
+
let bitBuffer = 0;
|
|
26
|
+
let bitCount = 0;
|
|
27
|
+
let pos = 0;
|
|
28
|
+
let codeLength = 9;
|
|
29
|
+
const readCode = () => {
|
|
30
|
+
while (bitCount < codeLength) {
|
|
31
|
+
if (pos >= data.length) return -1;
|
|
32
|
+
bitBuffer = (bitBuffer << 8 | data[pos++]) >>> 0;
|
|
33
|
+
bitCount += 8;
|
|
34
|
+
}
|
|
35
|
+
bitCount -= codeLength;
|
|
36
|
+
return bitBuffer >>> bitCount & (1 << codeLength) - 1;
|
|
37
|
+
};
|
|
38
|
+
const dict = new Array(4096);
|
|
39
|
+
let nextCode = 258;
|
|
40
|
+
let prev = -1;
|
|
41
|
+
const reset = () => {
|
|
42
|
+
for (let i = 0; i < 256; i++) dict[i] = Uint8Array.of(i);
|
|
43
|
+
nextCode = 258;
|
|
44
|
+
codeLength = 9;
|
|
45
|
+
prev = -1;
|
|
46
|
+
};
|
|
47
|
+
reset();
|
|
48
|
+
for (;;) {
|
|
49
|
+
const code = readCode();
|
|
50
|
+
if (code < 0 || code === 257) break;
|
|
51
|
+
if (code === 256) {
|
|
52
|
+
reset();
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (prev < 0) {
|
|
56
|
+
const first = dict[code];
|
|
57
|
+
if (!first || !emit(first)) break;
|
|
58
|
+
prev = code;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const prevEntry = dict[prev];
|
|
62
|
+
let entry = code < nextCode ? dict[code] : void 0;
|
|
63
|
+
if (!entry) {
|
|
64
|
+
entry = new Uint8Array(prevEntry.length + 1);
|
|
65
|
+
entry.set(prevEntry);
|
|
66
|
+
entry[prevEntry.length] = prevEntry[0];
|
|
67
|
+
}
|
|
68
|
+
if (!emit(entry)) break;
|
|
69
|
+
if (nextCode < 4096) {
|
|
70
|
+
const added = new Uint8Array(prevEntry.length + 1);
|
|
71
|
+
added.set(prevEntry);
|
|
72
|
+
added[prevEntry.length] = entry[0];
|
|
73
|
+
dict[nextCode++] = added;
|
|
74
|
+
if (nextCode + earlyChange === 512) codeLength = 10;
|
|
75
|
+
else if (nextCode + earlyChange === 1024) codeLength = 11;
|
|
76
|
+
else if (nextCode + earlyChange === 2048) codeLength = 12;
|
|
77
|
+
}
|
|
78
|
+
prev = code;
|
|
79
|
+
}
|
|
80
|
+
return out.subarray(0, outLen);
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { lzwDecodeMsb };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PathSegment } from '../vector.js';
|
|
2
|
+
import { MetaPicture } from './picture.js';
|
|
3
|
+
/** Whether the bytes open with an EMF header (MS-EMF §2.3.4.2: type 1 + " EMF"). */
|
|
4
|
+
export declare function isEmf(bytes: Uint8Array): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Read an EMF into its primitives.
|
|
7
|
+
*
|
|
8
|
+
* @throws Error when the bytes are not an EMF or its header is malformed.
|
|
9
|
+
*/
|
|
10
|
+
export declare function readEmf(bytes: Uint8Array): MetaPicture;
|
|
11
|
+
type Mapper = (x: number, y: number) => {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
};
|
|
15
|
+
export declare function ellipseSegments(map: Mapper, l: number, t: number, r: number, b: number): Array<PathSegment>;
|
|
16
|
+
export {};
|