reamkit 1.31.0 → 1.32.1

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 (98) hide show
  1. package/README.md +10 -0
  2. package/dist/esm/core/bidi/index.d.ts +10 -0
  3. package/dist/esm/core/bidi/index.js +18 -2
  4. package/dist/esm/core/converter/facade.js +10 -5
  5. package/dist/esm/core/converter/project.js +1 -0
  6. package/dist/esm/core/converter/ream.js +17 -6
  7. package/dist/esm/core/converter/sheet-images.d.ts +17 -0
  8. package/dist/esm/core/converter/sheet-images.js +33 -0
  9. package/dist/esm/core/crypto/offcrypto.js +56 -12
  10. package/dist/esm/core/document-model/index.d.ts +1 -1
  11. package/dist/esm/core/document-model/types.d.ts +112 -0
  12. package/dist/esm/core/drawingml/chart-geometry.d.ts +36 -13
  13. package/dist/esm/core/drawingml/chart-geometry.js +372 -146
  14. package/dist/esm/core/drawingml/chart-parser.d.ts +19 -0
  15. package/dist/esm/core/drawingml/chart-parser.js +126 -18
  16. package/dist/esm/core/drawingml/colors.d.ts +6 -0
  17. package/dist/esm/core/drawingml/colors.js +23 -3
  18. package/dist/esm/core/drawingml/shape-render.d.ts +3 -1
  19. package/dist/esm/core/drawingml/shape-render.js +4 -2
  20. package/dist/esm/core/font/measure.d.ts +9 -0
  21. package/dist/esm/core/font/measure.js +9 -1
  22. package/dist/esm/core/fonts/families.js +4 -2
  23. package/dist/esm/core/fonts/scripts.js +4 -2
  24. package/dist/esm/core/ir/flow.d.ts +6 -0
  25. package/dist/esm/core/ir/sheet.d.ts +43 -0
  26. package/dist/esm/core/number-format.d.ts +40 -15
  27. package/dist/esm/core/number-format.js +295 -83
  28. package/dist/esm/core/opc/tag-scan.d.ts +15 -0
  29. package/dist/esm/core/opc/tag-scan.js +41 -0
  30. package/dist/esm/core/opc/xml-entities.js +39 -2
  31. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  32. package/dist/esm/core/spreadsheet-model/types.d.ts +96 -0
  33. package/dist/esm/core/style-cascade/resolver.js +4 -2
  34. package/dist/esm/excel/column-bands.d.ts +39 -6
  35. package/dist/esm/excel/column-bands.js +41 -17
  36. package/dist/esm/excel/comments-parser.js +19 -0
  37. package/dist/esm/excel/conditional-format.d.ts +7 -3
  38. package/dist/esm/excel/conditional-format.js +77 -39
  39. package/dist/esm/excel/formula/context.d.ts +18 -0
  40. package/dist/esm/excel/formula/eval.js +51 -0
  41. package/dist/esm/excel/formula/functions.js +37 -14
  42. package/dist/esm/excel/formula/index.d.ts +2 -2
  43. package/dist/esm/excel/formula/lexer.d.ts +1 -1
  44. package/dist/esm/excel/formula/lexer.js +41 -0
  45. package/dist/esm/excel/formula/parser.d.ts +25 -0
  46. package/dist/esm/excel/formula/parser.js +95 -0
  47. package/dist/esm/excel/print-model.d.ts +38 -1
  48. package/dist/esm/excel/print-model.js +536 -83
  49. package/dist/esm/excel/shared-strings-parser.d.ts +9 -0
  50. package/dist/esm/excel/shared-strings-parser.js +11 -1
  51. package/dist/esm/excel/sheet-drawing.d.ts +38 -2
  52. package/dist/esm/excel/sheet-drawing.js +47 -13
  53. package/dist/esm/excel/sheet-shape-parser.d.ts +6 -2
  54. package/dist/esm/excel/sheet-shape-parser.js +18 -6
  55. package/dist/esm/excel/sheet-to-flow.d.ts +17 -0
  56. package/dist/esm/excel/sheet-to-flow.js +434 -47
  57. package/dist/esm/excel/styles-parser.d.ts +9 -0
  58. package/dist/esm/excel/styles-parser.js +80 -26
  59. package/dist/esm/excel/table-parser.js +5 -0
  60. package/dist/esm/excel/table-style-presets.d.ts +26 -0
  61. package/dist/esm/excel/table-style-presets.js +1555 -0
  62. package/dist/esm/excel/table-style.d.ts +13 -0
  63. package/dist/esm/excel/table-style.js +75 -0
  64. package/dist/esm/excel/tint.d.ts +10 -0
  65. package/dist/esm/excel/tint.js +75 -0
  66. package/dist/esm/excel/vml-drawing.d.ts +41 -3
  67. package/dist/esm/excel/vml-drawing.js +144 -11
  68. package/dist/esm/excel/worksheet-parser.js +89 -15
  69. package/dist/esm/excel/xls/biff-reader.js +10 -2
  70. package/dist/esm/excel/xlsx-reader.js +234 -19
  71. package/dist/esm/excel/xlsx-writer.js +8 -7
  72. package/dist/esm/html/html-writer.js +202 -20
  73. package/dist/esm/layout/fit-pages.d.ts +11 -0
  74. package/dist/esm/layout/fit-pages.js +86 -0
  75. package/dist/esm/layout/line-paint.d.ts +58 -0
  76. package/dist/esm/layout/line-paint.js +107 -0
  77. package/dist/esm/layout/page-doc.d.ts +51 -0
  78. package/dist/esm/layout/page-doc.js +141 -1
  79. package/dist/esm/layout/styled-layout.d.ts +7 -0
  80. package/dist/esm/layout/styled-layout.js +275 -52
  81. package/dist/esm/layout/turned-section.js +14 -2
  82. package/dist/esm/markdown/markdown-writer.js +75 -13
  83. package/dist/esm/pdf/styled-page-emitter.js +49 -71
  84. package/dist/esm/pdf/styled-page-renderer.js +1 -1
  85. package/dist/esm/pdf-reader/annot-draw.js +2 -2
  86. package/dist/esm/pdf-reader/annots.js +3 -1
  87. package/dist/esm/pdf-reader/font.js +4 -4
  88. package/dist/esm/pdf-reader/layout.js +1 -1
  89. package/dist/esm/svg/svg-text.d.ts +58 -0
  90. package/dist/esm/svg/svg-text.js +322 -0
  91. package/dist/esm/svg/svg-writer.js +135 -71
  92. package/dist/esm/word/document-parser.js +1 -1
  93. package/dist/esm/word/docx-reader.js +13 -8
  94. package/dist/esm/word/docx-to-pdf.js +3 -3
  95. package/dist/esm/word/docx-writer.js +11 -2
  96. package/dist/esm/word/font-table.js +89 -10
  97. package/dist/esm/word/numbering-parser.js +1 -1
  98. package/package.json +7 -3
@@ -15,6 +15,7 @@ import { OpcPackage } from "../core/opc/package.js";
15
15
  import { parseCoreProperties } from "../core/opc/core-properties.js";
16
16
  import "../core/opc/index.js";
17
17
  import { parseXml } from "../pptx/pptx-reader.js";
18
+ import { firstTagMatch } from "../core/opc/tag-scan.js";
18
19
  import { EMPTY_NUMBERING, parseNumbering } from "./numbering-parser.js";
19
20
  import { EMPTY_SECTION, applyAuthorIds, bodyIndexForBlock, newBlockCounter, parseBackgroundColor, parseBackgroundFill, parseCommentThreads, parseDocument, parseHeaderFooter, parseNotes, parsePeople, parseSections } from "./document-parser.js";
20
21
  import { parseStyles } from "./styles-parser.js";
@@ -214,21 +215,26 @@ function makeDiagramResolver(pkg, partName, store) {
214
215
  return resolved;
215
216
  };
216
217
  }
218
+ function partIndex(path) {
219
+ const dot = path.lastIndexOf(".");
220
+ if (dot < 0 || dot === path.length - 1) return "";
221
+ let from = dot;
222
+ while (from > 0 && path.charCodeAt(from - 1) >= 48 && path.charCodeAt(from - 1) <= 57) from--;
223
+ return path.slice(from, dot);
224
+ }
217
225
  function siblingPart(pkg, ownerPart, dataPart, type) {
218
226
  const own = pkg.getPartRelationships(dataPart).find((r) => r.type.endsWith(type));
219
227
  if (own) return pkg.resolveRelatedPart(dataPart, own)?.data;
220
- const index = (path) => /(\d*)\.[^.]+$/u.exec(path)?.[1] ?? "";
221
- const want = index(dataPart);
228
+ const want = partIndex(dataPart);
222
229
  const rels = pkg.getPartRelationships(ownerPart).filter((r) => r.type.endsWith(type));
223
- const rel = rels.find((r) => index(r.target) === want) ?? (rels.length === 1 ? rels[0] : void 0);
230
+ const rel = rels.find((r) => partIndex(r.target) === want) ?? (rels.length === 1 ? rels[0] : void 0);
224
231
  return rel ? pkg.resolveRelatedPart(ownerPart, rel)?.data : void 0;
225
232
  }
226
233
  function drawingFromOwner(pkg, ownerPart, dataPart) {
227
234
  const drawings = pkg.getPartRelationships(ownerPart).filter((r) => r.type.endsWith("/diagramDrawing"));
228
235
  if (drawings.length === 0) return void 0;
229
- const index = (path) => /(\d*)\.[^.]+$/u.exec(path)?.[1] ?? "";
230
- const want = index(dataPart);
231
- const rel = drawings.find((r) => index(r.target) === want) ?? (drawings.length === 1 ? drawings[0] : void 0);
236
+ const want = partIndex(dataPart);
237
+ const rel = drawings.find((r) => partIndex(r.target) === want) ?? (drawings.length === 1 ? drawings[0] : void 0);
232
238
  return rel ? pkg.resolveRelatedPart(ownerPart, rel) : void 0;
233
239
  }
234
240
  function makeImageResolver(pkg, store, partName = MAIN_DOCUMENT_PART) {
@@ -377,11 +383,10 @@ function chartOwningParts(pkg, sections, mainPart) {
377
383
  return parts;
378
384
  }
379
385
  function detectDocxLanguage(stylesData, documentData) {
380
- const re = /<w:lang\b[^>]*\bw:val="([^"]+)"/;
381
386
  const decoder = new TextDecoder();
382
387
  for (const data of [stylesData, documentData]) {
383
388
  if (!data) continue;
384
- const m = re.exec(decoder.decode(data));
389
+ const m = firstTagMatch(decoder.decode(data), "<w:lang", true, /<w:lang\b[^>]*\bw:val="([^"]+)"/y);
385
390
  if (m?.[1]) return m[1];
386
391
  }
387
392
  }
@@ -6,6 +6,7 @@ import "../pdf/index.js";
6
6
  import { parseThemeFonts } from "../core/drawingml/theme-parser.js";
7
7
  import { OpcPackage } from "../core/opc/package.js";
8
8
  import "../core/opc/index.js";
9
+ import { tagMatches } from "../core/opc/tag-scan.js";
9
10
  import { resolveWordThemeFont } from "./theme-fonts.js";
10
11
  import { readDocx } from "./docx-reader.js";
11
12
  import { flowRenderOptions } from "../core/converter/project.js";
@@ -13,6 +14,7 @@ import { flowRenderOptions } from "../core/converter/project.js";
13
14
  var STYLES_PART = "word/styles.xml";
14
15
  var THEME_PART = "word/theme/theme1.xml";
15
16
  var MAIN_DOCUMENT_PART = "word/document.xml";
17
+ var ASCII_FONT = /<w:rFonts[^>]*?\bw:(ascii|asciiTheme)="([^"]+)"/y;
16
18
  /**
17
19
  * Synchronous one-shot conversion (requires `fonts`/`fontBytes`, no network).
18
20
  *
@@ -72,13 +74,11 @@ function detectDocxFamilyKeys(docx) {
72
74
  }
73
75
  const decoder = new TextDecoder("utf-8");
74
76
  const themeFonts = docxThemeFonts(pkg);
75
- const re = /<w:rFonts[^>]*?\bw:(ascii|asciiTheme)="([^"]+)"/g;
76
77
  for (const part of [STYLES_PART, MAIN_DOCUMENT_PART]) {
77
78
  const data = pkg.getPart(part);
78
79
  if (!data) continue;
79
80
  const xml = decoder.decode(data);
80
- let m;
81
- while ((m = re.exec(xml)) !== null) {
81
+ for (const m of tagMatches(xml, "<w:rFonts", false, ASCII_FONT)) {
82
82
  const name = m[1] === "ascii" ? m[2] : resolveWordThemeFont(m[2], themeFonts);
83
83
  if (name !== void 0) keys.add(resolveFamilyKey(name));
84
84
  }
@@ -156,7 +156,8 @@ function writeDocx(flow) {
156
156
  familiesUsed: /* @__PURE__ */ new Map(),
157
157
  ...flow.faceOutlines ? { faceOutlines: flow.faceOutlines } : {},
158
158
  facesUsed: /* @__PURE__ */ new Set(),
159
- zRanks: zRanksOf(flow)
159
+ zRanks: zRanksOf(flow),
160
+ overlaid: /* @__PURE__ */ new Set()
160
161
  };
161
162
  const docScope = newScope();
162
163
  const extraParts = [];
@@ -355,7 +356,10 @@ function zRanksOf(flow) {
355
356
  for (const el of blocks) {
356
357
  const z = el.kind === "image" ? el.image.float?.zOrder : el.kind === "shape" ? el.shape.float?.zOrder : el.kind === "chart" ? el.chart.float?.zOrder : void 0;
357
358
  if (z !== void 0 && Number.isFinite(z)) seen.add(z);
358
- if (el.kind === "table") for (const row of el.table.rows) for (const cell of row.cells) visit(cell.content);
359
+ if (el.kind === "table") {
360
+ for (const row of el.table.rows) for (const cell of row.cells) visit(cell.content);
361
+ visit(el.table.overlay?.drawings ?? []);
362
+ }
359
363
  }
360
364
  };
361
365
  visit(flow.body);
@@ -593,6 +597,11 @@ function emitBlock(out, el, losses, state, scope, closingSectPr) {
593
597
  }
594
598
  if (el.kind === "table") {
595
599
  out.push(tableXml(el.table, losses, state, scope));
600
+ for (const drawing of el.table.overlay?.drawings ?? []) {
601
+ if (state.overlaid.has(drawing)) continue;
602
+ state.overlaid.add(drawing);
603
+ emitBlock(out, drawing, losses, state, scope);
604
+ }
596
605
  if (closingSectPr) out.push(`<w:p><w:pPr>${closingSectPr}</w:pPr></w:p>`);
597
606
  return;
598
607
  }
@@ -1,4 +1,5 @@
1
1
  import { FontRegistry } from "../core/font/font-registry.js";
2
+ import { isWordChar, tagMatches } from "../core/opc/tag-scan.js";
2
3
  //#region src/word/font-table.ts
3
4
  var FONT_TABLE_PART = "word/fontTable.xml";
4
5
  var REL_FONT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/font";
@@ -48,19 +49,15 @@ function obfuscateEmbeddedFont(data, fontKey) {
48
49
  function parseFontTable(data) {
49
50
  const xml = new TextDecoder("utf-8").decode(data);
50
51
  const out = [];
51
- const fontRe = /<w:font\b[^>]*\bw:name="([^"]+)"[^>]*>([\s\S]*?)<\/w:font>/g;
52
- let fm;
53
- while ((fm = fontRe.exec(xml)) !== null) {
52
+ const closed = xml.slice(0, xml.lastIndexOf("</w:font>") + 9);
53
+ for (const fm of tagMatches(closed, "<w:font", true, FONT)) {
54
54
  const name = fm[1];
55
- const inner = fm[2];
56
55
  const embeds = {};
57
- const embedRe = /<w:embed(Regular|Bold|Italic|BoldItalic)\b[^>]*\br:id="([^"]+)"[^>]*\bw:fontKey="([^"]+)"/g;
58
- let em;
59
- while ((em = embedRe.exec(inner)) !== null) {
60
- const variant = VARIANT_BY_TAG[em[1]];
56
+ for (const em of embedRefs(fm[2])) {
57
+ const variant = VARIANT_BY_TAG[em.tag];
61
58
  if (variant) embeds[variant] = {
62
- rId: em[2],
63
- fontKey: em[3]
59
+ rId: em.rId,
60
+ fontKey: em.fontKey
64
61
  };
65
62
  }
66
63
  if (Object.keys(embeds).length > 0) out.push({
@@ -70,6 +67,88 @@ function parseFontTable(data) {
70
67
  }
71
68
  return out;
72
69
  }
70
+ /** A named `w:font` and everything up to its close. Sticky, for tagMatches. */
71
+ var FONT = /<w:font\b[^>]*\bw:name="([^"]+)"[^>]*>([\s\S]*?)<\/w:font>/y;
72
+ var EMBED_HEAD = /<w:embed(Regular|Bold|Italic|BoldItalic)\b/y;
73
+ /**
74
+ * The faces a font's element embeds — what
75
+ * `/<w:embed(Regular|Bold|Italic|BoldItalic)\b[^>]*\br:id="([^"]+)"[^>]*\bw:fontKey="([^"]+)"/g`
76
+ * finds in it, in one pass. The expression gave the tag back one `r:id` at a
77
+ * time and read the rest of it again for a `w:fontKey` after each, quadratic
78
+ * in a tag with many; and searching, it read each tag again from every
79
+ * `<w:embed` inside it. Where a start fails, every later one before the tag's
80
+ * `>` does too, so the search moves past it.
81
+ */
82
+ function embedRefs(inner) {
83
+ const out = [];
84
+ let at = inner.indexOf("<w:embed");
85
+ while (at >= 0) {
86
+ EMBED_HEAD.lastIndex = at;
87
+ const head = EMBED_HEAD.exec(inner);
88
+ if (head === null) {
89
+ at = inner.indexOf("<w:embed", at + 1);
90
+ continue;
91
+ }
92
+ const from = EMBED_HEAD.lastIndex;
93
+ const tagEnd = endOfStretch(inner, from);
94
+ const found = embedRef(inner, from, tagEnd);
95
+ if (found) {
96
+ out.push({
97
+ tag: head[1],
98
+ rId: found.rId,
99
+ fontKey: found.fontKey
100
+ });
101
+ at = inner.indexOf("<w:embed", found.end);
102
+ } else if (tagEnd < inner.length) at = inner.indexOf("<w:embed", tagEnd + 1);
103
+ else break;
104
+ }
105
+ return out;
106
+ }
107
+ /** The first `>` at or after `from`, or the end of the text. */
108
+ function endOfStretch(s, from) {
109
+ const gt = s.indexOf(">", from);
110
+ return gt < 0 ? s.length : gt;
111
+ }
112
+ /**
113
+ * The reference one `<w:embed…` makes, its attributes read from `from` up to
114
+ * the tag's `>` at `tagEnd`. The first `[^>]*` gives back the last `r:id`
115
+ * first; the second takes the last `w:fontKey` before the `>` that follows the
116
+ * `r:id`'s value — and that one is the same for every `r:id` whose value ends
117
+ * in one stretch, so it is looked for once per stretch.
118
+ */
119
+ function embedRef(s, from, tagEnd) {
120
+ const keys = /* @__PURE__ */ new Map();
121
+ const lastKey = (stretchEnd) => {
122
+ if (keys.has(stretchEnd)) return keys.get(stretchEnd);
123
+ const start = s.lastIndexOf(">", stretchEnd - 1) + 1;
124
+ let key;
125
+ for (let f = s.lastIndexOf("w:fontKey=\"", stretchEnd - 11); f >= start; f = s.lastIndexOf("w:fontKey=\"", f - 1)) {
126
+ const close = s.indexOf("\"", f + 11);
127
+ if (!isWordChar(s.charCodeAt(f - 1)) && close > f + 11) {
128
+ key = {
129
+ at: f,
130
+ value: s.slice(f + 11, close),
131
+ end: close + 1
132
+ };
133
+ break;
134
+ }
135
+ if (f === 0) break;
136
+ }
137
+ keys.set(stretchEnd, key);
138
+ return key;
139
+ };
140
+ for (let p = s.lastIndexOf("r:id=\"", tagEnd - 6); p >= from; p = s.lastIndexOf("r:id=\"", p - 1)) {
141
+ if (isWordChar(s.charCodeAt(p - 1))) continue;
142
+ const close = s.indexOf("\"", p + 6);
143
+ if (close <= p + 6) continue;
144
+ const key = lastKey(close < tagEnd ? tagEnd : endOfStretch(s, close + 1));
145
+ if (key !== void 0 && key.at > close) return {
146
+ rId: s.slice(p + 6, close),
147
+ fontKey: key.value,
148
+ end: key.end
149
+ };
150
+ }
151
+ }
73
152
  /**
74
153
  * De-obfuscate and load every embedded font in the package, building one
75
154
  * {@link FontRegistry} per font keyed by its normalized (trimmed, lower-cased)
@@ -171,7 +171,7 @@ var CSS_UNITS = new Map([
171
171
  ["px", .75]
172
172
  ]);
173
173
  function cssLengthPt(raw) {
174
- const m = raw !== void 0 ? /^\s*(-?[\d.]+)\s*([a-z%]*)\s*$/u.exec(raw) : null;
174
+ const m = raw !== void 0 ? /^(-?[\d.]+)\s*([a-z%]*)$/u.exec(raw.trim()) : null;
175
175
  if (!m) return void 0;
176
176
  const n = Number(m[1]);
177
177
  if (!Number.isFinite(n)) return void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reamkit",
3
- "version": "1.31.0",
3
+ "version": "1.32.1",
4
4
  "description": "Ream — convert DOCX, XLSX, PPTX, PDF and legacy DOC, XLS and PPT to PDF, SVG, HTML, Markdown, DOCX and XLSX, built from scratch on the ECMA-376 and ISO 32000 specifications. Parse once, convert anywhere.",
5
5
  "license": "MIT",
6
6
  "author": "Alex Krassavin <info@reamkit.dev>",
@@ -84,10 +84,14 @@
84
84
  "corpus:pixel": "tsx scripts/corpus/pixel-scout.ts",
85
85
  "stand": "tsx stand/serve.ts",
86
86
  "stand:build": "tsx stand/build.ts",
87
+ "stand:html": "tsx stand/serve.ts html",
88
+ "stand:html:build": "tsx stand/html-build.ts",
89
+ "stand:svg": "tsx stand/serve.ts svg",
90
+ "stand:svg:build": "tsx stand/svg-build.ts",
87
91
  "typecheck:dist": "tsc -p tsconfig.json --noEmit"
88
92
  },
89
93
  "dependencies": {
90
- "fast-xml-parser": "5.7.0",
94
+ "fast-xml-parser": "5.7.3",
91
95
  "fflate": "^0.8.2"
92
96
  },
93
97
  "devDependencies": {
@@ -99,7 +103,7 @@
99
103
  "tsx": "^4.19.0",
100
104
  "typescript": "^5.7.0",
101
105
  "vite": "8.0.5",
102
- "vitest": "^2.1.0"
106
+ "vitest": "^5.0.3"
103
107
  },
104
108
  "engines": {
105
109
  "node": ">=20"