reamkit 1.23.0 → 1.25.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 (102) hide show
  1. package/README.md +22 -11
  2. package/dist/esm/core/bmp.d.ts +33 -0
  3. package/dist/esm/core/bmp.js +276 -0
  4. package/dist/esm/core/converter/facade.d.ts +3 -3
  5. package/dist/esm/core/converter/facade.js +12 -0
  6. package/dist/esm/core/converter/ream.d.ts +48 -4
  7. package/dist/esm/core/converter/ream.js +25 -4
  8. package/dist/esm/core/document-model/index.d.ts +1 -1
  9. package/dist/esm/core/document-model/types.d.ts +54 -0
  10. package/dist/esm/core/drawingml/chart-geometry.js +3 -2
  11. package/dist/esm/core/drawingml/diagram/colors.d.ts +52 -0
  12. package/dist/esm/core/drawingml/diagram/colors.js +125 -0
  13. package/dist/esm/core/drawingml/diagram/data-model.d.ts +47 -0
  14. package/dist/esm/core/drawingml/diagram/data-model.js +119 -0
  15. package/dist/esm/core/drawingml/diagram/layout-engine.d.ts +46 -0
  16. package/dist/esm/core/drawingml/diagram/layout-engine.js +948 -0
  17. package/dist/esm/core/drawingml/diagram/run.d.ts +37 -0
  18. package/dist/esm/core/drawingml/diagram/run.js +50 -0
  19. package/dist/esm/core/drawingml/diagram/to-drawing.d.ts +16 -0
  20. package/dist/esm/core/drawingml/diagram/to-drawing.js +86 -0
  21. package/dist/esm/core/drawingml/preset-geometry.js +116 -6
  22. package/dist/esm/core/drawingml/text-warp.d.ts +58 -0
  23. package/dist/esm/core/drawingml/text-warp.js +355 -0
  24. package/dist/esm/core/drawingml/theme-parser.js +9 -3
  25. package/dist/esm/core/font/measure.d.ts +12 -0
  26. package/dist/esm/core/font/measure.js +36 -0
  27. package/dist/esm/core/images.d.ts +30 -2
  28. package/dist/esm/core/images.js +121 -5
  29. package/dist/esm/core/metafile/emf.js +144 -16
  30. package/dist/esm/core/metafile/picture.d.ts +49 -0
  31. package/dist/esm/core/metafile/picture.js +70 -1
  32. package/dist/esm/core/metafile/wmf.js +53 -4
  33. package/dist/esm/core/ole/escher-blip.js +11 -1
  34. package/dist/esm/core/outline.d.ts +17 -0
  35. package/dist/esm/core/outline.js +30 -0
  36. package/dist/esm/core/style-cascade/resolver.js +1 -0
  37. package/dist/esm/core/style-cascade/types.d.ts +3 -1
  38. package/dist/esm/excel/print-model.js +2 -2
  39. package/dist/esm/excel/sheet-drawing.js +1 -1
  40. package/dist/esm/excel/sheet-to-flow.d.ts +10 -0
  41. package/dist/esm/excel/sheet-to-flow.js +30 -3
  42. package/dist/esm/html/html-writer.js +5 -3
  43. package/dist/esm/index.d.ts +3 -0
  44. package/dist/esm/index.js +2 -1
  45. package/dist/esm/layout/page-doc.d.ts +35 -0
  46. package/dist/esm/layout/page-doc.js +14 -1
  47. package/dist/esm/layout/styled-layout.js +217 -30
  48. package/dist/esm/markdown/markdown-writer.d.ts +41 -0
  49. package/dist/esm/markdown/markdown-writer.js +733 -0
  50. package/dist/esm/pdf/shading.js +8 -6
  51. package/dist/esm/pdf/styled-page-emitter.js +62 -4
  52. package/dist/esm/pdf/vector-graphics.js +1 -1
  53. package/dist/esm/pdf-reader/annots.d.ts +24 -0
  54. package/dist/esm/pdf-reader/annots.js +126 -0
  55. package/dist/esm/pdf-reader/content.d.ts +131 -5
  56. package/dist/esm/pdf-reader/content.js +169 -12
  57. package/dist/esm/pdf-reader/display.d.ts +56 -0
  58. package/dist/esm/pdf-reader/display.js +162 -0
  59. package/dist/esm/pdf-reader/document.d.ts +36 -1
  60. package/dist/esm/pdf-reader/document.js +92 -25
  61. package/dist/esm/pdf-reader/embedded-fonts.d.ts +31 -0
  62. package/dist/esm/pdf-reader/embedded-fonts.js +94 -0
  63. package/dist/esm/pdf-reader/flow-build.d.ts +61 -6
  64. package/dist/esm/pdf-reader/flow-build.js +128 -22
  65. package/dist/esm/pdf-reader/font.js +185 -4
  66. package/dist/esm/pdf-reader/image-decode.js +56 -5
  67. package/dist/esm/pdf-reader/images.d.ts +6 -0
  68. package/dist/esm/pdf-reader/images.js +25 -5
  69. package/dist/esm/pdf-reader/jpeg.d.ts +18 -0
  70. package/dist/esm/pdf-reader/jpeg.js +419 -0
  71. package/dist/esm/pdf-reader/layout.d.ts +1 -1
  72. package/dist/esm/pdf-reader/layout.js +221 -32
  73. package/dist/esm/pdf-reader/pattern-tint.d.ts +17 -0
  74. package/dist/esm/pdf-reader/pattern-tint.js +181 -0
  75. package/dist/esm/pdf-reader/reader.d.ts +9 -1
  76. package/dist/esm/pdf-reader/reader.js +22 -6
  77. package/dist/esm/pdf-reader/shading.d.ts +14 -0
  78. package/dist/esm/pdf-reader/shading.js +27 -1
  79. package/dist/esm/pdf-reader/tagged.js +156 -17
  80. package/dist/esm/pdf-reader/text.d.ts +13 -1
  81. package/dist/esm/pdf-reader/text.js +70 -3
  82. package/dist/esm/pdf-reader/vector.d.ts +25 -1
  83. package/dist/esm/pdf-reader/vector.js +168 -12
  84. package/dist/esm/pptx/ppt/ppt-reader.js +95 -27
  85. package/dist/esm/pptx/ppt/ppt-text.d.ts +41 -0
  86. package/dist/esm/pptx/ppt/ppt-text.js +327 -37
  87. package/dist/esm/pptx/pptx-reader.js +90 -10
  88. package/dist/esm/pptx/slide-parser.d.ts +4 -1
  89. package/dist/esm/pptx/slide-parser.js +104 -13
  90. package/dist/esm/pptx/sp-helpers.d.ts +7 -3
  91. package/dist/esm/pptx/sp-helpers.js +45 -7
  92. package/dist/esm/pptx/table-style.d.ts +7 -0
  93. package/dist/esm/pptx/table-style.js +59 -7
  94. package/dist/esm/svg/svg-writer.js +1 -0
  95. package/dist/esm/word/document-parser.d.ts +4 -1
  96. package/dist/esm/word/document-parser.js +4 -1
  97. package/dist/esm/word/docx-reader.js +26 -13
  98. package/dist/esm/word/docx-writer.js +15 -1
  99. package/dist/esm/word/drawing-parser.d.ts +20 -0
  100. package/dist/esm/word/drawing-parser.js +44 -5
  101. package/dist/esm/word/table-parser.js +3 -0
  102. package/package.json +1 -1
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Ream
2
2
 
3
- > Read Word, Excel, PowerPoint and PDF — and convert any of them to PDF, SVG, HTML, DOCX or XLSX. From scratch, in the browser. No LibreOffice, no headless Office, no commercial SDK.
3
+ > Read Word, Excel, PowerPoint and PDF — and convert any of them to PDF, SVG, HTML, Markdown, DOCX or XLSX. From scratch, in the browser. No LibreOffice, no headless Office, no commercial SDK.
4
4
 
5
5
  Ream parses **seven document formats** — the modern Office Open XML trio (`.docx`,
6
6
  `.xlsx`, `.pptx`), `.pdf`, and the legacy binary `.doc` / `.xls` / `.ppt` — into one
7
- format-neutral interlayer, then renders that to **PDF, SVG, HTML, DOCX or XLSX**.
7
+ format-neutral interlayer, then renders that to **PDF, SVG, HTML, Markdown, DOCX or XLSX**.
8
8
  It is implemented directly from the **ECMA-376** (OOXML), **ISO 32000 / 19005**
9
9
  (PDF / PDF/A), and Microsoft's binary-format specifications — no wrapper around
10
10
  LibreOffice, headless Office, or any commercial SDK. Pure TypeScript/JavaScript on
@@ -14,7 +14,7 @@ Node.js, serverless and edge runtimes.
14
14
  | | |
15
15
  | ---------- | ------------------------------------------------------------------------------------------- |
16
16
  | **Reads** | `.docx` · `.xlsx` · `.pptx` · `.pdf` · legacy `.doc` · `.xls` · `.ppt` |
17
- | **Writes** | `.pdf` (incl. PDF/A-1/2/3, PDF/UA-1, signed, encrypted) · `.svg` · `.html` · `.docx` · `.xlsx` |
17
+ | **Writes** | `.pdf` (incl. PDF/A-1/2/3, PDF/UA-1, signed, encrypted) · `.svg` · `.html` · `.md` · `.docx` · `.xlsx` |
18
18
 
19
19
  ## Install
20
20
 
@@ -42,6 +42,7 @@ const doc = Ream.parse(bytes); // docx, xlsx, pptx or pdf — sniffed
42
42
  const pdf = await doc.convert('pdf'); // async — fetches a font if needed
43
43
  const svg = await doc.convert('svg'); // same parse, different target
44
44
  const html = await doc.convert('html'); // flowed HTML — needs no fonts at all
45
+ const md = await doc.convert('md'); // GitHub-Flavored Markdown — same, narrower
45
46
  const docx = await doc.convert('docx'); // write WordprocessingML back out
46
47
  const xlsx = await doc.convert('xlsx'); // write SpreadsheetML back (xlsx source)
47
48
 
@@ -160,7 +161,7 @@ automatically from the document's `docProps/core.xml`), `attachments`
160
161
 
161
162
  ### Lower-level APIs
162
163
 
163
- - `docxReader` / `xlsxReader`, `svgWriter`, `htmlWriter`, `docxWriter` — the `@experimental`
164
+ - `docxReader` / `xlsxReader`, `svgWriter`, `htmlWriter`, `markdownWriter`, `docxWriter` — the `@experimental`
164
165
  reader/writer interfaces of the interlayer, for building custom pipelines (and
165
166
  keeping unused formats out of your bundle); `layoutStyledDocument` produces the
166
167
  frozen page model (`PageItem` pages in a top-left `Pt` frame) the page-based
@@ -190,17 +191,27 @@ preview, flowed HTML export, and **docx + xlsx output** (write WordprocessingML
190
191
  tagged PDF from its structure tree (headings, tables, lists, reading order), an
191
192
  untagged one heuristically from glyph positions (lines, paragraphs, headings,
192
193
  and a clean two-column split). It lifts back the text (via each font's
193
- `/ToUnicode`), raster images (JPEG verbatim; PNG/Flate/LZW/CCITT-fax decoded and
194
- re-encoded), `/Link` hyperlinks, form-XObject content, and filled / stroked /
195
- gradient vector shapes. It reads modern compressed files (cross-reference + object
196
- streams) and encrypted ones (RC4 / AES — the user password is passed to
197
- `Ream.parse(bytes, { password })`, defaulting to the permissions-only case).
194
+ `/ToUnicode`, or the embedded program's own `cmap` where there is none), the
195
+ font programs themselves, raster images (JPEG verbatim; PNG/Flate/LZW/CCITT-fax
196
+ decoded and re-encoded), `/Link` hyperlinks, form-XObject content, annotation
197
+ appearances, and the page's artwork: filled / stroked / gradient shapes,
198
+ clipping paths, tiling patterns, constant alpha, and the Type 3 glyphs that are
199
+ drawings rather than letters. It reads modern compressed files (cross-reference
200
+ + object streams) and encrypted ones (RC4 / AES — the user password is passed to
201
+ `Ream.parse(bytes, { password })`, defaulting to the permissions-only case); a
202
+ filter it does not carry can be handed to it through
203
+ `Ream.parse(bytes, { filters })`.
204
+
205
+ A form or a drawing is not a reflowable document, so
206
+ `Ream.parse(bytes, { pdfLayout: 'positional' })` reads the page instead: every
207
+ line stands where its glyphs stand, beside the artwork, the way up the page is
208
+ shown.
198
209
 
199
210
  **Reads PowerPoint, too.** `Ream.parse` accepts a `.pptx` and turns each slide
200
211
  into a page at the deck size — text boxes (with run formatting, alignment,
201
212
  bullets and indents), layout/master placeholders, pictures, shapes, DrawingML
202
213
  tables, embedded charts, theme colours, slide backgrounds, grouped shapes and
203
- hyperlinks — then converts onward to PDF, SVG, HTML or DOCX like any source.
214
+ hyperlinks — then converts onward to PDF, SVG, HTML, Markdown or DOCX like any source.
204
215
 
205
216
  **Reads legacy `.doc`, `.xls` and `.ppt`, too.** The binary Word / Excel /
206
217
  PowerPoint 97–2003 formats (OLE2/CFB) parse through a shared container reader: a
@@ -217,7 +228,7 @@ text (with run and paragraph formatting), embedded images, per-shape placement
217
228
  (anchored text boxes and pictures at their slide rectangles) and decorative
218
229
  autoshapes (preset or exact freeform geometry, with fill / line colours resolved
219
230
  through the slide's colour scheme), one page per slide — all convert onward to PDF,
220
- SVG, HTML, or back to `.docx` / `.xlsx` like any source.
231
+ SVG, HTML, Markdown, or back to `.docx` / `.xlsx` like any source.
221
232
 
222
233
  See [`CHANGELOG.md`](./CHANGELOG.md) for the release history; the docs
223
234
  [**Scope**](https://reamkit.dev/guides/scope/) guide has the full feature matrix
@@ -0,0 +1,33 @@
1
+ /** One decoded bitmap: chunky 8-bit samples, plus alpha when the file carries it. */
2
+ export interface DecodedBmp {
3
+ readonly width: number;
4
+ readonly height: number;
5
+ /** Row-major RGB samples, `width * height * 3` long. */
6
+ readonly data: Uint8Array;
7
+ /** One byte per pixel, present only when the file states real transparency. */
8
+ readonly alpha?: Uint8Array;
9
+ /** `biXPelsPerMeter`/`biYPelsPerMeter` turned into pixels per inch. */
10
+ readonly dpiX?: number;
11
+ readonly dpiY?: number;
12
+ }
13
+ /** Whether these bytes open with the `BM` signature of a bitmap FILE. */
14
+ export declare function isBmp(bytes: Uint8Array): boolean;
15
+ /**
16
+ * MS-ODRAW §2.2.28 — a `BlipDIB`'s payload is a bitmap without its 14-byte
17
+ * BITMAPFILEHEADER, which the Escher record makes redundant. Put it back.
18
+ *
19
+ * @param dib The bytes from the DIB header onwards.
20
+ * @returns A complete BMP file, or `undefined` when the header does not read
21
+ * as a bitmap at all.
22
+ */
23
+ export declare function dibToBmp(dib: Uint8Array): Uint8Array | undefined;
24
+ /**
25
+ * Decode a Windows bitmap into 8-bit RGB samples.
26
+ *
27
+ * @param bytes A complete BMP file (`BM` signature included).
28
+ * @returns The samples, their size and any alpha channel.
29
+ * @throws Error when the file is malformed, or carries a bitmap this does not
30
+ * read — a JPEG or PNG smuggled inside a DIB (`BI_JPEG`/`BI_PNG`),
31
+ * which is a whole other file wearing a bitmap header.
32
+ */
33
+ export declare function decodeBmp(bytes: Uint8Array): DecodedBmp;
@@ -0,0 +1,276 @@
1
+ //#region src/core/bmp.ts
2
+ var FILE_HEADER_BYTES = 14;
3
+ var CORE_HEADER_BYTES = 12;
4
+ var INFO_HEADER_BYTES = 40;
5
+ var MAX_PIXELS = 4e7;
6
+ var BI_RGB = 0;
7
+ var BI_RLE8 = 1;
8
+ var BI_RLE4 = 2;
9
+ var BI_BITFIELDS = 3;
10
+ var BI_ALPHABITFIELDS = 6;
11
+ /** Whether these bytes open with the `BM` signature of a bitmap FILE. */
12
+ function isBmp(bytes) {
13
+ return bytes.length >= FILE_HEADER_BYTES && bytes[0] === 66 && bytes[1] === 77;
14
+ }
15
+ /**
16
+ * MS-ODRAW §2.2.28 — a `BlipDIB`'s payload is a bitmap without its 14-byte
17
+ * BITMAPFILEHEADER, which the Escher record makes redundant. Put it back.
18
+ *
19
+ * @param dib The bytes from the DIB header onwards.
20
+ * @returns A complete BMP file, or `undefined` when the header does not read
21
+ * as a bitmap at all.
22
+ */
23
+ function dibToBmp(dib) {
24
+ const head = readDibHeader(dib, 0);
25
+ if (!head) return void 0;
26
+ const offBits = FILE_HEADER_BYTES + head.headerBytes + head.maskBytes + head.paletteBytes;
27
+ const out = new Uint8Array(FILE_HEADER_BYTES + dib.length);
28
+ const view = new DataView(out.buffer);
29
+ out[0] = 66;
30
+ out[1] = 77;
31
+ view.setUint32(2, out.length, true);
32
+ view.setUint32(10, offBits, true);
33
+ out.set(dib, FILE_HEADER_BYTES);
34
+ return out;
35
+ }
36
+ /**
37
+ * Decode a Windows bitmap into 8-bit RGB samples.
38
+ *
39
+ * @param bytes A complete BMP file (`BM` signature included).
40
+ * @returns The samples, their size and any alpha channel.
41
+ * @throws Error when the file is malformed, or carries a bitmap this does not
42
+ * read — a JPEG or PNG smuggled inside a DIB (`BI_JPEG`/`BI_PNG`),
43
+ * which is a whole other file wearing a bitmap header.
44
+ */
45
+ function decodeBmp(bytes) {
46
+ if (!isBmp(bytes)) throw new Error("BMP: not a bitmap");
47
+ const file = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
48
+ const head = readDibHeader(bytes, FILE_HEADER_BYTES);
49
+ if (!head) throw new Error("BMP: unreadable header");
50
+ const { width, height, topDown, bitCount, compression } = head;
51
+ if (width <= 0 || height <= 0) throw new Error("BMP: empty bitmap");
52
+ if (width * height > MAX_PIXELS) throw new Error("BMP: image too large");
53
+ if (compression === 4 || compression === 5) throw new Error("BMP: embedded JPEG/PNG bitmaps are not supported");
54
+ const stated = file.getUint32(10, true);
55
+ const computed = FILE_HEADER_BYTES + head.headerBytes + head.maskBytes + head.paletteBytes;
56
+ const pixelsAt = stated >= computed && stated < bytes.length ? stated : computed;
57
+ const palette = readPalette(bytes, FILE_HEADER_BYTES + head.headerBytes, head);
58
+ const count = width * height;
59
+ const data = new Uint8Array(count * 3);
60
+ const rowOf = (y) => topDown ? y : height - 1 - y;
61
+ if (compression === BI_RLE8 || compression === BI_RLE4) {
62
+ const indices = decodeRle(bytes.subarray(pixelsAt), width, height, compression === BI_RLE4);
63
+ for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) {
64
+ const at = (rowOf(y) * width + x) * 3;
65
+ const p = (indices[y * width + x] ?? 0) * 3;
66
+ data[at] = palette[p] ?? 0;
67
+ data[at + 1] = palette[p + 1] ?? 0;
68
+ data[at + 2] = palette[p + 2] ?? 0;
69
+ }
70
+ return {
71
+ width,
72
+ height,
73
+ data,
74
+ ...density(head)
75
+ };
76
+ }
77
+ const stride = ((width * bitCount + 31) / 32 | 0) * 4;
78
+ const alpha = head.alphaMask !== 0 ? new Uint8Array(count) : void 0;
79
+ const shifts = {
80
+ r: maskShift(head.redMask),
81
+ g: maskShift(head.greenMask),
82
+ b: maskShift(head.blueMask),
83
+ a: maskShift(head.alphaMask)
84
+ };
85
+ for (let y = 0; y < height; y++) {
86
+ const row = pixelsAt + y * stride;
87
+ if (row + stride > bytes.length + stride) break;
88
+ for (let x = 0; x < width; x++) {
89
+ const at = (rowOf(y) * width + x) * 3;
90
+ if (bitCount <= 8) {
91
+ const p = paletteIndex(bytes, row, x, bitCount) * 3;
92
+ data[at] = palette[p] ?? 0;
93
+ data[at + 1] = palette[p + 1] ?? 0;
94
+ data[at + 2] = palette[p + 2] ?? 0;
95
+ continue;
96
+ }
97
+ if (bitCount === 24) {
98
+ const o = row + x * 3;
99
+ data[at] = bytes[o + 2] ?? 0;
100
+ data[at + 1] = bytes[o + 1] ?? 0;
101
+ data[at + 2] = bytes[o] ?? 0;
102
+ continue;
103
+ }
104
+ const raw = bitCount === 16 ? (bytes[row + x * 2] ?? 0) | (bytes[row + x * 2 + 1] ?? 0) << 8 : ((bytes[row + x * 4] ?? 0) | (bytes[row + x * 4 + 1] ?? 0) << 8 | (bytes[row + x * 4 + 2] ?? 0) << 16 | (bytes[row + x * 4 + 3] ?? 0) << 24) >>> 0;
105
+ data[at] = channel(raw, head.redMask, shifts.r);
106
+ data[at + 1] = channel(raw, head.greenMask, shifts.g);
107
+ data[at + 2] = channel(raw, head.blueMask, shifts.b);
108
+ if (alpha) alpha[rowOf(y) * width + x] = channel(raw, head.alphaMask, shifts.a);
109
+ }
110
+ }
111
+ const opaque = alpha?.every((v) => v === 0) ?? true;
112
+ return {
113
+ width,
114
+ height,
115
+ data,
116
+ ...alpha && !opaque ? { alpha } : {},
117
+ ...density(head)
118
+ };
119
+ }
120
+ function readDibHeader(bytes, at) {
121
+ if (bytes.length < at + 4) return void 0;
122
+ const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
123
+ const headerBytes = v.getUint32(at, true);
124
+ const core = headerBytes === CORE_HEADER_BYTES;
125
+ if (!core && headerBytes < INFO_HEADER_BYTES) return void 0;
126
+ if (bytes.length < at + headerBytes) return void 0;
127
+ const width = core ? v.getInt16(at + 4, true) : v.getInt32(at + 4, true);
128
+ const rawHeight = core ? v.getInt16(at + 6, true) : v.getInt32(at + 8, true);
129
+ const planes = core ? v.getUint16(at + 6, true) : v.getUint16(at + 12, true);
130
+ const bitCount = core ? v.getUint16(at + 8, true) : v.getUint16(at + 14, true);
131
+ const compression = core ? BI_RGB : v.getUint32(at + 16, true);
132
+ if (planes !== 1) return void 0;
133
+ if (![
134
+ 1,
135
+ 2,
136
+ 4,
137
+ 8,
138
+ 16,
139
+ 24,
140
+ 32
141
+ ].includes(bitCount)) return void 0;
142
+ if (compression > 6) return void 0;
143
+ if (width <= 0 || rawHeight === 0) return void 0;
144
+ const clrUsed = core ? 0 : v.getUint32(at + 32, true);
145
+ const inHeader = headerBytes >= 52;
146
+ const maskBytes = !inHeader && (compression === BI_BITFIELDS || compression === BI_ALPHABITFIELDS) ? compression === BI_ALPHABITFIELDS ? 16 : 12 : 0;
147
+ const maskAt = inHeader ? at + 40 : at + headerBytes;
148
+ const stated = compression === BI_BITFIELDS || compression === BI_ALPHABITFIELDS || inHeader;
149
+ const readMask = (i) => stated && bytes.length >= maskAt + (i + 1) * 4 ? v.getUint32(maskAt + i * 4, true) : 0;
150
+ const defaults = bitCount === 16 ? [
151
+ 31744,
152
+ 992,
153
+ 31
154
+ ] : [
155
+ 16711680,
156
+ 65280,
157
+ 255
158
+ ];
159
+ const red = readMask(0) || defaults[0];
160
+ const green = readMask(1) || defaults[1];
161
+ const blue = readMask(2) || defaults[2];
162
+ const alphaMask = headerBytes >= 56 && bytes.length >= maskAt + 16 ? v.getUint32(maskAt + 12, true) : 0;
163
+ const paletteEntryBytes = core ? 3 : 4;
164
+ return {
165
+ headerBytes,
166
+ maskBytes,
167
+ paletteBytes: (bitCount <= 8 ? clrUsed || 1 << bitCount : 0) * paletteEntryBytes,
168
+ paletteEntryBytes,
169
+ width,
170
+ height: Math.abs(rawHeight),
171
+ topDown: rawHeight < 0,
172
+ bitCount,
173
+ compression,
174
+ redMask: red,
175
+ greenMask: green,
176
+ blueMask: blue,
177
+ alphaMask,
178
+ pxPerMeterX: core ? 0 : v.getInt32(at + 24, true),
179
+ pxPerMeterY: core ? 0 : v.getInt32(at + 28, true)
180
+ };
181
+ }
182
+ /** The palette as flat RGB triples, whichever entry width the header uses. */
183
+ function readPalette(bytes, at, head) {
184
+ const entries = head.paletteBytes / head.paletteEntryBytes;
185
+ const out = new Uint8Array(entries * 3);
186
+ for (let i = 0; i < entries; i++) {
187
+ const o = at + i * head.paletteEntryBytes;
188
+ out[i * 3] = bytes[o + 2] ?? 0;
189
+ out[i * 3 + 1] = bytes[o + 1] ?? 0;
190
+ out[i * 3 + 2] = bytes[o] ?? 0;
191
+ }
192
+ return out;
193
+ }
194
+ /** The palette index of one pixel of a 1/2/4/8-bit row. */
195
+ function paletteIndex(bytes, row, x, bitCount) {
196
+ if (bitCount === 8) return bytes[row + x] ?? 0;
197
+ const perByte = 8 / bitCount;
198
+ return (bytes[row + (x / perByte | 0)] ?? 0) >> 8 - bitCount * (x % perByte + 1) & (1 << bitCount) - 1;
199
+ }
200
+ /** How far right a mask's field sits, so its bits can be read off. */
201
+ function maskShift(mask) {
202
+ if (mask === 0) return 0;
203
+ let shift = 0;
204
+ while ((mask >>> shift & 1) === 0) shift++;
205
+ return shift;
206
+ }
207
+ /** One channel of a packed pixel, stretched to the full byte range. */
208
+ function channel(raw, mask, shift) {
209
+ if (mask === 0) return 0;
210
+ const width = (mask >>> shift).toString(2).length;
211
+ const value = (raw & mask) >>> shift;
212
+ const max = (1 << width) - 1;
213
+ return max === 0 ? 0 : Math.round(value * 255 / max);
214
+ }
215
+ /** §biXPelsPerMeter — the resolution the bitmap claims, in pixels per inch. */
216
+ function density(head) {
217
+ if (head.pxPerMeterX <= 0 || head.pxPerMeterY <= 0) return {};
218
+ return {
219
+ dpiX: head.pxPerMeterX * .0254,
220
+ dpiY: head.pxPerMeterY * .0254
221
+ };
222
+ }
223
+ /**
224
+ * §BI_RLE8 / §BI_RLE4 — run-length rows of palette indices.
225
+ *
226
+ * Each pair is either a run (a non-zero count and the index to repeat) or an
227
+ * escape (a zero count and a code): 0 ends the row, 1 ends the bitmap, 2 is a
228
+ * delta that skips forward, and anything else is that many literal pixels,
229
+ * padded to a two-byte boundary.
230
+ *
231
+ * @param data The bytes from the first run onwards.
232
+ * @param width The bitmap's width in pixels.
233
+ * @param height Its height in rows.
234
+ * @param four Whether this is the four-bit form, which packs two indices per byte.
235
+ * @returns One palette index per pixel, top row first.
236
+ */
237
+ function decodeRle(data, width, height, four) {
238
+ const out = new Uint8Array(width * height);
239
+ let x = 0;
240
+ let y = 0;
241
+ let at = 0;
242
+ const put = (index) => {
243
+ if (x < width && y < height) out[y * width + x] = index;
244
+ x++;
245
+ };
246
+ while (at + 1 < data.length && y < height) {
247
+ const count = data[at];
248
+ const value = data[at + 1];
249
+ at += 2;
250
+ if (count > 0) {
251
+ for (let i = 0; i < count; i++) put(four ? i % 2 === 0 ? value >> 4 : value & 15 : value);
252
+ continue;
253
+ }
254
+ if (value === 0) {
255
+ x = 0;
256
+ y++;
257
+ continue;
258
+ }
259
+ if (value === 1) break;
260
+ if (value === 2) {
261
+ x += data[at] ?? 0;
262
+ y += data[at + 1] ?? 0;
263
+ at += 2;
264
+ continue;
265
+ }
266
+ const bytesUsed = four ? (value + 1) / 2 | 0 : value;
267
+ for (let i = 0; i < value; i++) {
268
+ const byte = data[at + (four ? i / 2 | 0 : i)] ?? 0;
269
+ put(four ? i % 2 === 0 ? byte >> 4 : byte & 15 : byte);
270
+ }
271
+ at += bytesUsed + bytesUsed % 2;
272
+ }
273
+ return out;
274
+ }
275
+ //#endregion
276
+ export { decodeBmp, dibToBmp, isBmp };
@@ -9,10 +9,10 @@ import { ProjectSheetOptions } from '../../excel/sheet-to-flow.js';
9
9
  /** Options for a {@link Converter.convert} call (extends the docx PDF options). */
10
10
  export interface ConvertOptions extends ConvertDocxOptions {
11
11
  /**
12
- * Target: 'pdf' (default), 'svg' (page-stack preview), 'html'/'docx' (flowed),
13
- * or 'xlsx' (the native grid writer — spreadsheet input only).
12
+ * Target: 'pdf' (default), 'svg' (page-stack preview), 'html'/'md'/'docx'
13
+ * (flowed), or 'xlsx' (the native grid writer — spreadsheet input only).
14
14
  */
15
- readonly to?: 'pdf' | 'svg' | 'html' | 'docx' | 'xlsx';
15
+ readonly to?: 'pdf' | 'svg' | 'html' | 'md' | 'docx' | 'xlsx';
16
16
  /**
17
17
  * Strict mode (handoff v1 §5): throw ConversionLossError on the first
18
18
  * recorded loss instead of returning it in the report.
@@ -14,6 +14,7 @@ import { flowRenderOptions } from "./project.js";
14
14
  import { writeDocx } from "../../word/docx-writer.js";
15
15
  import { writeXlsx } from "../../excel/xlsx-writer.js";
16
16
  import { writeHtml } from "../../html/html-writer.js";
17
+ import { writeMarkdown } from "../../markdown/markdown-writer.js";
17
18
  import { writeSvg } from "../../svg/svg-writer.js";
18
19
  import { convertDocxToPdf } from "../../word/docx-to-pdf.js";
19
20
  import { convertXlsxToPdf } from "../../excel/xlsx-to-pdf.js";
@@ -87,6 +88,17 @@ function createConverter(opts = {}) {
87
88
  losses
88
89
  };
89
90
  }
91
+ if (to === "md") {
92
+ const { doc: flow, losses: readLosses } = readToFlow(reader, bytes, rest.now ? { now: rest.now } : void 0);
93
+ losses.push(...readLosses);
94
+ const markdown = writeMarkdown(flow);
95
+ losses.push(...markdown.losses);
96
+ if (strict && losses.length > 0) throw new ConversionLossError(losses[0]);
97
+ return {
98
+ bytes: markdown.bytes,
99
+ losses
100
+ };
101
+ }
90
102
  if (to === "xlsx") {
91
103
  const { doc, losses: readLosses } = reader.read(bytes);
92
104
  losses.push(...readLosses);
@@ -6,9 +6,10 @@ import { Loss } from '../ir/index.js';
6
6
  import { DocumentReader } from '../ir/adapters.js';
7
7
  import { FlowDoc } from '../ir/flow.js';
8
8
  import { SheetDoc } from '../ir/sheet.js';
9
+ import { StreamFilters } from '../../pdf-reader/document.js';
9
10
  import { SignatureOptions, StyledRenderOptions } from '../../pdf/index.js';
10
11
  /** The output formats {@link Ream.convert} can produce. */
11
- export type ReamTarget = 'pdf' | 'svg' | 'html' | 'docx' | 'xlsx';
12
+ export type ReamTarget = 'pdf' | 'svg' | 'html' | 'md' | 'docx' | 'xlsx';
12
13
  /** Options for {@link Ream.parse}. */
13
14
  export interface ReamParseOptions {
14
15
  /** Reader registry override; defaults to the built-in docx + xlsx readers. */
@@ -20,6 +21,30 @@ export interface ReamParseOptions {
20
21
  * encryption (EP14); an encrypted OOXML package always names a password.
21
22
  */
22
23
  readonly password?: string;
24
+ /**
25
+ * PDF only: what to read out of the page. `'flow'` (the default) reconstructs
26
+ * a re-flowable document — paragraphs and tables in reading order, from the
27
+ * structure tree where the file has one. `'positional'` keeps the page as a
28
+ * page: every line stands where its glyphs stand, beside the rules and fills
29
+ * the page draws. A form or a drawing needs the second; a report to be edited
30
+ * or converted to markdown needs the first.
31
+ */
32
+ readonly pdfLayout?: 'flow' | 'positional';
33
+ /**
34
+ * PDF only: decoders for `/Filter` names the reader does not implement
35
+ * (§7.4). A filter it cannot undo leaves that stream unread — and when the
36
+ * unread one is the cross-reference, the whole document is missing — so a
37
+ * caller who needs such a file supplies the decoder rather than the library
38
+ * carrying one for every filter anyone might write:
39
+ *
40
+ * ```ts
41
+ * import { brotliDecompressSync } from 'node:zlib';
42
+ * Ream.parse(pdf, { filters: { BrotliDecode: (b) => brotliDecompressSync(b) } });
43
+ * ```
44
+ *
45
+ * Absent, or throwing, the filter is reported unreadable by name.
46
+ */
47
+ readonly filters?: StreamFilters;
23
48
  }
24
49
  /**
25
50
  * Options for {@link Ream.convert} and {@link Ream.convertWithReport}. Extends
@@ -62,6 +87,25 @@ export interface ReamConvertOptions extends Omit<StyledRenderOptions, 'registry'
62
87
  * the code resolves, and omitted it is dropped exactly as before.
63
88
  */
64
89
  readonly fileName?: string;
90
+ /**
91
+ * Markdown only: how a picture reaches the output — inlined as a `data:` URI
92
+ * (the default), named under `./media/` for a caller that writes the bytes
93
+ * itself, or dropped. See {@link MarkdownWriteOptions}.
94
+ */
95
+ readonly images?: 'dataUri' | 'link' | 'drop';
96
+ /**
97
+ * Markdown only: what a page break becomes — nothing (the default), or the
98
+ * `---` thematic break a slide deck wants between its slides. See
99
+ * {@link MarkdownWriteOptions}.
100
+ */
101
+ readonly pageBreaks?: 'rule' | 'drop';
102
+ /**
103
+ * Markdown from a SPREADSHEET only: open each sheet with a heading carrying
104
+ * its tab name. On by default — markdown has no pages to tell one sheet from
105
+ * the next by, so without them a workbook is a pile of tables with nothing to
106
+ * say which is which. Set `false` for the bare tables.
107
+ */
108
+ readonly sheetNames?: boolean;
65
109
  }
66
110
  /**
67
111
  * The object face of the library: parse a document once into the format-neutral
@@ -121,9 +165,9 @@ export declare class Ream {
121
165
  /**
122
166
  * Convert the parsed document to `to`, returning the output bytes together with
123
167
  * the accumulated {@link Loss} report (read-time losses plus any added while
124
- * writing). HTML, DOCX and XLSX are produced straight from the interlayer — no
125
- * layout, no fonts, zero I/O; SVG and PDF run the layout engine and resolve
126
- * fonts first.
168
+ * writing). HTML, Markdown, DOCX and XLSX are produced straight from the
169
+ * interlayer — no layout, no fonts, zero I/O; SVG and PDF run the layout
170
+ * engine and resolve fonts first.
127
171
  *
128
172
  * @param to The target format. `'xlsx'` requires a spreadsheet source.
129
173
  * @param options Font resolution and target-specific options.
@@ -12,6 +12,7 @@ import { flowRenderOptions } from "./project.js";
12
12
  import { writeDocx } from "../../word/docx-writer.js";
13
13
  import { writeXlsx } from "../../excel/xlsx-writer.js";
14
14
  import { writeHtml } from "../../html/html-writer.js";
15
+ import { writeMarkdown } from "../../markdown/markdown-writer.js";
15
16
  import { writeSvg } from "../../svg/svg-writer.js";
16
17
  import { DEFAULT_READERS, resolveFontsViaChain, toFlowDoc } from "./facade.js";
17
18
  import { familiesInFlow } from "../fonts/families.js";
@@ -85,7 +86,11 @@ var Ream = class Ream {
85
86
  }
86
87
  const reader = readers.find((r) => r.sniff(source));
87
88
  if (!reader) throw new Error(`Unrecognized document format (readers: ${readers.map((r) => r.id).join(", ")})`);
88
- const { doc, losses } = reader.read(source, { password: options.password });
89
+ const { doc, losses } = reader.read(source, {
90
+ password: options.password,
91
+ ...options.pdfLayout ? { pdfLayout: options.pdfLayout } : {},
92
+ ...options.filters ? { filters: options.filters } : {}
93
+ });
89
94
  const sheet = doc.kind === "sheet" ? doc : void 0;
90
95
  return new Ream(toFlowDoc(doc), sheet, losses, bytes, reader.id);
91
96
  }
@@ -107,9 +112,9 @@ var Ream = class Ream {
107
112
  /**
108
113
  * Convert the parsed document to `to`, returning the output bytes together with
109
114
  * the accumulated {@link Loss} report (read-time losses plus any added while
110
- * writing). HTML, DOCX and XLSX are produced straight from the interlayer — no
111
- * layout, no fonts, zero I/O; SVG and PDF run the layout engine and resolve
112
- * fonts first.
115
+ * writing). HTML, Markdown, DOCX and XLSX are produced straight from the
116
+ * interlayer — no layout, no fonts, zero I/O; SVG and PDF run the layout
117
+ * engine and resolve fonts first.
113
118
  *
114
119
  * @param to The target format. `'xlsx'` requires a spreadsheet source.
115
120
  * @param options Font resolution and target-specific options.
@@ -132,6 +137,22 @@ var Ream = class Ream {
132
137
  losses
133
138
  };
134
139
  }
140
+ if (to === "md") {
141
+ const markdown = writeMarkdown(this.sheet && options.sheetNames !== false ? projectSheetDoc(this.sheet, {
142
+ ...options.now ? { now: options.now } : {},
143
+ ...options.fileName ? { fileName: options.fileName } : {},
144
+ sheetHeadings: true
145
+ }) : flow, {
146
+ ...options.images ? { images: options.images } : {},
147
+ ...options.pageBreaks ? { pageBreaks: options.pageBreaks } : {}
148
+ });
149
+ losses.push(...markdown.losses);
150
+ this.enforceStrict(options, losses);
151
+ return {
152
+ bytes: markdown.bytes,
153
+ losses
154
+ };
155
+ }
135
156
  if (to === "docx") {
136
157
  const docx = writeDocx(flow);
137
158
  losses.push(...docx.losses);
@@ -5,4 +5,4 @@
5
5
  *
6
6
  * @packageDocumentation
7
7
  */
8
- export type { AbstractNumbering, Alignment, Border, BorderStyle, BodyElement, Comment, CellBorders, CellMerge, DocumentInfo, CellMargins, CellProperties, CellShading, CellDataBar, CellIcon, CellIconShape, CellSparkline, Chart, ChartBlock, ChartDataPoint, ChartLineStyle, ChartMarker, ChartMarkerSymbol, ChartSeries, ChartType, CustomGeometry, CustomPathCmd, DocumentModel, FontFamilyMap, HeaderFooterReference, HeaderFooterType, ImageBlock, ImageCrop, InlineImage, MathAccent, MathBar, MathDelimiter, MathEqArray, MathFraction, MathFunc, MathGroupChr, MathLimit, MathMatrix, MathNary, MathNode, MathRadical, MathRow, MathRun, MathScript, Numbering, NumberingFormat, NumberingInstance, NumberingLevel, PictureBullet, PictureOutline, NumberingReference, PageMargins, PageSize, Paragraph, ParagraphProperties, FrameProperties, RelativeSize, TabStop, Run, RunProperties, RowConditionalFormat, RowProperties, Section, FloatAnchor, SectionColumns, SectionProperties, ShapeBlock, ShapeShadow, ShapeDash, ShapeFill, ShapeFillKind, ShapeGeometry, ShapeGroupChild, ShapeLine, LineEnd, ShapeTextBody, ShapeTransform, Style, StyleSheet, StyleType, Table, TableCell, TableLook, TableProperties, TableStyleCondition, TableStyleConditionType, TableStyleLayer, TableRow, UnderlineStyle, VerticalAlign, } from './types.js';
8
+ export type { AbstractNumbering, Alignment, Border, BorderStyle, BodyElement, Comment, CellBorders, CellMerge, DocumentInfo, CellMargins, CellProperties, CellShading, CellDataBar, CellIcon, CellIconShape, CellSparkline, Chart, ChartBlock, ChartDataPoint, ChartLineStyle, ChartMarker, ChartMarkerSymbol, ChartSeries, ChartType, CustomGeometry, CustomPathCmd, DocumentModel, FontFamilyMap, HeaderFooterReference, HeaderFooterType, ImageBlock, ImageCrop, InlineImage, MathAccent, MathBar, MathDelimiter, MathEqArray, MathFraction, MathFunc, MathGroupChr, MathLimit, MathMatrix, MathNary, MathNode, MathRadical, MathRow, MathRun, MathScript, Numbering, NumberingFormat, NumberingInstance, NumberingLevel, PictureBullet, PictureOutline, NumberingReference, PageMargins, PageSize, Paragraph, ParagraphProperties, FrameProperties, RelativeSize, TabStop, Run, RunProperties, TextOutline, RowConditionalFormat, RowProperties, Section, FloatAnchor, SectionColumns, SectionProperties, ShapeBlock, ShapeShadow, ShapeDash, ShapeFill, ShapeFillKind, ShapeGeometry, ShapeGroupChild, ShapeLine, LineEnd, ShapeTextBody, ShapeTransform, Style, StyleSheet, StyleType, Table, TableCell, TableLook, TableProperties, TableStyleCondition, TableStyleConditionType, TableStyleLayer, TableRow, UnderlineStyle, VerticalAlign, } from './types.js';
@@ -63,6 +63,17 @@ export interface RunProperties {
63
63
  * (negative tightens). Word states it in twentieths of a point.
64
64
  */
65
65
  readonly letterSpacingPt?: Pt;
66
+ /**
67
+ * §21.1.2.3.9 `a:rPr/a:ln` — a line drawn round the glyphs themselves, which
68
+ * DrawingML puts on a run and ISO 32000-1 §9.3.6 calls a text rendering mode
69
+ * that strokes as well as fills.
70
+ */
71
+ readonly textOutline?: TextOutline;
72
+ }
73
+ /** A line drawn round a run's glyphs: its colour and how wide the pen is. */
74
+ export interface TextOutline {
75
+ readonly colorHex: string;
76
+ readonly widthPt: Pt;
66
77
  }
67
78
  /**
68
79
  * ECMA-376 Part 1 §17.3.1 — Paragraph Properties (`pPr`). All lengths in
@@ -787,6 +798,13 @@ export interface CellProperties {
787
798
  * is a word-processor table's default.
788
799
  */
789
800
  readonly verticalAlign?: 'top' | 'center' | 'bottom';
801
+ /**
802
+ * §17.4.71 `w:textDirection` / §21.1.3.17 `a:tcPr@vert` — the cell's text is
803
+ * turned a quarter: `vert` reads top-to-bottom (turned clockwise), `vert270`
804
+ * bottom-to-top. Its lines then run along the cell's HEIGHT and stack across
805
+ * its width, which is what makes a narrow header column readable.
806
+ */
807
+ readonly textDirection?: 'vert' | 'vert270';
790
808
  }
791
809
  /** §17.4.81 `w:trPr` — a table row's properties: height, split/header flags. */
792
810
  export interface RowProperties {
@@ -1043,6 +1061,16 @@ export interface ShapeFill {
1043
1061
  readonly sx: number;
1044
1062
  readonly sy: number;
1045
1063
  };
1064
+ /**
1065
+ * Where the grid of copies is anchored. DrawingML pins it to the shape's
1066
+ * top-left corner (§20.1.8.58 `a:tile @algn`, which defaults to `tl`); an
1067
+ * MS-ODRAW texture fill (§2.3.7.13 `fillOriginX`/`fillOriginY`, both
1068
+ * defaulting to nothing) centres it on the shape instead. It only shows when
1069
+ * the copies do not divide the box evenly — and it shows most when ONE copy
1070
+ * is larger than the box, where the difference is which part of the picture
1071
+ * is visible at all.
1072
+ */
1073
+ readonly tileFromCentre?: boolean;
1046
1074
  /**
1047
1075
  * §20.1.8.14 `a:blipFill` — the picture painted across the shape's box. A
1048
1076
  * DrawingML picture IS a shape with one of these, which is how a `pic:pic`
@@ -1152,6 +1180,12 @@ export interface ShapeTextBody {
1152
1180
  * quarter clockwise), `vert270` bottom-to-top.
1153
1181
  */
1154
1182
  readonly vertical?: 'vert' | 'vert270';
1183
+ /**
1184
+ * §20.1.10.55 `a:bodyPr @upright` — the words stay level however far the
1185
+ * shape is turned. bnc762542.xlsx turns each legend label a quarter and asks
1186
+ * for this, and every reader draws those labels lying flat.
1187
+ */
1188
+ readonly upright?: boolean;
1155
1189
  /**
1156
1190
  * §20.1.10.28 `a:spAutoFit` — the SHAPE follows its text: its height is
1157
1191
  * whatever the text needs, whatever the stated box says.
@@ -1162,6 +1196,26 @@ export interface ShapeTextBody {
1162
1196
  * WordArt is set at whatever size fills the box it was drawn in.
1163
1197
  */
1164
1198
  readonly fitToBox?: boolean;
1199
+ /**
1200
+ * §20.1.9.10 `a:prstTxWarp` — DrawingML WordArt: the preset curve the text is
1201
+ * bent through, and the `a:avLst` `adj` guide when the file states one. The
1202
+ * warped text does not wrap and is stretched to fill the shape's box, so the
1203
+ * size its runs state stops deciding how large it is drawn. `textNoShape` is
1204
+ * not carried: it is the enumeration's "no warp" member, and a body under it
1205
+ * is an ordinary text box.
1206
+ */
1207
+ readonly warp?: {
1208
+ readonly preset: string;
1209
+ readonly adjust?: number;
1210
+ };
1211
+ /**
1212
+ * §20.1.10.42 `a:normAutofit` — the text SHRINKS to fit the box, and only
1213
+ * shrinks: a size that already fits is left alone. PowerPoint writes the
1214
+ * scale it settled on into `@fontScale` and that is applied at parse; this
1215
+ * is for the text that arrives without one, which is every box of a diagram
1216
+ * laid out from its layout part rather than from a cached drawing.
1217
+ */
1218
+ readonly shrinkToFit?: boolean;
1165
1219
  /**
1166
1220
  * `wps:txbx @id` / `wps:linkedTxbx @id @seq` — the chain of boxes this one
1167
1221
  * belongs to. Text that overruns a box continues in the next of its chain;