reamkit 1.17.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.
@@ -0,0 +1,339 @@
1
+ import { lzwDecodeMsb } from "./lzw.js";
2
+ import { unzlibSync } from "fflate";
3
+ //#region src/core/tiff.ts
4
+ var MAX_PIXELS = 4e7;
5
+ var TAG = {
6
+ width: 256,
7
+ height: 257,
8
+ bitsPerSample: 258,
9
+ compression: 259,
10
+ photometric: 262,
11
+ fillOrder: 266,
12
+ stripOffsets: 273,
13
+ samplesPerPixel: 277,
14
+ rowsPerStrip: 278,
15
+ stripByteCounts: 279,
16
+ planarConfig: 284,
17
+ predictor: 317,
18
+ colorMap: 320,
19
+ tileWidth: 322,
20
+ tileLength: 323,
21
+ tileOffsets: 324,
22
+ tileByteCounts: 325,
23
+ extraSamples: 338,
24
+ sampleFormat: 339
25
+ };
26
+ /** Whether the bytes open with a TIFF header (§2: `II*\0` or `MM\0*`). */
27
+ function isTiff(bytes) {
28
+ if (bytes.length < 8) return false;
29
+ const le = bytes[0] === 73 && bytes[1] === 73;
30
+ const be = bytes[0] === 77 && bytes[1] === 77;
31
+ if (!le && !be) return false;
32
+ return (le ? bytes[2] | bytes[3] << 8 : bytes[2] << 8 | bytes[3]) === 42;
33
+ }
34
+ /**
35
+ * Decode the first image of a baseline TIFF into 8-bit chunky samples.
36
+ *
37
+ * @throws Error when the file is malformed or uses a feature outside baseline
38
+ * TIFF (planar separation, JPEG/fax compression, floating-point samples).
39
+ */
40
+ function decodeTiff(bytes) {
41
+ if (!isTiff(bytes)) throw new Error("TIFF: not a TIFF file");
42
+ const le = bytes[0] === 73;
43
+ const r = new Reader(bytes, le);
44
+ const ifd = r.readIfd(r.u32(4));
45
+ const width = num(ifd, TAG.width, 0);
46
+ const height = num(ifd, TAG.height, 0);
47
+ if (width <= 0 || height <= 0) throw new Error("TIFF: no image dimensions");
48
+ if (width * height > MAX_PIXELS) throw new Error("TIFF: image too large to decode");
49
+ const samples = num(ifd, TAG.samplesPerPixel, 1);
50
+ const bitsList = list(ifd, TAG.bitsPerSample, [1]);
51
+ const bits = bitsList[0] ?? 1;
52
+ if (bitsList.some((b) => b !== bits)) throw new Error("TIFF: mixed sample depths");
53
+ if (![
54
+ 1,
55
+ 2,
56
+ 4,
57
+ 8,
58
+ 16
59
+ ].includes(bits)) throw new Error(`TIFF: ${String(bits)}-bit samples`);
60
+ if (num(ifd, TAG.planarConfig, 1) !== 1) throw new Error("TIFF: planar separation");
61
+ if (list(ifd, TAG.sampleFormat, [1]).some((f) => f !== 1 && f !== 2)) throw new Error("TIFF: non-integer samples");
62
+ const photometric = num(ifd, TAG.photometric, samples >= 3 ? 2 : 1);
63
+ const compression = num(ifd, TAG.compression, 1);
64
+ const predictor = num(ifd, TAG.predictor, 1);
65
+ if (predictor !== 1 && predictor !== 2) throw new Error("TIFF: floating-point predictor");
66
+ const blocks = blockLayout(ifd, width, height);
67
+ const bytesPerRow = Math.ceil(blocks.blockWidth * samples * bits / 8);
68
+ const raw = new Uint8Array(width * height * samples * (bits === 16 ? 2 : 1));
69
+ const rawRowBytes = width * samples * (bits === 16 ? 2 : 1);
70
+ const blockBytes = bytesPerRow * blocks.blockHeight;
71
+ for (const b of blocks.blocks) {
72
+ const count = b.byteCount > 0 ? b.byteCount : blockBytes;
73
+ const rows = unpredict(decodeBlock(bytes.subarray(b.offset, b.offset + count), compression, blockBytes), predictor, bytesPerRow, samples, bits, le);
74
+ for (let y = 0; y < blocks.blockHeight; y++) {
75
+ const destY = b.y + y;
76
+ if (destY >= height) break;
77
+ const src = y * bytesPerRow;
78
+ if (src >= rows.length) break;
79
+ const row = expandRow(rows.subarray(src, src + bytesPerRow), bits, blocks.blockWidth * samples);
80
+ const keep = Math.min(row.length, (width - b.x) * samples * (bits === 16 ? 2 : 1));
81
+ raw.set(row.subarray(0, keep), destY * rawRowBytes + b.x * samples * (bits === 16 ? 2 : 1));
82
+ }
83
+ }
84
+ return toChannels(bits === 16 ? highBytes(raw, le) : raw, {
85
+ width,
86
+ height,
87
+ samples,
88
+ bits,
89
+ photometric,
90
+ palette: list(ifd, TAG.colorMap, []),
91
+ extra: list(ifd, TAG.extraSamples, [])
92
+ });
93
+ }
94
+ var TYPE_SIZE = new Map([
95
+ [1, 1],
96
+ [2, 1],
97
+ [3, 2],
98
+ [4, 4],
99
+ [5, 8],
100
+ [6, 1],
101
+ [7, 1],
102
+ [8, 2],
103
+ [9, 4],
104
+ [10, 8],
105
+ [11, 4],
106
+ [12, 8]
107
+ ]);
108
+ var Reader = class {
109
+ constructor(b, le) {
110
+ this.b = b;
111
+ this.le = le;
112
+ }
113
+ u16(o) {
114
+ if (o + 2 > this.b.length) throw new Error("TIFF: truncated");
115
+ return this.le ? this.b[o] | this.b[o + 1] << 8 : this.b[o] << 8 | this.b[o + 1];
116
+ }
117
+ u32(o) {
118
+ if (o + 4 > this.b.length) throw new Error("TIFF: truncated");
119
+ return this.le ? (this.b[o] | this.b[o + 1] << 8 | this.b[o + 2] << 16) + this.b[o + 3] * 16777216 : (this.b[o + 3] | this.b[o + 2] << 8 | this.b[o + 1] << 16) + this.b[o] * 16777216;
120
+ }
121
+ /** §2 — the directory at `offset`: a count, then 12-byte entries. */
122
+ readIfd(offset) {
123
+ const count = this.u16(offset);
124
+ const out = /* @__PURE__ */ new Map();
125
+ for (let i = 0; i < count; i++) {
126
+ const e = offset + 2 + i * 12;
127
+ const tag = this.u16(e);
128
+ const type = this.u16(e + 2);
129
+ const n = this.u32(e + 4);
130
+ const size = TYPE_SIZE.get(type) ?? 0;
131
+ if (size === 0 || n > 1e6) continue;
132
+ const at = size * n <= 4 ? e + 8 : this.u32(e + 8);
133
+ out.set(tag, {
134
+ type,
135
+ count: n,
136
+ values: this.readValues(type, n, at)
137
+ });
138
+ }
139
+ return out;
140
+ }
141
+ readValues(type, count, at) {
142
+ const size = TYPE_SIZE.get(type) ?? 1;
143
+ const out = [];
144
+ for (let i = 0; i < count; i++) {
145
+ const o = at + i * size;
146
+ if (o + size > this.b.length) break;
147
+ switch (type) {
148
+ case 1:
149
+ case 2:
150
+ case 6:
151
+ case 7:
152
+ out.push(this.b[o]);
153
+ break;
154
+ case 3:
155
+ out.push(this.u16(o));
156
+ break;
157
+ case 8: {
158
+ const v = this.u16(o);
159
+ out.push(v > 32767 ? v - 65536 : v);
160
+ break;
161
+ }
162
+ case 4:
163
+ out.push(this.u32(o));
164
+ break;
165
+ case 9: {
166
+ const v = this.u32(o);
167
+ out.push(v > 2147483647 ? v - 4294967296 : v);
168
+ break;
169
+ }
170
+ case 5:
171
+ case 10: {
172
+ const d = this.u32(o + 4);
173
+ out.push(d === 0 ? 0 : this.u32(o) / d);
174
+ break;
175
+ }
176
+ default: out.push(0);
177
+ }
178
+ }
179
+ return out;
180
+ }
181
+ };
182
+ function num(ifd, tag, fallback) {
183
+ return ifd.get(tag)?.values[0] ?? fallback;
184
+ }
185
+ function list(ifd, tag, fallback) {
186
+ const e = ifd.get(tag);
187
+ return e && e.values.length > 0 ? e.values : fallback;
188
+ }
189
+ function blockLayout(ifd, width, height) {
190
+ const tileW = num(ifd, TAG.tileWidth, 0);
191
+ const tileH = num(ifd, TAG.tileLength, 0);
192
+ if (tileW > 0 && tileH > 0) {
193
+ const offsets = list(ifd, TAG.tileOffsets, []);
194
+ const counts = list(ifd, TAG.tileByteCounts, []);
195
+ const across = Math.ceil(width / tileW);
196
+ return {
197
+ blocks: offsets.map((offset, i) => ({
198
+ offset,
199
+ byteCount: counts[i] ?? 0,
200
+ x: i % across * tileW,
201
+ y: Math.floor(i / across) * tileH
202
+ })),
203
+ blockWidth: tileW,
204
+ blockHeight: tileH
205
+ };
206
+ }
207
+ const rows = num(ifd, TAG.rowsPerStrip, height) || height;
208
+ const offsets = list(ifd, TAG.stripOffsets, []);
209
+ const counts = list(ifd, TAG.stripByteCounts, []);
210
+ if (offsets.length === 0) throw new Error("TIFF: no image data");
211
+ return {
212
+ blocks: offsets.map((offset, i) => ({
213
+ offset,
214
+ byteCount: counts[i] ?? 0,
215
+ x: 0,
216
+ y: i * rows
217
+ })),
218
+ blockWidth: width,
219
+ blockHeight: rows
220
+ };
221
+ }
222
+ function decodeBlock(data, compression, expected) {
223
+ switch (compression) {
224
+ case 1: return data;
225
+ case 5: return lzwDecodeMsb(data, { limit: expected });
226
+ case 8:
227
+ case 32946: try {
228
+ return unzlibSync(data);
229
+ } catch {
230
+ throw new Error("TIFF: corrupt Deflate strip");
231
+ }
232
+ case 32773: return packBits(data, expected);
233
+ default: throw new Error(`TIFF: compression ${String(compression)}`);
234
+ }
235
+ }
236
+ function packBits(data, expected) {
237
+ const out = new Uint8Array(expected);
238
+ let o = 0;
239
+ for (let i = 0; i < data.length && o < expected;) {
240
+ const n = data[i++];
241
+ if (n === 128) continue;
242
+ if (n < 128) {
243
+ const run = Math.min(n + 1, data.length - i, expected - o);
244
+ out.set(data.subarray(i, i + run), o);
245
+ i += n + 1;
246
+ o += run;
247
+ } else {
248
+ const byte = data[i++] ?? 0;
249
+ const run = Math.min(257 - n, expected - o);
250
+ out.fill(byte, o, o + run);
251
+ o += run;
252
+ }
253
+ }
254
+ return out.subarray(0, o);
255
+ }
256
+ function unpredict(data, predictor, rowBytes, samples, bits, le) {
257
+ if (predictor !== 2 || rowBytes <= 0) return data;
258
+ if (bits !== 8 && bits !== 16) return data;
259
+ const out = data.slice();
260
+ const rows = Math.floor(out.length / rowBytes);
261
+ if (bits === 8) {
262
+ for (let r = 0; r < rows; r++) {
263
+ const off = r * rowBytes;
264
+ for (let i = samples; i < rowBytes; i++) out[off + i] = out[off + i] + out[off + i - samples] & 255;
265
+ }
266
+ return out;
267
+ }
268
+ const stride = samples * 2;
269
+ const hi = le ? 1 : 0;
270
+ const lo = le ? 0 : 1;
271
+ for (let r = 0; r < rows; r++) {
272
+ const off = r * rowBytes;
273
+ for (let i = stride; i + 1 < rowBytes; i += 2) {
274
+ const prev = out[off + i - stride + hi] << 8 | out[off + i - stride + lo];
275
+ const v = (out[off + i + hi] << 8 | out[off + i + lo]) + prev & 65535;
276
+ out[off + i + hi] = v >> 8;
277
+ out[off + i + lo] = v & 255;
278
+ }
279
+ }
280
+ return out;
281
+ }
282
+ function expandRow(row, bits, samplesInRow) {
283
+ if (bits >= 8) return row;
284
+ const out = new Uint8Array(samplesInRow);
285
+ const max = (1 << bits) - 1;
286
+ for (let i = 0; i < samplesInRow; i++) {
287
+ const bit = i * bits;
288
+ const byte = row[bit >> 3] ?? 0;
289
+ const shift = 8 - bits - (bit & 7);
290
+ out[i] = Math.round((byte >> shift & max) * 255 / max);
291
+ }
292
+ return out;
293
+ }
294
+ function highBytes(raw, le) {
295
+ const out = new Uint8Array(raw.length >> 1);
296
+ const hi = le ? 1 : 0;
297
+ for (let i = 0; i < out.length; i++) out[i] = raw[i * 2 + hi];
298
+ return out;
299
+ }
300
+ function toChannels(eight, info) {
301
+ const pixels = info.width * info.height;
302
+ const { photometric, samples } = info;
303
+ if (photometric === 3) {
304
+ const rgb = new Uint8Array(pixels * 3);
305
+ const entries = info.palette.length / 3;
306
+ for (let i = 0; i < pixels; i++) {
307
+ const idx = eight[i * samples] ?? 0;
308
+ const e = info.bits < 8 ? Math.round(idx * ((1 << info.bits) - 1) / 255) : idx;
309
+ rgb[i * 3] = (info.palette[e] ?? 0) >> 8;
310
+ rgb[i * 3 + 1] = (info.palette[entries + e] ?? 0) >> 8;
311
+ rgb[i * 3 + 2] = (info.palette[entries * 2 + e] ?? 0) >> 8;
312
+ }
313
+ return {
314
+ width: info.width,
315
+ height: info.height,
316
+ channels: 3,
317
+ data: rgb
318
+ };
319
+ }
320
+ const color = photometric === 5 ? 4 : photometric === 2 ? 3 : 1;
321
+ if (samples < color) throw new Error("TIFF: too few samples for its colour");
322
+ const alphaAt = info.extra.length > 0 && samples > color ? color : -1;
323
+ const data = samples === color ? eight.subarray(0, pixels * color) : new Uint8Array(pixels * color);
324
+ const alpha = alphaAt >= 0 ? new Uint8Array(pixels) : void 0;
325
+ if (samples !== color) for (let i = 0; i < pixels; i++) {
326
+ for (let c = 0; c < color; c++) data[i * color + c] = eight[i * samples + c] ?? 0;
327
+ if (alpha) alpha[i] = eight[i * samples + alphaAt] ?? 255;
328
+ }
329
+ if (photometric === 0) for (let i = 0; i < data.length; i++) data[i] = 255 - data[i];
330
+ return {
331
+ width: info.width,
332
+ height: info.height,
333
+ channels: color === 4 ? 4 : color === 3 ? 3 : 1,
334
+ data,
335
+ ...alpha ? { alpha } : {}
336
+ };
337
+ }
338
+ //#endregion
339
+ export { decodeTiff, isTiff };
@@ -97,6 +97,11 @@ export interface VectorShape {
97
97
  * over `fillColorHex`.
98
98
  */
99
99
  readonly fillGradient?: ShapeGradient;
100
+ /**
101
+ * §20.1.2.3.1 — the fill's opacity, `0..1`. Absent is opaque. Writers without
102
+ * transparency paint `fillColorHex` at full strength.
103
+ */
104
+ readonly fillAlpha?: number;
100
105
  /** Stroke description. Omitted = no stroke. */
101
106
  readonly stroke?: StrokeStyle;
102
107
  /**
@@ -873,6 +873,7 @@ function worksheetToBody(worksheet, sharedStrings, styles, date1904, print) {
873
873
  const rowHeightTwips = scaled && baseRowProps !== void 0 ? Math.max(1, Math.round(baseRowProps.heightTwips * printScale)) : baseRowProps?.heightTwips;
874
874
  const isTitleRow = print.titleRows !== void 0 && absR >= print.titleRows.startRow && absR <= print.titleRows.endRow;
875
875
  const rowProps = {
876
+ cantSplit: true,
876
877
  ...rowHeightTwips !== void 0 ? {
877
878
  height: twipsToPt(rowHeightTwips),
878
879
  heightRule: baseRowProps?.heightRule ?? "atLeast"
@@ -1736,7 +1737,8 @@ function slicerTable(slicer) {
1736
1737
  };
1737
1738
  const rowProps = {
1738
1739
  height: pt(SLICER_ROW_PT),
1739
- heightRule: "atLeast"
1740
+ heightRule: "atLeast",
1741
+ cantSplit: true
1740
1742
  };
1741
1743
  const rows = [];
1742
1744
  rows.push({
@@ -1,8 +1,8 @@
1
1
  import { FEATURES } from "../core/ir/features.js";
2
2
  import { detectImageFormat } from "../core/images.js";
3
+ import { PathBuilder, flipTransform, svgPathData } from "../core/vector.js";
3
4
  import { EMPTY_STYLE_SHEET, resolveParagraphProperties, resolveRunProperties } from "../core/style-cascade/resolver.js";
4
5
  import "../core/style-cascade/index.js";
5
- import { PathBuilder, flipTransform, svgPathData } from "../core/vector.js";
6
6
  import { buildSparkline } from "../core/drawingml/sparkline-geometry.js";
7
7
  import { arcPoint, arcToBeziers } from "../core/arc-to-bezier.js";
8
8
  import { buildChartScene } from "../core/drawingml/chart-geometry.js";
@@ -652,7 +652,8 @@ var MIME_BY_FORMAT = {
652
652
  jpeg: "image/jpeg",
653
653
  png: "image/png",
654
654
  jpeg2000: "image/jp2",
655
- gif: "image/gif"
655
+ gif: "image/gif",
656
+ tiff: "image/tiff"
656
657
  };
657
658
  function dataUri(resource, store) {
658
659
  if (resource === void 0) return void 0;
@@ -2,8 +2,9 @@ import { BorderStyle, ImageCrop, PictureOutline, ShapeShadow } from '../core/doc
2
2
  import { Pt, ResourceId, ResourceStore } from '../core/ir/index.js';
3
3
  import { FontMeasure, ParsedTtf } from '../core/font/index.js';
4
4
  import { ResolvedParagraphProperties, ResolvedRunProperties } from '../core/style-cascade/index.js';
5
- import { PathSegment, VectorPath, VectorShape } from '../core/vector.js';
5
+ import { PathSegment, StrokeStyle, VectorPath, VectorShape } from '../core/vector.js';
6
6
  import { PreparedImage } from '../core/images.js';
7
+ import { MetaPicture } from '../core/metafile/picture.js';
7
8
  /** A font bound into a {@link LaidOutDocument}: the parsed face plus what layout/emit need from it. */
8
9
  export interface FontResource {
9
10
  readonly resourceName: string;
@@ -84,6 +85,17 @@ export interface ImageToken {
84
85
  readonly outline?: PictureOutline;
85
86
  /** §20.1.8.40 — the drop shadow under the picture, when it casts one. */
86
87
  readonly shadow?: ShapeShadow;
88
+ /** §14.1.2.10 — the contrast/brightness wash the picture is drawn through. */
89
+ readonly wash?: {
90
+ readonly gain: number;
91
+ readonly black: number;
92
+ };
93
+ /**
94
+ * MS-EMF / MS-WMF — the picture to DRAW in this token's box, for an inline
95
+ * metafile: it has no raster to place, so the emitter plays its primitives
96
+ * where the token stands.
97
+ */
98
+ readonly metafile?: MetafileDrawing;
87
99
  /** §20.1.8.55 `a:srcRect` — the part of the source the frame shows. */
88
100
  readonly crop?: ImageCrop;
89
101
  /** §20.1.7.6 — degrees clockwise about the box's centre. */
@@ -175,13 +187,39 @@ export interface Line {
175
187
  readonly metricDescentPt?: number;
176
188
  }
177
189
  /** An image bound into a {@link LaidOutDocument}: its resource name plus the decoded/validated bytes. */
190
+ /**
191
+ * MS-EMF / MS-WMF — a metafile picture, ready to draw: paths and words in a
192
+ * local y-up frame whose origin is the picture box's bottom-left corner. The
193
+ * same vocabulary a chart's layout carries, because a metafile draws the same
194
+ * two things.
195
+ */
196
+ export interface MetafileDrawing {
197
+ readonly shapes: ReadonlyArray<{
198
+ readonly paths: ReadonlyArray<VectorPath>;
199
+ readonly fillColorHex?: string;
200
+ readonly stroke?: StrokeStyle;
201
+ }>;
202
+ readonly texts: ReadonlyArray<{
203
+ readonly line: Line;
204
+ readonly x: number;
205
+ readonly y: number;
206
+ readonly rotationDeg?: number;
207
+ }>;
208
+ }
178
209
  export interface ImageResource {
179
210
  readonly resourceName: string;
180
211
  /**
181
212
  * Decoded/validated at layout time (the probe); the emit phase replays it
182
- * without touching the source bytes again.
213
+ * without touching the source bytes again. Absent for a METAFILE, which has
214
+ * no raster to embed: it is drawn as the primitives `metafile` holds.
183
215
  */
184
- readonly prepared: PreparedImage;
216
+ readonly prepared?: PreparedImage;
217
+ /**
218
+ * MS-EMF / MS-WMF — the picture read out of the resource, in its own logical
219
+ * units. A metafile is a little drawing program, not a raster; the layout
220
+ * turns these into the same primitives a chart is made of.
221
+ */
222
+ readonly metafile?: MetaPicture;
185
223
  }
186
224
  /**
187
225
  * The fields every {@link PageItem} shares (ir-design §6, frozen at stage 6.4):
@@ -271,6 +309,11 @@ export interface ImageItem extends PageItemBase {
271
309
  readonly imageResourceName: string;
272
310
  /** §20.1.8.55 `a:srcRect` — the part of the source the frame shows. */
273
311
  readonly crop?: ImageCrop;
312
+ /** §14.1.2.10 — the contrast/brightness wash the picture is drawn through. */
313
+ readonly wash?: {
314
+ readonly gain: number;
315
+ readonly black: number;
316
+ };
274
317
  /** §20.1.7.6 — degrees clockwise about the box's centre. */
275
318
  readonly rotationDeg?: number;
276
319
  /** §20.1.7.6 — the picture drawn mirrored in its box. */