reamkit 1.15.4 → 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 (127) hide show
  1. package/dist/esm/core/bidi/segments.js +1 -2
  2. package/dist/esm/core/converter/facade.d.ts +5 -0
  3. package/dist/esm/core/converter/project.js +5 -1
  4. package/dist/esm/core/converter/ream.d.ts +6 -0
  5. package/dist/esm/core/converter/ream.js +34 -8
  6. package/dist/esm/core/document-model/index.d.ts +1 -1
  7. package/dist/esm/core/document-model/types.d.ts +659 -7
  8. package/dist/esm/core/drawingml/chart-geometry.d.ts +34 -10
  9. package/dist/esm/core/drawingml/chart-geometry.js +424 -92
  10. package/dist/esm/core/drawingml/chart-parser.js +301 -11
  11. package/dist/esm/core/drawingml/colors.d.ts +7 -7
  12. package/dist/esm/core/drawingml/colors.js +84 -18
  13. package/dist/esm/core/drawingml/preset-geometry.js +240 -6
  14. package/dist/esm/core/drawingml/shape-render.d.ts +12 -0
  15. package/dist/esm/core/drawingml/shape-render.js +116 -3
  16. package/dist/esm/core/drawingml/sparkline-geometry.js +6 -4
  17. package/dist/esm/core/drawingml/theme-parser.d.ts +51 -0
  18. package/dist/esm/core/drawingml/theme-parser.js +92 -1
  19. package/dist/esm/core/font/measure.d.ts +5 -0
  20. package/dist/esm/core/font/measure.js +21 -1
  21. package/dist/esm/core/font/ttf-subset.js +26 -1
  22. package/dist/esm/core/images.d.ts +2 -2
  23. package/dist/esm/core/images.js +353 -43
  24. package/dist/esm/core/indexed-colors.d.ts +11 -0
  25. package/dist/esm/core/indexed-colors.js +81 -0
  26. package/dist/esm/core/ir/flow.d.ts +23 -1
  27. package/dist/esm/core/ir/sheet.d.ts +61 -0
  28. package/dist/esm/{excel → core}/number-format.d.ts +37 -0
  29. package/dist/esm/core/number-format.js +800 -0
  30. package/dist/esm/core/numbering/apply.d.ts +5 -3
  31. package/dist/esm/core/numbering/apply.js +42 -11
  32. package/dist/esm/core/numbering/index.js +2 -0
  33. package/dist/esm/core/numbering/state.d.ts +13 -1
  34. package/dist/esm/core/numbering/state.js +256 -15
  35. package/dist/esm/core/opc/alternate-content.d.ts +13 -0
  36. package/dist/esm/core/opc/alternate-content.js +72 -0
  37. package/dist/esm/core/opc/core-properties.js +1 -0
  38. package/dist/esm/core/opc/package.d.ts +14 -1
  39. package/dist/esm/core/opc/package.js +43 -4
  40. package/dist/esm/core/opc/relationships.js +1 -0
  41. package/dist/esm/core/opc/xml-entities.d.ts +12 -0
  42. package/dist/esm/core/opc/xml-entities.js +91 -0
  43. package/dist/esm/core/po-helpers.js +16 -1
  44. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  45. package/dist/esm/core/spreadsheet-model/types.d.ts +137 -0
  46. package/dist/esm/core/style-cascade/resolver.js +66 -11
  47. package/dist/esm/core/style-cascade/table.js +35 -11
  48. package/dist/esm/core/style-cascade/types.d.ts +42 -1
  49. package/dist/esm/core/style-cascade/types.js +4 -0
  50. package/dist/esm/core/vector.d.ts +30 -0
  51. package/dist/esm/excel/activex-parser.js +1 -0
  52. package/dist/esm/excel/column-bands.d.ts +26 -1
  53. package/dist/esm/excel/column-bands.js +156 -20
  54. package/dist/esm/excel/comments-parser.js +5 -3
  55. package/dist/esm/excel/conditional-format.d.ts +25 -2
  56. package/dist/esm/excel/conditional-format.js +63 -19
  57. package/dist/esm/excel/escaped-text.d.ts +9 -0
  58. package/dist/esm/excel/escaped-text.js +20 -0
  59. package/dist/esm/excel/form-control-parser.js +1 -0
  60. package/dist/esm/excel/formula/dates.js +1 -1
  61. package/dist/esm/excel/header-footer.d.ts +9 -1
  62. package/dist/esm/excel/header-footer.js +162 -14
  63. package/dist/esm/excel/index.d.ts +1 -1
  64. package/dist/esm/excel/index.js +2 -2
  65. package/dist/esm/excel/pivot-table-parser.js +1 -0
  66. package/dist/esm/excel/print-model.d.ts +75 -0
  67. package/dist/esm/excel/print-model.js +899 -77
  68. package/dist/esm/excel/printer-settings.d.ts +16 -0
  69. package/dist/esm/excel/printer-settings.js +36 -0
  70. package/dist/esm/excel/rich-value.d.ts +13 -0
  71. package/dist/esm/excel/rich-value.js +120 -0
  72. package/dist/esm/excel/shared-strings-parser.js +11 -7
  73. package/dist/esm/excel/sheet-drawing.d.ts +11 -0
  74. package/dist/esm/excel/sheet-drawing.js +26 -3
  75. package/dist/esm/excel/sheet-shape-parser.d.ts +53 -1
  76. package/dist/esm/excel/sheet-shape-parser.js +461 -32
  77. package/dist/esm/excel/sheet-to-flow.d.ts +23 -2
  78. package/dist/esm/excel/sheet-to-flow.js +553 -36
  79. package/dist/esm/excel/slicer-parser.js +1 -0
  80. package/dist/esm/excel/styles-parser.d.ts +30 -2
  81. package/dist/esm/excel/styles-parser.js +177 -40
  82. package/dist/esm/excel/table-parser.js +1 -0
  83. package/dist/esm/excel/vml-drawing.d.ts +68 -0
  84. package/dist/esm/excel/vml-drawing.js +158 -0
  85. package/dist/esm/excel/workbook-parser.d.ts +5 -0
  86. package/dist/esm/excel/workbook-parser.js +5 -1
  87. package/dist/esm/excel/worksheet-parser.d.ts +2 -1
  88. package/dist/esm/excel/worksheet-parser.js +150 -61
  89. package/dist/esm/excel/xlsx-reader.js +242 -11
  90. package/dist/esm/excel/xlsx-to-pdf.js +12 -1
  91. package/dist/esm/html/html-writer.js +29 -6
  92. package/dist/esm/layout/page-doc.d.ts +87 -1
  93. package/dist/esm/layout/styled-layout.d.ts +46 -2
  94. package/dist/esm/layout/styled-layout.js +2345 -274
  95. package/dist/esm/pdf/cid-font.js +20 -9
  96. package/dist/esm/pdf/objects.d.ts +13 -0
  97. package/dist/esm/pdf/objects.js +18 -1
  98. package/dist/esm/pdf/shading.js +76 -6
  99. package/dist/esm/pdf/styled-page-emitter.js +355 -49
  100. package/dist/esm/pdf/text-page-renderer.js +1 -0
  101. package/dist/esm/pdf/vector-graphics.d.ts +31 -2
  102. package/dist/esm/pdf/vector-graphics.js +103 -3
  103. package/dist/esm/pptx/pptx-reader.js +2 -0
  104. package/dist/esm/pptx/slide-parser.d.ts +21 -4
  105. package/dist/esm/pptx/slide-parser.js +98 -24
  106. package/dist/esm/svg/svg-writer.js +23 -4
  107. package/dist/esm/word/document-parser.d.ts +96 -7
  108. package/dist/esm/word/document-parser.js +538 -116
  109. package/dist/esm/word/docx-reader.js +135 -43
  110. package/dist/esm/word/docx-writer.js +6 -1
  111. package/dist/esm/word/drawing-parser.d.ts +86 -5
  112. package/dist/esm/word/drawing-parser.js +1590 -54
  113. package/dist/esm/word/index.d.ts +4 -4
  114. package/dist/esm/word/numbering-parser.d.ts +2 -1
  115. package/dist/esm/word/numbering-parser.js +108 -21
  116. package/dist/esm/word/paragraph-properties.d.ts +11 -1
  117. package/dist/esm/word/paragraph-properties.js +219 -10
  118. package/dist/esm/word/run-properties.js +45 -1
  119. package/dist/esm/word/settings-parser.d.ts +24 -0
  120. package/dist/esm/word/settings-parser.js +32 -5
  121. package/dist/esm/word/shading.d.ts +11 -0
  122. package/dist/esm/word/shading.js +64 -0
  123. package/dist/esm/word/styles-parser.js +9 -2
  124. package/dist/esm/word/table-parser.d.ts +9 -1
  125. package/dist/esm/word/table-parser.js +182 -30
  126. package/package.json +7 -3
  127. package/dist/esm/excel/number-format.js +0 -476
@@ -2,6 +2,31 @@
2
2
  var HEAD_CHECKSUM_ADJUSTMENT_OFFSET = 8;
3
3
  var HEAD_INDEX_TO_LOC_FORMAT_OFFSET = 50;
4
4
  var MAGIC_HEAD_CHECKSUM = 2981146554;
5
+ /**
6
+ * The tables a `/FontFile2` may carry (ISO 32000-1 §9.9.2).
7
+ *
8
+ * The spec names `glyf`, `head`, `hhea`, `hmtx`, `loca` and `maxp` as required,
9
+ * `cvt `/`fpgm`/`prep` when the glyphs are hinted, and `cmap` for a SYMBOLIC
10
+ * font. A CIDFontType2 with `/CIDToGIDMap /Identity` is not symbolic in that
11
+ * sense — §9.7.4.2 makes the CID the glyph index and never consults `cmap`, so
12
+ * 9.3 KB of it rode along in every font we embedded. Everything else is dead
13
+ * weight too, in a PDF that does its own positioning and text extraction: we copied `post`, `GPOS`, `GSUB`, `GDEF`, `name`,
14
+ * `hdmx`, `LTSH` and the rest through untouched, and they were 120 KB of the
15
+ * 136 KB font program in 55906-MultiSheetRefs.xlsx. `OS/2` is kept because
16
+ * viewers read its metrics when a descriptor is incomplete.
17
+ */
18
+ var EMBEDDABLE_TABLES = new Set([
19
+ "glyf",
20
+ "head",
21
+ "hhea",
22
+ "hmtx",
23
+ "loca",
24
+ "maxp",
25
+ "cvt ",
26
+ "fpgm",
27
+ "prep",
28
+ "OS/2"
29
+ ]);
5
30
  var COMPOSITE_ARG_1_AND_2_ARE_WORDS = 1;
6
31
  var COMPOSITE_WE_HAVE_A_SCALE = 8;
7
32
  var COMPOSITE_MORE_COMPONENTS = 32;
@@ -115,7 +140,7 @@ function assembleSubsetTtf(parsed, newGlyf, newLoca) {
115
140
  newHead[HEAD_CHECKSUM_ADJUSTMENT_OFFSET + 3] = 0;
116
141
  newHead[HEAD_INDEX_TO_LOC_FORMAT_OFFSET] = 0;
117
142
  newHead[HEAD_INDEX_TO_LOC_FORMAT_OFFSET + 1] = 1;
118
- const orderedTags = [...parsed.tables.keys()];
143
+ const orderedTags = [...parsed.tables.keys()].filter((t) => EMBEDDABLE_TABLES.has(t));
119
144
  const tableData = /* @__PURE__ */ new Map();
120
145
  for (const tag of orderedTags) if (tag === "glyf") tableData.set(tag, newGlyf);
121
146
  else if (tag === "loca") tableData.set(tag, newLocaBytes);
@@ -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);
@@ -146,6 +327,8 @@ function decodePng(bytes) {
146
327
  let bitDepth = 0;
147
328
  let colorType = 0;
148
329
  let interlaceMethod = 0;
330
+ let palette;
331
+ let paletteAlpha;
149
332
  const idatChunks = [];
150
333
  let pos = 8;
151
334
  while (pos + 12 <= bytes.length) {
@@ -158,57 +341,161 @@ function decodePng(bytes) {
158
341
  bitDepth = bytes[dataOff + 8];
159
342
  colorType = bytes[dataOff + 9];
160
343
  interlaceMethod = bytes[dataOff + 12];
161
- } else if (type === "IDAT") idatChunks.push(bytes.subarray(dataOff, dataOff + len));
344
+ } else if (type === "PLTE") palette = bytes.subarray(dataOff, dataOff + len);
345
+ else if (type === "tRNS") paletteAlpha = bytes.subarray(dataOff, dataOff + len);
346
+ else if (type === "IDAT") idatChunks.push(bytes.subarray(dataOff, dataOff + len));
162
347
  else if (type === "IEND") break;
163
348
  pos = dataOff + len + 4;
164
349
  }
165
350
  if (width === 0 || height === 0) throw new Error("PNG: missing IHDR");
166
351
  if (idatChunks.length === 0) throw new Error("PNG: no IDAT");
167
- if (interlaceMethod !== 0) throw new Error("PNG: interlaced not supported");
168
- if (bitDepth !== 8) throw new Error(`PNG: bit depth ${bitDepth} not supported`);
169
- const inflated = unzlibSync(concatBytes(idatChunks));
352
+ if (![
353
+ 1,
354
+ 2,
355
+ 4,
356
+ 8,
357
+ 16
358
+ ].includes(bitDepth)) throw new Error(`PNG: bit depth ${bitDepth} not supported`);
170
359
  const channels = pngChannels(colorType);
171
360
  if (channels === 0) throw new Error(`PNG color type ${colorType} not supported`);
172
- const bpp = channels;
173
- const scanlineBytes = width * bpp;
174
- const expected = height * (1 + scanlineBytes);
175
- if (inflated.length !== expected) throw new Error(`PNG: inflated size ${inflated.length} != expected ${expected} (interlaced or malformed)`);
176
- const raw = new Uint8Array(height * scanlineBytes);
177
- let prev = null;
178
- for (let y = 0; y < height; y++) {
179
- const inOff = y * (1 + scanlineBytes);
180
- const filterType = inflated[inOff];
181
- const decoded = new Uint8Array(scanlineBytes);
182
- for (let x = 0; x < scanlineBytes; x++) {
183
- const filt = inflated[inOff + 1 + x];
184
- const a = x < bpp ? 0 : decoded[x - bpp];
185
- const b = prev ? prev[x] : 0;
186
- const c = !prev || x < bpp ? 0 : prev[x - bpp];
187
- let v;
188
- switch (filterType) {
189
- case 0:
190
- v = filt;
191
- break;
192
- case 1:
193
- v = filt + a;
194
- break;
195
- case 2:
196
- v = filt + b;
197
- break;
198
- case 3:
199
- v = filt + Math.floor((a + b) / 2);
200
- break;
201
- case 4:
202
- v = filt + paethPredictor(a, b, c);
203
- break;
204
- default: throw new Error(`PNG: unknown filter type ${filterType}`);
361
+ const raw = decodeSamples(unzlibSync(concatBytes(idatChunks)), width, height, channels, bitDepth, interlaceMethod === 1, colorType !== 3);
362
+ return splitChannels(width, height, colorType, raw, palette, paletteAlpha);
363
+ }
364
+ /**
365
+ * §7.2 / §9 — the image's samples, one byte each, in `width × height ×
366
+ * channels` order: the IDAT stream inflated, each scanline unfiltered against
367
+ * the one before it, and (when the image is interlaced) each of Adam7's seven
368
+ * passes scattered back into the full-size grid.
369
+ *
370
+ * @param inflated The inflated IDAT stream.
371
+ * @param width Image width in pixels.
372
+ * @param height Image height in pixels.
373
+ * @param channels Samples per pixel for the colour type.
374
+ * @param bitDepth Bits per sample (1, 2, 4, 8 or 16).
375
+ * @param interlaced True for interlace method 1 (Adam7).
376
+ * @param scaleToByte Stretch a sub-byte sample to 0…255 — right for an
377
+ * intensity, wrong for a palette index.
378
+ * @returns One byte per sample.
379
+ */
380
+ function decodeSamples(inflated, width, height, channels, bitDepth, interlaced, scaleToByte) {
381
+ const out = new Uint8Array(width * height * channels);
382
+ const passes = interlaced ? ADAM7_PASSES : [{
383
+ xStart: 0,
384
+ yStart: 0,
385
+ xStep: 1,
386
+ yStep: 1
387
+ }];
388
+ const maxSample = (1 << Math.min(bitDepth, 8)) - 1;
389
+ const bpp = Math.max(1, Math.ceil(channels * bitDepth / 8));
390
+ let off = 0;
391
+ for (const p of passes) {
392
+ const pw = Math.ceil((width - p.xStart) / p.xStep);
393
+ const ph = Math.ceil((height - p.yStart) / p.yStep);
394
+ if (pw <= 0 || ph <= 0) continue;
395
+ const lineBytes = Math.ceil(pw * channels * bitDepth / 8);
396
+ let prev = null;
397
+ for (let y = 0; y < ph; y++) {
398
+ if (off + 1 + lineBytes > inflated.length) throw new Error("PNG: truncated image data");
399
+ const filterType = inflated[off];
400
+ const line = unfilterScanline(inflated, off + 1, lineBytes, bpp, filterType, prev);
401
+ off += 1 + lineBytes;
402
+ const destY = p.yStart + y * p.yStep;
403
+ for (let px = 0; px < pw; px++) {
404
+ const destX = p.xStart + px * p.xStep;
405
+ const dest = (destY * width + destX) * channels;
406
+ for (let ch = 0; ch < channels; ch++) {
407
+ const v = sampleAt(line, px * channels + ch, bitDepth);
408
+ out[dest + ch] = scaleToByte && bitDepth < 8 ? Math.round(v * 255 / maxSample) : v;
409
+ }
205
410
  }
206
- decoded[x] = v & 255;
411
+ prev = line;
412
+ }
413
+ }
414
+ return out;
415
+ }
416
+ /**
417
+ * §9.2 — Adam7's seven passes, each a sub-grid of the image: where the pass
418
+ * starts and how far apart its pixels sit.
419
+ */
420
+ var ADAM7_PASSES = [
421
+ {
422
+ xStart: 0,
423
+ yStart: 0,
424
+ xStep: 8,
425
+ yStep: 8
426
+ },
427
+ {
428
+ xStart: 4,
429
+ yStart: 0,
430
+ xStep: 8,
431
+ yStep: 8
432
+ },
433
+ {
434
+ xStart: 0,
435
+ yStart: 4,
436
+ xStep: 4,
437
+ yStep: 8
438
+ },
439
+ {
440
+ xStart: 2,
441
+ yStart: 0,
442
+ xStep: 4,
443
+ yStep: 4
444
+ },
445
+ {
446
+ xStart: 0,
447
+ yStart: 2,
448
+ xStep: 2,
449
+ yStep: 4
450
+ },
451
+ {
452
+ xStart: 1,
453
+ yStart: 0,
454
+ xStep: 2,
455
+ yStep: 2
456
+ },
457
+ {
458
+ xStart: 0,
459
+ yStart: 1,
460
+ xStep: 1,
461
+ yStep: 2
462
+ }
463
+ ];
464
+ function unfilterScanline(src, from, lineBytes, bpp, filterType, prev) {
465
+ const decoded = new Uint8Array(lineBytes);
466
+ for (let x = 0; x < lineBytes; x++) {
467
+ const filt = src[from + x];
468
+ const a = x < bpp ? 0 : decoded[x - bpp];
469
+ const b = prev ? prev[x] : 0;
470
+ const c = !prev || x < bpp ? 0 : prev[x - bpp];
471
+ let v;
472
+ switch (filterType) {
473
+ case 0:
474
+ v = filt;
475
+ break;
476
+ case 1:
477
+ v = filt + a;
478
+ break;
479
+ case 2:
480
+ v = filt + b;
481
+ break;
482
+ case 3:
483
+ v = filt + Math.floor((a + b) / 2);
484
+ break;
485
+ case 4:
486
+ v = filt + paethPredictor(a, b, c);
487
+ break;
488
+ default: throw new Error(`PNG: unknown filter type ${filterType}`);
207
489
  }
208
- raw.set(decoded, y * scanlineBytes);
209
- prev = decoded;
490
+ decoded[x] = v & 255;
210
491
  }
211
- return splitChannels(width, height, colorType, raw);
492
+ return decoded;
493
+ }
494
+ function sampleAt(line, i, bitDepth) {
495
+ if (bitDepth === 8) return line[i];
496
+ if (bitDepth === 16) return line[i * 2];
497
+ const per = 8 / bitDepth;
498
+ return (line[Math.floor(i / per)] ?? 0) >> 8 - bitDepth * (i % per + 1) & (1 << bitDepth) - 1;
212
499
  }
213
500
  function flattenAlphaOnWhite(decoded) {
214
501
  const alpha = decoded.smaskRaw;
@@ -231,7 +518,7 @@ function flattenAlphaOnWhite(decoded) {
231
518
  bitsPerComponent: decoded.bitsPerComponent
232
519
  };
233
520
  }
234
- function splitChannels(width, height, colorType, raw) {
521
+ function splitChannels(width, height, colorType, raw, palette, paletteAlpha) {
235
522
  const pixelCount = width * height;
236
523
  if (colorType === 0) return {
237
524
  width,
@@ -240,6 +527,28 @@ function splitChannels(width, height, colorType, raw) {
240
527
  colorSpace: "DeviceGray",
241
528
  bitsPerComponent: 8
242
529
  };
530
+ if (colorType === 3) {
531
+ if (!palette || palette.length < 3) throw new Error("PNG: palette image without PLTE");
532
+ const entries = Math.floor(palette.length / 3);
533
+ const rgb = new Uint8Array(pixelCount * 3);
534
+ const alpha = paletteAlpha && paletteAlpha.length > 0 ? new Uint8Array(pixelCount) : void 0;
535
+ for (let i = 0; i < pixelCount; i++) {
536
+ const idx = raw[i];
537
+ const src = (idx < entries ? idx : 0) * 3;
538
+ rgb[i * 3] = palette[src];
539
+ rgb[i * 3 + 1] = palette[src + 1];
540
+ rgb[i * 3 + 2] = palette[src + 2];
541
+ if (alpha) alpha[i] = paletteAlpha[idx] ?? 255;
542
+ }
543
+ return {
544
+ width,
545
+ height,
546
+ raw: rgb,
547
+ colorSpace: "DeviceRGB",
548
+ bitsPerComponent: 8,
549
+ ...alpha ? { smaskRaw: alpha } : {}
550
+ };
551
+ }
243
552
  if (colorType === 2) return {
244
553
  width,
245
554
  height,
@@ -281,11 +590,12 @@ function splitChannels(width, height, colorType, raw) {
281
590
  smaskRaw: alpha
282
591
  };
283
592
  }
284
- throw new Error(`PNG color type ${colorType} not supported (palette/indexed?)`);
593
+ throw new Error(`PNG color type ${colorType} not supported`);
285
594
  }
286
595
  function pngChannels(colorType) {
287
596
  if (colorType === 0) return 1;
288
597
  if (colorType === 2) return 3;
598
+ if (colorType === 3) return 1;
289
599
  if (colorType === 4) return 2;
290
600
  if (colorType === 6) return 4;
291
601
  return 0;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * ECMA-376 §18.8.27 — the legacy indexed colour palette a `<color indexed="N">`
3
+ * refers to. It predates `rgb` and theme colours and is still what Excel writes
4
+ * for anything picked from the classic 40-colour dropdown, so a file using it
5
+ * is not unusual: tdf58243.xlsx colours its header cells `indexed="10"`, and
6
+ * ignoring the attribute rendered them black where every other reader shows red.
7
+ *
8
+ * Entries 0-7 are repeated at 8-15 (a quirk of the original palette); 64 and 65
9
+ * are the system foreground and background.
10
+ */
11
+ export declare const INDEXED_COLORS: ReadonlyArray<string>;
@@ -0,0 +1,81 @@
1
+ //#region src/core/indexed-colors.ts
2
+ /**
3
+ * ECMA-376 §18.8.27 — the legacy indexed colour palette a `<color indexed="N">`
4
+ * refers to. It predates `rgb` and theme colours and is still what Excel writes
5
+ * for anything picked from the classic 40-colour dropdown, so a file using it
6
+ * is not unusual: tdf58243.xlsx colours its header cells `indexed="10"`, and
7
+ * ignoring the attribute rendered them black where every other reader shows red.
8
+ *
9
+ * Entries 0-7 are repeated at 8-15 (a quirk of the original palette); 64 and 65
10
+ * are the system foreground and background.
11
+ */
12
+ var INDEXED_COLORS = [
13
+ "000000",
14
+ "FFFFFF",
15
+ "FF0000",
16
+ "00FF00",
17
+ "0000FF",
18
+ "FFFF00",
19
+ "FF00FF",
20
+ "00FFFF",
21
+ "000000",
22
+ "FFFFFF",
23
+ "FF0000",
24
+ "00FF00",
25
+ "0000FF",
26
+ "FFFF00",
27
+ "FF00FF",
28
+ "00FFFF",
29
+ "800000",
30
+ "008000",
31
+ "000080",
32
+ "808000",
33
+ "800080",
34
+ "008080",
35
+ "C0C0C0",
36
+ "808080",
37
+ "9999FF",
38
+ "993366",
39
+ "FFFFCC",
40
+ "CCFFFF",
41
+ "660066",
42
+ "FF8080",
43
+ "0066CC",
44
+ "CCCCFF",
45
+ "000080",
46
+ "FF00FF",
47
+ "FFFF00",
48
+ "00FFFF",
49
+ "800080",
50
+ "800000",
51
+ "008080",
52
+ "0000FF",
53
+ "00CCFF",
54
+ "CCFFFF",
55
+ "CCFFCC",
56
+ "FFFF99",
57
+ "99CCFF",
58
+ "FF99CC",
59
+ "CC99FF",
60
+ "FFCC99",
61
+ "3366FF",
62
+ "33CCCC",
63
+ "99CC00",
64
+ "FFCC00",
65
+ "FF9900",
66
+ "FF6600",
67
+ "666699",
68
+ "969696",
69
+ "003366",
70
+ "339966",
71
+ "003300",
72
+ "333300",
73
+ "993300",
74
+ "993366",
75
+ "333399",
76
+ "333333",
77
+ "000000",
78
+ "FFFFFF"
79
+ ];
80
+ //#endregion
81
+ export { INDEXED_COLORS };
@@ -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
  }