reamkit 1.22.0 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/core/converter/ream.d.ts +16 -0
- package/dist/esm/core/converter/ream.js +59 -9
- package/dist/esm/core/document-model/types.d.ts +18 -0
- package/dist/esm/core/drawingml/chart-geometry.js +11 -5
- package/dist/esm/core/drawingml/colors.d.ts +4 -2
- package/dist/esm/core/drawingml/colors.js +15 -0
- package/dist/esm/core/drawingml/theme-parser.d.ts +38 -0
- package/dist/esm/core/drawingml/theme-parser.js +64 -1
- package/dist/esm/core/fonts/families.d.ts +10 -0
- package/dist/esm/core/fonts/families.js +44 -0
- package/dist/esm/core/fonts/index.d.ts +3 -2
- package/dist/esm/core/fonts/remote-fonts.d.ts +50 -0
- package/dist/esm/core/fonts/remote-fonts.js +114 -8
- package/dist/esm/core/fonts/scripts.d.ts +26 -0
- package/dist/esm/core/fonts/scripts.js +177 -0
- package/dist/esm/core/metafile/blit.d.ts +33 -0
- package/dist/esm/core/metafile/blit.js +67 -0
- package/dist/esm/core/metafile/dib.d.ts +61 -0
- package/dist/esm/core/metafile/dib.js +205 -0
- package/dist/esm/core/metafile/emf.js +137 -23
- package/dist/esm/core/metafile/picture.d.ts +12 -1
- package/dist/esm/core/metafile/wmf.js +149 -9
- package/dist/esm/core/ole/escher-blip.d.ts +12 -0
- package/dist/esm/core/ole/escher-blip.js +88 -0
- package/dist/esm/{pdf-reader → core}/png-encode.js +1 -1
- package/dist/esm/excel/print-model.js +3 -1
- package/dist/esm/excel/styles-parser.js +1 -1
- package/dist/esm/excel/xls/biff-reader.js +53 -18
- package/dist/esm/excel/xls/biff-styles.d.ts +5 -2
- package/dist/esm/excel/xls/biff-styles.js +9 -7
- package/dist/esm/excel/xls/escher.js +90 -24
- package/dist/esm/layout/page-doc.d.ts +39 -0
- package/dist/esm/layout/styled-layout.d.ts +2 -2
- package/dist/esm/layout/styled-layout.js +301 -35
- package/dist/esm/pdf/styled-page-emitter.js +35 -2
- package/dist/esm/pdf-reader/image-decode.js +1 -1
- package/dist/esm/pptx/embedded-fonts.d.ts +31 -0
- package/dist/esm/pptx/embedded-fonts.js +104 -0
- package/dist/esm/pptx/placeholder-cascade.d.ts +73 -6
- package/dist/esm/pptx/placeholder-cascade.js +114 -18
- package/dist/esm/pptx/ppt/ppt-reader.js +120 -10
- package/dist/esm/pptx/ppt/ppt-text.d.ts +66 -1
- package/dist/esm/pptx/ppt/ppt-text.js +576 -137
- package/dist/esm/pptx/pptx-reader.d.ts +8 -0
- package/dist/esm/pptx/pptx-reader.js +27 -5
- package/dist/esm/pptx/preset-table-styles.d.ts +9 -0
- package/dist/esm/pptx/preset-table-styles.js +27 -0
- package/dist/esm/pptx/slide-parser.d.ts +7 -1
- package/dist/esm/pptx/slide-parser.js +106 -33
- package/dist/esm/pptx/sp-helpers.d.ts +2 -1
- package/dist/esm/pptx/sp-helpers.js +3 -2
- package/dist/esm/pptx/table-style.d.ts +9 -1
- package/dist/esm/pptx/table-style.js +49 -7
- package/dist/esm/word/document-parser.d.ts +7 -1
- package/dist/esm/word/document-parser.js +6 -6
- package/dist/esm/word/docx-reader.js +6 -3
- package/dist/esm/word/docx-to-pdf.d.ts +8 -7
- package/dist/esm/word/docx-to-pdf.js +25 -7
- package/dist/esm/word/drawing-parser.js +31 -19
- package/dist/esm/word/numbering-parser.d.ts +2 -1
- package/dist/esm/word/numbering-parser.js +6 -6
- package/dist/esm/word/paragraph-properties.d.ts +2 -1
- package/dist/esm/word/paragraph-properties.js +2 -2
- package/dist/esm/word/run-properties.d.ts +5 -2
- package/dist/esm/word/run-properties.js +13 -8
- package/dist/esm/word/styles-parser.d.ts +2 -1
- package/dist/esm/word/styles-parser.js +12 -12
- package/dist/esm/word/table-parser.js +3 -3
- package/dist/esm/word/theme-fonts.d.ts +10 -0
- package/dist/esm/word/theme-fonts.js +28 -0
- package/package.json +1 -1
- /package/dist/esm/{pdf-reader → core}/png-encode.d.ts +0 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Loss } from '../core/ir/index.js';
|
|
2
|
+
import { OpcPackage } from '../core/opc/index.js';
|
|
3
|
+
import { PoNode } from '../core/po-helpers.js';
|
|
4
|
+
import { FontRegistry } from '../core/font/index.js';
|
|
5
|
+
/** What an `fntdata` part turned out to hold. */
|
|
6
|
+
type FaceBytes = {
|
|
7
|
+
readonly kind: 'font';
|
|
8
|
+
readonly bytes: Uint8Array;
|
|
9
|
+
} | {
|
|
10
|
+
readonly kind: 'compressed';
|
|
11
|
+
} | {
|
|
12
|
+
readonly kind: 'unreadable';
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Read one `fntdata` part: the font itself when the container stores it
|
|
16
|
+
* plainly, and what stopped it otherwise.
|
|
17
|
+
*
|
|
18
|
+
* @param data The part's bytes.
|
|
19
|
+
*/
|
|
20
|
+
export declare function readFntData(data: Uint8Array): FaceBytes;
|
|
21
|
+
/**
|
|
22
|
+
* §19.2.1.13 — the fonts a deck embeds, by normalized family name.
|
|
23
|
+
*
|
|
24
|
+
* @param pkg The opened `.pptx` package.
|
|
25
|
+
* @param presPath The presentation part's path (relationships resolve from it).
|
|
26
|
+
* @param pres Its parsed `p:presentation` element.
|
|
27
|
+
* @param onLoss Where a face that cannot be unpacked records itself.
|
|
28
|
+
* @returns The families whose faces were readable (empty when none were).
|
|
29
|
+
*/
|
|
30
|
+
export declare function loadPptxEmbeddedFonts(pkg: OpcPackage, presPath: string, pres: PoNode | undefined, onLoss: (loss: Loss) => void): Map<string, FontRegistry>;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { FontRegistry } from "../core/font/font-registry.js";
|
|
2
|
+
import { poAttr, poChildren, poIs } from "../core/po-helpers.js";
|
|
3
|
+
//#region src/pptx/embedded-fonts.ts
|
|
4
|
+
var REL_FONT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/font";
|
|
5
|
+
/** §19.2.1.13 — the four faces a `p:embeddedFont` may name, by element. */
|
|
6
|
+
var VARIANTS = [
|
|
7
|
+
["p:regular", "regular"],
|
|
8
|
+
["p:bold", "bold"],
|
|
9
|
+
["p:italic", "italic"],
|
|
10
|
+
["p:boldItalic", "boldItalic"]
|
|
11
|
+
];
|
|
12
|
+
var EOT_VERSIONS = new Set([
|
|
13
|
+
65536,
|
|
14
|
+
131073,
|
|
15
|
+
131074
|
|
16
|
+
]);
|
|
17
|
+
var TTEMBED_TTCOMPRESSED = 4;
|
|
18
|
+
var TTEMBED_XORENCRYPTDATA = 268435456;
|
|
19
|
+
/**
|
|
20
|
+
* Read one `fntdata` part: the font itself when the container stores it
|
|
21
|
+
* plainly, and what stopped it otherwise.
|
|
22
|
+
*
|
|
23
|
+
* @param data The part's bytes.
|
|
24
|
+
*/
|
|
25
|
+
function readFntData(data) {
|
|
26
|
+
if (isSfnt(data)) return {
|
|
27
|
+
kind: "font",
|
|
28
|
+
bytes: data
|
|
29
|
+
};
|
|
30
|
+
if (data.length < 82) return { kind: "unreadable" };
|
|
31
|
+
const v = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
32
|
+
const eotSize = v.getUint32(0, true);
|
|
33
|
+
const version = v.getUint32(8, true);
|
|
34
|
+
const flags = v.getUint32(12, true);
|
|
35
|
+
if (eotSize !== data.length || !EOT_VERSIONS.has(version)) return { kind: "unreadable" };
|
|
36
|
+
if ((flags & (TTEMBED_TTCOMPRESSED | TTEMBED_XORENCRYPTDATA)) !== 0) return { kind: "compressed" };
|
|
37
|
+
let at = 82;
|
|
38
|
+
for (let i = 0; i < 4; i++) {
|
|
39
|
+
if (at + 2 > data.length) return { kind: "unreadable" };
|
|
40
|
+
at += 2 + v.getUint16(at, true) + 2;
|
|
41
|
+
}
|
|
42
|
+
if (version !== 65536) {
|
|
43
|
+
if (at + 2 > data.length) return { kind: "unreadable" };
|
|
44
|
+
at += 2 + v.getUint16(at, true);
|
|
45
|
+
at = data.length - v.getUint32(4, true);
|
|
46
|
+
}
|
|
47
|
+
const bytes = data.subarray(at);
|
|
48
|
+
return isSfnt(bytes) ? {
|
|
49
|
+
kind: "font",
|
|
50
|
+
bytes
|
|
51
|
+
} : { kind: "unreadable" };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* §19.2.1.13 — the fonts a deck embeds, by normalized family name.
|
|
55
|
+
*
|
|
56
|
+
* @param pkg The opened `.pptx` package.
|
|
57
|
+
* @param presPath The presentation part's path (relationships resolve from it).
|
|
58
|
+
* @param pres Its parsed `p:presentation` element.
|
|
59
|
+
* @param onLoss Where a face that cannot be unpacked records itself.
|
|
60
|
+
* @returns The families whose faces were readable (empty when none were).
|
|
61
|
+
*/
|
|
62
|
+
function loadPptxEmbeddedFonts(pkg, presPath, pres, onLoss) {
|
|
63
|
+
const out = /* @__PURE__ */ new Map();
|
|
64
|
+
const list = pres ? poChildren(pres).find((c) => poIs(c, "p:embeddedFontLst")) : void 0;
|
|
65
|
+
if (!list) return out;
|
|
66
|
+
const relById = new Map(pkg.getPartRelationships(presPath).map((r) => [r.id, r]));
|
|
67
|
+
for (const font of poChildren(list).filter((c) => poIs(c, "p:embeddedFont"))) {
|
|
68
|
+
const bytes = {};
|
|
69
|
+
let compressed = false;
|
|
70
|
+
for (const [tag, variant] of VARIANTS) {
|
|
71
|
+
const node = poChildren(font).find((c) => poIs(c, tag));
|
|
72
|
+
const rId = node ? poAttr(node, "r:id") : void 0;
|
|
73
|
+
const rel = rId !== void 0 ? relById.get(rId) : void 0;
|
|
74
|
+
if (!rel || rel.type !== REL_FONT) continue;
|
|
75
|
+
const part = pkg.resolveRelatedPart(presPath, rel);
|
|
76
|
+
if (!part) continue;
|
|
77
|
+
const face = readFntData(part.data);
|
|
78
|
+
if (face.kind === "compressed") compressed = true;
|
|
79
|
+
if (face.kind === "font") bytes[variant] = face.bytes;
|
|
80
|
+
}
|
|
81
|
+
const typeface = poChildren(font).find((c) => poIs(c, "p:font"));
|
|
82
|
+
const name = (typeface ? poAttr(typeface, "typeface") : void 0)?.trim() ?? "";
|
|
83
|
+
if (bytes.regular) try {
|
|
84
|
+
out.set(name.toLowerCase(), FontRegistry.fromBytes({
|
|
85
|
+
...bytes,
|
|
86
|
+
regular: bytes.regular
|
|
87
|
+
}));
|
|
88
|
+
continue;
|
|
89
|
+
} catch {}
|
|
90
|
+
if (compressed) onLoss({
|
|
91
|
+
feature: "font",
|
|
92
|
+
detail: `the deck embeds ${name || "a font"}, packed with MicroType Express (EOT TTEMBED_TTCOMPRESSED); it renders in a substitute`,
|
|
93
|
+
severity: "substituted"
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
function isSfnt(data) {
|
|
99
|
+
if (data.length < 4) return false;
|
|
100
|
+
const sig = (data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]) >>> 0;
|
|
101
|
+
return sig === 65536 || sig === 1330926671 || sig === 1953658213 || sig === 1953784678;
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
export { loadPptxEmbeddedFonts };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { ParagraphProperties, RunProperties } from '../core/document-model/index.js';
|
|
2
2
|
import { ColorResolver } from '../core/drawingml/colors.js';
|
|
3
|
+
import { ThemeFonts } from '../core/drawingml/theme-parser.js';
|
|
3
4
|
import { PoNode } from '../core/po-helpers.js';
|
|
4
5
|
import { PlaceholderRef, ShapeBoxEmu } from './sp-helpers.js';
|
|
6
|
+
import { Pt } from '../core/ir/index.js';
|
|
5
7
|
/**
|
|
6
8
|
* The placeholder cascade (PX2): the resolver a slide parser consults whenever a
|
|
7
9
|
* slide placeholder shape (`p:sp` with a `p:ph`) lacks its own geometry or run
|
|
@@ -27,17 +29,32 @@ export interface PlaceholderCascade {
|
|
|
27
29
|
*/
|
|
28
30
|
readonly anchorFor: (ph: PlaceholderRef) => 't' | 'ctr' | 'b' | undefined;
|
|
29
31
|
/**
|
|
30
|
-
* The
|
|
31
|
-
*
|
|
32
|
-
*
|
|
32
|
+
* The prototypes' own `p:spPr`, nearest first — the layout's, then the
|
|
33
|
+
* master's — for the properties a slide placeholder inherits rather than
|
|
34
|
+
* states: its fill, its outline, its geometry.
|
|
35
|
+
*
|
|
36
|
+
* A CHAIN and not one node, because the inheritance is per property. The
|
|
37
|
+
* layout's prototype may state a box and nothing else, and the fill then
|
|
38
|
+
* comes from the master: tdf104015's title is red in every reader and was a
|
|
39
|
+
* bare outline here, because the layout's `p:spPr` existed and stopped the
|
|
40
|
+
* search.
|
|
33
41
|
*/
|
|
34
|
-
readonly shapePropsFor: (ph: PlaceholderRef) => PoNode
|
|
42
|
+
readonly shapePropsFor: (ph: PlaceholderRef) => ReadonlyArray<PoNode>;
|
|
35
43
|
}
|
|
36
44
|
/** One level of a text style: how its paragraphs sit and how their runs read. */
|
|
37
45
|
interface LevelStyle {
|
|
38
46
|
readonly run: RunProperties;
|
|
39
47
|
readonly paragraph: ParagraphProperties;
|
|
40
48
|
readonly bullet?: LevelBullet;
|
|
49
|
+
/**
|
|
50
|
+
* §21.1.2.2.9/.10 — the space around the paragraph stated as a FRACTION of a
|
|
51
|
+
* line rather than a distance. A fraction of WHAT is only known once the
|
|
52
|
+
* levels have merged and the size is settled, so it travels this far raw.
|
|
53
|
+
*/
|
|
54
|
+
readonly spacingPct?: {
|
|
55
|
+
readonly before?: number;
|
|
56
|
+
readonly after?: number;
|
|
57
|
+
};
|
|
41
58
|
}
|
|
42
59
|
/**
|
|
43
60
|
* §21.1.2.4.4/.5/.6 — the bullet a level declares: none at all, a literal
|
|
@@ -78,8 +95,24 @@ export declare function parseBullet(pPr: PoNode | undefined, colors?: ColorResol
|
|
|
78
95
|
* @param layoutTree The parsed `p:sldLayout` part.
|
|
79
96
|
* @param masterTree The parsed `p:sldMaster` part, when present.
|
|
80
97
|
* @param colors The deck's colour resolver, for scheme colours in the text styles.
|
|
98
|
+
* @param deckDefaults The presentation's own default text style, as the floor.
|
|
99
|
+
* @param themeFonts The theme's font scheme, for the `+mn-lt` tokens.
|
|
100
|
+
*/
|
|
101
|
+
export declare function buildPlaceholderCascade(layoutTree: ReadonlyArray<PoNode>, masterTree: ReadonlyArray<PoNode> | undefined, colors: ColorResolver, deckDefaults?: ReadonlyArray<LevelStyle>, themeFonts?: ThemeFonts): PlaceholderCascade;
|
|
102
|
+
/**
|
|
103
|
+
* §21.1.2.2.9/.10 — the space a paragraph states as a FRACTION of a line, in
|
|
104
|
+
* points.
|
|
105
|
+
*
|
|
106
|
+
* A "line" here is the paragraph's own text at its natural height, which is the
|
|
107
|
+
* size times the same 1.2 the layout gives an unstated line. A level that
|
|
108
|
+
* states the fraction and leaves the size to another level is resolved once
|
|
109
|
+
* they have merged, which is why this is not read where the fraction is.
|
|
110
|
+
*
|
|
111
|
+
* @param pct The fraction, `0..n` (20% arrives as 0.2).
|
|
112
|
+
* @param sizePt The paragraph's resolved font size.
|
|
113
|
+
* @returns The distance, or `undefined` when the size is not known.
|
|
81
114
|
*/
|
|
82
|
-
export declare function
|
|
115
|
+
export declare function spacingFromLineFraction(pct: number | undefined, sizePt: number | undefined): Pt | undefined;
|
|
83
116
|
/**
|
|
84
117
|
* `a:lstStyle` (or a `p:txStyles` family) → its nine levels. Shared by the
|
|
85
118
|
* deck's default text style, the master's families and a prototype's own list,
|
|
@@ -90,5 +123,39 @@ export declare function buildPlaceholderCascade(layoutTree: ReadonlyArray<PoNode
|
|
|
90
123
|
* @param prefix The level element prefix: `a:lvl` here, always.
|
|
91
124
|
* @returns The nine levels, or an empty list when there is no such node.
|
|
92
125
|
*/
|
|
93
|
-
export declare function parseLevelStyles(list: PoNode | undefined, colors: ColorResolver): Array<LevelStyle>;
|
|
126
|
+
export declare function parseLevelStyles(list: PoNode | undefined, colors: ColorResolver, themeFonts?: ThemeFonts): Array<LevelStyle>;
|
|
127
|
+
/**
|
|
128
|
+
* A level's paragraph shape — §21.1.2.2.7 `@algn`, `@marL`/`@indent`, and the
|
|
129
|
+
* space around and between its lines.
|
|
130
|
+
*
|
|
131
|
+
* Both spacings come in two spellings: `a:spcPts` is a distance (hundredths of
|
|
132
|
+
* a point) and `a:spcPct` a FRACTION of a line (thousandths of a percent). Only
|
|
133
|
+
* the distance was read, so a deck that spaces its bullets the usual way — by
|
|
134
|
+
* fraction — set them solid.
|
|
135
|
+
*/
|
|
136
|
+
export declare function pPrToParagraphProps(lvlPr: PoNode | undefined): ParagraphProperties;
|
|
137
|
+
/**
|
|
138
|
+
* §21.1.2.2.5 `a:lnSpc` — the height of each line: a fraction of the natural
|
|
139
|
+
* one (`a:spcPct`) or a distance (`a:spcPts`).
|
|
140
|
+
*
|
|
141
|
+
* The model states a multiple the way §17.3.1.33 does — 12pt under the `auto`
|
|
142
|
+
* rule IS single — so a fraction needs no font size to express, which is why
|
|
143
|
+
* this resolves here and the paragraph SPACING has to wait for the size.
|
|
144
|
+
*
|
|
145
|
+
* @param pPr The paragraph properties (a level's or a paragraph's own).
|
|
146
|
+
* @returns The line spacing, or nothing when the node states none.
|
|
147
|
+
*/
|
|
148
|
+
export declare function lineSpacing(pPr: PoNode | undefined): ParagraphProperties;
|
|
149
|
+
/**
|
|
150
|
+
* §21.1.2.2.9/.10 — the fraction of a line a paragraph puts before and after
|
|
151
|
+
* itself, when it states one. The distance form is read beside it; this is what
|
|
152
|
+
* the caller must still resolve against the size.
|
|
153
|
+
*
|
|
154
|
+
* @param pPr The paragraph properties.
|
|
155
|
+
* @returns The two fractions (`0..n`), each present only when stated.
|
|
156
|
+
*/
|
|
157
|
+
export declare function spacingFractions(pPr: PoNode | undefined): {
|
|
158
|
+
readonly before?: number;
|
|
159
|
+
readonly after?: number;
|
|
160
|
+
} | undefined;
|
|
94
161
|
export {};
|
|
@@ -55,11 +55,13 @@ function categoryOf(type) {
|
|
|
55
55
|
* @param layoutTree The parsed `p:sldLayout` part.
|
|
56
56
|
* @param masterTree The parsed `p:sldMaster` part, when present.
|
|
57
57
|
* @param colors The deck's colour resolver, for scheme colours in the text styles.
|
|
58
|
+
* @param deckDefaults The presentation's own default text style, as the floor.
|
|
59
|
+
* @param themeFonts The theme's font scheme, for the `+mn-lt` tokens.
|
|
58
60
|
*/
|
|
59
|
-
function buildPlaceholderCascade(layoutTree, masterTree, colors, deckDefaults = []) {
|
|
60
|
-
const layoutPhs = collectPlaceholders(layoutTree, "p:sldLayout", colors);
|
|
61
|
-
const masterPhs = masterTree ? collectPlaceholders(masterTree, "p:sldMaster", colors) : [];
|
|
62
|
-
const txStyles = masterTree ? collectTxStyles(masterTree, colors) : {
|
|
61
|
+
function buildPlaceholderCascade(layoutTree, masterTree, colors, deckDefaults = [], themeFonts) {
|
|
62
|
+
const layoutPhs = collectPlaceholders(layoutTree, "p:sldLayout", colors, themeFonts);
|
|
63
|
+
const masterPhs = masterTree ? collectPlaceholders(masterTree, "p:sldMaster", colors, themeFonts) : [];
|
|
64
|
+
const txStyles = masterTree ? collectTxStyles(masterTree, colors, themeFonts) : {
|
|
63
65
|
title: [],
|
|
64
66
|
body: [],
|
|
65
67
|
other: []
|
|
@@ -67,7 +69,8 @@ function buildPlaceholderCascade(layoutTree, masterTree, colors, deckDefaults =
|
|
|
67
69
|
const styleAt = (ph, level) => {
|
|
68
70
|
const layers = [at(deckDefaults, level)];
|
|
69
71
|
if (ph) layers.push(at(txStyles[categoryOf(ph.type)], level), at(matchPlaceholder(masterPhs, ph)?.levels ?? [], level), at(matchPlaceholder(layoutPhs, ph)?.levels ?? [], level));
|
|
70
|
-
|
|
72
|
+
const merged = layers.reduce(mergeLevels, EMPTY_LEVEL);
|
|
73
|
+
return merged.spacingPct === void 0 ? merged : withResolvedSpacing(merged);
|
|
71
74
|
};
|
|
72
75
|
return {
|
|
73
76
|
geometryFor(ph) {
|
|
@@ -86,7 +89,7 @@ function buildPlaceholderCascade(layoutTree, masterTree, colors, deckDefaults =
|
|
|
86
89
|
return matchPlaceholder(layoutPhs, ph)?.anchor ?? matchPlaceholder(masterPhs, ph)?.anchor;
|
|
87
90
|
},
|
|
88
91
|
shapePropsFor(ph) {
|
|
89
|
-
return matchPlaceholder(layoutPhs, ph)?.spPr
|
|
92
|
+
return [matchPlaceholder(layoutPhs, ph)?.spPr, matchPlaceholder(masterPhs, ph)?.spPr].filter((n) => n !== void 0);
|
|
90
93
|
}
|
|
91
94
|
};
|
|
92
95
|
}
|
|
@@ -94,6 +97,39 @@ var EMPTY_LEVEL = {
|
|
|
94
97
|
run: {},
|
|
95
98
|
paragraph: {}
|
|
96
99
|
};
|
|
100
|
+
/**
|
|
101
|
+
* §21.1.2.2.9/.10 — the space a paragraph states as a FRACTION of a line, in
|
|
102
|
+
* points.
|
|
103
|
+
*
|
|
104
|
+
* A "line" here is the paragraph's own text at its natural height, which is the
|
|
105
|
+
* size times the same 1.2 the layout gives an unstated line. A level that
|
|
106
|
+
* states the fraction and leaves the size to another level is resolved once
|
|
107
|
+
* they have merged, which is why this is not read where the fraction is.
|
|
108
|
+
*
|
|
109
|
+
* @param pct The fraction, `0..n` (20% arrives as 0.2).
|
|
110
|
+
* @param sizePt The paragraph's resolved font size.
|
|
111
|
+
* @returns The distance, or `undefined` when the size is not known.
|
|
112
|
+
*/
|
|
113
|
+
function spacingFromLineFraction(pct, sizePt) {
|
|
114
|
+
if (pct === void 0 || sizePt === void 0) return void 0;
|
|
115
|
+
return pt(pct * sizePt * NATURAL_LINE);
|
|
116
|
+
}
|
|
117
|
+
/** What the layout gives a line of text with nothing said about its height. */
|
|
118
|
+
var NATURAL_LINE = 1.2;
|
|
119
|
+
function withResolvedSpacing(level) {
|
|
120
|
+
const size = level.run.fontSizePt;
|
|
121
|
+
const before = spacingFromLineFraction(level.spacingPct?.before, size);
|
|
122
|
+
const after = spacingFromLineFraction(level.spacingPct?.after, size);
|
|
123
|
+
if (before === void 0 && after === void 0) return level;
|
|
124
|
+
return {
|
|
125
|
+
...level,
|
|
126
|
+
paragraph: {
|
|
127
|
+
...level.paragraph,
|
|
128
|
+
...before !== void 0 ? { spacingBefore: before } : {},
|
|
129
|
+
...after !== void 0 ? { spacingAfter: after } : {}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
97
133
|
/** The style at `level`, clamped to the list — a deeper level keeps the last. */
|
|
98
134
|
function at(levels, level) {
|
|
99
135
|
if (levels.length === 0) return EMPTY_LEVEL;
|
|
@@ -105,6 +141,10 @@ function mergeLevels(base, next) {
|
|
|
105
141
|
...base.bullet,
|
|
106
142
|
...next.bullet
|
|
107
143
|
} : void 0;
|
|
144
|
+
const spacingPct = base.spacingPct || next.spacingPct ? {
|
|
145
|
+
...base.spacingPct,
|
|
146
|
+
...next.spacingPct
|
|
147
|
+
} : void 0;
|
|
108
148
|
return {
|
|
109
149
|
run: {
|
|
110
150
|
...base.run,
|
|
@@ -114,7 +154,8 @@ function mergeLevels(base, next) {
|
|
|
114
154
|
...base.paragraph,
|
|
115
155
|
...next.paragraph
|
|
116
156
|
},
|
|
117
|
-
...bullet ? { bullet } : {}
|
|
157
|
+
...bullet ? { bullet } : {},
|
|
158
|
+
...spacingPct ? { spacingPct } : {}
|
|
118
159
|
};
|
|
119
160
|
}
|
|
120
161
|
/**
|
|
@@ -127,17 +168,19 @@ function mergeLevels(base, next) {
|
|
|
127
168
|
* @param prefix The level element prefix: `a:lvl` here, always.
|
|
128
169
|
* @returns The nine levels, or an empty list when there is no such node.
|
|
129
170
|
*/
|
|
130
|
-
function parseLevelStyles(list, colors) {
|
|
171
|
+
function parseLevelStyles(list, colors, themeFonts) {
|
|
131
172
|
if (!list) return [];
|
|
132
173
|
const out = [];
|
|
133
174
|
for (let lvl = 1; lvl <= 9; lvl++) {
|
|
134
175
|
const lvlPr = poChildren(list).find((c) => poIs(c, `a:lvl${String(lvl)}pPr`));
|
|
135
176
|
const defRPr = lvlPr ? poChildren(lvlPr).find((c) => poIs(c, "a:defRPr")) : void 0;
|
|
136
177
|
const bullet = parseBullet(lvlPr, colors);
|
|
178
|
+
const spacingPct = spacingFractions(lvlPr);
|
|
137
179
|
out.push({
|
|
138
|
-
run: rPrToRunProps(defRPr, colors),
|
|
180
|
+
run: rPrToRunProps(defRPr, colors, themeFonts),
|
|
139
181
|
paragraph: pPrToParagraphProps(lvlPr),
|
|
140
|
-
...bullet ? { bullet } : {}
|
|
182
|
+
...bullet ? { bullet } : {},
|
|
183
|
+
...spacingPct ? { spacingPct } : {}
|
|
141
184
|
});
|
|
142
185
|
}
|
|
143
186
|
return out;
|
|
@@ -151,8 +194,12 @@ var ALGN = {
|
|
|
151
194
|
};
|
|
152
195
|
/**
|
|
153
196
|
* A level's paragraph shape — §21.1.2.2.7 `@algn`, `@marL`/`@indent`, and the
|
|
154
|
-
* space around
|
|
155
|
-
*
|
|
197
|
+
* space around and between its lines.
|
|
198
|
+
*
|
|
199
|
+
* Both spacings come in two spellings: `a:spcPts` is a distance (hundredths of
|
|
200
|
+
* a point) and `a:spcPct` a FRACTION of a line (thousandths of a percent). Only
|
|
201
|
+
* the distance was read, so a deck that spaces its bullets the usual way — by
|
|
202
|
+
* fraction — set them solid.
|
|
156
203
|
*/
|
|
157
204
|
function pPrToParagraphProps(lvlPr) {
|
|
158
205
|
if (!lvlPr) return {};
|
|
@@ -164,7 +211,56 @@ function pPrToParagraphProps(lvlPr) {
|
|
|
164
211
|
...marL !== void 0 ? { indentLeft: emuToPt(marL) } : {},
|
|
165
212
|
...indent !== void 0 ? { indentFirstLine: emuToPt(indent) } : {},
|
|
166
213
|
...spacingPt(lvlPr, "a:spcBef", "spacingBefore"),
|
|
167
|
-
...spacingPt(lvlPr, "a:spcAft", "spacingAfter")
|
|
214
|
+
...spacingPt(lvlPr, "a:spcAft", "spacingAfter"),
|
|
215
|
+
...lineSpacing(lvlPr)
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* §21.1.2.2.5 `a:lnSpc` — the height of each line: a fraction of the natural
|
|
220
|
+
* one (`a:spcPct`) or a distance (`a:spcPts`).
|
|
221
|
+
*
|
|
222
|
+
* The model states a multiple the way §17.3.1.33 does — 12pt under the `auto`
|
|
223
|
+
* rule IS single — so a fraction needs no font size to express, which is why
|
|
224
|
+
* this resolves here and the paragraph SPACING has to wait for the size.
|
|
225
|
+
*
|
|
226
|
+
* @param pPr The paragraph properties (a level's or a paragraph's own).
|
|
227
|
+
* @returns The line spacing, or nothing when the node states none.
|
|
228
|
+
*/
|
|
229
|
+
function lineSpacing(pPr) {
|
|
230
|
+
const holder = pPr ? poChildren(pPr).find((c) => poIs(c, "a:lnSpc")) : void 0;
|
|
231
|
+
if (!holder) return {};
|
|
232
|
+
const pctVal = poIntAttr(poChildren(holder).find((c) => poIs(c, "a:spcPct")), "val");
|
|
233
|
+
if (pctVal !== void 0 && pctVal > 0) return {
|
|
234
|
+
spacingLine: pt(pctVal / 1e5 * 12),
|
|
235
|
+
spacingLineRule: "auto"
|
|
236
|
+
};
|
|
237
|
+
const ptsVal = poIntAttr(poChildren(holder).find((c) => poIs(c, "a:spcPts")), "val");
|
|
238
|
+
if (ptsVal !== void 0 && ptsVal > 0) return {
|
|
239
|
+
spacingLine: pt(ptsVal / 100),
|
|
240
|
+
spacingLineRule: "exact"
|
|
241
|
+
};
|
|
242
|
+
return {};
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* §21.1.2.2.9/.10 — the fraction of a line a paragraph puts before and after
|
|
246
|
+
* itself, when it states one. The distance form is read beside it; this is what
|
|
247
|
+
* the caller must still resolve against the size.
|
|
248
|
+
*
|
|
249
|
+
* @param pPr The paragraph properties.
|
|
250
|
+
* @returns The two fractions (`0..n`), each present only when stated.
|
|
251
|
+
*/
|
|
252
|
+
function spacingFractions(pPr) {
|
|
253
|
+
const frac = (tag) => {
|
|
254
|
+
const holder = pPr ? poChildren(pPr).find((c) => poIs(c, tag)) : void 0;
|
|
255
|
+
const pctNode = holder ? poChildren(holder).find((c) => poIs(c, "a:spcPct")) : void 0;
|
|
256
|
+
const val = pctNode ? poIntAttr(pctNode, "val") : void 0;
|
|
257
|
+
return val === void 0 || val < 0 ? void 0 : val / 1e5;
|
|
258
|
+
};
|
|
259
|
+
const [before, after] = [frac("a:spcBef"), frac("a:spcAft")];
|
|
260
|
+
if (before === void 0 && after === void 0) return void 0;
|
|
261
|
+
return {
|
|
262
|
+
...before !== void 0 ? { before } : {},
|
|
263
|
+
...after !== void 0 ? { after } : {}
|
|
168
264
|
};
|
|
169
265
|
}
|
|
170
266
|
function spacingPt(lvlPr, tag, key) {
|
|
@@ -173,7 +269,7 @@ function spacingPt(lvlPr, tag, key) {
|
|
|
173
269
|
const val = pts ? poIntAttr(pts, "val") : void 0;
|
|
174
270
|
return val === void 0 ? {} : { [key]: pt(val / 100) };
|
|
175
271
|
}
|
|
176
|
-
function collectPlaceholders(tree, root, colors) {
|
|
272
|
+
function collectPlaceholders(tree, root, colors, themeFonts) {
|
|
177
273
|
const sld = tree.find((n) => poIs(n, root));
|
|
178
274
|
const cSld = sld ? poChildren(sld).find((c) => poIs(c, "p:cSld")) : void 0;
|
|
179
275
|
const spTree = cSld ? poChildren(cSld).find((c) => poIs(c, "p:spTree")) : void 0;
|
|
@@ -194,7 +290,7 @@ function collectPlaceholders(tree, root, colors) {
|
|
|
194
290
|
ref,
|
|
195
291
|
...box ? { box } : {},
|
|
196
292
|
...spPr ? { spPr } : {},
|
|
197
|
-
levels: parseLevelStyles(lstStyle, colors),
|
|
293
|
+
levels: parseLevelStyles(lstStyle, colors, themeFonts),
|
|
198
294
|
...anchor ? { anchor } : {}
|
|
199
295
|
});
|
|
200
296
|
}
|
|
@@ -210,10 +306,10 @@ function matchPlaceholder(list, ph) {
|
|
|
210
306
|
const cat = categoryOf(ph.type);
|
|
211
307
|
return list.find((p) => categoryOf(p.ref.type) === cat);
|
|
212
308
|
}
|
|
213
|
-
function collectTxStyles(masterTree, colors) {
|
|
309
|
+
function collectTxStyles(masterTree, colors, themeFonts) {
|
|
214
310
|
const sld = masterTree.find((n) => poIs(n, "p:sldMaster"));
|
|
215
311
|
const txStyles = sld ? poChildren(sld).find((c) => poIs(c, "p:txStyles")) : void 0;
|
|
216
|
-
const family = (tag) => parseLevelStyles(txStyles ? poChildren(txStyles).find((c) => poIs(c, tag)) : void 0, colors);
|
|
312
|
+
const family = (tag) => parseLevelStyles(txStyles ? poChildren(txStyles).find((c) => poIs(c, tag)) : void 0, colors, themeFonts);
|
|
217
313
|
return {
|
|
218
314
|
title: family("p:titleStyle"),
|
|
219
315
|
body: family("p:bodyStyle"),
|
|
@@ -221,4 +317,4 @@ function collectTxStyles(masterTree, colors) {
|
|
|
221
317
|
};
|
|
222
318
|
}
|
|
223
319
|
//#endregion
|
|
224
|
-
export { buildPlaceholderCascade, parseBullet, parseLevelStyles };
|
|
320
|
+
export { buildPlaceholderCascade, lineSpacing, parseBullet, parseLevelStyles, spacingFractions, spacingFromLineFraction };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { pt } from "../../core/ir/units.js";
|
|
2
2
|
import { ResourceStore } from "../../core/ir/resources.js";
|
|
3
3
|
import { FEATURES } from "../../core/ir/features.js";
|
|
4
|
+
import { isEmf } from "../../core/metafile/emf.js";
|
|
5
|
+
import { isWmf } from "../../core/metafile/wmf.js";
|
|
4
6
|
import { EMPTY_STYLE_SHEET, resolveBodyStyles } from "../../core/style-cascade/resolver.js";
|
|
5
7
|
import "../../core/style-cascade/index.js";
|
|
6
8
|
import { isCfb, openCfb } from "../../core/ole/cfb.js";
|
|
@@ -48,7 +50,7 @@ function readPpt(bytes) {
|
|
|
48
50
|
return {
|
|
49
51
|
doc: {
|
|
50
52
|
kind: "flow",
|
|
51
|
-
body: resolveBodyStyles(buildBody(content, resources), EMPTY_STYLE_SHEET),
|
|
53
|
+
body: resolveBodyStyles(buildBody(content, resources, width, height), EMPTY_STYLE_SHEET),
|
|
52
54
|
sections: [],
|
|
53
55
|
section: {
|
|
54
56
|
pageSize: {
|
|
@@ -70,19 +72,23 @@ function readPpt(bytes) {
|
|
|
70
72
|
losses: [content.encrypted ? PPT_ENCRYPTED_LOSS : PPT_TEXT_LOSS]
|
|
71
73
|
};
|
|
72
74
|
}
|
|
73
|
-
function buildBody(content, resources) {
|
|
75
|
+
function buildBody(content, resources, pageW, pageH) {
|
|
74
76
|
const body = [];
|
|
75
77
|
content.slides.forEach((slide, slideIndex) => {
|
|
76
78
|
const inFlowParas = [];
|
|
77
79
|
const inFlowImages = [];
|
|
78
80
|
const floats = [];
|
|
81
|
+
if (slide.background) {
|
|
82
|
+
const backdrop = slideBackdrop(slide.background, resources, pageW, pageH);
|
|
83
|
+
if (backdrop) floats.push(backdrop);
|
|
84
|
+
}
|
|
79
85
|
for (const shape of slide.shapes) if (shape.rectPt) {
|
|
80
|
-
if (shape.
|
|
86
|
+
if (shape.autoShape) floats.push(positionedAutoShape(shape.rectPt, shape.autoShape, resources));
|
|
81
87
|
if (shape.image) {
|
|
82
88
|
const block = positionedImage(shape.rectPt, shape.image, resources);
|
|
83
89
|
if (block) floats.push(block);
|
|
84
90
|
}
|
|
85
|
-
if (shape.
|
|
91
|
+
if (shape.paragraphs?.some((p) => paragraphText(p).length > 0)) floats.push(positionedTextShape(shape.rectPt, shape.paragraphs, shape.wordArt === true));
|
|
86
92
|
} else {
|
|
87
93
|
for (const para of shape.paragraphs ?? []) if (paragraphText(para).length > 0) inFlowParas.push(flowParagraph(para));
|
|
88
94
|
if (shape.image) {
|
|
@@ -122,10 +128,80 @@ function flowParagraph(para) {
|
|
|
122
128
|
kind: "paragraph",
|
|
123
129
|
paragraph: {
|
|
124
130
|
properties: toParaProperties(para, false),
|
|
125
|
-
runs: para
|
|
131
|
+
runs: bulletedRuns(para)
|
|
126
132
|
}
|
|
127
133
|
};
|
|
128
134
|
}
|
|
135
|
+
function bulletedRuns(para) {
|
|
136
|
+
const runs = para.runs.map(toRun);
|
|
137
|
+
if (para.bullet === void 0) return runs;
|
|
138
|
+
const { bold: _b, italic: _i, ...rest } = runs[0]?.properties ?? {};
|
|
139
|
+
return [{
|
|
140
|
+
text: `${para.bullet} `,
|
|
141
|
+
properties: rest
|
|
142
|
+
}, ...runs];
|
|
143
|
+
}
|
|
144
|
+
function slideBackdrop(bg, resources, pageW, pageH) {
|
|
145
|
+
const fill = backdropFill(bg, resources);
|
|
146
|
+
if (!fill) return void 0;
|
|
147
|
+
return {
|
|
148
|
+
kind: "shape",
|
|
149
|
+
shape: {
|
|
150
|
+
float: {
|
|
151
|
+
wrap: "none",
|
|
152
|
+
behind: true,
|
|
153
|
+
posH: {
|
|
154
|
+
relativeFrom: "page",
|
|
155
|
+
offsetPt: pt(0)
|
|
156
|
+
},
|
|
157
|
+
posV: {
|
|
158
|
+
relativeFrom: "page",
|
|
159
|
+
offsetPt: pt(0)
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
width: pageW,
|
|
163
|
+
height: pageH,
|
|
164
|
+
geometry: {
|
|
165
|
+
kind: "preset",
|
|
166
|
+
preset: "rect",
|
|
167
|
+
adjust: /* @__PURE__ */ new Map()
|
|
168
|
+
},
|
|
169
|
+
fill,
|
|
170
|
+
paragraphProperties: {}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function backdropFill(bg, resources) {
|
|
175
|
+
if (bg.image) return {
|
|
176
|
+
kind: "picture",
|
|
177
|
+
imageResource: resources.put(bg.image.bytes),
|
|
178
|
+
...bg.imageTiled ? { tiled: true } : {}
|
|
179
|
+
};
|
|
180
|
+
if (bg.gradient) {
|
|
181
|
+
const stops = [{
|
|
182
|
+
offset: 0,
|
|
183
|
+
colorHex: bg.gradient.fromHex
|
|
184
|
+
}, {
|
|
185
|
+
offset: 1,
|
|
186
|
+
colorHex: bg.gradient.toHex
|
|
187
|
+
}];
|
|
188
|
+
return {
|
|
189
|
+
kind: "gradient",
|
|
190
|
+
gradient: bg.gradient.radial ? {
|
|
191
|
+
kind: "radial",
|
|
192
|
+
stops
|
|
193
|
+
} : {
|
|
194
|
+
kind: "linear",
|
|
195
|
+
angle: bg.gradient.angleDeg,
|
|
196
|
+
stops
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return bg.fillColorHex ? {
|
|
201
|
+
kind: "solid",
|
|
202
|
+
colorHex: bg.fillColorHex
|
|
203
|
+
} : void 0;
|
|
204
|
+
}
|
|
129
205
|
function floatAt(rect) {
|
|
130
206
|
return {
|
|
131
207
|
wrap: "none",
|
|
@@ -139,7 +215,7 @@ function floatAt(rect) {
|
|
|
139
215
|
}
|
|
140
216
|
};
|
|
141
217
|
}
|
|
142
|
-
function positionedTextShape(rect, paragraphs) {
|
|
218
|
+
function positionedTextShape(rect, paragraphs, centred = false) {
|
|
143
219
|
return {
|
|
144
220
|
kind: "shape",
|
|
145
221
|
shape: {
|
|
@@ -154,14 +230,14 @@ function positionedTextShape(rect, paragraphs) {
|
|
|
154
230
|
fill: { kind: "none" },
|
|
155
231
|
text: {
|
|
156
232
|
content: paragraphs.filter((p) => paragraphText(p).length > 0).map(flowParagraph),
|
|
157
|
-
anchor: "t"
|
|
233
|
+
anchor: centred ? "ctr" : "t"
|
|
158
234
|
},
|
|
159
235
|
paragraphProperties: {}
|
|
160
236
|
}
|
|
161
237
|
};
|
|
162
238
|
}
|
|
163
239
|
function positionedImage(rect, image, resources) {
|
|
164
|
-
if (!imagePixelSize(image.bytes)) return void 0;
|
|
240
|
+
if (!isMetafile(image.bytes) && !imagePixelSize(image.bytes)) return void 0;
|
|
165
241
|
return {
|
|
166
242
|
kind: "image",
|
|
167
243
|
image: {
|
|
@@ -214,9 +290,36 @@ function toCustomGeometry(g) {
|
|
|
214
290
|
commands
|
|
215
291
|
};
|
|
216
292
|
}
|
|
217
|
-
function positionedAutoShape(rect, auto) {
|
|
293
|
+
function positionedAutoShape(rect, auto, resources) {
|
|
218
294
|
const line = isLineShape(auto.shapeType);
|
|
219
|
-
const fill = auto.
|
|
295
|
+
const fill = line ? { kind: "none" } : auto.image ? {
|
|
296
|
+
kind: "picture",
|
|
297
|
+
imageResource: resources.put(auto.image.bytes),
|
|
298
|
+
...auto.imageTiled ? { tiled: true } : {},
|
|
299
|
+
...auto.tileSizePt ? { tileSizePt: auto.tileSizePt } : {}
|
|
300
|
+
} : auto.gradient ? {
|
|
301
|
+
kind: "gradient",
|
|
302
|
+
gradient: auto.gradient.radial ? {
|
|
303
|
+
kind: "radial",
|
|
304
|
+
stops: [{
|
|
305
|
+
offset: 0,
|
|
306
|
+
colorHex: auto.gradient.fromHex
|
|
307
|
+
}, {
|
|
308
|
+
offset: 1,
|
|
309
|
+
colorHex: auto.gradient.toHex
|
|
310
|
+
}]
|
|
311
|
+
} : {
|
|
312
|
+
kind: "linear",
|
|
313
|
+
angle: auto.gradient.angleDeg,
|
|
314
|
+
stops: [{
|
|
315
|
+
offset: 0,
|
|
316
|
+
colorHex: auto.gradient.fromHex
|
|
317
|
+
}, {
|
|
318
|
+
offset: 1,
|
|
319
|
+
colorHex: auto.gradient.toHex
|
|
320
|
+
}]
|
|
321
|
+
}
|
|
322
|
+
} : auto.fillColorHex ? {
|
|
220
323
|
kind: "solid",
|
|
221
324
|
colorHex: auto.fillColorHex
|
|
222
325
|
} : { kind: "none" };
|
|
@@ -257,6 +360,9 @@ function inFlowImage(image, resources) {
|
|
|
257
360
|
}
|
|
258
361
|
};
|
|
259
362
|
}
|
|
363
|
+
function isMetafile(d) {
|
|
364
|
+
return isEmf(d) || isWmf(d);
|
|
365
|
+
}
|
|
260
366
|
function imagePixelSize(d) {
|
|
261
367
|
if (d.length >= 24 && d[0] === 137 && d[1] === 80 && d[2] === 78 && d[3] === 71) {
|
|
262
368
|
const w = u32be(d, 16);
|
|
@@ -298,6 +404,10 @@ function toRun(r) {
|
|
|
298
404
|
...r.italic ? { italic: true } : {},
|
|
299
405
|
...underline ? { underline } : {},
|
|
300
406
|
...r.sizePt ? { fontSizePt: pt(r.sizePt) } : {},
|
|
407
|
+
...r.fontFamily ? { fontFamily: {
|
|
408
|
+
ascii: r.fontFamily,
|
|
409
|
+
hAnsi: r.fontFamily
|
|
410
|
+
} } : {},
|
|
301
411
|
...r.colorHex ? { colorHex: r.colorHex } : {}
|
|
302
412
|
};
|
|
303
413
|
return {
|