reamkit 1.30.0 → 1.32.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 (147) hide show
  1. package/README.md +70 -24
  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 +4 -1
  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 +1 -1
  10. package/dist/esm/core/document-model/index.d.ts +1 -1
  11. package/dist/esm/core/document-model/types.d.ts +175 -0
  12. package/dist/esm/core/drawingml/chart-geometry.d.ts +21 -8
  13. package/dist/esm/core/drawingml/chart-geometry.js +198 -108
  14. package/dist/esm/core/drawingml/chart-parser.js +73 -2
  15. package/dist/esm/core/drawingml/colors.d.ts +6 -0
  16. package/dist/esm/core/drawingml/colors.js +23 -3
  17. package/dist/esm/core/drawingml/shape-render.d.ts +3 -1
  18. package/dist/esm/core/drawingml/shape-render.js +4 -2
  19. package/dist/esm/core/font/index.d.ts +2 -0
  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/font/ttf-build.d.ts +113 -0
  23. package/dist/esm/core/font/ttf-build.js +1224 -0
  24. package/dist/esm/core/font/ttf-subset.d.ts +19 -0
  25. package/dist/esm/core/font/ttf-subset.js +14 -1
  26. package/dist/esm/core/fonts/families.js +4 -2
  27. package/dist/esm/core/fonts/provider.d.ts +17 -0
  28. package/dist/esm/core/fonts/provider.js +27 -2
  29. package/dist/esm/core/fonts/remote-fonts.js +8 -1
  30. package/dist/esm/core/fonts/scripts.js +4 -2
  31. package/dist/esm/core/ir/flow.d.ts +77 -1
  32. package/dist/esm/core/ir/sheet.d.ts +43 -0
  33. package/dist/esm/core/number-format.js +16 -11
  34. package/dist/esm/core/numbering/index.d.ts +1 -1
  35. package/dist/esm/core/numbering/state.d.ts +11 -1
  36. package/dist/esm/core/numbering/state.js +10 -1
  37. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  38. package/dist/esm/core/spreadsheet-model/types.d.ts +96 -0
  39. package/dist/esm/core/style-cascade/resolver.js +39 -6
  40. package/dist/esm/core/style-cascade/types.d.ts +19 -1
  41. package/dist/esm/core/style-cascade/types.js +3 -0
  42. package/dist/esm/excel/column-bands.d.ts +39 -6
  43. package/dist/esm/excel/column-bands.js +41 -17
  44. package/dist/esm/excel/comments-parser.js +19 -0
  45. package/dist/esm/excel/conditional-format.d.ts +7 -3
  46. package/dist/esm/excel/conditional-format.js +77 -39
  47. package/dist/esm/excel/formula/context.d.ts +18 -0
  48. package/dist/esm/excel/formula/eval.js +51 -0
  49. package/dist/esm/excel/formula/index.d.ts +2 -2
  50. package/dist/esm/excel/formula/lexer.d.ts +1 -1
  51. package/dist/esm/excel/formula/lexer.js +41 -0
  52. package/dist/esm/excel/formula/parser.d.ts +25 -0
  53. package/dist/esm/excel/formula/parser.js +95 -0
  54. package/dist/esm/excel/print-model.d.ts +38 -1
  55. package/dist/esm/excel/print-model.js +522 -80
  56. package/dist/esm/excel/shared-strings-parser.d.ts +9 -0
  57. package/dist/esm/excel/shared-strings-parser.js +11 -1
  58. package/dist/esm/excel/sheet-drawing.d.ts +38 -2
  59. package/dist/esm/excel/sheet-drawing.js +47 -13
  60. package/dist/esm/excel/sheet-shape-parser.d.ts +6 -2
  61. package/dist/esm/excel/sheet-shape-parser.js +18 -6
  62. package/dist/esm/excel/sheet-to-flow.d.ts +17 -0
  63. package/dist/esm/excel/sheet-to-flow.js +434 -47
  64. package/dist/esm/excel/styles-parser.d.ts +9 -0
  65. package/dist/esm/excel/styles-parser.js +80 -26
  66. package/dist/esm/excel/table-parser.js +5 -0
  67. package/dist/esm/excel/table-style-presets.d.ts +26 -0
  68. package/dist/esm/excel/table-style-presets.js +1555 -0
  69. package/dist/esm/excel/table-style.d.ts +13 -0
  70. package/dist/esm/excel/table-style.js +75 -0
  71. package/dist/esm/excel/tint.d.ts +10 -0
  72. package/dist/esm/excel/tint.js +75 -0
  73. package/dist/esm/excel/vml-drawing.d.ts +41 -3
  74. package/dist/esm/excel/vml-drawing.js +143 -10
  75. package/dist/esm/excel/worksheet-parser.js +89 -15
  76. package/dist/esm/excel/xls/biff-reader.js +10 -2
  77. package/dist/esm/excel/xlsx-reader.js +195 -14
  78. package/dist/esm/excel/xlsx-writer.js +8 -7
  79. package/dist/esm/html/html-writer.js +202 -20
  80. package/dist/esm/index.d.ts +2 -2
  81. package/dist/esm/layout/fit-pages.d.ts +11 -0
  82. package/dist/esm/layout/fit-pages.js +86 -0
  83. package/dist/esm/layout/line-paint.d.ts +58 -0
  84. package/dist/esm/layout/line-paint.js +107 -0
  85. package/dist/esm/layout/page-doc.d.ts +61 -3
  86. package/dist/esm/layout/page-doc.js +141 -1
  87. package/dist/esm/layout/styled-layout.d.ts +55 -7
  88. package/dist/esm/layout/styled-layout.js +1154 -169
  89. package/dist/esm/layout/turned-section.d.ts +38 -0
  90. package/dist/esm/layout/turned-section.js +205 -0
  91. package/dist/esm/markdown/markdown-writer.js +17 -6
  92. package/dist/esm/pdf/styled-page-emitter.js +114 -60
  93. package/dist/esm/pdf/styled-page-renderer.js +1 -1
  94. package/dist/esm/pdf-reader/cff-outline.d.ts +27 -0
  95. package/dist/esm/pdf-reader/cff-outline.js +169 -21
  96. package/dist/esm/pdf-reader/content.d.ts +53 -0
  97. package/dist/esm/pdf-reader/content.js +9 -1
  98. package/dist/esm/pdf-reader/display.d.ts +36 -0
  99. package/dist/esm/pdf-reader/display.js +66 -1
  100. package/dist/esm/pdf-reader/document.js +5 -1
  101. package/dist/esm/pdf-reader/embedded-fonts.d.ts +2 -1
  102. package/dist/esm/pdf-reader/embedded-fonts.js +32 -2
  103. package/dist/esm/pdf-reader/encodings.d.ts +8 -0
  104. package/dist/esm/pdf-reader/encodings.js +25 -3
  105. package/dist/esm/pdf-reader/face-outlines.d.ts +78 -0
  106. package/dist/esm/pdf-reader/face-outlines.js +362 -0
  107. package/dist/esm/pdf-reader/figures.d.ts +52 -0
  108. package/dist/esm/pdf-reader/figures.js +433 -0
  109. package/dist/esm/pdf-reader/flow-build.d.ts +131 -5
  110. package/dist/esm/pdf-reader/flow-build.js +345 -26
  111. package/dist/esm/pdf-reader/font.js +321 -36
  112. package/dist/esm/pdf-reader/glyf-outline.d.ts +33 -0
  113. package/dist/esm/pdf-reader/glyf-outline.js +135 -1
  114. package/dist/esm/pdf-reader/glyph-names.js +154 -1
  115. package/dist/esm/pdf-reader/layout.d.ts +93 -0
  116. package/dist/esm/pdf-reader/layout.js +1703 -214
  117. package/dist/esm/pdf-reader/page-numbers.d.ts +53 -0
  118. package/dist/esm/pdf-reader/page-numbers.js +167 -0
  119. package/dist/esm/pdf-reader/tagged.js +182 -23
  120. package/dist/esm/pdf-reader/text.d.ts +6 -3
  121. package/dist/esm/pdf-reader/text.js +98 -9
  122. package/dist/esm/pdf-reader/type1-outline.d.ts +11 -0
  123. package/dist/esm/pdf-reader/type1-outline.js +63 -8
  124. package/dist/esm/pdf-reader/vector.js +71 -1
  125. package/dist/esm/svg/svg-text.d.ts +58 -0
  126. package/dist/esm/svg/svg-text.js +322 -0
  127. package/dist/esm/svg/svg-writer.js +135 -71
  128. package/dist/esm/word/doc/doc-reader.js +6 -2
  129. package/dist/esm/word/doc/doc-text.d.ts +6 -0
  130. package/dist/esm/word/doc/doc-text.js +19 -1
  131. package/dist/esm/word/document-parser.d.ts +2 -2
  132. package/dist/esm/word/document-parser.js +11 -1
  133. package/dist/esm/word/docx-reader.js +5 -3
  134. package/dist/esm/word/docx-writer.js +218 -23
  135. package/dist/esm/word/drawing-parser.d.ts +5 -3
  136. package/dist/esm/word/drawing-parser.js +49 -8
  137. package/dist/esm/word/font-embed.d.ts +30 -0
  138. package/dist/esm/word/font-embed.js +173 -0
  139. package/dist/esm/word/font-table.d.ts +10 -0
  140. package/dist/esm/word/font-table.js +13 -1
  141. package/dist/esm/word/index.js +1 -1
  142. package/dist/esm/word/numbering-parser.d.ts +3 -1
  143. package/dist/esm/word/numbering-parser.js +2 -1
  144. package/dist/esm/word/paragraph-properties.d.ts +7 -6
  145. package/dist/esm/word/paragraph-properties.js +14 -2
  146. package/dist/esm/word/run-properties.js +26 -0
  147. package/package.json +12 -3
@@ -22,8 +22,6 @@ var UNDERSTOOD_NS = new Set([
22
22
  ]);
23
23
  function parseFloatAnchor(anchor) {
24
24
  if (!poIs(anchor, "wp:anchor")) return void 0;
25
- const behindRaw = poAttr(anchor, "behindDoc");
26
- const behind = behindRaw === "1" || behindRaw === "true";
27
25
  let wrap = "none";
28
26
  let wrapSide;
29
27
  for (const child of poChildren(anchor)) {
@@ -35,6 +33,8 @@ function parseFloatAnchor(anchor) {
35
33
  const side = poAttr(child, "wrapText");
36
34
  wrapSide = side === "left" || side === "right" || side === "largest" ? side : "bothSides";
37
35
  }
36
+ const behindRaw = poAttr(anchor, "behindDoc");
37
+ const behind = (behindRaw === "1" || behindRaw === "true") && wrap === "none";
38
38
  const zOrder = poIntAttr(anchor, "relativeHeight");
39
39
  const posH = parseAnchorPos(anchor, "wp:positionH", [
40
40
  "margin",
@@ -163,18 +163,20 @@ function parseAnchorPos(anchor, tag, allowed) {
163
163
  * ECMA-376 Part 3 (Markup Compatibility) — resolve an `<mc:AlternateContent>` to
164
164
  * the children of the first `<mc:Choice>` whose `Requires` lists only namespaces
165
165
  * we understand, else the `<mc:Fallback>` children, else nothing. (`Requires`
166
- * holds space-separated namespace prefixes as declared in the document.)
166
+ * holds space-separated namespace prefixes as declared in the document.) The
167
+ * elements are known by their local names, whatever prefix the producer bound
168
+ * the namespace to: Word 2008 for Mac wrote `ve:`.
167
169
  *
168
170
  * @param altContent The `mc:AlternateContent` node.
169
171
  * @returns The chosen branch's children.
170
172
  */
171
173
  function resolveMc(altContent) {
172
174
  for (const choice of poChildren(altContent)) {
173
- if (!poIs(choice, "mc:Choice")) continue;
175
+ if (!poIsLocal(choice, "Choice")) continue;
174
176
  const requires = (poAttr(choice, "Requires") ?? "").split(/\s+/).filter(Boolean);
175
177
  if (requires.length > 0 && requires.every((r) => UNDERSTOOD_NS.has(r))) return poChildren(choice);
176
178
  }
177
- const fallback = poChildren(altContent).find((c) => poIs(c, "mc:Fallback"));
179
+ const fallback = poChildren(altContent).find((c) => poIsLocal(c, "Fallback"));
178
180
  return fallback ? poChildren(fallback) : [];
179
181
  }
180
182
  /**
@@ -188,22 +190,55 @@ function resolveMc(altContent) {
188
190
  */
189
191
  function expandMcChildren(children) {
190
192
  const out = [];
191
- for (const c of children) if (poIs(c, "mc:AlternateContent")) out.push(...resolveMc(c));
193
+ for (const c of children) if (poIsLocal(c, "AlternateContent")) out.push(...resolveMc(c));
192
194
  else out.push(c);
193
195
  return out;
194
196
  }
195
197
  /**
198
+ * The node with every markup-compatibility block beneath it resolved (see
199
+ * {@link resolveMc}), so that a search through its descendants finds only
200
+ * the content a reader like this one is to take. Bug51170.docx's header offers
201
+ * its logo twice — a PDF for Word for Mac as the Choice, a PNG as the Fallback
202
+ * — and the first `a:blip` in document order was the PDF, which is not a
203
+ * picture a page can show: the logo was left out.
204
+ *
205
+ * @param node The node to resolve.
206
+ * @returns The node itself where nothing beneath it needed resolving.
207
+ */
208
+ function resolveMcTree(node) {
209
+ const tag = poTag(node);
210
+ const children = poChildren(node);
211
+ if (tag === void 0 || children.length === 0) return node;
212
+ let changed = false;
213
+ const resolved = [];
214
+ for (const child of children) {
215
+ if (poIsLocal(child, "AlternateContent")) {
216
+ changed = true;
217
+ for (const chosen of resolveMc(child)) resolved.push(resolveMcTree(chosen));
218
+ continue;
219
+ }
220
+ const inner = resolveMcTree(child);
221
+ if (inner !== child) changed = true;
222
+ resolved.push(inner);
223
+ }
224
+ return changed ? {
225
+ ...node,
226
+ [tag]: resolved
227
+ } : node;
228
+ }
229
+ /**
196
230
  * Parse a `<w:drawing>` (ECMA-376 Part 1 §20) into a {@link DrawingContent}. The
197
231
  * `a:graphicData` `@uri` selects the branch: a `wps:wsp` shape, a chart, a
198
232
  * SmartArt diagram, or — falling through — an embedded picture from
199
233
  * `a:blip @r:embed`.
200
234
  *
201
- * @param drawing The `w:drawing` node.
235
+ * @param drawingNode The `w:drawing` node.
202
236
  * @param resolveColor Resolver for theme/scheme colours used by shape fills/lines.
203
237
  * @param parseBody Optional body parser for a shape's text box (omitted ⇒ no text).
204
238
  * @returns The parsed content, or `null` when no anchor / recognizable graphic is found.
205
239
  */
206
- function parseDrawing(drawing, resolveColor, parseBody, resolveImage, resolveChartPart, themeLineWidths, themeStyles, parseDrawingText) {
240
+ function parseDrawing(drawingNode, resolveColor, parseBody, resolveImage, resolveChartPart, themeLineWidths, themeStyles, parseDrawingText) {
241
+ const drawing = resolveMcTree(drawingNode);
207
242
  const anchor = poChildren(drawing).find((c) => poIs(c, "wp:inline")) ?? poChildren(drawing).find((c) => poIs(c, "wp:anchor"));
208
243
  if (!anchor) return null;
209
244
  const extent = poFindDescendant(anchor, "wp:extent");
@@ -1083,6 +1118,12 @@ function vmlFloat(style, zIndexAttr, shape) {
1083
1118
  const zIndex = prop("z-index") ?? zIndexAttr;
1084
1119
  return {
1085
1120
  wrap: vmlWrap(shape),
1121
+ wrapDist: {
1122
+ topPt: pt(prop("mso-wrap-distance-top") ?? 0),
1123
+ bottomPt: pt(prop("mso-wrap-distance-bottom") ?? 0),
1124
+ leftPt: pt(prop("mso-wrap-distance-left") ?? 9),
1125
+ rightPt: pt(prop("mso-wrap-distance-right") ?? 9)
1126
+ },
1086
1127
  ...poAttrLocal(shape, "allowincell") === "f" ? { inCell: false } : {},
1087
1128
  ...zIndex !== void 0 && zIndex < 0 ? { behind: true } : {},
1088
1129
  ...zIndex !== void 0 ? { zOrder: Math.abs(zIndex) } : {},
@@ -0,0 +1,30 @@
1
+ import { FaceFamily, FaceOutlines } from '../core/ir/flow.js';
2
+ import { Loss } from '../core/ir/index.js';
3
+ import { OpcPart, Relationship } from '../core/opc/index.js';
4
+ /** §17.8.1 — the content type of an obfuscated embedded font. */
5
+ export declare const OBFUSCATED_FONT_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.obfuscatedFont";
6
+ /** The faces written into a package, and how the font table reaches them. */
7
+ export interface EmbeddedFaces {
8
+ /** Family → the `w:embed*` elements its `w:font` carries (§17.8.3.3–6). */
9
+ readonly elements: ReadonlyMap<string, string>;
10
+ /** The obfuscated font parts, under `word/fonts/`. */
11
+ readonly parts: ReadonlyArray<OpcPart>;
12
+ /** The font table's relationships to them. */
13
+ readonly relationships: ReadonlyArray<Relationship>;
14
+ }
15
+ /**
16
+ * Build the embedded fonts for the faces the runs name (§17.8.1).
17
+ *
18
+ * The faces are gathered by family and by slot — the style each face IS. Two
19
+ * faces in one slot (a SemiBold and a Bold, both written bold) become one font:
20
+ * the face with the most characters, and the other's characters it lacks.
21
+ *
22
+ * A face its licence keeps out is reported, not written.
23
+ *
24
+ * @param used The run font names the document's runs carry.
25
+ * @param outlines Run font name → the face's outlines.
26
+ * @param families Run font name → the family a word processor knows it by.
27
+ * @param losses Appended to for every face left out.
28
+ * @returns The fonts to write and the font table's references to them.
29
+ */
30
+ export declare function embedFaces(used: Iterable<string>, outlines: ReadonlyMap<string, FaceOutlines>, families: ReadonlyMap<string, FaceFamily>, losses: Array<Loss>): EmbeddedFaces;
@@ -0,0 +1,173 @@
1
+ import { buildTrueType, editableEmbedding } from "../core/font/ttf-build.js";
2
+ import { FEATURES } from "../core/ir/features.js";
3
+ import { obfuscateEmbeddedFont } from "./font-table.js";
4
+ import { md5 } from "../core/crypto/primitives.js";
5
+ //#region src/word/font-embed.ts
6
+ /** §17.8.1 — the content type of an obfuscated embedded font. */
7
+ var OBFUSCATED_FONT_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.obfuscatedFont";
8
+ var REL_FONT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/font";
9
+ /** §17.8.3 — the four faces a family may embed, in the order CT_Font lists them. */
10
+ var SLOTS = [
11
+ "Regular",
12
+ "Bold",
13
+ "Italic",
14
+ "BoldItalic"
15
+ ];
16
+ /**
17
+ * Build the embedded fonts for the faces the runs name (§17.8.1).
18
+ *
19
+ * The faces are gathered by family and by slot — the style each face IS. Two
20
+ * faces in one slot (a SemiBold and a Bold, both written bold) become one font:
21
+ * the face with the most characters, and the other's characters it lacks.
22
+ *
23
+ * A face its licence keeps out is reported, not written.
24
+ *
25
+ * @param used The run font names the document's runs carry.
26
+ * @param outlines Run font name → the face's outlines.
27
+ * @param families Run font name → the family a word processor knows it by.
28
+ * @param losses Appended to for every face left out.
29
+ * @returns The fonts to write and the font table's references to them.
30
+ */
31
+ function embedFaces(used, outlines, families, losses) {
32
+ const bySlot = /* @__PURE__ */ new Map();
33
+ for (const name of new Set(used)) {
34
+ const face = outlines.get(name);
35
+ const family = families.get(name)?.family;
36
+ if (!face || family === void 0) continue;
37
+ if (!editableEmbedding(face.fsType)) {
38
+ losses.push({
39
+ severity: "degraded",
40
+ feature: FEATURES.fontsEmbedding,
41
+ detail: `font ${face.postScriptName} not embedded: its licence (OS/2 fsType ${face.fsType}) does not allow it in an editable document`
42
+ });
43
+ continue;
44
+ }
45
+ const slot = face.bold ? face.italic ? "BoldItalic" : "Bold" : face.italic ? "Italic" : "Regular";
46
+ let slots = bySlot.get(family);
47
+ if (!slots) bySlot.set(family, slots = /* @__PURE__ */ new Map());
48
+ slots.set(slot, [...slots.get(slot) ?? [], face]);
49
+ }
50
+ const elements = /* @__PURE__ */ new Map();
51
+ const parts = [];
52
+ const relationships = [];
53
+ for (const family of [...bySlot.keys()].sort()) {
54
+ const slots = bySlot.get(family);
55
+ for (const slot of SLOTS) {
56
+ const faces = [...slots.get(slot) ?? []].sort((a, b) => b.glyphs.size - a.glyphs.size);
57
+ const first = faces[0];
58
+ if (!first) continue;
59
+ const glyphs = new Map(first.glyphs);
60
+ const kerning = new Map(first.kerning);
61
+ const ligatures = new Map(first.ligatures);
62
+ for (const other of faces.slice(1)) {
63
+ for (const [char, glyph] of other.glyphs) if (!glyphs.has(char)) glyphs.set(char, glyph);
64
+ for (const [pair, value] of other.kerning ?? []) if (!kerning.has(pair)) kerning.set(pair, value);
65
+ for (const [letters, glyph] of other.ligatures ?? []) if (!ligatures.has(letters)) ligatures.set(letters, glyph);
66
+ }
67
+ const built = builtGlyphs(glyphs);
68
+ if (built.length + ligatures.size > 8e3) {
69
+ losses.push({
70
+ severity: "degraded",
71
+ feature: FEATURES.fontsEmbedding,
72
+ detail: `font ${first.postScriptName} not embedded: ${built.length + ligatures.size} glyphs is more than an embedded subset holds`
73
+ });
74
+ continue;
75
+ }
76
+ const program = buildTrueType({
77
+ family,
78
+ bold: slot === "Bold" || slot === "BoldItalic",
79
+ italic: slot === "Italic" || slot === "BoldItalic",
80
+ postScriptName: first.postScriptName,
81
+ glyphs: built,
82
+ ascent: first.ascent,
83
+ descent: first.descent,
84
+ ...first.capHeight !== void 0 ? { capHeight: first.capHeight } : {},
85
+ ...first.xHeight !== void 0 ? { xHeight: first.xHeight } : {},
86
+ italicAngle: first.italicAngle,
87
+ fixedPitch: first.fixedPitch,
88
+ fsType: statedFsType(first.fsType),
89
+ kerning: kernPairs(kerning),
90
+ ligatures: [...ligatures].map(([letters, glyph]) => ({
91
+ codePoints: [...letters].map((c) => c.codePointAt(0)),
92
+ outline: glyph.outline,
93
+ advance: glyph.advance
94
+ }))
95
+ });
96
+ const fontKey = fontKeyOf(program);
97
+ const index = parts.length + 1;
98
+ const target = `fonts/font${index}.odttf`;
99
+ const id = `rId${index}`;
100
+ parts.push({
101
+ path: `word/${target}`,
102
+ data: obfuscateEmbeddedFont(program, fontKey),
103
+ contentType: OBFUSCATED_FONT_CONTENT_TYPE
104
+ });
105
+ relationships.push({
106
+ id,
107
+ type: REL_FONT,
108
+ target,
109
+ targetMode: "Internal"
110
+ });
111
+ elements.set(family, `${elements.get(family) ?? ""}<w:embed${slot} r:id="${id}" w:fontKey="${fontKey}" w:subsetted="1"/>`);
112
+ }
113
+ }
114
+ return {
115
+ elements,
116
+ parts,
117
+ relationships
118
+ };
119
+ }
120
+ /**
121
+ * A face's characters as the glyphs of a font: characters that share one glyph
122
+ * (the space and the no-break space) select the same one, and the glyphs stand
123
+ * in the order of their first character.
124
+ */
125
+ function builtGlyphs(glyphs) {
126
+ const byGlyph = /* @__PURE__ */ new Map();
127
+ for (const [char, glyph] of glyphs) {
128
+ const cp = char.codePointAt(0);
129
+ if (cp === void 0) continue;
130
+ byGlyph.set(glyph, [...byGlyph.get(glyph) ?? [], cp]);
131
+ }
132
+ return [...byGlyph].map(([glyph, codePoints]) => ({
133
+ codePoints: codePoints.sort((a, b) => a - b),
134
+ outline: glyph.outline,
135
+ advance: glyph.advance
136
+ })).sort((a, b) => a.codePoints[0] - b.codePoints[0]);
137
+ }
138
+ /** The face's kerning pairs as the font states them: by code point, left then right. */
139
+ function kernPairs(kerning) {
140
+ const out = [];
141
+ for (const [pair, value] of kerning) {
142
+ const [left, right] = [...pair].map((c) => c.codePointAt(0));
143
+ if (left !== void 0 && right !== void 0) out.push({
144
+ left,
145
+ right,
146
+ value
147
+ });
148
+ }
149
+ return out;
150
+ }
151
+ /**
152
+ * The `fsType` the built font states: the program's own, less the bits the
153
+ * format reserves; Editable where the program stated none — which is the use
154
+ * the font is put to here, and no more than that.
155
+ */
156
+ function statedFsType(fsType) {
157
+ if (fsType === void 0) return EDITABLE_EMBEDDING;
158
+ return fsType & DEFINED_FS_TYPE_BITS;
159
+ }
160
+ /** OS/2 `fsType` — Editable embedding, and every bit the format defines. */
161
+ var EDITABLE_EMBEDDING = 8;
162
+ var DEFINED_FS_TYPE_BITS = 782;
163
+ /**
164
+ * §17.8.1 — the GUID a font is obfuscated with, drawn from the font's own bytes
165
+ * so the same document always writes the same package.
166
+ */
167
+ function fontKeyOf(program) {
168
+ const hex = [...md5(program)].map((b) => b.toString(16).padStart(2, "0").toUpperCase());
169
+ const group = (from, to) => hex.slice(from, to).join("");
170
+ return `{${group(0, 4)}-${group(4, 6)}-${group(6, 8)}-${group(8, 10)}-${group(10, 16)}}`;
171
+ }
172
+ //#endregion
173
+ export { OBFUSCATED_FONT_CONTENT_TYPE, embedFaces };
@@ -18,6 +18,16 @@ interface FontTableEntry {
18
18
  * @returns The de-obfuscated bytes, or `data` unchanged when `fontKey` is not a GUID.
19
19
  */
20
20
  export declare function deobfuscateEmbeddedFont(data: Uint8Array, fontKey: string): Uint8Array;
21
+ /**
22
+ * §17.8.1 — obfuscate a font for embedding. The obfuscation is an XOR of the
23
+ * first 32 bytes with the `fontKey` GUID's bytes in reverse order, so it is its
24
+ * own inverse: this is {@link deobfuscateEmbeddedFont} run on a plain font.
25
+ *
26
+ * @param data The plain sfnt bytes.
27
+ * @param fontKey The GUID the font table states as its `w:fontKey`.
28
+ * @returns The bytes to store as the `.odttf` part.
29
+ */
30
+ export declare function obfuscateEmbeddedFont(data: Uint8Array, fontKey: string): Uint8Array;
21
31
  /**
22
32
  * Parse `word/fontTable.xml` for its embedded-font references (§17.8). Returns
23
33
  * one `FontTableEntry` per `w:font` that carries at least one `w:embed*` child,
@@ -28,6 +28,18 @@ function deobfuscateEmbeddedFont(data, fontKey) {
28
28
  return out;
29
29
  }
30
30
  /**
31
+ * §17.8.1 — obfuscate a font for embedding. The obfuscation is an XOR of the
32
+ * first 32 bytes with the `fontKey` GUID's bytes in reverse order, so it is its
33
+ * own inverse: this is {@link deobfuscateEmbeddedFont} run on a plain font.
34
+ *
35
+ * @param data The plain sfnt bytes.
36
+ * @param fontKey The GUID the font table states as its `w:fontKey`.
37
+ * @returns The bytes to store as the `.odttf` part.
38
+ */
39
+ function obfuscateEmbeddedFont(data, fontKey) {
40
+ return deobfuscateEmbeddedFont(data, fontKey);
41
+ }
42
+ /**
31
43
  * Parse `word/fontTable.xml` for its embedded-font references (§17.8). Returns
32
44
  * one `FontTableEntry` per `w:font` that carries at least one `w:embed*` child,
33
45
  * each naming the relationship id + `w:fontKey` for a Regular/Bold/Italic/
@@ -103,4 +115,4 @@ function loadEmbeddedFonts(pkg) {
103
115
  return out;
104
116
  }
105
117
  //#endregion
106
- export { deobfuscateEmbeddedFont, loadEmbeddedFonts, parseFontTable };
118
+ export { deobfuscateEmbeddedFont, loadEmbeddedFonts, obfuscateEmbeddedFont, parseFontTable };
@@ -1,9 +1,9 @@
1
1
  import "./run-properties.js";
2
2
  import "./paragraph-properties.js";
3
3
  import "./table-parser.js";
4
+ import "./numbering-parser.js";
4
5
  import "./document-parser.js";
5
6
  import "./text-extractor.js";
6
7
  import "./styles-parser.js";
7
8
  import "./font-table.js";
8
- import "./numbering-parser.js";
9
9
  import "./settings-parser.js";
@@ -1,6 +1,8 @@
1
- import { Numbering } from '../core/document-model/index.js';
1
+ import { Numbering, NumberingFormat } from '../core/document-model/index.js';
2
2
  import { ResourceId } from '../core/ir/index.js';
3
3
  import { ThemeFonts } from '../core/drawingml/theme-parser.js';
4
+ /** §17.18.59 ST_NumberFormat — the formats this reader knows by name. */
5
+ export declare const FORMATS: ReadonlySet<NumberingFormat>;
4
6
  /** The empty {@link Numbering} returned when a document has no `numbering.xml`. */
5
7
  export declare const EMPTY_NUMBERING: Numbering;
6
8
  /**
@@ -14,6 +14,7 @@ var parser = new XMLParser({
14
14
  parseTagValue: false,
15
15
  trimValues: false
16
16
  });
17
+ /** §17.18.59 ST_NumberFormat — the formats this reader knows by name. */
17
18
  var FORMATS = new Set([
18
19
  "decimal",
19
20
  "decimalZero",
@@ -184,4 +185,4 @@ function getValVal(node) {
184
185
  return typeof v === "string" ? v : void 0;
185
186
  }
186
187
  //#endregion
187
- export { EMPTY_NUMBERING, parseNumbering };
188
+ export { EMPTY_NUMBERING, FORMATS, parseNumbering };
@@ -1,10 +1,11 @@
1
1
  import { ParagraphProperties } from '../core/document-model/index.js';
2
2
  import { ThemeFonts } from '../core/drawingml/theme-parser.js';
3
3
  /**
4
- * §17.3.1.1/§17.3.1.3 — the gap `w:*Autospacing` asks the consumer for. Word's
5
- * own automatic value is HTML's 14pt, and only a document old enough to state
6
- * no `w:compatSetting compatibilityMode` still gets it: from Word 2007 on the
7
- * automatic value is nothing at all, which is what both references leave.
4
+ * §17.3.1.1/§17.3.1.3 — the gap `w:*Autospacing` asks the consumer for:
5
+ * HTML's 14pt, in every compatibility mode. Word for Mac stands two such
6
+ * paragraphs 14.02pt apart in modes 14, 15 and none alike; LibreOffice leaves
7
+ * nothing for a document that states a mode, which is where the earlier
8
+ * reading of "nothing from Word 2007 on" came from.
8
9
  */
9
10
  export declare const HTML_AUTO_SPACING_PT = 14;
10
11
  /**
@@ -16,8 +17,8 @@ export declare const HTML_AUTO_SPACING_PT = 14;
16
17
  * @param pPr The `w:pPr` element in flat (fast-xml-parser) shape, or anything
17
18
  * non-element (yielding an empty result).
18
19
  * @param autoSpacingPt What `w:beforeAutospacing`/`w:afterAutospacing` resolve
19
- * to — {@link HTML_AUTO_SPACING_PT} for a document that states no
20
- * compatibility mode, and 0 (the default) for every Word 2007-or-later one.
20
+ * to — {@link HTML_AUTO_SPACING_PT} for a Word document, 0 (the default)
21
+ * where the caller has no automatic spacing to give.
21
22
  * @returns The extracted properties; an empty object when `pPr` is absent.
22
23
  */
23
24
  export declare function parseParagraphProperties(pPr: unknown, autoSpacingPt?: number, themeFonts?: ThemeFonts): ParagraphProperties;
@@ -100,8 +100,8 @@ function parseFramePr(node) {
100
100
  * @param pPr The `w:pPr` element in flat (fast-xml-parser) shape, or anything
101
101
  * non-element (yielding an empty result).
102
102
  * @param autoSpacingPt What `w:beforeAutospacing`/`w:afterAutospacing` resolve
103
- * to — {@link HTML_AUTO_SPACING_PT} for a document that states no
104
- * compatibility mode, and 0 (the default) for every Word 2007-or-later one.
103
+ * to — {@link HTML_AUTO_SPACING_PT} for a Word document, 0 (the default)
104
+ * where the caller has no automatic spacing to give.
105
105
  * @returns The extracted properties; an empty object when `pPr` is absent.
106
106
  */
107
107
  function parseParagraphProperties(pPr, autoSpacingPt = 0, themeFonts) {
@@ -172,6 +172,18 @@ function parseParagraphProperties(pPr, autoSpacingPt = 0, themeFonts) {
172
172
  const v = parseToggle(el["w:pageBreakBefore"]);
173
173
  if (v !== void 0) out.pageBreakBefore = v;
174
174
  }
175
+ if ("w:keepNext" in el) {
176
+ const v = parseToggle(el["w:keepNext"]);
177
+ if (v !== void 0) out.keepNext = v;
178
+ }
179
+ if ("w:keepLines" in el) {
180
+ const v = parseToggle(el["w:keepLines"]);
181
+ if (v !== void 0) out.keepLines = v;
182
+ }
183
+ if ("w:widowControl" in el) {
184
+ const v = parseToggle(el["w:widowControl"]);
185
+ if (v !== void 0) out.widowControl = v;
186
+ }
175
187
  if ("w:bidi" in el) {
176
188
  const v = parseToggle(el["w:bidi"]);
177
189
  if (v !== void 0) out.bidi = v;
@@ -20,6 +20,24 @@ var VERTICAL_ALIGNS = new Set([
20
20
  "superscript",
21
21
  "subscript"
22
22
  ]);
23
+ var LIGATURES = new Set([
24
+ "none",
25
+ "standard",
26
+ "contextual",
27
+ "historical",
28
+ "discretional",
29
+ "standardContextual",
30
+ "standardHistorical",
31
+ "contextualHistorical",
32
+ "standardDiscretional",
33
+ "contextualDiscretional",
34
+ "historicalDiscretional",
35
+ "standardContextualHistorical",
36
+ "standardContextualDiscretional",
37
+ "standardHistoricalDiscretional",
38
+ "contextualHistoricalDiscretional",
39
+ "all"
40
+ ]);
23
41
  var HIGHLIGHT_COLORS = new Map([
24
42
  ["black", "000000"],
25
43
  ["blue", "0000FF"],
@@ -110,6 +128,14 @@ function parseRunProperties(rPr, themeFonts) {
110
128
  const n = v === void 0 ? NaN : Number(v);
111
129
  if (Number.isFinite(n) && n !== 0) out.letterSpacingPt = twipsToPt(n);
112
130
  }
131
+ if ("w:kern" in el) {
132
+ const n = Number(getVal(el["w:kern"]));
133
+ if (Number.isFinite(n) && n >= 0) out.kerningMinPt = halfPtToPt(n);
134
+ }
135
+ if ("w14:ligatures" in el) {
136
+ const v = asElement(el["w14:ligatures"])?.["@_w14:val"];
137
+ if (typeof v === "string" && LIGATURES.has(v)) out.ligatures = v;
138
+ }
113
139
  if ("w:shd" in el) {
114
140
  const shd = el["w:shd"];
115
141
  const hex = shadingFillHex(getVal(shd), getAttr(shd, "color"), getAttr(shd, "fill"));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "reamkit",
3
- "version": "1.30.0",
4
- "description": "Ream — convert DOCX, XLSX, PPTX and PDF to PDF, SVG, HTML, DOCX and XLSX, built from scratch on the ECMA-376 and ISO 32000 specifications. Parse once, convert anywhere.",
3
+ "version": "1.32.0",
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>",
7
7
  "repository": {
@@ -48,7 +48,12 @@
48
48
  "word",
49
49
  "excel",
50
50
  "powerpoint",
51
- "converter"
51
+ "converter",
52
+ "doc",
53
+ "xls",
54
+ "ppt",
55
+ "markdown",
56
+ "pdf-to-docx"
52
57
  ],
53
58
  "scripts": {
54
59
  "build": "vite build",
@@ -79,6 +84,10 @@
79
84
  "corpus:pixel": "tsx scripts/corpus/pixel-scout.ts",
80
85
  "stand": "tsx stand/serve.ts",
81
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",
82
91
  "typecheck:dist": "tsc -p tsconfig.json --noEmit"
83
92
  },
84
93
  "dependencies": {