reamkit 1.20.0 → 1.22.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 (41) 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/crypto/offcrypto.d.ts +1 -0
  4. package/dist/esm/core/crypto/offcrypto.js +2 -1
  5. package/dist/esm/core/document-model/types.d.ts +60 -0
  6. package/dist/esm/core/drawingml/chart-parser.d.ts +2 -1
  7. package/dist/esm/core/drawingml/chart-parser.js +23 -1
  8. package/dist/esm/core/drawingml/colors.d.ts +27 -3
  9. package/dist/esm/core/drawingml/colors.js +30 -6
  10. package/dist/esm/core/drawingml/preset-geometry.js +236 -0
  11. package/dist/esm/core/metafile/emf.js +2 -1
  12. package/dist/esm/core/metafile/symbol-fonts.d.ts +42 -0
  13. package/dist/esm/core/metafile/symbol-fonts.js +154 -0
  14. package/dist/esm/core/metafile/wmf.js +35 -2
  15. package/dist/esm/core/vector.d.ts +5 -0
  16. package/dist/esm/excel/sheet-shape-parser.js +1 -12
  17. package/dist/esm/excel/xlsx-reader.d.ts +1 -1
  18. package/dist/esm/excel/xlsx-reader.js +3 -3
  19. package/dist/esm/index.d.ts +1 -0
  20. package/dist/esm/index.js +2 -1
  21. package/dist/esm/layout/page-doc.d.ts +38 -0
  22. package/dist/esm/layout/page-doc.js +34 -18
  23. package/dist/esm/layout/styled-layout.js +174 -49
  24. package/dist/esm/pdf/shading.d.ts +19 -0
  25. package/dist/esm/pdf/shading.js +84 -11
  26. package/dist/esm/pdf/styled-page-emitter.js +614 -444
  27. package/dist/esm/pptx/ole-preview.d.ts +10 -0
  28. package/dist/esm/pptx/ole-preview.js +52 -0
  29. package/dist/esm/pptx/placeholder-cascade.d.ts +69 -5
  30. package/dist/esm/pptx/placeholder-cascade.js +162 -25
  31. package/dist/esm/pptx/pptx-reader.js +247 -39
  32. package/dist/esm/pptx/slide-parser.d.ts +72 -10
  33. package/dist/esm/pptx/slide-parser.js +310 -82
  34. package/dist/esm/pptx/sp-helpers.js +19 -4
  35. package/dist/esm/pptx/table-style.d.ts +56 -0
  36. package/dist/esm/pptx/table-style.js +161 -0
  37. package/dist/esm/svg/svg-writer.js +53 -5
  38. package/dist/esm/word/docx-reader.js +2 -2
  39. package/dist/esm/word/drawing-parser.d.ts +34 -0
  40. package/dist/esm/word/drawing-parser.js +159 -13
  41. package/package.json +1 -1
@@ -0,0 +1,56 @@
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
+ /**
33
+ * The style a cell wears, composed from every part that reaches it.
34
+ *
35
+ * @param style The `a:tblStyle` node.
36
+ * @param flags Which conditional parts the table asks for.
37
+ * @param at Where the cell sits.
38
+ * @param colors The deck's colour resolver.
39
+ * @returns The composed part, empty when the style says nothing.
40
+ */
41
+ export declare function cellStyle(style: PoNode, flags: TableStyleFlags, at: CellPosition, colors: ColorResolver): TableStylePart;
42
+ /**
43
+ * §20.1.2.1 `a:ln` → the rule it draws on one side of a cell.
44
+ *
45
+ * Shared with the cell's OWN `a:lnL`/`a:lnR`/`a:lnT`/`a:lnB`, which are the
46
+ * same element under a different name.
47
+ *
48
+ * @param ln The line node.
49
+ * @param colors The deck's colour resolver.
50
+ * @returns The border, `none` when the line is one that draws nothing.
51
+ */
52
+ export declare function lineBorder(ln: PoNode, colors: ColorResolver): Border;
53
+ /** The same part with the style's fill dropped, for a cell that says `a:noFill`. */
54
+ export declare function withoutFill(part: TableStylePart): TableStylePart;
55
+ /** A cell with the style's fill, borders and run properties filled in under its own. */
56
+ export declare function withCellStyle(cell: TableCell, part: TableStylePart): TableCell;
@@ -0,0 +1,161 @@
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
+ * @returns The composed part, empty when the style says nothing.
30
+ */
31
+ function cellStyle(style, flags, at, colors) {
32
+ const names = ["a:wholeTbl"];
33
+ if (flags.bandCol) names.push(bandName(at.col, flags.firstCol, "V"));
34
+ if (flags.bandRow) names.push(bandName(at.row, flags.firstRow, "H"));
35
+ if (flags.firstCol && at.col === 0) names.push("a:firstCol");
36
+ if (flags.lastCol && at.col === at.colCount - 1) names.push("a:lastCol");
37
+ if (flags.firstRow && at.row === 0) names.push("a:firstRow");
38
+ if (flags.lastRow && at.row === at.rowCount - 1) names.push("a:lastRow");
39
+ let out = {};
40
+ for (const name of names) {
41
+ const part = poChildren(style).find((c) => poIs(c, name));
42
+ if (part) out = {
43
+ ...out,
44
+ ...partStyle(part, colors)
45
+ };
46
+ }
47
+ return out;
48
+ }
49
+ function bandName(index, edge, axis) {
50
+ return (edge ? index - 1 : index) % 2 === 0 ? `a:band1${axis}` : `a:band2${axis}`;
51
+ }
52
+ function partStyle(part, colors) {
53
+ const tcStyle = poChildren(part).find((c) => poIs(c, "a:tcStyle"));
54
+ const txStyle = poChildren(part).find((c) => poIs(c, "a:tcTxStyle"));
55
+ const fill = tcStyle ? poChildren(tcStyle).find((c) => poIs(c, "a:fill")) : void 0;
56
+ const solid = fill ? poChildren(fill).find((c) => poIs(c, "a:solidFill")) : void 0;
57
+ const shadingHex = solid ? colorOf(solid, colors) : void 0;
58
+ const bdr = tcStyle ? poChildren(tcStyle).find((c) => poIs(c, "a:tcBdr")) : void 0;
59
+ const borders = bdr ? partBorders(bdr, colors) : void 0;
60
+ const on = (name) => poAttr(txStyle, name) === "on";
61
+ const colorHex = txStyle ? colorOf(txStyle, colors) : void 0;
62
+ return {
63
+ ...shadingHex ? { shadingHex } : {},
64
+ ...borders ? { borders } : {},
65
+ ...txStyle && on("b") ? { bold: true } : {},
66
+ ...txStyle && on("i") ? { italic: true } : {},
67
+ ...colorHex ? { colorHex } : {}
68
+ };
69
+ }
70
+ var SIDES = [
71
+ ["a:left", "left"],
72
+ ["a:right", "right"],
73
+ ["a:top", "top"],
74
+ ["a:bottom", "bottom"],
75
+ ["a:insideH", "insideH"],
76
+ ["a:insideV", "insideV"]
77
+ ];
78
+ function partBorders(bdr, colors) {
79
+ const out = {};
80
+ for (const [tag, side] of SIDES) {
81
+ const holder = poChildren(bdr).find((c) => poIs(c, tag));
82
+ const ln = holder ? poChildren(holder).find((c) => poIs(c, "a:ln")) : void 0;
83
+ if (ln) out[side] = lineBorder(ln, colors);
84
+ }
85
+ return Object.keys(out).length > 0 ? out : void 0;
86
+ }
87
+ /**
88
+ * §20.1.2.1 `a:ln` → the rule it draws on one side of a cell.
89
+ *
90
+ * Shared with the cell's OWN `a:lnL`/`a:lnR`/`a:lnT`/`a:lnB`, which are the
91
+ * same element under a different name.
92
+ *
93
+ * @param ln The line node.
94
+ * @param colors The deck's colour resolver.
95
+ * @returns The border, `none` when the line is one that draws nothing.
96
+ */
97
+ function lineBorder(ln, colors) {
98
+ if (poChildren(ln).some((c) => poIs(c, "a:noFill")) || isTransparent(ln)) return { style: "none" };
99
+ const colorHex = colorOf(ln, colors);
100
+ const w = poIntAttr(ln, "w");
101
+ return {
102
+ style: "single",
103
+ ...colorHex ? { colorHex } : {},
104
+ ...w !== void 0 ? { width: pt(Math.max(.25, w / 12700)) } : {}
105
+ };
106
+ }
107
+ function isTransparent(ln) {
108
+ const fill = poChildren(ln).find((c) => poIs(c, "a:solidFill"));
109
+ const color = fill ? poChildren(fill).find((c) => poTag(c) !== void 0) : void 0;
110
+ const alpha = color ? readColorMods(color).find((m) => m.kind === "alpha") : void 0;
111
+ return alpha !== void 0 && alpha.val <= .001;
112
+ }
113
+ /** The same part with the style's fill dropped, for a cell that says `a:noFill`. */
114
+ function withoutFill(part) {
115
+ const { shadingHex: _fill, ...rest } = part;
116
+ return rest;
117
+ }
118
+ function colorOf(node, colors) {
119
+ for (const child of poChildren(node)) {
120
+ const hex = resolveColorNode(child, colors);
121
+ if (hex !== void 0) return hex;
122
+ const deeper = poTag(child) !== void 0 ? colorOf(child, colors) : void 0;
123
+ if (deeper !== void 0) return deeper;
124
+ }
125
+ }
126
+ /** A cell with the style's fill, borders and run properties filled in under its own. */
127
+ function withCellStyle(cell, part) {
128
+ const runProps = {
129
+ ...part.bold === true ? { bold: true } : {},
130
+ ...part.italic === true ? { italic: true } : {},
131
+ ...part.colorHex !== void 0 ? { colorHex: part.colorHex } : {}
132
+ };
133
+ const content = Object.keys(runProps).length === 0 ? cell.content : cell.content.map((block) => block.kind === "paragraph" ? {
134
+ ...block,
135
+ paragraph: {
136
+ ...block.paragraph,
137
+ runs: block.paragraph.runs.map((run) => ({
138
+ ...run,
139
+ properties: {
140
+ ...runProps,
141
+ ...run.properties
142
+ }
143
+ }))
144
+ }
145
+ } : block);
146
+ const borders = part.borders || cell.properties.borders ? {
147
+ ...part.borders,
148
+ ...cell.properties.borders
149
+ } : void 0;
150
+ return {
151
+ ...cell,
152
+ properties: {
153
+ ...part.shadingHex !== void 0 ? { shading: { colorHex: part.shadingHex } } : {},
154
+ ...cell.properties,
155
+ ...borders ? { borders } : {}
156
+ },
157
+ content
158
+ };
159
+ }
160
+ //#endregion
161
+ 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) {
@@ -6,7 +6,7 @@ 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
12
  import { parseTheme, parseThemeBgFillStyles, parseThemeEffectStyles, parseThemeFillStyles, parseThemeLineWidths } from "../core/drawingml/theme-parser.js";
@@ -166,7 +166,7 @@ var docxReader = {
166
166
  FEATURES.trackedChanges,
167
167
  FEATURES.fontsEmbedding
168
168
  ]),
169
- sniff: (bytes) => bytes[0] === 80 && bytes[1] === 75 && (bytesIncludePartName(bytes, "word/document.xml") || bytesIncludePartName(bytes, "word/styles.xml") || bytesIncludePartName(bytes, "word/_rels/")),
169
+ sniff: (bytes) => bytes[0] === 80 && bytes[1] === 75 && (packageHasPart(bytes, "word/document.xml") || packageHasPart(bytes, "word/styles.xml") || packageHasPart(bytes, "word/_rels/")),
170
170
  read: (bytes) => readDocx(bytes)
171
171
  };
172
172
  function infoFromCore(core) {
@@ -157,6 +157,26 @@ export declare function parseVmlPicture(node: PoNode, parseBody?: ParseBody): Dr
157
157
  * @returns The fill.
158
158
  */
159
159
  export declare function vmlFill(shape: PoNode, shapeType?: PoNode, resolveImage?: (id: string) => ResourceId | undefined): ShapeFill;
160
+ /**
161
+ * Whether an `spPr` states a fill AT ALL — including `a:noFill`, which is a
162
+ * shape saying it has none rather than saying nothing. A placeholder that says
163
+ * nothing inherits its prototype's.
164
+ *
165
+ * @param spPr The shape properties, or undefined.
166
+ * @returns Whether a fill element is present.
167
+ */
168
+ export declare function statesFill(spPr: PoNode | undefined): boolean;
169
+ export declare function styleRefFill(style: PoNode, resolveColor: ColorResolver, themeStyles?: ThemeStyles): ShapeFill;
170
+ export declare function styleRefLine(style: PoNode, resolveColor: ColorResolver, themeLineWidths?: ReadonlyArray<number>): ShapeLine | undefined;
171
+ /**
172
+ * §20.1.4.2.14 `a:fontRef` — the colour a gallery style writes its text in.
173
+ *
174
+ * @param style The shape's `p:style`/`wps:style` node.
175
+ * @param resolveColor The document's colour resolver.
176
+ * @returns The 6-hex colour, or `undefined` when the style names none.
177
+ */
178
+ export declare function styleRefFontColor(style: PoNode, resolveColor: ColorResolver): string | undefined;
179
+ export declare function withStyleFontColor(text: ShapeTextBody, style: PoNode, resolveColor: ColorResolver): ShapeTextBody;
160
180
  /**
161
181
  * §20.1.7.6 `a:xfrm` — how a shape sits in its box: rotated about its centre by
162
182
  * `@rot` (sixtieth-thousandths of a degree) and mirrored by `@flipH`/`@flipV`.
@@ -190,6 +210,20 @@ export declare function parseCustGeom(cust: PoNode): ShapeGeometry;
190
210
  * @param resolveColor Resolver for theme/scheme colours.
191
211
  */
192
212
  export declare function parseFill(spPr: PoNode, resolveColor: ColorResolver, resolveImage?: (relId: string) => ResourceId | undefined): ShapeFill;
213
+ /**
214
+ * §20.1.8.16 `a:clrChange` — the colour a picture declares away, and what it
215
+ * becomes. `useA` (default true) says the destination's alpha counts, so a
216
+ * destination at zero alpha knocks the colour OUT rather than replacing it.
217
+ *
218
+ * @param blip The `a:blip` node.
219
+ * @param resolveColor The colour resolver.
220
+ * @returns The change, or `undefined` when the blip declares none.
221
+ */
222
+ export declare function colorChangeOf(blip: PoNode, resolveColor: ColorResolver): {
223
+ readonly fromHex: string;
224
+ readonly toHex: string;
225
+ readonly transparent: boolean;
226
+ } | undefined;
193
227
  /**
194
228
  * Parse a shape's drop shadow from its `a:spPr` (§20.1.8.40 `a:effectLst` →
195
229
  * `a:outerShdw`).
@@ -1,7 +1,7 @@
1
1
  import { emuToPt, pt } from "../core/ir/units.js";
2
2
  import { buildStroke } from "../core/drawingml/shape-render.js";
3
3
  import { poAttr, poAttrLocal, poChildren, poFindDescendant, poIntAttr, poIs, poIsLocal, poTag, poText } from "../core/po-helpers.js";
4
- import { applyColorMods, readColorMods, resolveColorNode } from "../core/drawingml/colors.js";
4
+ import { placeholderColors, readColorMods, resolveColorNode } from "../core/drawingml/colors.js";
5
5
  //#region src/word/drawing-parser.ts
6
6
  var WPS_URI = "http://schemas.microsoft.com/office/word/2010/wordprocessingShape";
7
7
  var DRAWINGML_URI_BASES = ["http://schemas.openxmlformats.org/drawingml/2006/", "http://purl.oclc.org/ooxml/drawingml/"];
@@ -1524,9 +1524,6 @@ function styleRefShadow(style, resolveColor, themeEffectStyles) {
1524
1524
  const phHex = child ? resolveColorNode(child, resolveColor) : void 0;
1525
1525
  return shadowFromOuterShdw(shdw, phHex ? placeholderColors(resolveColor, phHex) : resolveColor);
1526
1526
  }
1527
- function placeholderColors(base, phHex) {
1528
- return (raw) => "scheme" in raw && raw.scheme === "phClr" ? applyColorMods(phHex, raw.mods ?? []) : base(raw);
1529
- }
1530
1527
  var FILL_TAGS = new Set([
1531
1528
  "a:noFill",
1532
1529
  "a:solidFill",
@@ -1535,6 +1532,14 @@ var FILL_TAGS = new Set([
1535
1532
  "a:pattFill",
1536
1533
  "a:grpFill"
1537
1534
  ]);
1535
+ /**
1536
+ * Whether an `spPr` states a fill AT ALL — including `a:noFill`, which is a
1537
+ * shape saying it has none rather than saying nothing. A placeholder that says
1538
+ * nothing inherits its prototype's.
1539
+ *
1540
+ * @param spPr The shape properties, or undefined.
1541
+ * @returns Whether a fill element is present.
1542
+ */
1538
1543
  function statesFill(spPr) {
1539
1544
  return spPr !== void 0 && poChildren(spPr).some((c) => FILL_TAGS.has(poTag(c) ?? ""));
1540
1545
  }
@@ -1567,9 +1572,19 @@ function styleRefLine(style, resolveColor, themeLineWidths) {
1567
1572
  width: pt((idx !== void 0 && idx > 0 ? themeLineWidths?.[idx - 1] : void 0) ?? .75)
1568
1573
  };
1569
1574
  }
1570
- function withStyleFontColor(text, style, resolveColor) {
1575
+ /**
1576
+ * §20.1.4.2.14 `a:fontRef` — the colour a gallery style writes its text in.
1577
+ *
1578
+ * @param style The shape's `p:style`/`wps:style` node.
1579
+ * @param resolveColor The document's colour resolver.
1580
+ * @returns The 6-hex colour, or `undefined` when the style names none.
1581
+ */
1582
+ function styleRefFontColor(style, resolveColor) {
1571
1583
  const child = firstElementChild(poChildren(style).find((c) => poIs(c, "a:fontRef")));
1572
- const colorHex = child ? resolveColorNode(child, resolveColor) : void 0;
1584
+ return child ? resolveColorNode(child, resolveColor) : void 0;
1585
+ }
1586
+ function withStyleFontColor(text, style, resolveColor) {
1587
+ const colorHex = styleRefFontColor(style, resolveColor);
1573
1588
  if (colorHex === void 0) return text;
1574
1589
  return {
1575
1590
  ...text,
@@ -1801,11 +1816,20 @@ function fillFromNode(child, resolveColor, resolveImage) {
1801
1816
  const resource = relId !== void 0 ? resolveImage?.(relId) : void 0;
1802
1817
  if (resource === void 0) return { kind: "none" };
1803
1818
  const crop = parseSrcRect(poFindDescendant(child, "a:srcRect")) ?? fillRectCrop(child);
1819
+ const rect = fillRectBox(child);
1820
+ const duotone = blip ? duotoneOf(blip, resolveColor) : void 0;
1821
+ const tiled = poChildren(child).some((c) => poIs(c, "a:tile"));
1822
+ const fixed = blip ? poChildren(blip).find((c) => poIs(c, "a:alphaModFix")) : void 0;
1823
+ const amt = fixed ? poIntAttr(fixed, "amt") : void 0;
1824
+ const alpha = amt === void 0 ? void 0 : Math.min(1, Math.max(0, amt / 1e5));
1804
1825
  return {
1805
1826
  kind: "picture",
1806
1827
  imageResource: resource,
1807
- ...poChildren(child).some((c) => poIs(c, "a:tile")) ? { tiled: true } : {},
1808
- ...crop ? { imageCrop: crop } : {}
1828
+ ...tiled ? { tiled: true } : {},
1829
+ ...crop ? { imageCrop: crop } : {},
1830
+ ...rect ? { imageFillRect: rect } : {},
1831
+ ...duotone ? { duotone } : {},
1832
+ ...alpha !== void 0 && alpha < 1 ? { alpha } : {}
1809
1833
  };
1810
1834
  }
1811
1835
  if (poIs(child, "a:solidFill")) {
@@ -1830,10 +1854,13 @@ function fillFromNode(child, resolveColor, resolveImage) {
1830
1854
  }
1831
1855
  if (poIs(child, "a:gradFill")) {
1832
1856
  const gradient = parseGradient(child, resolveColor);
1833
- return gradient ? {
1857
+ if (!gradient) return { kind: "none" };
1858
+ const alpha = gradientAlpha(gradient.stops);
1859
+ return {
1834
1860
  kind: "gradient",
1835
- gradient
1836
- } : { kind: "none" };
1861
+ gradient,
1862
+ ...alpha !== void 0 ? { alpha } : {}
1863
+ };
1837
1864
  }
1838
1865
  }
1839
1866
  /**
@@ -1907,6 +1934,90 @@ function fillRectCrop(blipFill) {
1907
1934
  };
1908
1935
  }
1909
1936
  /**
1937
+ * §20.1.8.16 `a:clrChange` — the colour a picture declares away, and what it
1938
+ * becomes. `useA` (default true) says the destination's alpha counts, so a
1939
+ * destination at zero alpha knocks the colour OUT rather than replacing it.
1940
+ *
1941
+ * @param blip The `a:blip` node.
1942
+ * @param resolveColor The colour resolver.
1943
+ * @returns The change, or `undefined` when the blip declares none.
1944
+ */
1945
+ function colorChangeOf(blip, resolveColor) {
1946
+ const change = poChildren(blip).find((c) => poIs(c, "a:clrChange"));
1947
+ if (!change) return void 0;
1948
+ const side = (tag) => poChildren(change).find((c) => poIs(c, tag));
1949
+ const from = side("a:clrFrom");
1950
+ const to = side("a:clrTo");
1951
+ const inner = (holder) => holder ? poChildren(holder).find((c) => poTag(c) !== void 0) : void 0;
1952
+ const fromNode = inner(from);
1953
+ const toNode = inner(to);
1954
+ const fromHex = fromNode ? resolveColorNode(fromNode, resolveColor) : void 0;
1955
+ if (fromHex === void 0) return void 0;
1956
+ const toHex = (toNode ? resolveColorNode(toNode, resolveColor) : void 0) ?? fromHex;
1957
+ const useA = poAttr(change, "useA") !== "0";
1958
+ const alpha = toNode ? nodeAlpha(toNode) : void 0;
1959
+ return {
1960
+ fromHex,
1961
+ toHex,
1962
+ transparent: useA && alpha !== void 0 && alpha <= .001
1963
+ };
1964
+ }
1965
+ /** §20.1.2.3.1 — a colour node's own `a:alpha`, as a fraction. */
1966
+ function nodeAlpha(color) {
1967
+ const alpha = poChildren(color).find((c) => poIs(c, "a:alpha"));
1968
+ const val = alpha ? poIntAttr(alpha, "val") : void 0;
1969
+ return val === void 0 ? void 0 : val / 1e5;
1970
+ }
1971
+ /**
1972
+ * §20.1.8.23 `a:duotone` — the two colours a picture is recoloured between,
1973
+ * dark end first. Both are ordinary colour containers, so a theme's `phClr`
1974
+ * resolves through whatever resolver the caller bound.
1975
+ *
1976
+ * @param blip The `a:blip` node.
1977
+ * @param resolveColor The colour resolver.
1978
+ * @returns The pair, or `undefined` when the blip states no duotone.
1979
+ */
1980
+ function duotoneOf(blip, resolveColor) {
1981
+ const duotone = poChildren(blip).find((c) => poIs(c, "a:duotone"));
1982
+ if (!duotone) return void 0;
1983
+ const [shadowHex, highlightHex] = poChildren(duotone).map((c) => resolveColorNode(c, resolveColor)).filter((hex) => hex !== void 0);
1984
+ return shadowHex !== void 0 && highlightHex !== void 0 ? {
1985
+ shadowHex,
1986
+ highlightHex
1987
+ } : void 0;
1988
+ }
1989
+ /**
1990
+ * §20.1.8.30 `a:stretch/a:fillRect` with POSITIVE insets — the part of the box
1991
+ * the picture is stretched INTO. The negative case is the zoom {@link
1992
+ * fillRectCrop} reads; this is the other one, and unread it drew a background
1993
+ * picture inset into the corner of a slide across the whole of it
1994
+ * (tdf153466.pptx: a triangle five times its size).
1995
+ *
1996
+ * @param blipFill The `a:blipFill` node.
1997
+ * @returns The rect as fractions of the box, or `undefined` when every inset
1998
+ * is zero or negative.
1999
+ */
2000
+ function fillRectBox(blipFill) {
2001
+ const stretch = poChildren(blipFill).find((c) => poIs(c, "a:stretch"));
2002
+ const rect = stretch ? poChildren(stretch).find((c) => poIs(c, "a:fillRect")) : void 0;
2003
+ if (!rect) return void 0;
2004
+ const side = (name) => Math.max(0, (poIntAttr(rect, name) ?? 0) / 1e5);
2005
+ const [left, top, right, bottom] = [
2006
+ side("l"),
2007
+ side("t"),
2008
+ side("r"),
2009
+ side("b")
2010
+ ];
2011
+ if (left + top + right + bottom === 0) return void 0;
2012
+ if (left + right >= 1 || top + bottom >= 1) return void 0;
2013
+ return {
2014
+ left,
2015
+ top,
2016
+ right,
2017
+ bottom
2018
+ };
2019
+ }
2020
+ /**
1910
2021
  * Parse a shape's drop shadow from its `a:spPr` (§20.1.8.40 `a:effectLst` →
1911
2022
  * `a:outerShdw`).
1912
2023
  *
@@ -2089,6 +2200,38 @@ function colorFromContainer(parent, resolveColor) {
2089
2200
  if (hex) return hex;
2090
2201
  }
2091
2202
  }
2203
+ /**
2204
+ * §20.1.2.3.1 — a gradient's stop transparencies, settled.
2205
+ *
2206
+ * A page paints a gradient at ONE transparency, so only a gradient whose every
2207
+ * stop is translucent can carry it: the strongest stop decides whether the
2208
+ * shape is there at all, which is the question a 7%-and-fading glow asks
2209
+ * (tdf123684's master draws one over a dark slide, and composited over the
2210
+ * paper instead it was an opaque white disc).
2211
+ *
2212
+ * A gradient that mixes a translucent stop with an opaque one has no such
2213
+ * answer, so it keeps the colours the resolver already washed toward the paper
2214
+ * — an approximation, but the one that has always been drawn: smartart-simple's
2215
+ * cyan-to-purple sweep sets 75% on its purple end alone.
2216
+ */
2217
+ function normalizeStopAlpha(stops) {
2218
+ if (gradientAlpha(stops) === void 0) {
2219
+ for (const [i, s] of stops.entries()) if (s.alpha !== void 0) stops[i] = {
2220
+ offset: s.offset,
2221
+ colorHex: s.colorHex
2222
+ };
2223
+ return;
2224
+ }
2225
+ for (const [i, s] of stops.entries()) stops[i] = {
2226
+ ...s,
2227
+ colorHex: unblendWhite(s.colorHex, s.alpha ?? 1)
2228
+ };
2229
+ }
2230
+ /** The one transparency a gradient can be drawn at; see {@link normalizeStopAlpha}. */
2231
+ function gradientAlpha(stops) {
2232
+ const strongest = Math.max(...stops.map((s) => s.alpha ?? 1));
2233
+ return strongest < 1 ? strongest : void 0;
2234
+ }
2092
2235
  function parseGradient(grad, resolveColor) {
2093
2236
  const gsLst = poChildren(grad).find((c) => poIs(c, "a:gsLst"));
2094
2237
  if (!gsLst) return void 0;
@@ -2103,13 +2246,16 @@ function parseGradient(grad, resolveColor) {
2103
2246
  if (!hex) continue;
2104
2247
  const pos = poIntAttr(gs, "pos");
2105
2248
  const offset = pos !== void 0 ? clampUnit(pos / 1e5) : stops.length === 0 ? 0 : 1;
2249
+ const alpha = containerAlpha(gs);
2106
2250
  stops.push({
2107
2251
  offset,
2108
- colorHex: hex
2252
+ colorHex: hex,
2253
+ ...alpha !== void 0 ? { alpha } : {}
2109
2254
  });
2110
2255
  }
2111
2256
  if (stops.length === 0) return void 0;
2112
2257
  stops.sort((a, b) => a.offset - b.offset);
2258
+ normalizeStopAlpha(stops);
2113
2259
  const path = poChildren(grad).find((c) => poIs(c, "a:path"));
2114
2260
  if (path) {
2115
2261
  const rect = poChildren(path).find((c) => poIs(c, "a:fillToRect"));
@@ -2144,4 +2290,4 @@ function clampUnit(x) {
2144
2290
  return x < 0 ? 0 : x > 1 ? 1 : x;
2145
2291
  }
2146
2292
  //#endregion
2147
- export { expandMcChildren, parseCustGeom, parseDrawing, parseFill, parseLine, parsePrstGeom, parseShadow, parseVmlPicture, parseXfrm, shadowFromOuterShdw, vmlFill };
2293
+ export { colorChangeOf, expandMcChildren, parseCustGeom, parseDrawing, parseFill, parseLine, parsePrstGeom, parseShadow, parseVmlPicture, parseXfrm, shadowFromOuterShdw, statesFill, styleRefFill, styleRefFontColor, styleRefLine, vmlFill };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reamkit",
3
- "version": "1.20.0",
3
+ "version": "1.22.0",
4
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.",
5
5
  "license": "MIT",
6
6
  "author": "Alex Krassavin <info@reamkit.dev>",