reamkit 1.21.0 → 1.23.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 (90) hide show
  1. package/dist/esm/core/bytes.d.ts +22 -4
  2. package/dist/esm/core/bytes.js +73 -5
  3. package/dist/esm/core/converter/ream.d.ts +16 -0
  4. package/dist/esm/core/converter/ream.js +59 -9
  5. package/dist/esm/core/document-model/types.d.ts +78 -0
  6. package/dist/esm/core/drawingml/chart-geometry.js +11 -5
  7. package/dist/esm/core/drawingml/chart-parser.d.ts +2 -1
  8. package/dist/esm/core/drawingml/chart-parser.js +23 -1
  9. package/dist/esm/core/drawingml/colors.d.ts +30 -4
  10. package/dist/esm/core/drawingml/colors.js +45 -6
  11. package/dist/esm/core/drawingml/preset-geometry.js +236 -0
  12. package/dist/esm/core/drawingml/theme-parser.d.ts +38 -0
  13. package/dist/esm/core/drawingml/theme-parser.js +64 -1
  14. package/dist/esm/core/fonts/families.d.ts +10 -0
  15. package/dist/esm/core/fonts/families.js +44 -0
  16. package/dist/esm/core/fonts/index.d.ts +3 -2
  17. package/dist/esm/core/fonts/remote-fonts.d.ts +50 -0
  18. package/dist/esm/core/fonts/remote-fonts.js +114 -8
  19. package/dist/esm/core/fonts/scripts.d.ts +26 -0
  20. package/dist/esm/core/fonts/scripts.js +177 -0
  21. package/dist/esm/core/metafile/blit.d.ts +33 -0
  22. package/dist/esm/core/metafile/blit.js +67 -0
  23. package/dist/esm/core/metafile/dib.d.ts +61 -0
  24. package/dist/esm/core/metafile/dib.js +205 -0
  25. package/dist/esm/core/metafile/emf.js +139 -24
  26. package/dist/esm/core/metafile/picture.d.ts +12 -1
  27. package/dist/esm/core/metafile/symbol-fonts.d.ts +42 -0
  28. package/dist/esm/core/metafile/symbol-fonts.js +154 -0
  29. package/dist/esm/core/metafile/wmf.js +183 -10
  30. package/dist/esm/core/ole/escher-blip.d.ts +12 -0
  31. package/dist/esm/core/ole/escher-blip.js +88 -0
  32. package/dist/esm/{pdf-reader → core}/png-encode.js +1 -1
  33. package/dist/esm/core/vector.d.ts +5 -0
  34. package/dist/esm/excel/print-model.js +3 -1
  35. package/dist/esm/excel/sheet-shape-parser.js +1 -12
  36. package/dist/esm/excel/styles-parser.js +1 -1
  37. package/dist/esm/excel/xls/biff-reader.js +53 -18
  38. package/dist/esm/excel/xls/biff-styles.d.ts +5 -2
  39. package/dist/esm/excel/xls/biff-styles.js +9 -7
  40. package/dist/esm/excel/xls/escher.js +90 -24
  41. package/dist/esm/excel/xlsx-reader.d.ts +1 -1
  42. package/dist/esm/excel/xlsx-reader.js +3 -3
  43. package/dist/esm/layout/page-doc.d.ts +77 -0
  44. package/dist/esm/layout/page-doc.js +34 -18
  45. package/dist/esm/layout/styled-layout.d.ts +2 -2
  46. package/dist/esm/layout/styled-layout.js +467 -76
  47. package/dist/esm/pdf/shading.d.ts +19 -0
  48. package/dist/esm/pdf/shading.js +84 -11
  49. package/dist/esm/pdf/styled-page-emitter.js +647 -444
  50. package/dist/esm/pdf-reader/image-decode.js +1 -1
  51. package/dist/esm/pptx/embedded-fonts.d.ts +31 -0
  52. package/dist/esm/pptx/embedded-fonts.js +104 -0
  53. package/dist/esm/pptx/ole-preview.d.ts +10 -0
  54. package/dist/esm/pptx/ole-preview.js +52 -0
  55. package/dist/esm/pptx/placeholder-cascade.d.ts +136 -5
  56. package/dist/esm/pptx/placeholder-cascade.js +260 -27
  57. package/dist/esm/pptx/ppt/ppt-reader.js +120 -10
  58. package/dist/esm/pptx/ppt/ppt-text.d.ts +66 -1
  59. package/dist/esm/pptx/ppt/ppt-text.js +576 -137
  60. package/dist/esm/pptx/pptx-reader.d.ts +8 -0
  61. package/dist/esm/pptx/pptx-reader.js +271 -41
  62. package/dist/esm/pptx/preset-table-styles.d.ts +9 -0
  63. package/dist/esm/pptx/preset-table-styles.js +27 -0
  64. package/dist/esm/pptx/slide-parser.d.ts +78 -10
  65. package/dist/esm/pptx/slide-parser.js +396 -95
  66. package/dist/esm/pptx/sp-helpers.d.ts +2 -1
  67. package/dist/esm/pptx/sp-helpers.js +22 -6
  68. package/dist/esm/pptx/table-style.d.ts +64 -0
  69. package/dist/esm/pptx/table-style.js +203 -0
  70. package/dist/esm/svg/svg-writer.js +53 -5
  71. package/dist/esm/word/document-parser.d.ts +7 -1
  72. package/dist/esm/word/document-parser.js +6 -6
  73. package/dist/esm/word/docx-reader.js +8 -5
  74. package/dist/esm/word/docx-to-pdf.d.ts +8 -7
  75. package/dist/esm/word/docx-to-pdf.js +25 -7
  76. package/dist/esm/word/drawing-parser.d.ts +34 -0
  77. package/dist/esm/word/drawing-parser.js +173 -15
  78. package/dist/esm/word/numbering-parser.d.ts +2 -1
  79. package/dist/esm/word/numbering-parser.js +6 -6
  80. package/dist/esm/word/paragraph-properties.d.ts +2 -1
  81. package/dist/esm/word/paragraph-properties.js +2 -2
  82. package/dist/esm/word/run-properties.d.ts +5 -2
  83. package/dist/esm/word/run-properties.js +13 -8
  84. package/dist/esm/word/styles-parser.d.ts +2 -1
  85. package/dist/esm/word/styles-parser.js +12 -12
  86. package/dist/esm/word/table-parser.js +3 -3
  87. package/dist/esm/word/theme-fonts.d.ts +10 -0
  88. package/dist/esm/word/theme-fonts.js +28 -0
  89. package/package.json +1 -1
  90. /package/dist/esm/{pdf-reader → core}/png-encode.d.ts +0 -0
@@ -1,5 +1,6 @@
1
1
  import { RunProperties } from '../core/document-model/index.js';
2
2
  import { ColorResolver } from '../core/drawingml/colors.js';
3
+ import { ThemeFonts } from '../core/drawingml/theme-parser.js';
3
4
  import { PoNode } from '../core/po-helpers.js';
4
5
  /**
5
6
  * §19.3.1.36 `p:ph` — a shape's placeholder reference. `type`/`idx` tie a slide
@@ -42,4 +43,4 @@ export declare function parseXfrmBox(spPr: PoNode | undefined): ShapeBoxEmu | un
42
43
  * (`sz`, hundredths of a point), bold/italic/underline, solid colour and the
43
44
  * latin typeface. Scheme colours and the theme font cascade come in PX5.
44
45
  */
45
- export declare function rPrToRunProps(rPr: PoNode | undefined, colors: ColorResolver): RunProperties;
46
+ export declare function rPrToRunProps(rPr: PoNode | undefined, colors: ColorResolver, themeFonts?: ThemeFonts): RunProperties;
@@ -1,6 +1,7 @@
1
1
  import { pt } from "../core/ir/units.js";
2
2
  import { poAttr, poChildren, poIntAttr, poIs } from "../core/po-helpers.js";
3
3
  import { resolveColorNode } from "../core/drawingml/colors.js";
4
+ import { resolveThemeFont } from "../core/drawingml/theme-parser.js";
4
5
  //#region src/pptx/sp-helpers.ts
5
6
  var isTrue = (v) => v === "1" || v === "true" || v === "on";
6
7
  /**
@@ -54,21 +55,36 @@ function parseXfrmBox(spPr) {
54
55
  * (`sz`, hundredths of a point), bold/italic/underline, solid colour and the
55
56
  * latin typeface. Scheme colours and the theme font cascade come in PX5.
56
57
  */
57
- function rPrToRunProps(rPr, colors) {
58
+ function rPrToRunProps(rPr, colors, themeFonts) {
58
59
  if (!rPr) return {};
59
60
  const sz = poIntAttr(rPr, "sz");
60
61
  const u = poAttr(rPr, "u");
61
62
  const colorHex = solidFillColor(rPr, colors);
62
63
  const latin = poChildren(rPr).find((c) => poIs(c, "a:latin"));
63
- const typeface = latin ? poAttr(latin, "typeface")?.trim() : void 0;
64
+ const typeface = resolveThemeFont(latin ? poAttr(latin, "typeface")?.trim() : void 0, themeFonts);
65
+ const flag = (name) => {
66
+ const v = poAttr(rPr, name);
67
+ return v === void 0 ? void 0 : isTrue(v);
68
+ };
69
+ const [b, i] = [flag("b"), flag("i")];
64
70
  return {
65
- ...isTrue(poAttr(rPr, "b")) ? { bold: true } : {},
66
- ...isTrue(poAttr(rPr, "i")) ? { italic: true } : {},
67
- ...u !== void 0 && u !== "none" ? { underline: "single" } : {},
71
+ ...b !== void 0 ? { bold: b } : {},
72
+ ...i !== void 0 ? { italic: i } : {},
73
+ ...u !== void 0 ? { underline: u === "none" ? "none" : "single" } : {},
68
74
  ...sz !== void 0 ? { fontSizePt: pt(sz / 100) } : {},
69
75
  ...colorHex ? { colorHex } : {},
70
- ...typeface ? { fontFamily: { ascii: typeface } } : {}
76
+ ...typeface ? { fontFamily: { ascii: typeface } } : {},
77
+ ...capsOf(poAttr(rPr, "cap"))
78
+ };
79
+ }
80
+ function capsOf(cap) {
81
+ if (cap === "all") return { caps: true };
82
+ if (cap === "small") return { smallCaps: true };
83
+ if (cap === "none") return {
84
+ caps: false,
85
+ smallCaps: false
71
86
  };
87
+ return {};
72
88
  }
73
89
  function solidFillColor(rPr, colors) {
74
90
  const solidFill = poChildren(rPr).find((c) => poIs(c, "a:solidFill"));
@@ -0,0 +1,64 @@
1
+ import { Border, CellBorders, TableCell } from '../core/document-model/index.js';
2
+ import { ColorResolver } from '../core/drawingml/colors.js';
3
+ import { PoNode } from '../core/po-helpers.js';
4
+ /** Which conditional parts of a style a table asks for (`a:tblPr` flags). */
5
+ export interface TableStyleFlags {
6
+ readonly firstRow: boolean;
7
+ readonly lastRow: boolean;
8
+ readonly firstCol: boolean;
9
+ readonly lastCol: boolean;
10
+ readonly bandRow: boolean;
11
+ readonly bandCol: boolean;
12
+ }
13
+ /** What one part of a table style says about a cell. */
14
+ export interface TableStylePart {
15
+ readonly shadingHex?: string;
16
+ readonly borders?: CellBorders;
17
+ readonly bold?: boolean;
18
+ readonly italic?: boolean;
19
+ readonly colorHex?: string;
20
+ }
21
+ /** Where a cell sits, which decides the parts that reach it. */
22
+ export interface CellPosition {
23
+ readonly row: number;
24
+ readonly rowCount: number;
25
+ readonly col: number;
26
+ readonly colCount: number;
27
+ }
28
+ /** The `a:tblPr` flags, all off unless the table says otherwise. */
29
+ export declare function tableStyleFlags(tblPr: PoNode | undefined): TableStyleFlags;
30
+ /** The GUID a table names, if it names one (`a:tblPr/a:tableStyleId`). */
31
+ export declare function tableStyleId(tblPr: PoNode | undefined): string | undefined;
32
+ /** What the deck's theme lends a style that points at it instead of spelling it out. */
33
+ export interface TableStyleTheme {
34
+ /** §20.1.4.1.14 `a:fillStyleLst` — the fills an `a:fillRef` indexes. */
35
+ readonly fills?: ReadonlyArray<PoNode>;
36
+ /** §20.1.4.2.19 `a:lnStyleLst` — the widths an `a:lnRef` indexes, in points. */
37
+ readonly lineWidths?: ReadonlyArray<number>;
38
+ }
39
+ /**
40
+ * The style a cell wears, composed from every part that reaches it.
41
+ *
42
+ * @param style The `a:tblStyle` node.
43
+ * @param flags Which conditional parts the table asks for.
44
+ * @param at Where the cell sits.
45
+ * @param colors The deck's colour resolver.
46
+ * @param theme The theme's style lists, for the parts that point at them.
47
+ * @returns The composed part, empty when the style says nothing.
48
+ */
49
+ export declare function cellStyle(style: PoNode, flags: TableStyleFlags, at: CellPosition, colors: ColorResolver, theme?: TableStyleTheme): TableStylePart;
50
+ /**
51
+ * §20.1.2.1 `a:ln` → the rule it draws on one side of a cell.
52
+ *
53
+ * Shared with the cell's OWN `a:lnL`/`a:lnR`/`a:lnT`/`a:lnB`, which are the
54
+ * same element under a different name.
55
+ *
56
+ * @param ln The line node.
57
+ * @param colors The deck's colour resolver.
58
+ * @returns The border, `none` when the line is one that draws nothing.
59
+ */
60
+ export declare function lineBorder(ln: PoNode, colors: ColorResolver): Border;
61
+ /** The same part with the style's fill dropped, for a cell that says `a:noFill`. */
62
+ export declare function withoutFill(part: TableStylePart): TableStylePart;
63
+ /** A cell with the style's fill, borders and run properties filled in under its own. */
64
+ export declare function withCellStyle(cell: TableCell, part: TableStylePart): TableCell;
@@ -0,0 +1,203 @@
1
+ import { pt } from "../core/ir/units.js";
2
+ import { poAttr, poChildren, poIntAttr, poIs, poTag, poText } from "../core/po-helpers.js";
3
+ import { readColorMods, resolveColorNode } from "../core/drawingml/colors.js";
4
+ //#region src/pptx/table-style.ts
5
+ /** The `a:tblPr` flags, all off unless the table says otherwise. */
6
+ function tableStyleFlags(tblPr) {
7
+ const on = (name) => tblPr !== void 0 && (poAttr(tblPr, name) === "1" || poAttr(tblPr, name) === "true");
8
+ return {
9
+ firstRow: on("firstRow"),
10
+ lastRow: on("lastRow"),
11
+ firstCol: on("firstCol"),
12
+ lastCol: on("lastCol"),
13
+ bandRow: on("bandRow"),
14
+ bandCol: on("bandCol")
15
+ };
16
+ }
17
+ /** The GUID a table names, if it names one (`a:tblPr/a:tableStyleId`). */
18
+ function tableStyleId(tblPr) {
19
+ const id = tblPr ? poChildren(tblPr).find((c) => poIs(c, "a:tableStyleId")) : void 0;
20
+ return id ? poText(id).trim() || void 0 : void 0;
21
+ }
22
+ /**
23
+ * The style a cell wears, composed from every part that reaches it.
24
+ *
25
+ * @param style The `a:tblStyle` node.
26
+ * @param flags Which conditional parts the table asks for.
27
+ * @param at Where the cell sits.
28
+ * @param colors The deck's colour resolver.
29
+ * @param theme The theme's style lists, for the parts that point at them.
30
+ * @returns The composed part, empty when the style says nothing.
31
+ */
32
+ function cellStyle(style, flags, at, colors, theme) {
33
+ const names = ["a:wholeTbl"];
34
+ if (flags.bandCol) names.push(bandName(at.col, flags.firstCol, "V"));
35
+ if (flags.bandRow) names.push(bandName(at.row, flags.firstRow, "H"));
36
+ if (flags.firstCol && at.col === 0) names.push("a:firstCol");
37
+ if (flags.lastCol && at.col === at.colCount - 1) names.push("a:lastCol");
38
+ if (flags.firstRow && at.row === 0) names.push("a:firstRow");
39
+ if (flags.lastRow && at.row === at.rowCount - 1) names.push("a:lastRow");
40
+ let out = tableBackground(style, colors, theme);
41
+ for (const name of names) {
42
+ const part = poChildren(style).find((c) => poIs(c, name));
43
+ if (part) out = {
44
+ ...out,
45
+ ...partStyle(part, colors, theme)
46
+ };
47
+ }
48
+ return out;
49
+ }
50
+ function tableBackground(style, colors, theme) {
51
+ const bg = poChildren(style).find((c) => poIs(c, "a:tblBg"));
52
+ if (!bg) return {};
53
+ const solid = poChildren(bg).find((c) => poIs(c, "a:solidFill"));
54
+ const hex = (solid ? colorOf(solid, colors) : void 0) ?? refFillColor(poChildren(bg).find((c) => poIs(c, "a:fillRef")), colors, theme);
55
+ return hex ? { shadingHex: hex } : {};
56
+ }
57
+ /**
58
+ * §20.1.4.1.16 `a:fillRef` — a fill named by its place in the theme's list,
59
+ * with the colour to put wherever that fill says `phClr`.
60
+ *
61
+ * Only its COLOUR is taken: a slot may be a gradient or a picture, and a cell's
62
+ * shading is one colour. The first solid colour the slot names, or the
63
+ * reference's own, is the nearest true answer.
64
+ */
65
+ function refFillColor(ref, colors, theme) {
66
+ if (!ref) return void 0;
67
+ const own = colorOf(ref, colors);
68
+ const idx = poIntAttr(ref, "idx");
69
+ const slot = idx !== void 0 && idx > 0 ? theme?.fills?.[idx - 1] : void 0;
70
+ if (!slot) return own;
71
+ const solid = poChildren(slot).find((c) => poIs(c, "a:solidFill"));
72
+ const slotHex = solid ? colorOf(solid, colors) : void 0;
73
+ return slotHex && slotHex !== "PHCLR" ? own ?? slotHex : own;
74
+ }
75
+ function bandName(index, edge, axis) {
76
+ return (edge ? index - 1 : index) % 2 === 0 ? `a:band1${axis}` : `a:band2${axis}`;
77
+ }
78
+ function partStyle(part, colors, theme) {
79
+ const tcStyle = poChildren(part).find((c) => poIs(c, "a:tcStyle"));
80
+ const txStyle = poChildren(part).find((c) => poIs(c, "a:tcTxStyle"));
81
+ const fill = tcStyle ? poChildren(tcStyle).find((c) => poIs(c, "a:fill")) : void 0;
82
+ const solid = fill ? poChildren(fill).find((c) => poIs(c, "a:solidFill")) : void 0;
83
+ const shadingHex = solid ? colorOf(solid, colors) : void 0;
84
+ const bdr = tcStyle ? poChildren(tcStyle).find((c) => poIs(c, "a:tcBdr")) : void 0;
85
+ const borders = bdr ? partBorders(bdr, colors, theme) : void 0;
86
+ const on = (name) => poAttr(txStyle, name) === "on";
87
+ const colorHex = txStyle ? colorOf(txStyle, colors) : void 0;
88
+ return {
89
+ ...shadingHex ? { shadingHex } : {},
90
+ ...borders ? { borders } : {},
91
+ ...txStyle && on("b") ? { bold: true } : {},
92
+ ...txStyle && on("i") ? { italic: true } : {},
93
+ ...colorHex ? { colorHex } : {}
94
+ };
95
+ }
96
+ var SIDES = [
97
+ ["a:left", "left"],
98
+ ["a:right", "right"],
99
+ ["a:top", "top"],
100
+ ["a:bottom", "bottom"],
101
+ ["a:insideH", "insideH"],
102
+ ["a:insideV", "insideV"]
103
+ ];
104
+ function partBorders(bdr, colors, theme) {
105
+ const out = {};
106
+ for (const [tag, side] of SIDES) {
107
+ const holder = poChildren(bdr).find((c) => poIs(c, tag));
108
+ const ln = holder ? poChildren(holder).find((c) => poIs(c, "a:ln")) : void 0;
109
+ if (ln) {
110
+ out[side] = lineBorder(ln, colors);
111
+ continue;
112
+ }
113
+ const border = refBorder(holder ? poChildren(holder).find((c) => poIs(c, "a:lnRef")) : void 0, colors, theme);
114
+ if (border) out[side] = border;
115
+ }
116
+ return Object.keys(out).length > 0 ? out : void 0;
117
+ }
118
+ function refBorder(ref, colors, theme) {
119
+ if (!ref || poAttr(ref, "idx") === "0") return void 0;
120
+ const colorHex = colorOf(ref, colors);
121
+ if (colorHex === void 0) return void 0;
122
+ const idx = poIntAttr(ref, "idx");
123
+ return {
124
+ style: "single",
125
+ colorHex,
126
+ width: pt((idx !== void 0 && idx > 0 ? theme?.lineWidths?.[idx - 1] : void 0) ?? .75)
127
+ };
128
+ }
129
+ /**
130
+ * §20.1.2.1 `a:ln` → the rule it draws on one side of a cell.
131
+ *
132
+ * Shared with the cell's OWN `a:lnL`/`a:lnR`/`a:lnT`/`a:lnB`, which are the
133
+ * same element under a different name.
134
+ *
135
+ * @param ln The line node.
136
+ * @param colors The deck's colour resolver.
137
+ * @returns The border, `none` when the line is one that draws nothing.
138
+ */
139
+ function lineBorder(ln, colors) {
140
+ if (poChildren(ln).some((c) => poIs(c, "a:noFill")) || isTransparent(ln)) return { style: "none" };
141
+ const colorHex = colorOf(ln, colors);
142
+ const w = poIntAttr(ln, "w");
143
+ return {
144
+ style: "single",
145
+ ...colorHex ? { colorHex } : {},
146
+ ...w !== void 0 ? { width: pt(Math.max(.25, w / 12700)) } : {}
147
+ };
148
+ }
149
+ function isTransparent(ln) {
150
+ const fill = poChildren(ln).find((c) => poIs(c, "a:solidFill"));
151
+ const color = fill ? poChildren(fill).find((c) => poTag(c) !== void 0) : void 0;
152
+ const alpha = color ? readColorMods(color).find((m) => m.kind === "alpha") : void 0;
153
+ return alpha !== void 0 && alpha.val <= .001;
154
+ }
155
+ /** The same part with the style's fill dropped, for a cell that says `a:noFill`. */
156
+ function withoutFill(part) {
157
+ const { shadingHex: _fill, ...rest } = part;
158
+ return rest;
159
+ }
160
+ function colorOf(node, colors) {
161
+ for (const child of poChildren(node)) {
162
+ const hex = resolveColorNode(child, colors);
163
+ if (hex !== void 0) return hex;
164
+ const deeper = poTag(child) !== void 0 ? colorOf(child, colors) : void 0;
165
+ if (deeper !== void 0) return deeper;
166
+ }
167
+ }
168
+ /** A cell with the style's fill, borders and run properties filled in under its own. */
169
+ function withCellStyle(cell, part) {
170
+ const runProps = {
171
+ ...part.bold === true ? { bold: true } : {},
172
+ ...part.italic === true ? { italic: true } : {},
173
+ ...part.colorHex !== void 0 ? { colorHex: part.colorHex } : {}
174
+ };
175
+ const content = Object.keys(runProps).length === 0 ? cell.content : cell.content.map((block) => block.kind === "paragraph" ? {
176
+ ...block,
177
+ paragraph: {
178
+ ...block.paragraph,
179
+ runs: block.paragraph.runs.map((run) => ({
180
+ ...run,
181
+ properties: {
182
+ ...runProps,
183
+ ...run.properties
184
+ }
185
+ }))
186
+ }
187
+ } : block);
188
+ const borders = part.borders || cell.properties.borders ? {
189
+ ...part.borders,
190
+ ...cell.properties.borders
191
+ } : void 0;
192
+ return {
193
+ ...cell,
194
+ properties: {
195
+ ...part.shadingHex !== void 0 ? { shading: { colorHex: part.shadingHex } } : {},
196
+ ...cell.properties,
197
+ ...borders ? { borders } : {}
198
+ },
199
+ content
200
+ };
201
+ }
202
+ //#endregion
203
+ export { cellStyle, lineBorder, tableStyleFlags, tableStyleId, withCellStyle, withoutFill };
@@ -56,12 +56,9 @@ var svgWriter = {
56
56
  };
57
57
  function emitPage(out, page, laid, losses, idc) {
58
58
  const plan = paintPlan(page.commands);
59
+ for (const item of plan.behind) emitPageItem(out, item, laid, losses, idc);
59
60
  for (const f of plan.fills) out.push(`<rect x="${fmt(f.x)}" y="${fmt(f.y)}" width="${fmt(f.width)}" height="${fmt(f.height)}" fill="#${f.fillColorHex}"/>`);
60
- for (const img of plan.images) {
61
- const href = imageHref(img.imageResourceName, laid);
62
- if (!href) continue;
63
- out.push(`<image x="${fmt(img.x)}" y="${fmt(img.y)}" width="${fmt(img.width)}" height="${fmt(img.height)}" href="${href}" preserveAspectRatio="none"/>`);
64
- }
61
+ for (const img of plan.images) emitImage(out, img, laid, idc);
65
62
  for (const b of plan.borders) {
66
63
  const x2 = b.x + b.width;
67
64
  const yTop = b.y;
@@ -90,8 +87,59 @@ function emitPage(out, page, laid, losses, idc) {
90
87
  out.push(`<line x1="${fmt(ax)}" y1="${fmt(ay)}" x2="${fmt(bx)}" y2="${fmt(by)}" stroke="#${b.borderColorHex}" stroke-width="${fmt(b.borderSizePt)}"${b.borderStyle === "dashed" ? " stroke-dasharray=\"8 2.5\"" : b.borderStyle === "dashSmallGap" ? " stroke-dasharray=\"3 1\"" : b.borderStyle === "dotted" ? " stroke-dasharray=\"0.5 1\"" : ""}/>`);
91
88
  }
92
89
  for (const sh of plan.shapes) emitShape(out, sh.shape, idc);
90
+ for (const picture of plan.pictures) for (const item of picture) emitPageItem(out, item, laid, losses, idc);
93
91
  for (const t of plan.lines) emitTextLine(out, t, losses, idc);
94
92
  }
93
+ /**
94
+ * One picture. §20.1.8.23 `a:duotone` recolours it between two colours — its
95
+ * dark end and its light end — which is a luminance-to-two-tone map, and SVG
96
+ * states exactly that: luminance into every channel, then a two-entry transfer
97
+ * table per channel.
98
+ *
99
+ * @param out The SVG fragment sink.
100
+ * @param img The image item.
101
+ * @param laid The laid-out document, for the image's data URI.
102
+ * @param idc The shared id counter, for the filter's id.
103
+ */
104
+ function emitImage(out, img, laid, idc) {
105
+ const href = imageHref(img.imageResourceName, laid);
106
+ if (!href) return;
107
+ let filter = "";
108
+ if (img.duotone) {
109
+ const id = `duo${String(idc.n++)}`;
110
+ const chan = (hex, at) => parseInt(hex.slice(at, at + 2), 16) / 255;
111
+ const table = (at) => `${fmt(chan(img.duotone.shadowHex, at))} ${fmt(chan(img.duotone.highlightHex, at))}`;
112
+ out.push(`<filter id="${id}" color-interpolation-filters="sRGB"><feColorMatrix type="matrix" values="0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0"/><feComponentTransfer><feFuncR type="table" tableValues="${table(0)}"/><feFuncG type="table" tableValues="${table(2)}"/><feFuncB type="table" tableValues="${table(4)}"/></feComponentTransfer></filter>`);
113
+ filter = ` filter="url(#${id})"`;
114
+ }
115
+ out.push(`<image x="${fmt(img.x)}" y="${fmt(img.y)}" width="${fmt(img.width)}" height="${fmt(img.height)}" href="${href}" preserveAspectRatio="none"${filter}/>`);
116
+ }
117
+ /**
118
+ * One page item, whatever its kind — for the runs that paint in their own order
119
+ * (what stands behind the content, and a picture's own primitives) rather than
120
+ * in the by-kind passes.
121
+ *
122
+ * @param out The SVG fragment sink.
123
+ * @param item The item to draw.
124
+ * @param laid The laid-out document, for image resources.
125
+ * @param losses Where an unrenderable item records itself.
126
+ * @param idc The shared id counter for generated defs.
127
+ */
128
+ function emitPageItem(out, item, laid, losses, idc) {
129
+ if (item.type === "shape") {
130
+ emitShape(out, item.shape, idc);
131
+ return;
132
+ }
133
+ if (item.type === "line") {
134
+ emitTextLine(out, item, losses, idc);
135
+ return;
136
+ }
137
+ if (item.type === "image") {
138
+ emitImage(out, item, laid, idc);
139
+ return;
140
+ }
141
+ if (item.type === "fill") out.push(`<rect x="${fmt(item.x)}" y="${fmt(item.y)}" width="${fmt(item.width)}" height="${fmt(item.height)}" fill="#${item.fillColorHex}"/>`);
142
+ }
95
143
  function emitTextLine(out, item, losses, idc) {
96
144
  const clip = item.clip;
97
145
  if (clip) {
@@ -2,6 +2,7 @@ import { BodyElement, Comment, RunProperties, Section, SectionProperties, ShapeF
2
2
  import { ColorResolver } from '../core/drawingml/colors.js';
3
3
  import { Loss, ResourceId } from '../core/ir/index.js';
4
4
  import { PoNode } from '../core/po-helpers.js';
5
+ import { ThemeFonts } from '../core/drawingml/theme-parser.js';
5
6
  import { ThemeStyles } from './drawing-parser.js';
6
7
  /**
7
8
  * Resolves a drawing relationship id to a content-addressed `ResourceId` —
@@ -57,6 +58,11 @@ export interface ParseContext {
57
58
  * the shadow a gallery-drawn shape wears.
58
59
  */
59
60
  readonly themeStyles?: ThemeStyles;
61
+ /**
62
+ * §20.1.4.1.16 — the theme's font scheme, for the typefaces a run names by
63
+ * slot (`w:asciiTheme="minorHAnsi"`) rather than by name.
64
+ */
65
+ readonly themeFonts?: ThemeFonts;
60
66
  /**
61
67
  * Sink for graceful-degradation notices (E-SMARTART SA3): a SmartArt with no
62
68
  * drawing override records a dropped-feature {@link Loss} rather than vanishing.
@@ -185,7 +191,7 @@ export declare function parseBodyElements(children: ReadonlyArray<PoNode>, ctx?:
185
191
  * @param sdt The `w:sdt` element.
186
192
  * @returns The properties, or `undefined` when the control states none.
187
193
  */
188
- export declare function sdtRunProperties(sdt: PoNode): RunProperties | undefined;
194
+ export declare function sdtRunProperties(sdt: PoNode, themeFonts?: ThemeFonts): RunProperties | undefined;
189
195
  /**
190
196
  * Parse `footnotes.xml` / `endnotes.xml` (§17.11) into note content by id. The
191
197
  * separator / continuationSeparator / continuationNotice stubs (negative ids or
@@ -425,7 +425,7 @@ function tryExtractDrawingFromParagraph(p, ctx) {
425
425
  if (!vml && drawings.every((d) => poChildren(d).some((c) => poIs(c, "wp:anchor")))) return null;
426
426
  const parseBody = (children) => parseBodyElements(children, ctx);
427
427
  const pPrNode = poChildren(p).find((c) => poIs(c, "w:pPr"));
428
- const paragraphProperties = pPrNode ? parseParagraphProperties(poElementToFlat(pPrNode), ctx.autoSpacingPt) : {};
428
+ const paragraphProperties = pPrNode ? parseParagraphProperties(poElementToFlat(pPrNode), ctx.autoSpacingPt, ctx.themeFonts) : {};
429
429
  if (drawings.length === 0) {
430
430
  const content = parseVmlPicture(vml, parseBody);
431
431
  if (!content) return null;
@@ -536,7 +536,7 @@ function parseParagraph(p, ctx, extraBookmarks, anchored, sdtRunProps) {
536
536
  if (bookmarkName !== void 0 && bookmarkName !== "" && bookmarkName !== "_GoBack") bookmarks.push(bookmarkName);
537
537
  }
538
538
  const pPr = poChildren(p).find((c) => poIs(c, "w:pPr"));
539
- let properties = parseParagraphProperties(pPr ? poElementToFlat(pPr) : void 0, ctx.autoSpacingPt);
539
+ let properties = parseParagraphProperties(pPr ? poElementToFlat(pPr) : void 0, ctx.autoSpacingPt, ctx.themeFonts);
540
540
  if (pPr && poChildren(pPr).some((c) => poIs(c, "w:sectPr"))) properties = {
541
541
  ...properties,
542
542
  sectionBreak: true
@@ -773,7 +773,7 @@ function collectRuns(container, out, ctx, href, anchor, anchored, sdtRunProps) {
773
773
  if (bookmark !== void 0 && bookmark !== "") childAnchor = bookmark;
774
774
  }
775
775
  }
776
- const childProps = tag === "w:sdt" ? sdtRunProperties(child) ?? sdtRunProps : sdtRunProps;
776
+ const childProps = tag === "w:sdt" ? sdtRunProperties(child, ctx.themeFonts) ?? sdtRunProps : sdtRunProps;
777
777
  collectRuns(child, out, ctx, childHref, childAnchor, anchored, childProps);
778
778
  }
779
779
  }
@@ -785,11 +785,11 @@ function collectRuns(container, out, ctx, href, anchor, anchored, sdtRunProps) {
785
785
  * @param sdt The `w:sdt` element.
786
786
  * @returns The properties, or `undefined` when the control states none.
787
787
  */
788
- function sdtRunProperties(sdt) {
788
+ function sdtRunProperties(sdt, themeFonts) {
789
789
  const sdtPr = poChildren(sdt).find((c) => poIs(c, "w:sdtPr"));
790
790
  const rPr = sdtPr ? poChildren(sdtPr).find((c) => poIs(c, "w:rPr")) : void 0;
791
791
  if (!rPr) return void 0;
792
- const props = parseRunProperties(poElementToFlat(rPr));
792
+ const props = parseRunProperties(poElementToFlat(rPr), themeFonts);
793
793
  return Object.keys(props).length > 0 ? props : void 0;
794
794
  }
795
795
  var WINGDINGS_TO_UNICODE = new Map([
@@ -870,7 +870,7 @@ function symbolChar(font, char) {
870
870
  }
871
871
  function parseRun(r, ctx, anchored) {
872
872
  const rPr = poChildren(r).find((c) => poIs(c, "w:rPr"));
873
- const properties = parseRunProperties(rPr ? poElementToFlat(rPr) : void 0);
873
+ const properties = parseRunProperties(rPr ? poElementToFlat(rPr) : void 0, ctx.themeFonts);
874
874
  let text = "";
875
875
  let pageBreak = false;
876
876
  let columnBreak = false;
@@ -6,10 +6,10 @@ import "../core/style-cascade/index.js";
6
6
  import { applyNumbering, applyNumberingToHeadersFooters } from "../core/numbering/apply.js";
7
7
  import "../core/numbering/index.js";
8
8
  import { poFindDescendant } from "../core/po-helpers.js";
9
- import { bytesIncludePartName } from "../core/bytes.js";
9
+ import { packageHasPart } from "../core/bytes.js";
10
10
  import { DEFAULT_THEME_PALETTE, makeColorResolver } from "../core/drawingml/colors.js";
11
11
  import { parseChart, withChartColorStyle } from "../core/drawingml/chart-parser.js";
12
- import { parseTheme, parseThemeBgFillStyles, parseThemeEffectStyles, parseThemeFillStyles, parseThemeLineWidths } from "../core/drawingml/theme-parser.js";
12
+ import { parseTheme, parseThemeBgFillStyles, parseThemeEffectStyles, parseThemeFillStyles, parseThemeFonts, parseThemeLineWidths } from "../core/drawingml/theme-parser.js";
13
13
  import { isOoxmlRel } from "../core/opc/relationship-types.js";
14
14
  import { OpcPackage } from "../core/opc/package.js";
15
15
  import { parseCoreProperties } from "../core/opc/core-properties.js";
@@ -52,6 +52,7 @@ function readDocx(docx) {
52
52
  const resolveColor = buildColorResolver(pkg, main.path);
53
53
  const themeData = loadTheme(pkg, main.path);
54
54
  const themeLineWidths = themeData ? parseThemeLineWidths(themeData) : void 0;
55
+ const themeFonts = themeData ? parseThemeFonts(themeData) : void 0;
55
56
  const themeStyles = themeData ? {
56
57
  fills: parseThemeFillStyles(themeData),
57
58
  bgFills: parseThemeBgFillStyles(themeData),
@@ -68,6 +69,7 @@ function readDocx(docx) {
68
69
  ...settings.compatibilityMode === void 0 ? { autoSpacingPt: 14 } : {},
69
70
  ...themeLineWidths && themeLineWidths.length > 0 ? { themeLineWidths } : {},
70
71
  ...themeStyles ? { themeStyles } : {},
72
+ ...themeFonts ? { themeFonts } : {},
71
73
  resolveImage,
72
74
  resolveHyperlink,
73
75
  resolveDiagram: makeDiagramResolver(pkg, main.path, resources),
@@ -84,11 +86,12 @@ function readDocx(docx) {
84
86
  endIndex: bodyIndexForBlock(blocks, s.endIndex)
85
87
  }));
86
88
  const stylesData = pkg.getPart(STYLES_PART);
87
- const styles = stylesData ? parseStyles(stylesData) : EMPTY_STYLE_SHEET;
89
+ const styles = stylesData ? parseStyles(stylesData, themeFonts) : EMPTY_STYLE_SHEET;
88
90
  const numberingData = pkg.getPart(NUMBERING_PART);
89
- const numbering = numberingData ? parseNumbering(numberingData, makeImageResolver(pkg, resources, NUMBERING_PART)) : EMPTY_NUMBERING;
91
+ const numbering = numberingData ? parseNumbering(numberingData, makeImageResolver(pkg, resources, NUMBERING_PART), themeFonts) : EMPTY_NUMBERING;
90
92
  const noteCtx = (part) => ({
91
93
  resolveColor,
94
+ ...themeFonts ? { themeFonts } : {},
92
95
  resolveImage: makeImageResolver(pkg, resources, part),
93
96
  resolveHyperlink: makeHyperlinkResolver(pkg, part)
94
97
  });
@@ -166,7 +169,7 @@ var docxReader = {
166
169
  FEATURES.trackedChanges,
167
170
  FEATURES.fontsEmbedding
168
171
  ]),
169
- sniff: (bytes) => bytes[0] === 80 && bytes[1] === 75 && (bytesIncludePartName(bytes, "word/document.xml") || bytesIncludePartName(bytes, "word/styles.xml") || bytesIncludePartName(bytes, "word/_rels/")),
172
+ sniff: (bytes) => bytes[0] === 80 && bytes[1] === 75 && (packageHasPart(bytes, "word/document.xml") || packageHasPart(bytes, "word/styles.xml") || packageHasPart(bytes, "word/_rels/")),
170
173
  read: (bytes) => readDocx(bytes)
171
174
  };
172
175
  function infoFromCore(core) {
@@ -38,10 +38,11 @@ export interface ConvertDocxOptions extends Omit<StyledRenderOptions, 'registry'
38
38
  */
39
39
  export declare function convertDocxToPdfSync(docx: Uint8Array, options: ConvertDocxOptions): Uint8Array;
40
40
  /**
41
- * The distinct curated font families the document references (scanning the
42
- * `w:ascii` of `styles.xml` + `document.xml`), always including the sans default
43
- * `'arimo'` as a fallback for unstyled runs / math / charts. The async path
44
- * fetches one substitute set per family so each run renders in the right one.
41
+ * The distinct curated font families the document references — the `w:ascii` of
42
+ * `styles.xml` + `document.xml`, and the theme slots they point at instead
43
+ * (§17.3.2.26) — always including the sans default `'arimo'` as a fallback for
44
+ * unstyled runs / math / charts. The async path fetches one substitute set per
45
+ * family so each run renders in the right one.
45
46
  *
46
47
  * @param docx The `.docx` bytes.
47
48
  * @returns The resolved {@link FamilyKey} set; just the default when the package
@@ -76,9 +77,9 @@ export declare function resolveDocxAutoFonts(docx: Uint8Array, options?: {
76
77
  }>;
77
78
  /**
78
79
  * Best-effort detection of the document's primary font family, used to choose a
79
- * substitute for auto-download. Prefers the document defaults' `w:ascii` font,
80
- * then falls back to the most frequent run font. A cheap regex over the XML — no
81
- * need to fully parse for this hint.
80
+ * substitute for auto-download. Prefers the document defaults' font — spelled
81
+ * out or pointed at in the theme — then falls back to the most frequent run
82
+ * font. A cheap regex over the XML: no need to fully parse for this hint.
82
83
  *
83
84
  * @param docx The `.docx` bytes.
84
85
  * @returns The detected family name, or `undefined` when none is found.
@@ -3,12 +3,15 @@ import { FontRegistry } from "../core/font/font-registry.js";
3
3
  import { signPdf } from "../pdf/signature.js";
4
4
  import { renderStyledPdf, renderStyledPdfEncrypted } from "../pdf/styled-page-renderer.js";
5
5
  import "../pdf/index.js";
6
+ import { parseThemeFonts } from "../core/drawingml/theme-parser.js";
6
7
  import { OpcPackage } from "../core/opc/package.js";
7
8
  import "../core/opc/index.js";
9
+ import { resolveWordThemeFont } from "./theme-fonts.js";
8
10
  import { readDocx } from "./docx-reader.js";
9
11
  import { flowRenderOptions } from "../core/converter/project.js";
10
12
  //#region src/word/docx-to-pdf.ts
11
13
  var STYLES_PART = "word/styles.xml";
14
+ var THEME_PART = "word/theme/theme1.xml";
12
15
  var MAIN_DOCUMENT_PART = "word/document.xml";
13
16
  /**
14
17
  * Synchronous one-shot conversion (requires `fonts`/`fontBytes`, no network).
@@ -49,10 +52,11 @@ function prepareDocxStyledRender(docx, options) {
49
52
  };
50
53
  }
51
54
  /**
52
- * The distinct curated font families the document references (scanning the
53
- * `w:ascii` of `styles.xml` + `document.xml`), always including the sans default
54
- * `'arimo'` as a fallback for unstyled runs / math / charts. The async path
55
- * fetches one substitute set per family so each run renders in the right one.
55
+ * The distinct curated font families the document references — the `w:ascii` of
56
+ * `styles.xml` + `document.xml`, and the theme slots they point at instead
57
+ * (§17.3.2.26) — always including the sans default `'arimo'` as a fallback for
58
+ * unstyled runs / math / charts. The async path fetches one substitute set per
59
+ * family so each run renders in the right one.
56
60
  *
57
61
  * @param docx The `.docx` bytes.
58
62
  * @returns The resolved {@link FamilyKey} set; just the default when the package
@@ -67,16 +71,30 @@ function detectDocxFamilyKeys(docx) {
67
71
  return keys;
68
72
  }
69
73
  const decoder = new TextDecoder("utf-8");
70
- const re = /<w:rFonts[^>]*\bw:ascii="([^"]+)"/g;
74
+ const themeFonts = docxThemeFonts(pkg);
75
+ const re = /<w:rFonts[^>]*?\bw:(ascii|asciiTheme)="([^"]+)"/g;
71
76
  for (const part of [STYLES_PART, MAIN_DOCUMENT_PART]) {
72
77
  const data = pkg.getPart(part);
73
78
  if (!data) continue;
74
79
  const xml = decoder.decode(data);
75
80
  let m;
76
- while ((m = re.exec(xml)) !== null) keys.add(resolveFamilyKey(m[1]));
81
+ while ((m = re.exec(xml)) !== null) {
82
+ const name = m[1] === "ascii" ? m[2] : resolveWordThemeFont(m[2], themeFonts);
83
+ if (name !== void 0) keys.add(resolveFamilyKey(name));
84
+ }
77
85
  }
78
86
  return keys;
79
87
  }
88
+ /** §20.1.4.1.16 — the theme's font scheme, for the slots `w:rFonts` points at. */
89
+ function docxThemeFonts(pkg) {
90
+ const data = pkg.getPart(THEME_PART);
91
+ if (!data) return void 0;
92
+ try {
93
+ return parseThemeFonts(data);
94
+ } catch {
95
+ return;
96
+ }
97
+ }
80
98
  /**
81
99
  * One-shot .docx → PDF (auto-downloads a substitute font set when the caller
82
100
  * supplies none; zero network with `fonts`/`fontBytes`).
@@ -140,4 +158,4 @@ async function resolveDocxAutoFonts(docx, options = {}) {
140
158
  };
141
159
  }
142
160
  //#endregion
143
- export { convertDocxToPdf, resolveDocxAutoFonts };
161
+ export { convertDocxToPdf };