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.
Files changed (76) 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 +526 -8
  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 +219 -0
  19. package/dist/esm/core/ir/flow.d.ts +23 -1
  20. package/dist/esm/core/lzw.d.ts +17 -0
  21. package/dist/esm/core/lzw.js +83 -0
  22. package/dist/esm/core/metafile/emf.d.ts +16 -0
  23. package/dist/esm/core/metafile/emf.js +635 -0
  24. package/dist/esm/core/metafile/picture.d.ts +106 -0
  25. package/dist/esm/core/metafile/picture.js +55 -0
  26. package/dist/esm/core/metafile/wmf.d.ts +9 -0
  27. package/dist/esm/core/metafile/wmf.js +371 -0
  28. package/dist/esm/core/numbering/apply.d.ts +5 -3
  29. package/dist/esm/core/numbering/apply.js +42 -11
  30. package/dist/esm/core/numbering/index.js +2 -0
  31. package/dist/esm/core/numbering/state.d.ts +13 -1
  32. package/dist/esm/core/numbering/state.js +256 -15
  33. package/dist/esm/core/po-helpers.js +16 -1
  34. package/dist/esm/core/style-cascade/resolver.js +66 -11
  35. package/dist/esm/core/style-cascade/table.js +35 -11
  36. package/dist/esm/core/style-cascade/types.d.ts +42 -1
  37. package/dist/esm/core/style-cascade/types.js +4 -0
  38. package/dist/esm/core/tiff.d.ts +20 -0
  39. package/dist/esm/core/tiff.js +339 -0
  40. package/dist/esm/core/vector.d.ts +22 -0
  41. package/dist/esm/excel/print-model.js +4 -2
  42. package/dist/esm/excel/sheet-to-flow.js +10 -8
  43. package/dist/esm/excel/worksheet-parser.js +1 -1
  44. package/dist/esm/html/html-writer.js +20 -5
  45. package/dist/esm/layout/page-doc.d.ts +107 -4
  46. package/dist/esm/layout/styled-layout.d.ts +44 -2
  47. package/dist/esm/layout/styled-layout.js +2330 -283
  48. package/dist/esm/pdf/shading.js +76 -6
  49. package/dist/esm/pdf/styled-page-emitter.js +294 -45
  50. package/dist/esm/pdf/vector-graphics.d.ts +29 -2
  51. package/dist/esm/pdf/vector-graphics.js +96 -12
  52. package/dist/esm/pdf-reader/image-decode.js +7 -75
  53. package/dist/esm/pptx/pptx-reader.js +1 -0
  54. package/dist/esm/pptx/slide-parser.d.ts +21 -4
  55. package/dist/esm/pptx/slide-parser.js +86 -23
  56. package/dist/esm/svg/svg-writer.js +2 -2
  57. package/dist/esm/word/document-parser.d.ts +96 -7
  58. package/dist/esm/word/document-parser.js +540 -116
  59. package/dist/esm/word/docx-reader.js +135 -43
  60. package/dist/esm/word/docx-writer.js +6 -1
  61. package/dist/esm/word/drawing-parser.d.ts +58 -5
  62. package/dist/esm/word/drawing-parser.js +1632 -63
  63. package/dist/esm/word/index.d.ts +4 -4
  64. package/dist/esm/word/numbering-parser.d.ts +2 -1
  65. package/dist/esm/word/numbering-parser.js +107 -21
  66. package/dist/esm/word/paragraph-properties.d.ts +11 -1
  67. package/dist/esm/word/paragraph-properties.js +219 -10
  68. package/dist/esm/word/run-properties.js +45 -1
  69. package/dist/esm/word/settings-parser.d.ts +24 -0
  70. package/dist/esm/word/settings-parser.js +31 -5
  71. package/dist/esm/word/shading.d.ts +11 -0
  72. package/dist/esm/word/shading.js +64 -0
  73. package/dist/esm/word/styles-parser.js +8 -2
  74. package/dist/esm/word/table-parser.d.ts +9 -1
  75. package/dist/esm/word/table-parser.js +182 -30
  76. 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' | '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
  /**
@@ -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 {};