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,6 +1,46 @@
1
- import { poChildren, poIs } from "../core/po-helpers.js";
1
+ import { emuToPt, pt } from "../core/ir/units.js";
2
+ import { fromSymbolFont } from "../core/metafile/symbol-fonts.js";
3
+ import { poAttr, poChildren, poIntAttr, poIs } from "../core/po-helpers.js";
4
+ import { resolveColorNode } from "../core/drawingml/colors.js";
2
5
  import { parsePh, parseXfrmBox, rPrToRunProps } from "./sp-helpers.js";
3
6
  //#region src/pptx/placeholder-cascade.ts
7
+ /**
8
+ * What an `a:pPr` (or an `a:lvlNpPr`, the same vocabulary) says about its
9
+ * bullet — each part on its own, because a paragraph may restate the SIZE and
10
+ * leave the character to its level.
11
+ *
12
+ * @param pPr The paragraph-properties node.
13
+ * @param colors The colour resolver, for `a:buClr`.
14
+ * @returns What it states, or `undefined` when it says nothing about bullets.
15
+ */
16
+ function parseBullet(pPr, colors) {
17
+ if (!pPr) return void 0;
18
+ const child = (tag) => poChildren(pPr).find((c) => poIs(c, tag));
19
+ const pct = poIntAttr(child("a:buSzPct"), "val");
20
+ const pts = poIntAttr(child("a:buSzPts"), "val");
21
+ const clr = child("a:buClr");
22
+ const colorHex = clr && colors ? poChildren(clr).map((c) => resolveColorNode(c, colors)).find((hex) => hex !== void 0) : void 0;
23
+ const size = {
24
+ ...pct !== void 0 ? { sizePct: pct / 1e5 } : {},
25
+ ...pts !== void 0 ? { sizePts: pts / 100 } : {},
26
+ ...colorHex !== void 0 ? { colorHex } : {}
27
+ };
28
+ const buChar = child("a:buChar");
29
+ const buAuto = child("a:buAutoNum");
30
+ const kind = poChildren(pPr).some((c) => poIs(c, "a:buNone")) ? { kind: "none" } : buChar ? {
31
+ kind: "char",
32
+ char: fromSymbolFont(poAttr(buChar, "char") ?? "•", poAttr(child("a:buFont"), "typeface"))
33
+ } : buAuto ? {
34
+ kind: "autoNum",
35
+ type: poAttr(buAuto, "type") ?? "arabicPeriod",
36
+ startAt: poIntAttr(buAuto, "startAt") ?? 1
37
+ } : void 0;
38
+ const out = {
39
+ ...size,
40
+ ...kind
41
+ };
42
+ return Object.keys(out).length > 0 ? out : void 0;
43
+ }
4
44
  function categoryOf(type) {
5
45
  if (type === "title" || type === "ctrTitle") return "title";
6
46
  if (type === void 0 || type === "body" || type === "subTitle" || type === "obj") return "body";
@@ -15,27 +55,221 @@ function categoryOf(type) {
15
55
  * @param layoutTree The parsed `p:sldLayout` part.
16
56
  * @param masterTree The parsed `p:sldMaster` part, when present.
17
57
  * @param colors The deck's colour resolver, for scheme colours in the text styles.
58
+ * @param deckDefaults The presentation's own default text style, as the floor.
59
+ * @param themeFonts The theme's font scheme, for the `+mn-lt` tokens.
18
60
  */
19
- function buildPlaceholderCascade(layoutTree, masterTree, colors) {
20
- const layoutPhs = collectPlaceholders(layoutTree, "p:sldLayout");
21
- const masterPhs = masterTree ? collectPlaceholders(masterTree, "p:sldMaster") : [];
22
- const txStyles = masterTree ? collectTxStyles(masterTree, colors) : {
61
+ function buildPlaceholderCascade(layoutTree, masterTree, colors, deckDefaults = [], themeFonts) {
62
+ const layoutPhs = collectPlaceholders(layoutTree, "p:sldLayout", colors, themeFonts);
63
+ const masterPhs = masterTree ? collectPlaceholders(masterTree, "p:sldMaster", colors, themeFonts) : [];
64
+ const txStyles = masterTree ? collectTxStyles(masterTree, colors, themeFonts) : {
23
65
  title: [],
24
66
  body: [],
25
67
  other: []
26
68
  };
69
+ const styleAt = (ph, level) => {
70
+ const layers = [at(deckDefaults, level)];
71
+ if (ph) layers.push(at(txStyles[categoryOf(ph.type)], level), at(matchPlaceholder(masterPhs, ph)?.levels ?? [], level), at(matchPlaceholder(layoutPhs, ph)?.levels ?? [], level));
72
+ const merged = layers.reduce(mergeLevels, EMPTY_LEVEL);
73
+ return merged.spacingPct === void 0 ? merged : withResolvedSpacing(merged);
74
+ };
27
75
  return {
28
76
  geometryFor(ph) {
29
77
  return matchPlaceholder(layoutPhs, ph)?.box ?? matchPlaceholder(masterPhs, ph)?.box;
30
78
  },
31
79
  defaultsFor(ph, level) {
32
- const levels = txStyles[categoryOf(ph.type)];
33
- if (levels.length === 0) return {};
34
- return levels[Math.min(Math.max(level, 0), levels.length - 1)] ?? {};
80
+ return styleAt(ph, level).run;
81
+ },
82
+ paragraphDefaultsFor(ph, level) {
83
+ return styleAt(ph, level).paragraph;
84
+ },
85
+ bulletFor(ph, level) {
86
+ return styleAt(ph, level).bullet;
87
+ },
88
+ anchorFor(ph) {
89
+ return matchPlaceholder(layoutPhs, ph)?.anchor ?? matchPlaceholder(masterPhs, ph)?.anchor;
90
+ },
91
+ shapePropsFor(ph) {
92
+ return [matchPlaceholder(layoutPhs, ph)?.spPr, matchPlaceholder(masterPhs, ph)?.spPr].filter((n) => n !== void 0);
93
+ }
94
+ };
95
+ }
96
+ var EMPTY_LEVEL = {
97
+ run: {},
98
+ paragraph: {}
99
+ };
100
+ /**
101
+ * §21.1.2.2.9/.10 — the space a paragraph states as a FRACTION of a line, in
102
+ * points.
103
+ *
104
+ * A "line" here is the paragraph's own text at its natural height, which is the
105
+ * size times the same 1.2 the layout gives an unstated line. A level that
106
+ * states the fraction and leaves the size to another level is resolved once
107
+ * they have merged, which is why this is not read where the fraction is.
108
+ *
109
+ * @param pct The fraction, `0..n` (20% arrives as 0.2).
110
+ * @param sizePt The paragraph's resolved font size.
111
+ * @returns The distance, or `undefined` when the size is not known.
112
+ */
113
+ function spacingFromLineFraction(pct, sizePt) {
114
+ if (pct === void 0 || sizePt === void 0) return void 0;
115
+ return pt(pct * sizePt * NATURAL_LINE);
116
+ }
117
+ /** What the layout gives a line of text with nothing said about its height. */
118
+ var NATURAL_LINE = 1.2;
119
+ function withResolvedSpacing(level) {
120
+ const size = level.run.fontSizePt;
121
+ const before = spacingFromLineFraction(level.spacingPct?.before, size);
122
+ const after = spacingFromLineFraction(level.spacingPct?.after, size);
123
+ if (before === void 0 && after === void 0) return level;
124
+ return {
125
+ ...level,
126
+ paragraph: {
127
+ ...level.paragraph,
128
+ ...before !== void 0 ? { spacingBefore: before } : {},
129
+ ...after !== void 0 ? { spacingAfter: after } : {}
35
130
  }
36
131
  };
37
132
  }
38
- function collectPlaceholders(tree, root) {
133
+ /** The style at `level`, clamped to the list — a deeper level keeps the last. */
134
+ function at(levels, level) {
135
+ if (levels.length === 0) return EMPTY_LEVEL;
136
+ return levels[Math.min(Math.max(level, 0), levels.length - 1)] ?? EMPTY_LEVEL;
137
+ }
138
+ /** `next` over `base` — a layer states only what it changes. */
139
+ function mergeLevels(base, next) {
140
+ const bullet = base.bullet || next.bullet ? {
141
+ ...base.bullet,
142
+ ...next.bullet
143
+ } : void 0;
144
+ const spacingPct = base.spacingPct || next.spacingPct ? {
145
+ ...base.spacingPct,
146
+ ...next.spacingPct
147
+ } : void 0;
148
+ return {
149
+ run: {
150
+ ...base.run,
151
+ ...next.run
152
+ },
153
+ paragraph: {
154
+ ...base.paragraph,
155
+ ...next.paragraph
156
+ },
157
+ ...bullet ? { bullet } : {},
158
+ ...spacingPct ? { spacingPct } : {}
159
+ };
160
+ }
161
+ /**
162
+ * `a:lstStyle` (or a `p:txStyles` family) → its nine levels. Shared by the
163
+ * deck's default text style, the master's families and a prototype's own list,
164
+ * which are the same vocabulary written in different places.
165
+ *
166
+ * @param list The `a:lstStyle` / `p:titleStyle` / … node.
167
+ * @param colors The deck's colour resolver.
168
+ * @param prefix The level element prefix: `a:lvl` here, always.
169
+ * @returns The nine levels, or an empty list when there is no such node.
170
+ */
171
+ function parseLevelStyles(list, colors, themeFonts) {
172
+ if (!list) return [];
173
+ const out = [];
174
+ for (let lvl = 1; lvl <= 9; lvl++) {
175
+ const lvlPr = poChildren(list).find((c) => poIs(c, `a:lvl${String(lvl)}pPr`));
176
+ const defRPr = lvlPr ? poChildren(lvlPr).find((c) => poIs(c, "a:defRPr")) : void 0;
177
+ const bullet = parseBullet(lvlPr, colors);
178
+ const spacingPct = spacingFractions(lvlPr);
179
+ out.push({
180
+ run: rPrToRunProps(defRPr, colors, themeFonts),
181
+ paragraph: pPrToParagraphProps(lvlPr),
182
+ ...bullet ? { bullet } : {},
183
+ ...spacingPct ? { spacingPct } : {}
184
+ });
185
+ }
186
+ return out;
187
+ }
188
+ var ALGN = {
189
+ l: "left",
190
+ ctr: "center",
191
+ r: "right",
192
+ just: "both",
193
+ dist: "distribute"
194
+ };
195
+ /**
196
+ * A level's paragraph shape — §21.1.2.2.7 `@algn`, `@marL`/`@indent`, and the
197
+ * space around and between its lines.
198
+ *
199
+ * Both spacings come in two spellings: `a:spcPts` is a distance (hundredths of
200
+ * a point) and `a:spcPct` a FRACTION of a line (thousandths of a percent). Only
201
+ * the distance was read, so a deck that spaces its bullets the usual way — by
202
+ * fraction — set them solid.
203
+ */
204
+ function pPrToParagraphProps(lvlPr) {
205
+ if (!lvlPr) return {};
206
+ const algn = poAttr(lvlPr, "algn");
207
+ const marL = poIntAttr(lvlPr, "marL");
208
+ const indent = poIntAttr(lvlPr, "indent");
209
+ return {
210
+ ...algn !== void 0 && ALGN[algn] ? { alignment: ALGN[algn] } : {},
211
+ ...marL !== void 0 ? { indentLeft: emuToPt(marL) } : {},
212
+ ...indent !== void 0 ? { indentFirstLine: emuToPt(indent) } : {},
213
+ ...spacingPt(lvlPr, "a:spcBef", "spacingBefore"),
214
+ ...spacingPt(lvlPr, "a:spcAft", "spacingAfter"),
215
+ ...lineSpacing(lvlPr)
216
+ };
217
+ }
218
+ /**
219
+ * §21.1.2.2.5 `a:lnSpc` — the height of each line: a fraction of the natural
220
+ * one (`a:spcPct`) or a distance (`a:spcPts`).
221
+ *
222
+ * The model states a multiple the way §17.3.1.33 does — 12pt under the `auto`
223
+ * rule IS single — so a fraction needs no font size to express, which is why
224
+ * this resolves here and the paragraph SPACING has to wait for the size.
225
+ *
226
+ * @param pPr The paragraph properties (a level's or a paragraph's own).
227
+ * @returns The line spacing, or nothing when the node states none.
228
+ */
229
+ function lineSpacing(pPr) {
230
+ const holder = pPr ? poChildren(pPr).find((c) => poIs(c, "a:lnSpc")) : void 0;
231
+ if (!holder) return {};
232
+ const pctVal = poIntAttr(poChildren(holder).find((c) => poIs(c, "a:spcPct")), "val");
233
+ if (pctVal !== void 0 && pctVal > 0) return {
234
+ spacingLine: pt(pctVal / 1e5 * 12),
235
+ spacingLineRule: "auto"
236
+ };
237
+ const ptsVal = poIntAttr(poChildren(holder).find((c) => poIs(c, "a:spcPts")), "val");
238
+ if (ptsVal !== void 0 && ptsVal > 0) return {
239
+ spacingLine: pt(ptsVal / 100),
240
+ spacingLineRule: "exact"
241
+ };
242
+ return {};
243
+ }
244
+ /**
245
+ * §21.1.2.2.9/.10 — the fraction of a line a paragraph puts before and after
246
+ * itself, when it states one. The distance form is read beside it; this is what
247
+ * the caller must still resolve against the size.
248
+ *
249
+ * @param pPr The paragraph properties.
250
+ * @returns The two fractions (`0..n`), each present only when stated.
251
+ */
252
+ function spacingFractions(pPr) {
253
+ const frac = (tag) => {
254
+ const holder = pPr ? poChildren(pPr).find((c) => poIs(c, tag)) : void 0;
255
+ const pctNode = holder ? poChildren(holder).find((c) => poIs(c, "a:spcPct")) : void 0;
256
+ const val = pctNode ? poIntAttr(pctNode, "val") : void 0;
257
+ return val === void 0 || val < 0 ? void 0 : val / 1e5;
258
+ };
259
+ const [before, after] = [frac("a:spcBef"), frac("a:spcAft")];
260
+ if (before === void 0 && after === void 0) return void 0;
261
+ return {
262
+ ...before !== void 0 ? { before } : {},
263
+ ...after !== void 0 ? { after } : {}
264
+ };
265
+ }
266
+ function spacingPt(lvlPr, tag, key) {
267
+ const holder = poChildren(lvlPr).find((c) => poIs(c, tag));
268
+ const pts = holder ? poChildren(holder).find((c) => poIs(c, "a:spcPts")) : void 0;
269
+ const val = pts ? poIntAttr(pts, "val") : void 0;
270
+ return val === void 0 ? {} : { [key]: pt(val / 100) };
271
+ }
272
+ function collectPlaceholders(tree, root, colors, themeFonts) {
39
273
  const sld = tree.find((n) => poIs(n, root));
40
274
  const cSld = sld ? poChildren(sld).find((c) => poIs(c, "p:cSld")) : void 0;
41
275
  const spTree = cSld ? poChildren(cSld).find((c) => poIs(c, "p:spTree")) : void 0;
@@ -45,10 +279,19 @@ function collectPlaceholders(tree, root) {
45
279
  if (!poIs(sp, "p:sp")) continue;
46
280
  const ref = parsePh(sp);
47
281
  if (!ref) continue;
48
- const box = parseXfrmBox(poChildren(sp).find((c) => poIs(c, "p:spPr")));
282
+ const spPr = poChildren(sp).find((c) => poIs(c, "p:spPr"));
283
+ const box = parseXfrmBox(spPr);
284
+ const txBody = poChildren(sp).find((c) => poIs(c, "p:txBody"));
285
+ const lstStyle = txBody ? poChildren(txBody).find((c) => poIs(c, "a:lstStyle")) : void 0;
286
+ const bodyPr = txBody ? poChildren(txBody).find((c) => poIs(c, "a:bodyPr")) : void 0;
287
+ const a = bodyPr ? poAttr(bodyPr, "anchor") : void 0;
288
+ const anchor = a === "ctr" || a === "b" || a === "t" ? a : void 0;
49
289
  out.push({
50
290
  ref,
51
- ...box ? { box } : {}
291
+ ...box ? { box } : {},
292
+ ...spPr ? { spPr } : {},
293
+ levels: parseLevelStyles(lstStyle, colors, themeFonts),
294
+ ...anchor ? { anchor } : {}
52
295
  });
53
296
  }
54
297
  return out;
@@ -63,25 +306,15 @@ function matchPlaceholder(list, ph) {
63
306
  const cat = categoryOf(ph.type);
64
307
  return list.find((p) => categoryOf(p.ref.type) === cat);
65
308
  }
66
- function collectTxStyles(masterTree, colors) {
309
+ function collectTxStyles(masterTree, colors, themeFonts) {
67
310
  const sld = masterTree.find((n) => poIs(n, "p:sldMaster"));
68
311
  const txStyles = sld ? poChildren(sld).find((c) => poIs(c, "p:txStyles")) : void 0;
312
+ const family = (tag) => parseLevelStyles(txStyles ? poChildren(txStyles).find((c) => poIs(c, tag)) : void 0, colors, themeFonts);
69
313
  return {
70
- title: levelStyles(txStyles, "p:titleStyle", colors),
71
- body: levelStyles(txStyles, "p:bodyStyle", colors),
72
- other: levelStyles(txStyles, "p:otherStyle", colors)
314
+ title: family("p:titleStyle"),
315
+ body: family("p:bodyStyle"),
316
+ other: family("p:otherStyle")
73
317
  };
74
318
  }
75
- function levelStyles(txStyles, tag, colors) {
76
- const style = txStyles ? poChildren(txStyles).find((c) => poIs(c, tag)) : void 0;
77
- if (!style) return [];
78
- const out = [];
79
- for (let lvl = 1; lvl <= 9; lvl++) {
80
- const lvlPr = poChildren(style).find((c) => poIs(c, `a:lvl${lvl}pPr`));
81
- const defRPr = lvlPr ? poChildren(lvlPr).find((c) => poIs(c, "a:defRPr")) : void 0;
82
- out.push(rPrToRunProps(defRPr, colors));
83
- }
84
- return out;
85
- }
86
319
  //#endregion
87
- export { buildPlaceholderCascade };
320
+ export { buildPlaceholderCascade, lineSpacing, parseBullet, parseLevelStyles, spacingFractions, spacingFromLineFraction };
@@ -1,6 +1,8 @@
1
1
  import { pt } from "../../core/ir/units.js";
2
2
  import { ResourceStore } from "../../core/ir/resources.js";
3
3
  import { FEATURES } from "../../core/ir/features.js";
4
+ import { isEmf } from "../../core/metafile/emf.js";
5
+ import { isWmf } from "../../core/metafile/wmf.js";
4
6
  import { EMPTY_STYLE_SHEET, resolveBodyStyles } from "../../core/style-cascade/resolver.js";
5
7
  import "../../core/style-cascade/index.js";
6
8
  import { isCfb, openCfb } from "../../core/ole/cfb.js";
@@ -48,7 +50,7 @@ function readPpt(bytes) {
48
50
  return {
49
51
  doc: {
50
52
  kind: "flow",
51
- body: resolveBodyStyles(buildBody(content, resources), EMPTY_STYLE_SHEET),
53
+ body: resolveBodyStyles(buildBody(content, resources, width, height), EMPTY_STYLE_SHEET),
52
54
  sections: [],
53
55
  section: {
54
56
  pageSize: {
@@ -70,19 +72,23 @@ function readPpt(bytes) {
70
72
  losses: [content.encrypted ? PPT_ENCRYPTED_LOSS : PPT_TEXT_LOSS]
71
73
  };
72
74
  }
73
- function buildBody(content, resources) {
75
+ function buildBody(content, resources, pageW, pageH) {
74
76
  const body = [];
75
77
  content.slides.forEach((slide, slideIndex) => {
76
78
  const inFlowParas = [];
77
79
  const inFlowImages = [];
78
80
  const floats = [];
81
+ if (slide.background) {
82
+ const backdrop = slideBackdrop(slide.background, resources, pageW, pageH);
83
+ if (backdrop) floats.push(backdrop);
84
+ }
79
85
  for (const shape of slide.shapes) if (shape.rectPt) {
80
- if (shape.paragraphs?.some((p) => paragraphText(p).length > 0)) floats.push(positionedTextShape(shape.rectPt, shape.paragraphs));
86
+ if (shape.autoShape) floats.push(positionedAutoShape(shape.rectPt, shape.autoShape, resources));
81
87
  if (shape.image) {
82
88
  const block = positionedImage(shape.rectPt, shape.image, resources);
83
89
  if (block) floats.push(block);
84
90
  }
85
- if (shape.autoShape) floats.push(positionedAutoShape(shape.rectPt, shape.autoShape));
91
+ if (shape.paragraphs?.some((p) => paragraphText(p).length > 0)) floats.push(positionedTextShape(shape.rectPt, shape.paragraphs, shape.wordArt === true));
86
92
  } else {
87
93
  for (const para of shape.paragraphs ?? []) if (paragraphText(para).length > 0) inFlowParas.push(flowParagraph(para));
88
94
  if (shape.image) {
@@ -122,10 +128,80 @@ function flowParagraph(para) {
122
128
  kind: "paragraph",
123
129
  paragraph: {
124
130
  properties: toParaProperties(para, false),
125
- runs: para.runs.map(toRun)
131
+ runs: bulletedRuns(para)
126
132
  }
127
133
  };
128
134
  }
135
+ function bulletedRuns(para) {
136
+ const runs = para.runs.map(toRun);
137
+ if (para.bullet === void 0) return runs;
138
+ const { bold: _b, italic: _i, ...rest } = runs[0]?.properties ?? {};
139
+ return [{
140
+ text: `${para.bullet} `,
141
+ properties: rest
142
+ }, ...runs];
143
+ }
144
+ function slideBackdrop(bg, resources, pageW, pageH) {
145
+ const fill = backdropFill(bg, resources);
146
+ if (!fill) return void 0;
147
+ return {
148
+ kind: "shape",
149
+ shape: {
150
+ float: {
151
+ wrap: "none",
152
+ behind: true,
153
+ posH: {
154
+ relativeFrom: "page",
155
+ offsetPt: pt(0)
156
+ },
157
+ posV: {
158
+ relativeFrom: "page",
159
+ offsetPt: pt(0)
160
+ }
161
+ },
162
+ width: pageW,
163
+ height: pageH,
164
+ geometry: {
165
+ kind: "preset",
166
+ preset: "rect",
167
+ adjust: /* @__PURE__ */ new Map()
168
+ },
169
+ fill,
170
+ paragraphProperties: {}
171
+ }
172
+ };
173
+ }
174
+ function backdropFill(bg, resources) {
175
+ if (bg.image) return {
176
+ kind: "picture",
177
+ imageResource: resources.put(bg.image.bytes),
178
+ ...bg.imageTiled ? { tiled: true } : {}
179
+ };
180
+ if (bg.gradient) {
181
+ const stops = [{
182
+ offset: 0,
183
+ colorHex: bg.gradient.fromHex
184
+ }, {
185
+ offset: 1,
186
+ colorHex: bg.gradient.toHex
187
+ }];
188
+ return {
189
+ kind: "gradient",
190
+ gradient: bg.gradient.radial ? {
191
+ kind: "radial",
192
+ stops
193
+ } : {
194
+ kind: "linear",
195
+ angle: bg.gradient.angleDeg,
196
+ stops
197
+ }
198
+ };
199
+ }
200
+ return bg.fillColorHex ? {
201
+ kind: "solid",
202
+ colorHex: bg.fillColorHex
203
+ } : void 0;
204
+ }
129
205
  function floatAt(rect) {
130
206
  return {
131
207
  wrap: "none",
@@ -139,7 +215,7 @@ function floatAt(rect) {
139
215
  }
140
216
  };
141
217
  }
142
- function positionedTextShape(rect, paragraphs) {
218
+ function positionedTextShape(rect, paragraphs, centred = false) {
143
219
  return {
144
220
  kind: "shape",
145
221
  shape: {
@@ -154,14 +230,14 @@ function positionedTextShape(rect, paragraphs) {
154
230
  fill: { kind: "none" },
155
231
  text: {
156
232
  content: paragraphs.filter((p) => paragraphText(p).length > 0).map(flowParagraph),
157
- anchor: "t"
233
+ anchor: centred ? "ctr" : "t"
158
234
  },
159
235
  paragraphProperties: {}
160
236
  }
161
237
  };
162
238
  }
163
239
  function positionedImage(rect, image, resources) {
164
- if (!imagePixelSize(image.bytes)) return void 0;
240
+ if (!isMetafile(image.bytes) && !imagePixelSize(image.bytes)) return void 0;
165
241
  return {
166
242
  kind: "image",
167
243
  image: {
@@ -214,9 +290,36 @@ function toCustomGeometry(g) {
214
290
  commands
215
291
  };
216
292
  }
217
- function positionedAutoShape(rect, auto) {
293
+ function positionedAutoShape(rect, auto, resources) {
218
294
  const line = isLineShape(auto.shapeType);
219
- const fill = auto.fillColorHex && !line ? {
295
+ const fill = line ? { kind: "none" } : auto.image ? {
296
+ kind: "picture",
297
+ imageResource: resources.put(auto.image.bytes),
298
+ ...auto.imageTiled ? { tiled: true } : {},
299
+ ...auto.tileSizePt ? { tileSizePt: auto.tileSizePt } : {}
300
+ } : auto.gradient ? {
301
+ kind: "gradient",
302
+ gradient: auto.gradient.radial ? {
303
+ kind: "radial",
304
+ stops: [{
305
+ offset: 0,
306
+ colorHex: auto.gradient.fromHex
307
+ }, {
308
+ offset: 1,
309
+ colorHex: auto.gradient.toHex
310
+ }]
311
+ } : {
312
+ kind: "linear",
313
+ angle: auto.gradient.angleDeg,
314
+ stops: [{
315
+ offset: 0,
316
+ colorHex: auto.gradient.fromHex
317
+ }, {
318
+ offset: 1,
319
+ colorHex: auto.gradient.toHex
320
+ }]
321
+ }
322
+ } : auto.fillColorHex ? {
220
323
  kind: "solid",
221
324
  colorHex: auto.fillColorHex
222
325
  } : { kind: "none" };
@@ -257,6 +360,9 @@ function inFlowImage(image, resources) {
257
360
  }
258
361
  };
259
362
  }
363
+ function isMetafile(d) {
364
+ return isEmf(d) || isWmf(d);
365
+ }
260
366
  function imagePixelSize(d) {
261
367
  if (d.length >= 24 && d[0] === 137 && d[1] === 80 && d[2] === 78 && d[3] === 71) {
262
368
  const w = u32be(d, 16);
@@ -298,6 +404,10 @@ function toRun(r) {
298
404
  ...r.italic ? { italic: true } : {},
299
405
  ...underline ? { underline } : {},
300
406
  ...r.sizePt ? { fontSizePt: pt(r.sizePt) } : {},
407
+ ...r.fontFamily ? { fontFamily: {
408
+ ascii: r.fontFamily,
409
+ hAnsi: r.fontFamily
410
+ } } : {},
301
411
  ...r.colorHex ? { colorHex: r.colorHex } : {}
302
412
  };
303
413
  return {
@@ -1,6 +1,8 @@
1
1
  /** A run of uniformly-formatted text within a paragraph (PPT-2). */
2
2
  export interface PptRun {
3
3
  readonly text: string;
4
+ /** The typeface the run names, resolved through the deck's font collection. */
5
+ readonly fontFamily?: string;
4
6
  readonly bold?: boolean;
5
7
  readonly italic?: boolean;
6
8
  readonly underline?: boolean;
@@ -20,6 +22,12 @@ export interface PptParagraph {
20
22
  readonly align?: number;
21
23
  /** The 0-based outline/indent level. */
22
24
  readonly level?: number;
25
+ /**
26
+ * §2.9.20 — the bullet character this paragraph is marked with, already
27
+ * translated out of whatever symbol font stated it. Absent when the paragraph
28
+ * carries none.
29
+ */
30
+ readonly bullet?: string;
23
31
  }
24
32
  /**
25
33
  * An embedded picture referenced by a slide shape — the raw image bytes pulled
@@ -80,6 +88,35 @@ export interface PptAutoShape {
80
88
  readonly fillColorHex?: string;
81
89
  /** The resolved line colour as 6-hex RGB, when present. */
82
90
  readonly lineColorHex?: string;
91
+ /**
92
+ * MS-ODRAW §2.3.7.1 — the two-colour sweep a `fillType` of 4..8 asks for.
93
+ * `fillColor` is one end and `fillBackColor` the other; `fillAngle` is where
94
+ * it runs. Read as its fillColor alone, 41246-2's title slide came out a flat
95
+ * teal where every reader fades it to white.
96
+ */
97
+ readonly gradient?: {
98
+ readonly fromHex: string;
99
+ readonly toHex: string;
100
+ readonly angleDeg: number;
101
+ readonly radial: boolean;
102
+ };
103
+ /**
104
+ * §2.3.7.1 MSOFILLTYPE 3 — a picture stretched over the shape, its blip named
105
+ * by `fillBlip`. A `.ppt` table is a group of cell rectangles, and a cell with
106
+ * a picture background states it exactly this way.
107
+ */
108
+ readonly image?: PptImage;
109
+ /** §2.3.7.1 MSOFILLTYPE 2 — the picture REPEATS at its own size, not stretched. */
110
+ readonly imageTiled?: boolean;
111
+ /**
112
+ * §2.3.7.11/.12 `fillWidth` / `fillHeight` — the size one copy occupies, in
113
+ * points, when the shape states it rather than leaving the tile at the
114
+ * picture's own size.
115
+ */
116
+ readonly tileSizePt?: {
117
+ readonly widthPt: number;
118
+ readonly heightPt: number;
119
+ };
83
120
  readonly geometry?: PptCustomGeometry;
84
121
  }
85
122
  /**
@@ -89,13 +126,41 @@ export interface PptAutoShape {
89
126
  */
90
127
  export interface PptShape {
91
128
  readonly rectPt?: PptRect;
129
+ /**
130
+ * §2.3.22 — the shape IS a piece of WordArt: its text is the shape, centred
131
+ * in its box rather than flowed from the top. Drawn as an autoshape it was a
132
+ * coloured rectangle where the reference has a word.
133
+ */
134
+ readonly wordArt?: boolean;
135
+ /**
136
+ * Whether the shape is a placeholder (§2.9.24). On a MASTER that means it is
137
+ * a prototype, not decoration: drawn on the slides that follow it, every one
138
+ * of them would carry "Click to edit Master title style".
139
+ */
140
+ readonly placeholder?: boolean;
92
141
  readonly paragraphs?: ReadonlyArray<PptParagraph>;
93
142
  readonly image?: PptImage;
94
143
  readonly autoShape?: PptAutoShape;
95
144
  }
96
- /** One slide: its shapes in document order. */
145
+ /**
146
+ * A slide's background (PPT-12) — the fill on the shape MS-ODRAW marks with
147
+ * `fBackground`. A `.ppt` states it as a shape like any other; drawn as one it
148
+ * would be a rectangle among the content, and dropped it leaves a black slide
149
+ * white.
150
+ */
151
+ export interface PptBackground {
152
+ /** A solid fill's colour, as 6-hex RGB. */
153
+ readonly fillColorHex?: string;
154
+ /** A shaded fill, in the same terms an autoshape's is (PPT-9). */
155
+ readonly gradient?: PptAutoShape['gradient'];
156
+ /** A picture fill, stretched over the slide (or tiled across it). */
157
+ readonly image?: PptImage;
158
+ readonly imageTiled?: boolean;
159
+ }
160
+ /** One slide: its shapes in document order, over its background. */
97
161
  export interface PptSlide {
98
162
  readonly shapes: ReadonlyArray<PptShape>;
163
+ readonly background?: PptBackground;
99
164
  }
100
165
  /** The extracted content of a `.ppt`: its slides, deck page size and encryption flag. */
101
166
  export interface PptContent {