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
@@ -5,13 +5,31 @@
5
5
  */
6
6
  export declare function toBase64(bytes: Uint8Array): string;
7
7
  /**
8
- * Naive scan for an ASCII `needle` inside raw `haystack` bytes — used by reader
9
- * sniffs to spot OPC part names (e.g. `'word/document.xml'`) without unzipping.
8
+ * Whether the ZIP in `bytes` holds a part named `partName` — the name exactly,
9
+ * or anything under it when `partName` names a directory (ends with `/`).
10
+ *
11
+ * This is what a reader sniff should ask. A substring probe cannot tell a
12
+ * package from one EMBEDDED in it: a deck with a chart carries the chart's
13
+ * workbook as `ppt/embeddings/*.xlsx` STORED, uncompressed, so that workbook's
14
+ * own `xl/workbook.xml` sits in the outer file's bytes verbatim — and the xlsx
15
+ * sniff, which runs before the pptx one, claimed thirteen corpus presentations
16
+ * and then failed on them. The ZIP's central directory lists what is actually
17
+ * in this package, and walking that entry table is cheaper than scanning every
18
+ * byte for a needle.
19
+ *
20
+ * Names are compared without case, as OPC compares part names (§9.1.1.1) and
21
+ * as the package reader behind the sniff resolves them.
22
+ *
23
+ * @param bytes The package bytes.
24
+ * @param partName The part name, or a `/`-terminated directory prefix.
25
+ * @returns Whether the package's directory names it.
10
26
  */
27
+ export declare function packageHasPart(bytes: Uint8Array, partName: string): boolean;
11
28
  /**
12
29
  * Scan raw package bytes for an OPC part name (e.g. `'xl/workbook.xml'`)
13
- * without unzipping — the reader sniffs' cheap format probe. Accepts both the
14
- * spec's `/` separator and the `\` that Windows producers write.
30
+ * without unzipping. Prefer {@link packageHasPart}, which asks the archive
31
+ * what it holds instead of hoping the name appears nowhere else. Accepts both
32
+ * the spec's `/` separator and the `\` that Windows producers write.
15
33
  */
16
34
  export declare function bytesIncludePartName(haystack: Uint8Array, partName: string): boolean;
17
35
  /**
@@ -10,13 +10,81 @@ function toBase64(bytes) {
10
10
  return btoa(bin);
11
11
  }
12
12
  /**
13
- * Naive scan for an ASCII `needle` inside raw `haystack` bytes — used by reader
14
- * sniffs to spot OPC part names (e.g. `'word/document.xml'`) without unzipping.
13
+ * Whether the ZIP in `bytes` holds a part named `partName` — the name exactly,
14
+ * or anything under it when `partName` names a directory (ends with `/`).
15
+ *
16
+ * This is what a reader sniff should ask. A substring probe cannot tell a
17
+ * package from one EMBEDDED in it: a deck with a chart carries the chart's
18
+ * workbook as `ppt/embeddings/*.xlsx` STORED, uncompressed, so that workbook's
19
+ * own `xl/workbook.xml` sits in the outer file's bytes verbatim — and the xlsx
20
+ * sniff, which runs before the pptx one, claimed thirteen corpus presentations
21
+ * and then failed on them. The ZIP's central directory lists what is actually
22
+ * in this package, and walking that entry table is cheaper than scanning every
23
+ * byte for a needle.
24
+ *
25
+ * Names are compared without case, as OPC compares part names (§9.1.1.1) and
26
+ * as the package reader behind the sniff resolves them.
27
+ *
28
+ * @param bytes The package bytes.
29
+ * @param partName The part name, or a `/`-terminated directory prefix.
30
+ * @returns Whether the package's directory names it.
15
31
  */
32
+ function packageHasPart(bytes, partName) {
33
+ const names = zipEntryNames(bytes);
34
+ if (names === void 0) return bytesIncludePartName(bytes, partName);
35
+ const wanted = partName.toLowerCase();
36
+ const isDir = wanted.endsWith("/");
37
+ return names.some((raw) => {
38
+ const name = raw.replace(/\\/g, "/").replace(/^\//u, "").toLowerCase();
39
+ return isDir ? name.startsWith(wanted) : name === wanted;
40
+ });
41
+ }
42
+ /**
43
+ * The names in a ZIP's central directory (APPNOTE §4.3.12), or `undefined`
44
+ * when it cannot be located or is malformed — including the ZIP64 spelling of
45
+ * an archive with more than 65 534 entries.
46
+ */
47
+ function zipEntryNames(bytes) {
48
+ if (bytes.length < 22) return void 0;
49
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
50
+ const floor = Math.max(0, bytes.length - 22 - 65535);
51
+ let end = -1;
52
+ for (let i = bytes.length - 22; i >= floor; i--) if (view.getUint32(i, true) === 101010256) {
53
+ end = i;
54
+ break;
55
+ }
56
+ if (end < 0) return void 0;
57
+ let count = view.getUint16(end + 10, true);
58
+ let start = view.getUint32(end + 16, true);
59
+ if (count === 65535 || start === 4294967295) {
60
+ const locator = end - 20;
61
+ if (locator < 0 || view.getUint32(locator, true) !== 117853008) return void 0;
62
+ const z64 = Number(view.getBigUint64(locator + 8, true));
63
+ if (z64 + 56 > bytes.length || view.getUint32(z64, true) !== 101075792) return void 0;
64
+ count = Number(view.getBigUint64(z64 + 32, true));
65
+ start = Number(view.getBigUint64(z64 + 48, true));
66
+ }
67
+ const names = [];
68
+ let p = start;
69
+ for (let i = 0; i < count; i++) {
70
+ if (p + 46 > bytes.length || view.getUint32(p, true) !== 33639248) return void 0;
71
+ const nameLen = view.getUint16(p + 28, true);
72
+ if (p + 46 + nameLen > bytes.length) return void 0;
73
+ names.push(decodeUtf8(bytes.subarray(p + 46, p + 46 + nameLen)));
74
+ p += 46 + nameLen + view.getUint16(p + 30, true) + view.getUint16(p + 32, true);
75
+ }
76
+ return names;
77
+ }
78
+ var utf8 = new TextDecoder();
79
+ /** An entry name as text. Names are UTF-8 or CP437; both decode ASCII alike. */
80
+ function decodeUtf8(bytes) {
81
+ return utf8.decode(bytes);
82
+ }
16
83
  /**
17
84
  * Scan raw package bytes for an OPC part name (e.g. `'xl/workbook.xml'`)
18
- * without unzipping — the reader sniffs' cheap format probe. Accepts both the
19
- * spec's `/` separator and the `\` that Windows producers write.
85
+ * without unzipping. Prefer {@link packageHasPart}, which asks the archive
86
+ * what it holds instead of hoping the name appears nowhere else. Accepts both
87
+ * the spec's `/` separator and the `\` that Windows producers write.
20
88
  */
21
89
  function bytesIncludePartName(haystack, partName) {
22
90
  if (bytesInclude(haystack, partName)) return true;
@@ -36,4 +104,4 @@ function bytesInclude(haystack, needle) {
36
104
  return false;
37
105
  }
38
106
  //#endregion
39
- export { bytesInclude, bytesIncludePartName, toBase64 };
107
+ export { bytesInclude, packageHasPart, toBase64 };
@@ -1,5 +1,6 @@
1
1
  /** Thrown when the bytes are encrypted and the password does not open them. */
2
2
  export declare class WrongPasswordError extends Error {
3
+ /** @param message The message to carry; the default states the failure. */
3
4
  constructor(message?: string);
4
5
  }
5
6
  /** Whether the bytes are an OOXML package encrypted per ECMA-376 §2.3. */
@@ -3,6 +3,7 @@ import { aesCbcDecrypt, aesEcbDecrypt, sha1, sha256, sha384, sha512 } from "./pr
3
3
  //#region src/core/crypto/offcrypto.ts
4
4
  /** Thrown when the bytes are encrypted and the password does not open them. */
5
5
  var WrongPasswordError = class extends Error {
6
+ /** @param message The message to carry; the default states the failure. */
6
7
  constructor(message = "Wrong password for this document") {
7
8
  super(message);
8
9
  this.name = "WrongPasswordError";
@@ -219,4 +220,4 @@ function b64(s) {
219
220
  return out.subarray(0, at);
220
221
  }
221
222
  //#endregion
222
- export { decryptPackage, isEncryptedPackage };
223
+ export { WrongPasswordError, decryptPackage, isEncryptedPackage };
@@ -257,6 +257,17 @@ export interface InlineImage {
257
257
  readonly gain: number;
258
258
  readonly black: number;
259
259
  };
260
+ /**
261
+ * §20.1.8.16 `a:clrChange` — one colour of the picture replaced by another,
262
+ * or knocked out entirely when the destination states `a:alpha` at zero. A
263
+ * logo on a white card goes onto a dark slide that way (corpus: tdf113163,
264
+ * whose whole slide is a metafile with its white ground declared away).
265
+ */
266
+ readonly colorChange?: {
267
+ readonly fromHex: string;
268
+ readonly toHex: string;
269
+ readonly transparent: boolean;
270
+ };
260
271
  readonly width: Pt;
261
272
  readonly height: Pt;
262
273
  /** §20.1.8.55 `a:srcRect` — the part of the source the frame shows. */
@@ -915,6 +926,22 @@ export interface ImageBlock {
915
926
  readonly gain: number;
916
927
  readonly black: number;
917
928
  };
929
+ /**
930
+ * §20.1.8.4 `a:alphaModFix` — how opaque the picture is DRAWN, `0..1`; absent
931
+ * is opaque. A layout that lays a photograph behind its text sets it low
932
+ * (ArtisticEffectSample's cover shows one at 52%), and drawn full-strength
933
+ * the words on it are unreadable.
934
+ */
935
+ readonly alpha?: number;
936
+ /**
937
+ * §20.1.8.16 `a:clrChange` — one colour of the picture replaced by another,
938
+ * or knocked out entirely when the destination states `a:alpha` at zero.
939
+ */
940
+ readonly colorChange?: {
941
+ readonly fromHex: string;
942
+ readonly toHex: string;
943
+ readonly transparent: boolean;
944
+ };
918
945
  readonly width: Pt;
919
946
  readonly height: Pt;
920
947
  /** §20.1.8.55 `a:srcRect` — the part of the source the frame shows. */
@@ -1009,6 +1036,30 @@ export interface ShapeFill {
1009
1036
  * of the picture the box shows, as the fractions cut from each side.
1010
1037
  */
1011
1038
  readonly imageCrop?: ImageCrop;
1039
+ /**
1040
+ * §20.1.8.30 `a:stretch/a:fillRect` with POSITIVE insets — the part of the
1041
+ * shape's box the picture is stretched into, as fractions of that box. A
1042
+ * slide backed by a picture inset 55 % from the left shows it in the corner,
1043
+ * not across the slide (corpus: tdf153466.pptx). Negative insets are the
1044
+ * other case entirely — the picture zooming past the box — and read as a
1045
+ * crop of the source.
1046
+ */
1047
+ /**
1048
+ * §20.1.8.23 `a:duotone` — the picture recoloured into two tones: its dark
1049
+ * end becomes the first colour, its light end the second. An Office theme
1050
+ * that ships a photograph tints it this way, so a deck whose background is a
1051
+ * brown ridged texture is stored as a grey one (corpus: themes.pptx).
1052
+ */
1053
+ readonly duotone?: {
1054
+ readonly shadowHex: string;
1055
+ readonly highlightHex: string;
1056
+ };
1057
+ readonly imageFillRect?: {
1058
+ readonly left: number;
1059
+ readonly top: number;
1060
+ readonly right: number;
1061
+ readonly bottom: number;
1062
+ };
1012
1063
  /**
1013
1064
  * §20.1.2.3.1 `a:alpha` / §14.1.2.5 `@opacity` — how opaque the fill is,
1014
1065
  * `0..1`. Absent is opaque. The colour above is the fill's own, NOT composited
@@ -1315,6 +1366,15 @@ export interface Chart {
1315
1366
  * creates, and both references draw them.
1316
1367
  */
1317
1368
  readonly frameFillHex?: string;
1369
+ /**
1370
+ * §20.1.8.14 `a:blipFill` — the PICTURE the frame is filled with, when it is
1371
+ * filled with one rather than a colour. chart-texture-bg.pptx papers its
1372
+ * whole chart with a woven cloth, and a chart on white is a different chart.
1373
+ */
1374
+ readonly frameFillImage?: {
1375
+ readonly resource: ResourceId;
1376
+ readonly tiled?: boolean;
1377
+ };
1318
1378
  readonly frameLineHex?: string;
1319
1379
  /** The frame rule's width and dash, when it states them. */
1320
1380
  readonly frameLineWidthPt?: number;
@@ -1,4 +1,5 @@
1
1
  import { Chart } from '../document-model/index.js';
2
+ import { ResourceId } from '../ir/index.js';
2
3
  import { OpcPackage } from '../opc/index.js';
3
4
  import { ColorResolver } from './colors.js';
4
5
  /**
@@ -14,7 +15,7 @@ import { ColorResolver } from './colors.js';
14
15
  * @param resolveColor Maps a DrawingML colour reference to a 6-hex string.
15
16
  * @returns The parsed chart, or `null` when there is no `c:chart` / `c:plotArea`.
16
17
  */
17
- export declare function parseChart(chartXml: Uint8Array, resolveColor: ColorResolver): Chart | null;
18
+ export declare function parseChart(chartXml: Uint8Array, resolveColor: ColorResolver, resolveImage?: (relId: string) => ResourceId | undefined): Chart | null;
18
19
  /**
19
20
  * MS-ODRAWXML chartColorStyle (charts/colorsN.xml): the top-level colour list is
20
21
  * the series cycle (`meth="cycle"` — the common case; variations are luminance
@@ -37,7 +37,7 @@ var TYPE_OF_TAG = {
37
37
  * @param resolveColor Maps a DrawingML colour reference to a 6-hex string.
38
38
  * @returns The parsed chart, or `null` when there is no `c:chart` / `c:plotArea`.
39
39
  */
40
- function parseChart(chartXml, resolveColor) {
40
+ function parseChart(chartXml, resolveColor, resolveImage) {
41
41
  const tree = parser.parse(decoder.decode(chartXml));
42
42
  const chart = poFindByPath(tree, ["c:chartSpace", "c:chart"]);
43
43
  if (!chart) return null;
@@ -105,6 +105,7 @@ function parseChart(chartXml, resolveColor) {
105
105
  const frameLine = spaceSpPr ? poChildren(spaceSpPr).find((c) => poIs(c, "a:ln")) : void 0;
106
106
  const frameStyle = lineStyleOf(chartSpace, resolveColor);
107
107
  const frameFillHex = spaceSpPr ? frameFillOf(spaceSpPr, resolveColor) : "FFFFFF";
108
+ const frameFillImage = spaceSpPr ? blipFillOf(spaceSpPr, resolveImage) : void 0;
108
109
  const plotSpPr = poChildren(plotArea).find((c) => poIs(c, "c:spPr"));
109
110
  const plotFillHex = plotSpPr ? frameFillOf(plotSpPr, resolveColor) : void 0;
110
111
  const plotLine = lineStyleOf(plotArea, resolveColor);
@@ -142,6 +143,7 @@ function parseChart(chartXml, resolveColor) {
142
143
  ...valAxisMin !== void 0 ? { valAxisMin } : {},
143
144
  ...valAxisMax !== void 0 ? { valAxisMax } : {},
144
145
  ...frameFillHex ? { frameFillHex } : {},
146
+ ...frameFillImage ? { frameFillImage } : {},
145
147
  ...frameLineHex ? { frameLineHex } : {},
146
148
  ...frameStyle?.widthPt !== void 0 ? { frameLineWidthPt: frameStyle.widthPt } : {},
147
149
  ...frameStyle?.dash ? { frameLineDash: frameStyle.dash } : {},
@@ -248,6 +250,26 @@ function dataPointColors(ser, resolveColor) {
248
250
  return out;
249
251
  }
250
252
  /**
253
+ * §20.1.8.14 `a:blipFill` — the picture a chart element is filled with, and
254
+ * whether it TILES at its own size rather than stretching over the box.
255
+ *
256
+ * @param spPr The element's `c:spPr`.
257
+ * @param resolveImage The chart part's own blip resolver, when the caller has one.
258
+ * @returns The stored picture, or `undefined` when the fill is not one.
259
+ */
260
+ function blipFillOf(spPr, resolveImage) {
261
+ const blipFill = poChildren(spPr).find((c) => poIs(c, "a:blipFill"));
262
+ const blip = blipFill ? poChildren(blipFill).find((c) => poIs(c, "a:blip")) : void 0;
263
+ const relId = blip ? poAttr(blip, "embed") : void 0;
264
+ const resource = relId !== void 0 ? resolveImage?.(relId) : void 0;
265
+ if (resource === void 0 || !blipFill) return void 0;
266
+ const tiled = poChildren(blipFill).some((c) => poIs(c, "a:tile"));
267
+ return {
268
+ resource,
269
+ ...tiled ? { tiled } : {}
270
+ };
271
+ }
272
+ /**
251
273
  * The BACKGROUND fill of a frame (the chart space, the plot rectangle): its own
252
274
  * `a:solidFill` and nothing else. Unlike a series, a frame that states
253
275
  * `<a:noFill/>` is not filled in the colour of its own rule —
@@ -6,7 +6,7 @@ import { PoNode } from '../po-helpers.js';
6
6
  * (solid fills emit no transparency).
7
7
  */
8
8
  export interface ColorMod {
9
- readonly kind: 'lumMod' | 'lumOff' | 'shade' | 'tint' | 'alpha' | 'satMod' | 'hueOff' | 'satOff';
9
+ readonly kind: 'lumMod' | 'lumOff' | 'shade' | 'tint' | 'alpha' | 'satMod' | 'hueMod' | 'hueOff' | 'satOff';
10
10
  readonly val: number;
11
11
  }
12
12
  /**
@@ -38,20 +38,44 @@ export declare function applyColorMods(hex: string, mods: ReadonlyArray<ColorMod
38
38
  * scheme slots when a document carries no custom theme part.
39
39
  */
40
40
  export declare const DEFAULT_THEME_PALETTE: ReadonlyMap<string, string>;
41
+ /**
42
+ * Which theme slot each `schemeClr` name stands for. Only the text/background
43
+ * aliases are ever remapped; anything absent resolves under its own name.
44
+ */
45
+ export type SchemeAliases = Readonly<Record<string, string>>;
46
+ export declare const DEFAULT_SCHEME_ALIAS: SchemeAliases;
41
47
  /**
42
48
  * Resolve a `schemeClr` text/background alias (`tx1`/`bg1`/`tx2`/`bg2`,
43
49
  * §20.1.2.3.29) to its underlying `dk`/`lt` slot name; other names pass through.
50
+ *
51
+ * @param name The `schemeClr` value.
52
+ * @param alias The mapping to read it under — a presentation states its own in
53
+ * the master's `p:clrMap` (§19.3.1.6), where `bg1` may well mean `dk2`.
54
+ * @returns The theme slot to look up.
44
55
  */
45
- export declare function resolveSchemeName(name: string): string;
56
+ export declare function resolveSchemeName(name: string, alias?: SchemeAliases): string;
46
57
  /**
47
58
  * Build a {@link ColorResolver} over a scheme-name → hex `palette`. sRGB
48
59
  * references pass through verbatim (upper-cased); scheme references are aliased
49
60
  * (via {@link resolveSchemeName}) then looked up; colour transforms are applied.
50
61
  *
51
62
  * @param palette The scheme-name → RRGGBB map (e.g. {@link DEFAULT_THEME_PALETTE}).
63
+ * @param alias How the bg/tx aliases map onto the theme's slots; the default
64
+ * is the fixed DrawingML one, which is what a document without a colour map of
65
+ * its own (docx, xlsx) uses.
52
66
  * @returns A resolver that maps a {@link RawColor} to a hex string or `undefined`.
53
67
  */
54
- export declare function makeColorResolver(palette: ReadonlyMap<string, string>): ColorResolver;
68
+ export declare function makeColorResolver(palette: ReadonlyMap<string, string>, alias?: SchemeAliases): ColorResolver;
69
+ /**
70
+ * §20.1.4.2.10 `phClr` — inside a theme's style list the colours are written as
71
+ * "the placeholder", and the reference that reaches the style says which colour
72
+ * that is. Everything else resolves as it always did.
73
+ *
74
+ * @param base The document's own resolver.
75
+ * @param phHex The colour the reference carries (`a:fillRef`, `p:bgRef`, …).
76
+ * @returns A resolver that answers `phClr` with that colour, transforms and all.
77
+ */
78
+ export declare function placeholderColors(base: ColorResolver, phHex: string): ColorResolver;
55
79
  /** A {@link ColorResolver} backed by the {@link DEFAULT_THEME_PALETTE}. */
56
80
  export declare const defaultColorResolver: ColorResolver;
57
81
  /**
@@ -103,6 +103,9 @@ function applyColorMods(hex, mods) {
103
103
  const [h, s, l] = rgbToHsl(r, g, b);
104
104
  const l2 = m.kind === "lumMod" ? l * m.val : clamp01(l + m.val);
105
105
  [r, g, b] = hslToRgb(h, s, l2);
106
+ } else if (m.kind === "hueMod") {
107
+ const [h, sat, l] = rgbToHsl(r, g, b);
108
+ [r, g, b] = hslToRgb((h * m.val % 360 + 360) % 360, sat, l);
106
109
  } else if (m.kind === "hueOff") {
107
110
  const [h, sat, l] = rgbToHsl(r, g, b);
108
111
  [r, g, b] = hslToRgb(((h + m.val) % 360 + 360) % 360, sat, l);
@@ -138,7 +141,7 @@ var DEFAULT_THEME_PALETTE = new Map([
138
141
  ["hlink", "0563C1"],
139
142
  ["folHlink", "954F72"]
140
143
  ]);
141
- var SCHEME_ALIAS = {
144
+ var DEFAULT_SCHEME_ALIAS = {
142
145
  tx1: "dk1",
143
146
  bg1: "lt1",
144
147
  tx2: "dk2",
@@ -147,9 +150,14 @@ var SCHEME_ALIAS = {
147
150
  /**
148
151
  * Resolve a `schemeClr` text/background alias (`tx1`/`bg1`/`tx2`/`bg2`,
149
152
  * §20.1.2.3.29) to its underlying `dk`/`lt` slot name; other names pass through.
153
+ *
154
+ * @param name The `schemeClr` value.
155
+ * @param alias The mapping to read it under — a presentation states its own in
156
+ * the master's `p:clrMap` (§19.3.1.6), where `bg1` may well mean `dk2`.
157
+ * @returns The theme slot to look up.
150
158
  */
151
- function resolveSchemeName(name) {
152
- return SCHEME_ALIAS[name] ?? name;
159
+ function resolveSchemeName(name, alias = DEFAULT_SCHEME_ALIAS) {
160
+ return alias[name] ?? name;
153
161
  }
154
162
  /**
155
163
  * Build a {@link ColorResolver} over a scheme-name → hex `palette`. sRGB
@@ -157,15 +165,30 @@ function resolveSchemeName(name) {
157
165
  * (via {@link resolveSchemeName}) then looked up; colour transforms are applied.
158
166
  *
159
167
  * @param palette The scheme-name → RRGGBB map (e.g. {@link DEFAULT_THEME_PALETTE}).
168
+ * @param alias How the bg/tx aliases map onto the theme's slots; the default
169
+ * is the fixed DrawingML one, which is what a document without a colour map of
170
+ * its own (docx, xlsx) uses.
160
171
  * @returns A resolver that maps a {@link RawColor} to a hex string or `undefined`.
161
172
  */
162
- function makeColorResolver(palette) {
173
+ function makeColorResolver(palette, alias = DEFAULT_SCHEME_ALIAS) {
163
174
  return (raw) => {
164
- const base = "srgb" in raw ? raw.srgb.toUpperCase() : palette.get(resolveSchemeName(raw.scheme));
175
+ const base = "srgb" in raw ? raw.srgb.toUpperCase() : palette.get(resolveSchemeName(raw.scheme, alias));
165
176
  if (base === void 0) return void 0;
166
177
  return raw.mods && raw.mods.length > 0 ? applyColorMods(base, raw.mods) : base;
167
178
  };
168
179
  }
180
+ /**
181
+ * §20.1.4.2.10 `phClr` — inside a theme's style list the colours are written as
182
+ * "the placeholder", and the reference that reaches the style says which colour
183
+ * that is. Everything else resolves as it always did.
184
+ *
185
+ * @param base The document's own resolver.
186
+ * @param phHex The colour the reference carries (`a:fillRef`, `p:bgRef`, …).
187
+ * @returns A resolver that answers `phClr` with that colour, transforms and all.
188
+ */
189
+ function placeholderColors(base, phHex) {
190
+ return (raw) => "scheme" in raw && raw.scheme === "phClr" ? applyColorMods(phHex, raw.mods ?? []) : base(raw);
191
+ }
169
192
  /** A {@link ColorResolver} backed by the {@link DEFAULT_THEME_PALETTE}. */
170
193
  var defaultColorResolver = makeColorResolver(DEFAULT_THEME_PALETTE);
171
194
  var PRESET_COLORS = new Map([
@@ -198,6 +221,7 @@ function readColorMods(colorNode) {
198
221
  "tint",
199
222
  "alpha",
200
223
  "satMod",
224
+ "hueMod",
201
225
  "satOff"
202
226
  ]) if (poIs(c, `a:${kind}`)) {
203
227
  const v = poIntAttr(c, "val");
@@ -266,4 +290,4 @@ function resolveColorNode(c, resolveColor) {
266
290
  } : raw);
267
291
  }
268
292
  //#endregion
269
- export { DEFAULT_THEME_PALETTE, applyColorMods, defaultColorResolver, makeColorResolver, readColorMods, resolveColorNode };
293
+ export { DEFAULT_SCHEME_ALIAS, DEFAULT_THEME_PALETTE, applyColorMods, defaultColorResolver, makeColorResolver, placeholderColors, readColorMods, resolveColorNode };