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.
Files changed (65) hide show
  1. package/dist/esm/core/bidi/segments.js +1 -2
  2. package/dist/esm/core/converter/project.js +5 -1
  3. package/dist/esm/core/document-model/index.d.ts +1 -1
  4. package/dist/esm/core/document-model/types.d.ts +473 -7
  5. package/dist/esm/core/drawingml/chart-geometry.d.ts +8 -12
  6. package/dist/esm/core/drawingml/chart-geometry.js +58 -9
  7. package/dist/esm/core/drawingml/chart-parser.js +44 -5
  8. package/dist/esm/core/drawingml/colors.d.ts +1 -1
  9. package/dist/esm/core/drawingml/colors.js +27 -1
  10. package/dist/esm/core/drawingml/preset-geometry.js +233 -0
  11. package/dist/esm/core/drawingml/shape-render.d.ts +12 -0
  12. package/dist/esm/core/drawingml/shape-render.js +116 -3
  13. package/dist/esm/core/drawingml/theme-parser.d.ts +10 -0
  14. package/dist/esm/core/drawingml/theme-parser.js +19 -1
  15. package/dist/esm/core/font/measure.d.ts +5 -0
  16. package/dist/esm/core/font/measure.js +21 -1
  17. package/dist/esm/core/images.d.ts +2 -2
  18. package/dist/esm/core/images.js +181 -0
  19. package/dist/esm/core/ir/flow.d.ts +23 -1
  20. package/dist/esm/core/numbering/apply.d.ts +5 -3
  21. package/dist/esm/core/numbering/apply.js +42 -11
  22. package/dist/esm/core/numbering/index.js +2 -0
  23. package/dist/esm/core/numbering/state.d.ts +13 -1
  24. package/dist/esm/core/numbering/state.js +256 -15
  25. package/dist/esm/core/po-helpers.js +16 -1
  26. package/dist/esm/core/style-cascade/resolver.js +66 -11
  27. package/dist/esm/core/style-cascade/table.js +35 -11
  28. package/dist/esm/core/style-cascade/types.d.ts +42 -1
  29. package/dist/esm/core/style-cascade/types.js +4 -0
  30. package/dist/esm/core/vector.d.ts +17 -0
  31. package/dist/esm/excel/print-model.js +1 -1
  32. package/dist/esm/excel/sheet-to-flow.js +10 -8
  33. package/dist/esm/excel/worksheet-parser.js +1 -1
  34. package/dist/esm/html/html-writer.js +18 -4
  35. package/dist/esm/layout/page-doc.d.ts +62 -2
  36. package/dist/esm/layout/styled-layout.d.ts +44 -2
  37. package/dist/esm/layout/styled-layout.js +1870 -228
  38. package/dist/esm/pdf/shading.js +76 -6
  39. package/dist/esm/pdf/styled-page-emitter.js +217 -43
  40. package/dist/esm/pdf/vector-graphics.d.ts +28 -1
  41. package/dist/esm/pdf/vector-graphics.js +94 -11
  42. package/dist/esm/pptx/pptx-reader.js +1 -0
  43. package/dist/esm/pptx/slide-parser.d.ts +21 -4
  44. package/dist/esm/pptx/slide-parser.js +86 -23
  45. package/dist/esm/svg/svg-writer.js +1 -1
  46. package/dist/esm/word/document-parser.d.ts +96 -7
  47. package/dist/esm/word/document-parser.js +537 -116
  48. package/dist/esm/word/docx-reader.js +135 -43
  49. package/dist/esm/word/docx-writer.js +6 -1
  50. package/dist/esm/word/drawing-parser.d.ts +53 -5
  51. package/dist/esm/word/drawing-parser.js +1540 -54
  52. package/dist/esm/word/index.d.ts +4 -4
  53. package/dist/esm/word/numbering-parser.d.ts +2 -1
  54. package/dist/esm/word/numbering-parser.js +107 -21
  55. package/dist/esm/word/paragraph-properties.d.ts +11 -1
  56. package/dist/esm/word/paragraph-properties.js +219 -10
  57. package/dist/esm/word/run-properties.js +45 -1
  58. package/dist/esm/word/settings-parser.d.ts +24 -0
  59. package/dist/esm/word/settings-parser.js +31 -5
  60. package/dist/esm/word/shading.d.ts +11 -0
  61. package/dist/esm/word/shading.js +64 -0
  62. package/dist/esm/word/styles-parser.js +8 -2
  63. package/dist/esm/word/table-parser.d.ts +9 -1
  64. package/dist/esm/word/table-parser.js +182 -30
  65. 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") return `<radialGradient id="${id}">${stops}</radialGradient>`;
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
  /**
@@ -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 { NumberingState } from "./state.js";
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
- const ref = p.properties.numbering;
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.abstractNums.get(instance.abstractNumId) : void 0)?.levels.get(ref.ilvl);
24
- const markerRun = {
25
- text: `${marker}\t`,
26
- properties: level?.runProperties ?? {},
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: [markerRun, ...p.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
@@ -0,0 +1,2 @@
1
+ import "./state.js";
2
+ import "./apply.js";
@@ -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`;