reamkit 1.16.0 → 1.17.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 +473 -7
- 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 +181 -0
- package/dist/esm/core/ir/flow.d.ts +23 -1
- 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/vector.d.ts +17 -0
- package/dist/esm/excel/print-model.js +1 -1
- 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 +18 -4
- package/dist/esm/layout/page-doc.d.ts +62 -2
- package/dist/esm/layout/styled-layout.d.ts +44 -2
- package/dist/esm/layout/styled-layout.js +1870 -228
- package/dist/esm/pdf/shading.js +76 -6
- package/dist/esm/pdf/styled-page-emitter.js +217 -43
- package/dist/esm/pdf/vector-graphics.d.ts +28 -1
- package/dist/esm/pdf/vector-graphics.js +94 -11
- 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 +1 -1
- package/dist/esm/word/document-parser.d.ts +96 -7
- package/dist/esm/word/document-parser.js +537 -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 +53 -5
- package/dist/esm/word/drawing-parser.js +1540 -54
- 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';
|
|
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';
|
|
28
28
|
readonly widthPx: number;
|
|
29
29
|
readonly heightPx: number;
|
|
30
30
|
/**
|
package/dist/esm/core/images.js
CHANGED
|
@@ -10,6 +10,7 @@ function detectImageFormat(bytes) {
|
|
|
10
10
|
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
11
|
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
12
|
if (bytes.length >= 4 && bytes[0] === 255 && bytes[1] === 79 && bytes[2] === 255 && bytes[3] === 81) return "jpeg2000";
|
|
13
|
+
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";
|
|
13
14
|
return null;
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
@@ -24,6 +25,7 @@ function prepareImage(bytes, options = {}) {
|
|
|
24
25
|
if (format === "jpeg") return prepareJpeg(bytes);
|
|
25
26
|
if (format === "png") return preparePng(bytes, options);
|
|
26
27
|
if (format === "jpeg2000") return prepareJpeg2000(bytes);
|
|
28
|
+
if (format === "gif") return prepareGif(bytes, options);
|
|
27
29
|
throw new Error("Unsupported image format");
|
|
28
30
|
}
|
|
29
31
|
function prepareJpeg2000(bytes) {
|
|
@@ -125,6 +127,185 @@ function readJpegInfo(bytes) {
|
|
|
125
127
|
}
|
|
126
128
|
throw new Error("JPEG SOFn marker not found");
|
|
127
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Decode a GIF (87a/89a) into a {@link PreparedImage}: the first frame's
|
|
132
|
+
* palette indices expanded to RGB and re-compressed, with a transparent index
|
|
133
|
+
* becoming a soft mask. PDF has no GIF filter, so the pixels have to be handed
|
|
134
|
+
* over as raw ones — dml-picture-in-textframe.docx (and seventeen other corpus
|
|
135
|
+
* documents) draw nothing at all without this.
|
|
136
|
+
*
|
|
137
|
+
* @param bytes The GIF file.
|
|
138
|
+
* @param options Emit options (PDF/A-1 forbids the soft mask).
|
|
139
|
+
* @returns The prepared image.
|
|
140
|
+
* @throws Error when the file is truncated or holds no image.
|
|
141
|
+
*/
|
|
142
|
+
function prepareGif(bytes, options = {}) {
|
|
143
|
+
const frame = decodeGif(bytes);
|
|
144
|
+
const rgb = new Uint8Array(frame.width * frame.height * 3);
|
|
145
|
+
const alpha = frame.transparentIndex !== void 0 ? new Uint8Array(frame.width * frame.height) : void 0;
|
|
146
|
+
for (let i = 0; i < frame.indices.length; i++) {
|
|
147
|
+
const idx = frame.indices[i];
|
|
148
|
+
const p = idx * 3;
|
|
149
|
+
rgb[i * 3] = frame.palette[p] ?? 0;
|
|
150
|
+
rgb[i * 3 + 1] = frame.palette[p + 1] ?? 0;
|
|
151
|
+
rgb[i * 3 + 2] = frame.palette[p + 2] ?? 0;
|
|
152
|
+
if (alpha) alpha[i] = idx === frame.transparentIndex ? 0 : 255;
|
|
153
|
+
}
|
|
154
|
+
if (alpha && options.flattenAlpha) {
|
|
155
|
+
for (let i = 0; i < alpha.length; i++) if (alpha[i] === 0) {
|
|
156
|
+
rgb[i * 3] = 255;
|
|
157
|
+
rgb[i * 3 + 1] = 255;
|
|
158
|
+
rgb[i * 3 + 2] = 255;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
format: "gif",
|
|
163
|
+
mimeType: "image/gif",
|
|
164
|
+
widthPx: frame.width,
|
|
165
|
+
heightPx: frame.height,
|
|
166
|
+
colorSpace: "DeviceRGB",
|
|
167
|
+
bitsPerComponent: 8,
|
|
168
|
+
filter: "FlateDecode",
|
|
169
|
+
data: zlibSync(rgb),
|
|
170
|
+
...alpha && !options.flattenAlpha ? { smaskData: zlibSync(alpha) } : {}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function decodeGif(bytes) {
|
|
174
|
+
const u16 = (i) => (bytes[i] ?? 0) | (bytes[i + 1] ?? 0) << 8;
|
|
175
|
+
const screenW = u16(6);
|
|
176
|
+
const screenH = u16(8);
|
|
177
|
+
const packed = bytes[10] ?? 0;
|
|
178
|
+
let pos = 13;
|
|
179
|
+
let globalPalette = new Uint8Array(0);
|
|
180
|
+
if ((packed & 128) !== 0) {
|
|
181
|
+
const size = 3 * (1 << (packed & 7) + 1);
|
|
182
|
+
globalPalette = bytes.subarray(pos, pos + size);
|
|
183
|
+
pos += size;
|
|
184
|
+
}
|
|
185
|
+
let transparentIndex;
|
|
186
|
+
while (pos < bytes.length) {
|
|
187
|
+
const block = bytes[pos];
|
|
188
|
+
if (block === 33) {
|
|
189
|
+
const label = bytes[pos + 1];
|
|
190
|
+
pos += 2;
|
|
191
|
+
if (label === 249 && (bytes[pos + 1] & 1) !== 0) transparentIndex = bytes[pos + 4];
|
|
192
|
+
while (pos < bytes.length && bytes[pos] !== 0) pos += bytes[pos] + 1;
|
|
193
|
+
pos++;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (block !== 44) break;
|
|
197
|
+
const width = u16(pos + 5) || screenW;
|
|
198
|
+
const height = u16(pos + 7) || screenH;
|
|
199
|
+
const flags = bytes[pos + 9];
|
|
200
|
+
pos += 10;
|
|
201
|
+
let palette = globalPalette;
|
|
202
|
+
if ((flags & 128) !== 0) {
|
|
203
|
+
const size = 3 * (1 << (flags & 7) + 1);
|
|
204
|
+
palette = bytes.subarray(pos, pos + size);
|
|
205
|
+
pos += size;
|
|
206
|
+
}
|
|
207
|
+
const minCodeSize = bytes[pos];
|
|
208
|
+
pos++;
|
|
209
|
+
const chunks = [];
|
|
210
|
+
while (pos < bytes.length && bytes[pos] !== 0) {
|
|
211
|
+
const len = bytes[pos];
|
|
212
|
+
chunks.push(bytes.subarray(pos + 1, pos + 1 + len));
|
|
213
|
+
pos += len + 1;
|
|
214
|
+
}
|
|
215
|
+
const indices = lzwDecode(concatBytes(chunks), minCodeSize, width * height);
|
|
216
|
+
return {
|
|
217
|
+
width,
|
|
218
|
+
height,
|
|
219
|
+
indices: (flags & 64) !== 0 ? deinterlace(indices, width, height) : indices,
|
|
220
|
+
palette,
|
|
221
|
+
...transparentIndex !== void 0 ? { transparentIndex } : {}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
throw new Error("GIF holds no image");
|
|
225
|
+
}
|
|
226
|
+
function lzwDecode(data, minCodeSize, pixelCount) {
|
|
227
|
+
const clear = 1 << minCodeSize;
|
|
228
|
+
const end = clear + 1;
|
|
229
|
+
const out = new Uint8Array(pixelCount);
|
|
230
|
+
let outPos = 0;
|
|
231
|
+
let prefix = new Int32Array(4096);
|
|
232
|
+
let suffix = new Uint8Array(4096);
|
|
233
|
+
let length = new Int32Array(4096);
|
|
234
|
+
const reset = () => {
|
|
235
|
+
prefix = new Int32Array(4096);
|
|
236
|
+
suffix = new Uint8Array(4096);
|
|
237
|
+
length = new Int32Array(4096);
|
|
238
|
+
for (let i = 0; i < clear; i++) {
|
|
239
|
+
prefix[i] = -1;
|
|
240
|
+
suffix[i] = i;
|
|
241
|
+
length[i] = 1;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
reset();
|
|
245
|
+
let next = end + 1;
|
|
246
|
+
let codeSize = minCodeSize + 1;
|
|
247
|
+
let prev = -1;
|
|
248
|
+
let bitBuf = 0;
|
|
249
|
+
let bitCount = 0;
|
|
250
|
+
const stack = new Uint8Array(4096);
|
|
251
|
+
for (let i = 0; i < data.length || bitCount >= codeSize;) {
|
|
252
|
+
while (bitCount < codeSize && i < data.length) {
|
|
253
|
+
bitBuf |= (data[i] & 255) << bitCount;
|
|
254
|
+
bitCount += 8;
|
|
255
|
+
i++;
|
|
256
|
+
}
|
|
257
|
+
if (bitCount < codeSize) break;
|
|
258
|
+
const code = bitBuf & (1 << codeSize) - 1;
|
|
259
|
+
bitBuf >>>= codeSize;
|
|
260
|
+
bitCount -= codeSize;
|
|
261
|
+
if (code === clear) {
|
|
262
|
+
reset();
|
|
263
|
+
next = end + 1;
|
|
264
|
+
codeSize = minCodeSize + 1;
|
|
265
|
+
prev = -1;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (code === end) break;
|
|
269
|
+
let cur = code;
|
|
270
|
+
if (code >= next) {
|
|
271
|
+
if (prev < 0) break;
|
|
272
|
+
cur = prev;
|
|
273
|
+
}
|
|
274
|
+
let depth = 0;
|
|
275
|
+
while (cur >= 0 && depth < 4096) {
|
|
276
|
+
stack[depth] = suffix[cur];
|
|
277
|
+
depth++;
|
|
278
|
+
cur = prefix[cur];
|
|
279
|
+
}
|
|
280
|
+
if (code >= next) stack[depth++] = stack[depth - 2] ?? 0;
|
|
281
|
+
for (let d = depth - 1; d >= 0 && outPos < out.length; d--) out[outPos++] = stack[d];
|
|
282
|
+
if (prev >= 0 && next < 4096) {
|
|
283
|
+
prefix[next] = prev;
|
|
284
|
+
suffix[next] = stack[depth - 1];
|
|
285
|
+
length[next] = (length[prev] ?? 1) + 1;
|
|
286
|
+
next++;
|
|
287
|
+
if (next === 1 << codeSize && codeSize < 12) codeSize++;
|
|
288
|
+
}
|
|
289
|
+
prev = code < next ? code : prev;
|
|
290
|
+
if (outPos >= out.length) break;
|
|
291
|
+
}
|
|
292
|
+
return out;
|
|
293
|
+
}
|
|
294
|
+
function deinterlace(src, width, height) {
|
|
295
|
+
const out = new Uint8Array(src.length);
|
|
296
|
+
const passes = [
|
|
297
|
+
[0, 8],
|
|
298
|
+
[4, 8],
|
|
299
|
+
[2, 4],
|
|
300
|
+
[1, 2]
|
|
301
|
+
];
|
|
302
|
+
let row = 0;
|
|
303
|
+
for (const [start, step] of passes) for (let y = start; y < height; y += step) {
|
|
304
|
+
out.set(src.subarray(row * width, (row + 1) * width), y * width);
|
|
305
|
+
row++;
|
|
306
|
+
}
|
|
307
|
+
return out;
|
|
308
|
+
}
|
|
128
309
|
function preparePng(bytes, options = {}) {
|
|
129
310
|
let decoded = decodePng(bytes);
|
|
130
311
|
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
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BodyElement, Numbering } from '../document-model/index.js';
|
|
1
|
+
import { BodyElement, Numbering, StyleSheet } from '../document-model/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Apply list numbering to a body as a FlowDoc transform (§17.9): walk the
|
|
4
4
|
* elements (recursing into table cells), advancing list counters and prepending
|
|
@@ -8,9 +8,11 @@ import { BodyElement, Numbering } from '../document-model/index.js';
|
|
|
8
8
|
*
|
|
9
9
|
* @param body The body elements to transform.
|
|
10
10
|
* @param numbering The parsed numbering definitions, or `undefined`.
|
|
11
|
+
* @param styles The style sheet, for a paragraph whose numbering comes from
|
|
12
|
+
* its STYLE (§17.9.24) rather than its own `w:numPr`.
|
|
11
13
|
* @returns A new body with markers materialized.
|
|
12
14
|
*/
|
|
13
|
-
export declare function applyNumbering(body: ReadonlyArray<BodyElement>, numbering: Numbering | undefined): Array<BodyElement>;
|
|
15
|
+
export declare function applyNumbering(body: ReadonlyArray<BodyElement>, numbering: Numbering | undefined, styles?: StyleSheet): Array<BodyElement>;
|
|
14
16
|
/**
|
|
15
17
|
* Apply {@link applyNumbering} to each header/footer band independently (each
|
|
16
18
|
* gets its own counter state), keyed as in the input map.
|
|
@@ -19,4 +21,4 @@ export declare function applyNumbering(body: ReadonlyArray<BodyElement>, numberi
|
|
|
19
21
|
* @param numbering The parsed numbering definitions, or `undefined`.
|
|
20
22
|
* @returns A new map with numbering applied (empty when `hf` is empty/absent).
|
|
21
23
|
*/
|
|
22
|
-
export declare function applyNumberingToHeadersFooters(hf: ReadonlyMap<string, ReadonlyArray<BodyElement>> | undefined, numbering: Numbering | undefined): ReadonlyMap<string, ReadonlyArray<BodyElement>>;
|
|
24
|
+
export declare function applyNumberingToHeadersFooters(hf: ReadonlyMap<string, ReadonlyArray<BodyElement>> | undefined, numbering: Numbering | undefined, styles?: StyleSheet): ReadonlyMap<string, ReadonlyArray<BodyElement>>;
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { pt } from "../ir/units.js";
|
|
2
|
+
import { NumberingState, effectiveAbstract } from "./state.js";
|
|
3
|
+
import { resolveParagraphProperties } from "../style-cascade/resolver.js";
|
|
4
|
+
import "../style-cascade/index.js";
|
|
2
5
|
//#region src/core/numbering/apply.ts
|
|
6
|
+
/** The height a picture bullet takes when neither the level nor the paragraph
|
|
7
|
+
* mark states a font size — Word's own default text size. */
|
|
8
|
+
var DEFAULT_BULLET_HEIGHT_PT = 11;
|
|
9
|
+
/** SOFT HYPHEN, which a `w:lvlText` may spell a dash bullet with (§17.9.11). */
|
|
10
|
+
var SOFT_HYPHEN = "";
|
|
3
11
|
/**
|
|
4
12
|
* Apply list numbering to a body as a FlowDoc transform (§17.9): walk the
|
|
5
13
|
* elements (recursing into table cells), advancing list counters and prepending
|
|
@@ -9,28 +17,51 @@ import { NumberingState } from "./state.js";
|
|
|
9
17
|
*
|
|
10
18
|
* @param body The body elements to transform.
|
|
11
19
|
* @param numbering The parsed numbering definitions, or `undefined`.
|
|
20
|
+
* @param styles The style sheet, for a paragraph whose numbering comes from
|
|
21
|
+
* its STYLE (§17.9.24) rather than its own `w:numPr`.
|
|
12
22
|
* @returns A new body with markers materialized.
|
|
13
23
|
*/
|
|
14
|
-
function applyNumbering(body, numbering) {
|
|
24
|
+
function applyNumbering(body, numbering, styles) {
|
|
15
25
|
if (!numbering || numbering.abstractNums.size === 0) return body.map((b) => b);
|
|
16
26
|
const state = new NumberingState();
|
|
27
|
+
const numberingOf = (p) => p.properties.numbering ?? (styles ? resolveParagraphProperties(p.properties, styles).numbering : void 0);
|
|
17
28
|
const transformParagraph = (p) => {
|
|
18
|
-
|
|
29
|
+
if (p.properties.sectionBreak === true && p.runs.every((r) => r.text === "")) return p;
|
|
30
|
+
const ref = numberingOf(p);
|
|
19
31
|
if (!ref) return p;
|
|
20
32
|
const marker = state.resolveMarker(numbering, ref);
|
|
21
33
|
if (marker === null) return p;
|
|
22
34
|
const instance = numbering.numInstances.get(ref.numId);
|
|
23
|
-
const level = (instance ? numbering
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
const level = (instance ? effectiveAbstract(numbering, instance) : void 0)?.levels.get(ref.ilvl);
|
|
36
|
+
const pic = level?.picBullet;
|
|
37
|
+
const bulletCapPt = level?.runProperties.fontSizePt ?? p.properties.runProperties?.fontSizePt ?? DEFAULT_BULLET_HEIGHT_PT;
|
|
38
|
+
const bulletScale = pic ? Math.min(1, bulletCapPt / Math.max(1, pic.heightPt)) : 1;
|
|
39
|
+
const markerRuns = pic ? [{
|
|
40
|
+
text: "",
|
|
41
|
+
properties: {},
|
|
42
|
+
listMarker: true,
|
|
43
|
+
inlineImage: {
|
|
44
|
+
resource: pic.resource,
|
|
45
|
+
width: pt(pic.widthPt * bulletScale),
|
|
46
|
+
height: pt(pic.heightPt * bulletScale)
|
|
47
|
+
}
|
|
48
|
+
}, {
|
|
49
|
+
text: " ",
|
|
50
|
+
properties: {},
|
|
27
51
|
listMarker: true
|
|
28
|
-
}
|
|
52
|
+
}] : [{
|
|
53
|
+
text: `${marker.replaceAll(SOFT_HYPHEN, "-")}\t`,
|
|
54
|
+
properties: {
|
|
55
|
+
...p.properties.runProperties,
|
|
56
|
+
...level?.runProperties
|
|
57
|
+
},
|
|
58
|
+
listMarker: true
|
|
59
|
+
}];
|
|
29
60
|
const newProps = mergeIndentFromLevel(p.properties, level?.paragraphProperties);
|
|
30
61
|
return {
|
|
31
62
|
...p,
|
|
32
63
|
properties: newProps,
|
|
33
|
-
runs: [
|
|
64
|
+
runs: [...markerRuns, ...p.runs]
|
|
34
65
|
};
|
|
35
66
|
};
|
|
36
67
|
const visit = (el) => {
|
|
@@ -73,11 +104,11 @@ function mergeIndentFromLevel(paragraphProps, levelProps) {
|
|
|
73
104
|
* @param numbering The parsed numbering definitions, or `undefined`.
|
|
74
105
|
* @returns A new map with numbering applied (empty when `hf` is empty/absent).
|
|
75
106
|
*/
|
|
76
|
-
function applyNumberingToHeadersFooters(hf, numbering) {
|
|
107
|
+
function applyNumberingToHeadersFooters(hf, numbering, styles) {
|
|
77
108
|
if (!hf || hf.size === 0) return /* @__PURE__ */ new Map();
|
|
78
109
|
if (!numbering || numbering.abstractNums.size === 0) return hf;
|
|
79
110
|
const out = /* @__PURE__ */ new Map();
|
|
80
|
-
for (const [key, value] of hf) out.set(key, applyNumbering(value, numbering));
|
|
111
|
+
for (const [key, value] of hf) out.set(key, applyNumbering(value, numbering, styles));
|
|
81
112
|
return out;
|
|
82
113
|
}
|
|
83
114
|
//#endregion
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import { AbstractNumbering, Numbering, NumberingLevel, NumberingReference } from '../document-model/index.js';
|
|
1
|
+
import { AbstractNumbering, Numbering, NumberingInstance, NumberingLevel, NumberingReference } from '../document-model/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* §17.9.27 — the levels an INSTANCE actually numbers by: its abstract
|
|
4
|
+
* definition's, with any `w:lvlOverride/w:lvl` shadowing them. Cached per
|
|
5
|
+
* instance, since every list paragraph asks for it.
|
|
6
|
+
*
|
|
7
|
+
* @param numbering The parsed numbering definitions.
|
|
8
|
+
* @param instance The `w:num` the paragraph refers to.
|
|
9
|
+
* @returns The effective abstract numbering, or `undefined` when the instance
|
|
10
|
+
* names one that is not there.
|
|
11
|
+
*/
|
|
12
|
+
export declare function effectiveAbstract(numbering: Numbering, instance: NumberingInstance): AbstractNumbering | undefined;
|
|
2
13
|
/**
|
|
3
14
|
* Mutable list-counter state for one numbering scope (e.g. a body or a single
|
|
4
15
|
* header/footer band). Tracks a counter per list per level and advances them as
|
|
@@ -6,6 +17,7 @@ import { AbstractNumbering, Numbering, NumberingLevel, NumberingReference } from
|
|
|
6
17
|
*/
|
|
7
18
|
export declare class NumberingState {
|
|
8
19
|
private readonly counters;
|
|
20
|
+
private readonly restarted;
|
|
9
21
|
/**
|
|
10
22
|
* Advance the counter for `ref` (resetting deeper levels) and format its
|
|
11
23
|
* marker text. The first hit at a level seeds it from the level's `start`;
|