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.
@@ -248,6 +248,15 @@ export interface InlineImage {
248
248
  * six stood flat on the page where both references lift them off it.
249
249
  */
250
250
  readonly shadow?: ShapeShadow;
251
+ /**
252
+ * §14.1.2.10 `@gain`/`@blacklevel` — the contrast and brightness the picture
253
+ * is drawn through, about mid grey: `out = (in - 0.5) * gain + 0.5 + black`.
254
+ * Word washes a watermark out this way.
255
+ */
256
+ readonly wash?: {
257
+ readonly gain: number;
258
+ readonly black: number;
259
+ };
251
260
  readonly width: Pt;
252
261
  readonly height: Pt;
253
262
  /** §20.1.8.55 `a:srcRect` — the part of the source the frame shows. */
@@ -897,6 +906,15 @@ export interface ImageBlock {
897
906
  * six stood flat on the page where both references lift them off it.
898
907
  */
899
908
  readonly shadow?: ShapeShadow;
909
+ /**
910
+ * §14.1.2.10 `@gain`/`@blacklevel` — the contrast and brightness the picture
911
+ * is drawn through, about mid grey: `out = (in - 0.5) * gain + 0.5 + black`.
912
+ * Word washes a watermark out this way.
913
+ */
914
+ readonly wash?: {
915
+ readonly gain: number;
916
+ readonly black: number;
917
+ };
900
918
  readonly width: Pt;
901
919
  readonly height: Pt;
902
920
  /** §20.1.8.55 `a:srcRect` — the part of the source the frame shows. */
@@ -985,6 +1003,12 @@ export interface ShapeFill {
985
1003
  * of the picture the box shows, as the fractions cut from each side.
986
1004
  */
987
1005
  readonly imageCrop?: ImageCrop;
1006
+ /**
1007
+ * §20.1.2.3.1 `a:alpha` / §14.1.2.5 `@opacity` — how opaque the fill is,
1008
+ * `0..1`. Absent is opaque. The colour above is the fill's own, NOT composited
1009
+ * over the paper: what is behind the shape shows through it.
1010
+ */
1011
+ readonly alpha?: number;
988
1012
  }
989
1013
  /** §20.1.10.49 ST_PresetLineDashVal — a shape outline's preset dash pattern. */
990
1014
  export type ShapeDash = 'solid' | 'dot' | 'dash' | 'dashDot' | 'lgDash' | 'lgDashDot' | 'sysDash' | 'sysDot';
@@ -1063,6 +1087,16 @@ export interface ShapeTextBody {
1063
1087
  * WordArt is set at whatever size fills the box it was drawn in.
1064
1088
  */
1065
1089
  readonly fitToBox?: boolean;
1090
+ /**
1091
+ * `wps:txbx @id` / `wps:linkedTxbx @id @seq` — the chain of boxes this one
1092
+ * belongs to. Text that overruns a box continues in the next of its chain;
1093
+ * `seq` 0 is the box that holds the words, and the rest carry none of their
1094
+ * own.
1095
+ */
1096
+ readonly chain?: {
1097
+ readonly id: string;
1098
+ readonly seq: number;
1099
+ };
1066
1100
  }
1067
1101
  /**
1068
1102
  * A block-level DrawingML shape (§20.1): its size, {@link ShapeGeometry}, fill,
@@ -1417,7 +1451,12 @@ export interface SectionProperties {
1417
1451
  * modes (square/tight/through) and `topAndBottom` stay in flow as blocks.
1418
1452
  */
1419
1453
  export interface FloatAnchor {
1420
- readonly wrap: 'none' | 'square' | 'tight' | 'through' | 'topAndBottom';
1454
+ /**
1455
+ * §20.4.2.3 / §17.18.104 — how body text runs past the drawing. `notBeside`
1456
+ * is the FRAME's own mode: the drawing keeps the place its anchor names, but
1457
+ * no text may stand beside it, so its band spans the whole column.
1458
+ */
1459
+ readonly wrap: 'none' | 'square' | 'tight' | 'through' | 'topAndBottom' | 'notBeside';
1421
1460
  readonly behind?: boolean;
1422
1461
  /**
1423
1462
  * §20.4.2.3 `wp:anchor @relativeHeight` — the z-order among the floats on the
@@ -1451,6 +1490,19 @@ export interface FloatAnchor {
1451
1490
  */
1452
1491
  readonly align?: 'top' | 'center' | 'bottom';
1453
1492
  };
1493
+ /**
1494
+ * §20.4.2.3 `@wrapText` on the wrap element — which side(s) of the drawing
1495
+ * text may stand on. `bothSides` is the default and the only value that
1496
+ * fills BOTH gaps; `left`/`right` name one, `largest` the wider.
1497
+ */
1498
+ readonly wrapSide?: 'bothSides' | 'left' | 'right' | 'largest';
1499
+ /**
1500
+ * §20.4.2.3 `wp:anchor @layoutInCell` (VML `o:allowincell`) — present and
1501
+ * false when a drawing anchored inside a TABLE CELL is placed against the
1502
+ * page rather than against the cell. Absent means the default: the cell is
1503
+ * the frame every `relativeFrom` is measured in.
1504
+ */
1505
+ readonly inCell?: boolean;
1454
1506
  /**
1455
1507
  * §20.4.2.3 `wp:anchor @distT/@distB/@distL/@distR` — how far the wrapped
1456
1508
  * text stands off each edge of the drawing. Absent sides are 0.
@@ -1,5 +1,5 @@
1
1
  /** The raster formats this module recognizes and can prepare for embedding. */
2
- export type ImageFormat = 'jpeg' | 'png' | 'jpeg2000' | 'gif';
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' | 'image/gif';
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
  /**
@@ -11,6 +12,7 @@ function detectImageFormat(bytes) {
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";
13
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";
14
16
  return null;
15
17
  }
16
18
  /**
@@ -26,6 +28,7 @@ function prepareImage(bytes, options = {}) {
26
28
  if (format === "png") return preparePng(bytes, options);
27
29
  if (format === "jpeg2000") return prepareJpeg2000(bytes);
28
30
  if (format === "gif") return prepareGif(bytes, options);
31
+ if (format === "tiff") return prepareTiff(bytes, options);
29
32
  throw new Error("Unsupported image format");
30
33
  }
31
34
  function prepareJpeg2000(bytes) {
@@ -170,6 +173,41 @@ function prepareGif(bytes, options = {}) {
170
173
  ...alpha && !options.flattenAlpha ? { smaskData: zlibSync(alpha) } : {}
171
174
  };
172
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
+ }
173
211
  function decodeGif(bytes) {
174
212
  const u16 = (i) => (bytes[i] ?? 0) | (bytes[i + 1] ?? 0) << 8;
175
213
  const screenW = u16(6);
@@ -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 {};