reamkit 1.21.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.
- package/dist/esm/core/bytes.d.ts +22 -4
- package/dist/esm/core/bytes.js +73 -5
- package/dist/esm/core/document-model/types.d.ts +60 -0
- package/dist/esm/core/drawingml/chart-parser.d.ts +2 -1
- package/dist/esm/core/drawingml/chart-parser.js +23 -1
- package/dist/esm/core/drawingml/colors.d.ts +27 -3
- package/dist/esm/core/drawingml/colors.js +30 -6
- package/dist/esm/core/drawingml/preset-geometry.js +236 -0
- package/dist/esm/core/metafile/emf.js +2 -1
- package/dist/esm/core/metafile/symbol-fonts.d.ts +42 -0
- package/dist/esm/core/metafile/symbol-fonts.js +154 -0
- package/dist/esm/core/metafile/wmf.js +35 -2
- package/dist/esm/core/vector.d.ts +5 -0
- package/dist/esm/excel/sheet-shape-parser.js +1 -12
- package/dist/esm/excel/xlsx-reader.d.ts +1 -1
- package/dist/esm/excel/xlsx-reader.js +3 -3
- package/dist/esm/layout/page-doc.d.ts +38 -0
- package/dist/esm/layout/page-doc.js +34 -18
- package/dist/esm/layout/styled-layout.js +174 -49
- package/dist/esm/pdf/shading.d.ts +19 -0
- package/dist/esm/pdf/shading.js +84 -11
- package/dist/esm/pdf/styled-page-emitter.js +614 -444
- package/dist/esm/pptx/ole-preview.d.ts +10 -0
- package/dist/esm/pptx/ole-preview.js +52 -0
- package/dist/esm/pptx/placeholder-cascade.d.ts +69 -5
- package/dist/esm/pptx/placeholder-cascade.js +162 -25
- package/dist/esm/pptx/pptx-reader.js +247 -39
- package/dist/esm/pptx/slide-parser.d.ts +72 -10
- package/dist/esm/pptx/slide-parser.js +310 -82
- package/dist/esm/pptx/sp-helpers.js +19 -4
- package/dist/esm/pptx/table-style.d.ts +56 -0
- package/dist/esm/pptx/table-style.js +161 -0
- package/dist/esm/svg/svg-writer.js +53 -5
- package/dist/esm/word/docx-reader.js +2 -2
- package/dist/esm/word/drawing-parser.d.ts +34 -0
- package/dist/esm/word/drawing-parser.js +159 -13
- package/package.json +1 -1
package/dist/esm/core/bytes.d.ts
CHANGED
|
@@ -5,13 +5,31 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export declare function toBase64(bytes: Uint8Array): string;
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
|
14
|
-
*
|
|
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
|
/**
|
package/dist/esm/core/bytes.js
CHANGED
|
@@ -10,13 +10,81 @@ function toBase64(bytes) {
|
|
|
10
10
|
return btoa(bin);
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
|
19
|
-
*
|
|
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,
|
|
107
|
+
export { bytesInclude, packageHasPart, toBase64 };
|
|
@@ -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
|
|
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
|
|
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
|
|
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 };
|
|
@@ -117,6 +117,242 @@ function presetPaths(preset, w, h, adjust) {
|
|
|
117
117
|
case "leftArrow": return [blockArrow(w, h, adjust, "left")];
|
|
118
118
|
case "upArrow": return [blockArrow(w, h, adjust, "up")];
|
|
119
119
|
case "downArrow": return [blockArrow(w, h, adjust, "down")];
|
|
120
|
+
case "leftRightArrow": {
|
|
121
|
+
const ss = Math.min(w, h);
|
|
122
|
+
const t = clamp(frac(adjust, "adj1", 5e4), 0, 1) * ss;
|
|
123
|
+
const hl = clamp(frac(adjust, "adj2", 5e4), 0, 1) * ss;
|
|
124
|
+
const [sb, st] = [(h - t) / 2, (h + t) / 2];
|
|
125
|
+
return [polygon([
|
|
126
|
+
[0, h / 2],
|
|
127
|
+
[hl, h],
|
|
128
|
+
[hl, st],
|
|
129
|
+
[w - hl, st],
|
|
130
|
+
[w - hl, h],
|
|
131
|
+
[w, h / 2],
|
|
132
|
+
[w - hl, 0],
|
|
133
|
+
[w - hl, sb],
|
|
134
|
+
[hl, sb],
|
|
135
|
+
[hl, 0]
|
|
136
|
+
])];
|
|
137
|
+
}
|
|
138
|
+
case "notchedRightArrow": {
|
|
139
|
+
const ss = Math.min(w, h);
|
|
140
|
+
const t = clamp(frac(adjust, "adj1", 5e4), 0, 1) * ss;
|
|
141
|
+
const hl = clamp(frac(adjust, "adj2", 5e4), 0, 1) * ss;
|
|
142
|
+
const [sb, st] = [(h - t) / 2, (h + t) / 2];
|
|
143
|
+
const notch = Math.min(hl, w / 2);
|
|
144
|
+
return [polygon([
|
|
145
|
+
[0, sb],
|
|
146
|
+
[w - hl, sb],
|
|
147
|
+
[w - hl, 0],
|
|
148
|
+
[w, h / 2],
|
|
149
|
+
[w - hl, h],
|
|
150
|
+
[w - hl, st],
|
|
151
|
+
[0, st],
|
|
152
|
+
[notch, h / 2]
|
|
153
|
+
])];
|
|
154
|
+
}
|
|
155
|
+
case "stripedRightArrow": {
|
|
156
|
+
const ss = Math.min(w, h);
|
|
157
|
+
const t = clamp(frac(adjust, "adj1", 5e4), 0, 1) * ss;
|
|
158
|
+
const hl = clamp(frac(adjust, "adj2", 5e4), 0, 1) * ss;
|
|
159
|
+
const [sb, st] = [(h - t) / 2, (h + t) / 2];
|
|
160
|
+
const unit = ss / 12;
|
|
161
|
+
return [
|
|
162
|
+
polygon([
|
|
163
|
+
[0, sb],
|
|
164
|
+
[unit, sb],
|
|
165
|
+
[unit, st],
|
|
166
|
+
[0, st]
|
|
167
|
+
]),
|
|
168
|
+
polygon([
|
|
169
|
+
[unit * 2, sb],
|
|
170
|
+
[unit * 4, sb],
|
|
171
|
+
[unit * 4, st],
|
|
172
|
+
[unit * 2, st]
|
|
173
|
+
]),
|
|
174
|
+
polygon([
|
|
175
|
+
[unit * 5, sb],
|
|
176
|
+
[w - hl, sb],
|
|
177
|
+
[w - hl, 0],
|
|
178
|
+
[w, h / 2],
|
|
179
|
+
[w - hl, h],
|
|
180
|
+
[w - hl, st],
|
|
181
|
+
[unit * 5, st]
|
|
182
|
+
])
|
|
183
|
+
];
|
|
184
|
+
}
|
|
185
|
+
case "bentUpArrow": {
|
|
186
|
+
const ss = Math.min(w, h);
|
|
187
|
+
const t = clamp(frac(adjust, "adj2", 25e3), 0, 1) * ss;
|
|
188
|
+
const hw = clamp(frac(adjust, "adj3", 25e3), 0, 1) * ss * 2;
|
|
189
|
+
const hh = Math.min(hw, h);
|
|
190
|
+
const cx = w - hw / 2;
|
|
191
|
+
return [polygon([
|
|
192
|
+
[0, 0],
|
|
193
|
+
[cx + t / 2, 0],
|
|
194
|
+
[cx + t / 2, h - hh],
|
|
195
|
+
[w, h - hh],
|
|
196
|
+
[cx, h],
|
|
197
|
+
[cx - hw / 2, h - hh],
|
|
198
|
+
[cx - t / 2, h - hh],
|
|
199
|
+
[cx - t / 2, t],
|
|
200
|
+
[0, t]
|
|
201
|
+
])];
|
|
202
|
+
}
|
|
203
|
+
case "chevron":
|
|
204
|
+
case "homePlate": {
|
|
205
|
+
const ss = Math.min(w, h);
|
|
206
|
+
const x = Math.min(clamp(frac(adjust, "adj", 5e4), 0, 1) * ss, w);
|
|
207
|
+
const points = [
|
|
208
|
+
[0, 0],
|
|
209
|
+
[w - x, 0],
|
|
210
|
+
[w, h / 2],
|
|
211
|
+
[w - x, h],
|
|
212
|
+
[0, h]
|
|
213
|
+
];
|
|
214
|
+
if (preset === "chevron") points.push([x, h / 2]);
|
|
215
|
+
return [polygon(points)];
|
|
216
|
+
}
|
|
217
|
+
case "corner": {
|
|
218
|
+
const ss = Math.min(w, h);
|
|
219
|
+
const th = clamp(frac(adjust, "adj1", 5e4), 0, 1) * ss;
|
|
220
|
+
const tw = clamp(frac(adjust, "adj2", 5e4), 0, 1) * ss;
|
|
221
|
+
return [polygon([
|
|
222
|
+
[0, 0],
|
|
223
|
+
[w, 0],
|
|
224
|
+
[w, th],
|
|
225
|
+
[tw, th],
|
|
226
|
+
[tw, h],
|
|
227
|
+
[0, h]
|
|
228
|
+
])];
|
|
229
|
+
}
|
|
230
|
+
case "pie":
|
|
231
|
+
case "blockArc": {
|
|
232
|
+
const [rx, ry] = [w / 2, h / 2];
|
|
233
|
+
const start = -((adjust.get("adj1") ?? 0) / 6e4) * (Math.PI / 180);
|
|
234
|
+
let sweep = -((adjust.get("adj2") ?? (preset === "pie" ? 162e5 : 108e5)) / 6e4) * (Math.PI / 180) - start;
|
|
235
|
+
if (sweep > 0) sweep -= 2 * Math.PI;
|
|
236
|
+
const at = (a, r) => [rx + rx * r * Math.cos(a), ry + ry * r * Math.sin(a)];
|
|
237
|
+
const b = new PathBuilder();
|
|
238
|
+
const [sx0, sy0] = at(start, 1);
|
|
239
|
+
b.moveTo(sx0, sy0).append(arcToBeziers(rx, ry, rx, ry, start, sweep));
|
|
240
|
+
if (preset === "pie") return [b.lineTo(rx, ry).close().build()];
|
|
241
|
+
const inner = 1 - clamp(frac(adjust, "adj3", 25e3), 0, 1);
|
|
242
|
+
const [ix, iy] = at(start + sweep, inner);
|
|
243
|
+
return [b.lineTo(ix, iy).append(arcToBeziers(rx, ry, rx * inner, ry * inner, start + sweep, -sweep)).close().build()];
|
|
244
|
+
}
|
|
245
|
+
case "gear6":
|
|
246
|
+
case "gear9": {
|
|
247
|
+
const teeth = preset === "gear6" ? 6 : 9;
|
|
248
|
+
const ss = Math.min(w, h);
|
|
249
|
+
const depth = clamp(frac(adjust, "adj1", 15e3), 0, .4) * ss;
|
|
250
|
+
const [cx, cy] = [w / 2, h / 2];
|
|
251
|
+
const outer = Math.min(w, h) / 2;
|
|
252
|
+
const root = outer - depth;
|
|
253
|
+
const half = Math.PI / teeth / 2.6;
|
|
254
|
+
const pts = [];
|
|
255
|
+
for (let i = 0; i < teeth; i++) {
|
|
256
|
+
const a = i * 2 * Math.PI / teeth;
|
|
257
|
+
const gap = Math.PI / teeth;
|
|
258
|
+
pts.push([cx + root * Math.cos(a - gap + half), cy + root * Math.sin(a - gap + half)]);
|
|
259
|
+
pts.push([cx + outer * Math.cos(a - half), cy + outer * Math.sin(a - half)]);
|
|
260
|
+
pts.push([cx + outer * Math.cos(a + half), cy + outer * Math.sin(a + half)]);
|
|
261
|
+
pts.push([cx + root * Math.cos(a + gap - half), cy + root * Math.sin(a + gap - half)]);
|
|
262
|
+
}
|
|
263
|
+
return [polygon(pts)];
|
|
264
|
+
}
|
|
265
|
+
case "circularArrow":
|
|
266
|
+
case "leftCircularArrow": {
|
|
267
|
+
const ss = Math.min(w, h);
|
|
268
|
+
const t = clamp(frac(adjust, "adj1", 12500), .02, .45) * ss;
|
|
269
|
+
const [rx, ry] = [Math.max(t, w / 2 - t), Math.max(t, h / 2 - t)];
|
|
270
|
+
const [cx, cy] = [w / 2, h / 2];
|
|
271
|
+
const deg = (v) => -(v / 6e4) * (Math.PI / 180);
|
|
272
|
+
const tail = deg(adjust.get("adj3") ?? (preset === "circularArrow" ? 20457681 : 125e5));
|
|
273
|
+
let sweep = deg(adjust.get("adj2") ?? (preset === "circularArrow" ? 1142319 : 1142319)) - tail;
|
|
274
|
+
const turn = preset === "circularArrow" ? -1 : 1;
|
|
275
|
+
while (sweep * turn < 0) sweep += turn * 2 * Math.PI;
|
|
276
|
+
while (Math.abs(sweep) > 2 * Math.PI) sweep -= turn * 2 * Math.PI;
|
|
277
|
+
const headSweep = turn * Math.min(Math.abs(sweep) / 3, .35);
|
|
278
|
+
const bandEnd = tail + sweep - headSweep;
|
|
279
|
+
const at = (a, r) => [clamp(cx + (rx + r) * Math.cos(a), 0, w), clamp(cy + (ry + r) * Math.sin(a), 0, h)];
|
|
280
|
+
const b = new PathBuilder();
|
|
281
|
+
const [ox, oy] = at(tail, t / 2);
|
|
282
|
+
b.moveTo(ox, oy).append(arcToBeziers(cx, cy, rx + t / 2, ry + t / 2, tail, bandEnd - tail).map((seg) => seg.op === "cubic" ? {
|
|
283
|
+
op: "cubic",
|
|
284
|
+
x1: clamp(seg.x1, 0, w),
|
|
285
|
+
y1: clamp(seg.y1, 0, h),
|
|
286
|
+
x2: clamp(seg.x2, 0, w),
|
|
287
|
+
y2: clamp(seg.y2, 0, h),
|
|
288
|
+
x: clamp(seg.x, 0, w),
|
|
289
|
+
y: clamp(seg.y, 0, h)
|
|
290
|
+
} : seg));
|
|
291
|
+
const [hx1, hy1] = at(bandEnd, t);
|
|
292
|
+
const [tipX, tipY] = at(tail + sweep, 0);
|
|
293
|
+
const [hx2, hy2] = at(bandEnd, -t);
|
|
294
|
+
const [ix, iy] = at(bandEnd, -t / 2);
|
|
295
|
+
return [b.lineTo(hx1, hy1).lineTo(tipX, tipY).lineTo(hx2, hy2).lineTo(ix, iy).append(arcToBeziers(cx, cy, rx - t / 2, ry - t / 2, bandEnd, tail - bandEnd)).close().build()];
|
|
296
|
+
}
|
|
297
|
+
case "actionButtonBlank": return [{ segments: roundRectSegments(w, h, Math.min(w, h) * .1) }];
|
|
298
|
+
case "flowChartMerge": return [polygon([
|
|
299
|
+
[0, h],
|
|
300
|
+
[w, h],
|
|
301
|
+
[w / 2, 0]
|
|
302
|
+
])];
|
|
303
|
+
case "flowChartExtract": return [polygon([
|
|
304
|
+
[0, 0],
|
|
305
|
+
[w, 0],
|
|
306
|
+
[w / 2, h]
|
|
307
|
+
])];
|
|
308
|
+
case "flowChartDelay": return [new PathBuilder().moveTo(0, 0).lineTo(w / 2, 0).append(arcToBeziers(w / 2, h / 2, w / 2, h / 2, -Math.PI / 2, Math.PI)).lineTo(0, h).close().build()];
|
|
309
|
+
case "flowChartMagneticDisk": {
|
|
310
|
+
const ry = h / 6;
|
|
311
|
+
return [new PathBuilder().moveTo(0, h - ry).append(arcToBeziers(w / 2, h - ry, w / 2, ry, Math.PI, -2 * Math.PI)).moveTo(0, h - ry).lineTo(0, ry).append(arcToBeziers(w / 2, ry, w / 2, ry, Math.PI, -Math.PI)).lineTo(w, h - ry).build()];
|
|
312
|
+
}
|
|
313
|
+
case "flowChartMagneticTape": {
|
|
314
|
+
const foot = h / 8;
|
|
315
|
+
return [{ segments: ellipseSegments(w, h) }, polygon([
|
|
316
|
+
[w / 2, 0],
|
|
317
|
+
[w, 0],
|
|
318
|
+
[w, foot],
|
|
319
|
+
[w / 2, foot]
|
|
320
|
+
])];
|
|
321
|
+
}
|
|
322
|
+
case "flowChartPunchedTape": {
|
|
323
|
+
const wave = h / 5;
|
|
324
|
+
return [new PathBuilder().moveTo(0, wave).cubicTo(w / 4, wave * 2, w * 3 / 4, 0, w, wave).lineTo(w, h - wave).cubicTo(w * 3 / 4, h - wave * 2, w / 4, h, 0, h - wave).close().build()];
|
|
325
|
+
}
|
|
326
|
+
case "wedgeRoundRectCallout": {
|
|
327
|
+
const r = clamp(frac(adjust, "adj3", 16667), 0, .5) * Math.min(w, h);
|
|
328
|
+
const tipX = w / 2 + frac(adjust, "adj1", -20833) * w;
|
|
329
|
+
const tipY = h / 2 - frac(adjust, "adj2", 62500) * h;
|
|
330
|
+
const [cx, cy] = [w / 2, h / 2];
|
|
331
|
+
const len = Math.hypot(tipX - cx, tipY - cy) || 1;
|
|
332
|
+
const [ux, uy] = [(tipX - cx) / len, (tipY - cy) / len];
|
|
333
|
+
const base = Math.min(w, h) / 6;
|
|
334
|
+
return [{ segments: roundRectSegments(w, h, r) }, polygon([
|
|
335
|
+
[cx - uy * base, cy + ux * base],
|
|
336
|
+
[cx + uy * base, cy - ux * base],
|
|
337
|
+
[tipX, tipY]
|
|
338
|
+
])];
|
|
339
|
+
}
|
|
340
|
+
case "leftBracket":
|
|
341
|
+
case "rightBracket":
|
|
342
|
+
case "leftBrace":
|
|
343
|
+
case "rightBrace": {
|
|
344
|
+
const brace = preset === "leftBrace" || preset === "rightBrace";
|
|
345
|
+
const r = Math.min(clamp(frac(adjust, "adj1", 8333), 0, .5) * h, brace ? w / 2 : w);
|
|
346
|
+
const mirrored = preset === "rightBracket" || preset === "rightBrace";
|
|
347
|
+
const x = (v) => mirrored ? w - v : v;
|
|
348
|
+
const spine = brace ? r : 0;
|
|
349
|
+
const b = new PathBuilder().moveTo(x(w), 0).lineTo(x(spine + r), 0).lineTo(x(spine), r);
|
|
350
|
+
if (brace) {
|
|
351
|
+
const mid = clamp(frac(adjust, "adj2", 5e4), 0, 1) * h;
|
|
352
|
+
b.lineTo(x(spine), Math.max(r, mid - r)).lineTo(x(0), mid).lineTo(x(spine), Math.min(h - r, mid + r));
|
|
353
|
+
}
|
|
354
|
+
return [b.lineTo(x(spine), h - r).lineTo(x(spine + r), h).lineTo(x(w), h).build()];
|
|
355
|
+
}
|
|
120
356
|
default: return null;
|
|
121
357
|
}
|
|
122
358
|
}
|