@openpresentation/opf 0.12.1 → 0.13.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/README.md +1 -1
- package/dist/audit.d.ts +9 -130
- package/dist/audit.js +5 -5
- package/dist/{catalogs-BnI7JKcn.d.ts → catalogs-ywCamd8C.d.ts} +1 -1
- package/dist/catalogs.d.ts +2 -2
- package/dist/chart-data-Cdr6rRVV.d.ts +177 -0
- package/dist/{chunk-6LZK2IZB.js → chunk-7N4VMAT4.js} +11 -7
- package/dist/{chunk-4LAM3N6S.js → chunk-7QBCWNWL.js} +424 -18
- package/dist/{chunk-SOSOBKQ2.js → chunk-ATGDDJPX.js} +4 -5
- package/dist/{chunk-V2537ENL.js → chunk-II5EYKZU.js} +9 -7
- package/dist/{chunk-MWPMJE6P.js → chunk-INU7ZNSB.js} +50 -2
- package/dist/{chunk-RUC7L5DJ.js → chunk-IS2CPNVJ.js} +1 -1
- package/dist/{chunk-JKVKYVWL.js → chunk-IYCJUKWD.js} +90 -471
- package/dist/{chunk-HOBSL7AJ.js → chunk-MPM7NRG7.js} +2396 -1408
- package/dist/{chunk-AUM5MXBR.js → chunk-RAYB46AB.js} +8 -6
- package/dist/{composition-DWbCiMwF.d.ts → composition-D7we69Tr.d.ts} +7 -0
- package/dist/composition.d.ts +2 -1
- package/dist/composition.js +1 -1
- package/dist/convert.d.ts +4 -1
- package/dist/convert.js +33 -16
- package/dist/data.d.ts +2 -1
- package/dist/data.js +3 -1
- package/dist/diff.d.ts +1 -1
- package/dist/diff.js +8 -7
- package/dist/docs.js +32 -26
- package/dist/examples.d.ts +1 -1
- package/dist/examples.js +102 -57
- package/dist/format.js +1 -1
- package/dist/index.d.ts +13 -10
- package/dist/index.js +8 -8
- package/dist/lint-B7azdiuE.d.ts +197 -0
- package/dist/lint.d.ts +7 -70
- package/dist/lint.js +4 -4
- package/dist/markdown.d.ts +7 -5
- package/dist/markdown.js +5 -5
- package/dist/pagination.d.ts +2 -1
- package/dist/pagination.js +4 -4
- package/dist/patch.js +4 -4
- package/dist/{presentation-DcF-MZcz.d.ts → presentation-mG-IYErQ.d.ts} +187 -14
- package/dist/repo-readme.js +1 -1
- package/dist/{schemas-BSjlG8-R.d.ts → schemas-BbPonsTz.d.ts} +18 -0
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.js +1 -1
- package/dist/spec/catalogs/manifest.json +3 -3
- package/dist/spec/schemas/opf.schema.json +238 -20
- package/dist/types.d.ts +4 -4
- package/dist/{validator-DfG9yxba.d.ts → validator-CIylxoIm.d.ts} +3 -3
- package/dist/validator.d.ts +4 -4
- package/dist/validator.js +3 -3
- package/package.json +3 -3
|
@@ -16,12 +16,13 @@ function tableGrid(value, path = "table") {
|
|
|
16
16
|
continue;
|
|
17
17
|
}
|
|
18
18
|
const object = record(input), styled = Object.hasOwn(object, "value");
|
|
19
|
+
const column = !styled && hasHeaders && r === 0 && typeof object.name === "string";
|
|
19
20
|
const rowSpan = styled ? object.rowSpan ?? 1 : 1, colSpan = styled ? object.colSpan ?? 1 : 1;
|
|
20
21
|
if (!Number.isSafeInteger(rowSpan) || !Number.isSafeInteger(colSpan) || rowSpan < 1 || colSpan < 1 || r + rowSpan > rowCount || c + colSpan > columnCount || hasHeaders && r === 0 && rowSpan > 1) {
|
|
21
22
|
issues.push({ path: cellPath, message: "Cell spans must be positive integers within the table grid and cannot cross from headers into body rows." });
|
|
22
23
|
continue;
|
|
23
24
|
}
|
|
24
|
-
const cell = { input, value: styled ? object.value : input, style: styled ? record(object.style) : {}, row: r, column: c, rowSpan, colSpan, header: hasHeaders && r === 0, path: cellPath, valuePath: styled ? cellPath + ".value" : cellPath };
|
|
25
|
+
const cell = { input, value: styled ? object.value : column ? object.name : input, style: styled ? record(object.style) : {}, row: r, column: c, rowSpan, colSpan, header: hasHeaders && r === 0, path: cellPath, valuePath: styled ? cellPath + ".value" : column ? cellPath + ".name" : cellPath };
|
|
25
26
|
rows2[r].push(cell);
|
|
26
27
|
for (let y = r; y < r + rowSpan; y++) for (let x = c; x < c + colSpan; x++) {
|
|
27
28
|
if (owners[y][x]) issues.push({ path: cellPath, message: "Cell spans cannot overlap another spanning cell." });
|
|
@@ -37,1131 +38,6 @@ function tableRowBoundaries(value) {
|
|
|
37
38
|
return [...allowed];
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
// src/image-aspect.ts
|
|
41
|
-
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
42
|
-
function sourceOf(value) {
|
|
43
|
-
if (typeof value === "string") return value;
|
|
44
|
-
return isRecord(value) && typeof value.src === "string" ? value.src : void 0;
|
|
45
|
-
}
|
|
46
|
-
var PREFIX_BYTES = 64 * 1024;
|
|
47
|
-
var JPEG_PREFIX_BYTES = 1024 * 1024;
|
|
48
|
-
function dataBytes(uri, limit) {
|
|
49
|
-
const match = /^data:([^;,]*)((?:;[^;,]*)*),/i.exec(uri);
|
|
50
|
-
if (!match) return void 0;
|
|
51
|
-
const type = (match[1] ?? "").toLowerCase(), start = match[0].length;
|
|
52
|
-
try {
|
|
53
|
-
if (/;base64/i.test(match[2] ?? "")) {
|
|
54
|
-
const chars = Math.ceil(limit / 3) * 4, text2 = uri.slice(start, start + chars + chars / 8).replace(/\s+/g, "").slice(0, chars);
|
|
55
|
-
return { type, bytes: Uint8Array.from(atob(text2.slice(0, text2.length - text2.length % 4)), (char) => char.charCodeAt(0)) };
|
|
56
|
-
}
|
|
57
|
-
const prefix = uri.slice(start, start + limit).replace(/%[0-9a-f]?$/i, "");
|
|
58
|
-
return { type, bytes: new TextEncoder().encode(decodeURIComponent(prefix)) };
|
|
59
|
-
} catch {
|
|
60
|
-
return void 0;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
var at = (b, i) => b[i] ?? 0;
|
|
64
|
-
var u16be = (b, i) => at(b, i) << 8 | at(b, i + 1);
|
|
65
|
-
var u32be = (b, i) => (at(b, i) << 24 | at(b, i + 1) << 16 | at(b, i + 2) << 8 | at(b, i + 3)) >>> 0;
|
|
66
|
-
var u16le = (b, i) => at(b, i) | at(b, i + 1) << 8;
|
|
67
|
-
var u24le = (b, i) => at(b, i) | at(b, i + 1) << 8 | at(b, i + 2) << 16;
|
|
68
|
-
var ascii = (b, i, n) => String.fromCharCode(...b.subarray(i, i + n));
|
|
69
|
-
function rasterDimensions(b) {
|
|
70
|
-
if (b.length >= 24 && u32be(b, 0) === 2303741511 && ascii(b, 12, 4) === "IHDR") return { width: u32be(b, 16), height: u32be(b, 20) };
|
|
71
|
-
if (b.length >= 10 && (ascii(b, 0, 6) === "GIF87a" || ascii(b, 0, 6) === "GIF89a")) return { width: u16le(b, 6), height: u16le(b, 8) };
|
|
72
|
-
if (b.length >= 4 && b[0] === 255 && b[1] === 216) {
|
|
73
|
-
let i = 2;
|
|
74
|
-
while (i + 9 < b.length) {
|
|
75
|
-
if (at(b, i) !== 255) {
|
|
76
|
-
i++;
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
const marker = at(b, i + 1);
|
|
80
|
-
if (marker === 255) {
|
|
81
|
-
i++;
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
if (marker === 216 || marker === 1 || marker >= 208 && marker <= 215) {
|
|
85
|
-
i += 2;
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) return { height: u16be(b, i + 5), width: u16be(b, i + 7) };
|
|
89
|
-
i += 2 + u16be(b, i + 2);
|
|
90
|
-
}
|
|
91
|
-
return void 0;
|
|
92
|
-
}
|
|
93
|
-
if (b.length >= 30 && ascii(b, 0, 4) === "RIFF" && ascii(b, 8, 4) === "WEBP") {
|
|
94
|
-
const chunk = ascii(b, 12, 4);
|
|
95
|
-
if (chunk === "VP8X") return { width: u24le(b, 24) + 1, height: u24le(b, 27) + 1 };
|
|
96
|
-
if (chunk === "VP8L" && at(b, 20) === 47) {
|
|
97
|
-
const bits = u16le(b, 21) | u16le(b, 23) << 16;
|
|
98
|
-
return { width: (bits & 16383) + 1, height: (bits >>> 14 & 16383) + 1 };
|
|
99
|
-
}
|
|
100
|
-
if (chunk === "VP8 ") return { width: u16le(b, 26) & 16383, height: u16le(b, 28) & 16383 };
|
|
101
|
-
}
|
|
102
|
-
return void 0;
|
|
103
|
-
}
|
|
104
|
-
function svgDimensions(text2) {
|
|
105
|
-
const root = /<svg\b([^>]*)>/i.exec(text2)?.[1];
|
|
106
|
-
if (root === void 0) return void 0;
|
|
107
|
-
const attribute = (name) => new RegExp(`\\s${name}\\s*=\\s*["']([^"']*)["']`, "i").exec(root)?.[1];
|
|
108
|
-
const length = (value) => {
|
|
109
|
-
const match = value && /^\s*([0-9]*\.?[0-9]+)\s*(px|pt|in|cm|mm|em)?\s*$/i.exec(value);
|
|
110
|
-
return match ? Number(match[1]) : void 0;
|
|
111
|
-
};
|
|
112
|
-
const width = length(attribute("width")), height = length(attribute("height"));
|
|
113
|
-
if (width && height) return { width, height };
|
|
114
|
-
const box = attribute("viewBox")?.trim().split(/[\s,]+/).map(Number);
|
|
115
|
-
if (box && box.length === 4 && box.every(Number.isFinite)) {
|
|
116
|
-
const [, , width2 = 0, height2 = 0] = box;
|
|
117
|
-
if (width2 > 0 && height2 > 0) return { width: width2, height: height2 };
|
|
118
|
-
}
|
|
119
|
-
return void 0;
|
|
120
|
-
}
|
|
121
|
-
var cache = /* @__PURE__ */ new Map();
|
|
122
|
-
var CACHE_ENTRIES = 64;
|
|
123
|
-
function readDimensions(source) {
|
|
124
|
-
const first = dataBytes(source, PREFIX_BYTES);
|
|
125
|
-
if (!first) return void 0;
|
|
126
|
-
let dimensions;
|
|
127
|
-
if (first.type.startsWith("image/svg")) dimensions = svgDimensions(new TextDecoder().decode(first.bytes));
|
|
128
|
-
else {
|
|
129
|
-
dimensions = rasterDimensions(first.bytes);
|
|
130
|
-
if (!dimensions && first.bytes.length >= 2 && first.bytes[0] === 255 && first.bytes[1] === 216) {
|
|
131
|
-
const more = dataBytes(source, JPEG_PREFIX_BYTES);
|
|
132
|
-
if (more) dimensions = rasterDimensions(more.bytes);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return dimensions && dimensions.width > 0 && dimensions.height > 0 ? dimensions : void 0;
|
|
136
|
-
}
|
|
137
|
-
function intrinsicImageSize(value, assets) {
|
|
138
|
-
let source = sourceOf(value);
|
|
139
|
-
for (let hop = 0; source?.startsWith("asset:") && hop < 8; hop++) source = sourceOf(isRecord(assets) ? assets[source.slice(6)] : void 0);
|
|
140
|
-
if (!source?.startsWith("data:")) return void 0;
|
|
141
|
-
if (!cache.has(source)) {
|
|
142
|
-
if (cache.size >= CACHE_ENTRIES) cache.delete(cache.keys().next().value);
|
|
143
|
-
cache.set(source, readDimensions(source));
|
|
144
|
-
}
|
|
145
|
-
const size = cache.get(source);
|
|
146
|
-
return size ? { ...size, svg: /^data:image\/svg/i.test(source) } : void 0;
|
|
147
|
-
}
|
|
148
|
-
function intrinsicImageAspect(value, assets) {
|
|
149
|
-
const size = intrinsicImageSize(value, assets);
|
|
150
|
-
return size ? size.width / size.height : void 0;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// src/reading-order.ts
|
|
154
|
-
function visualReadingOrder(items, direction = "ltr") {
|
|
155
|
-
const index = new Map(items.map((item, i) => [item, i]));
|
|
156
|
-
const along = direction === "rtl" ? (item) => -(item.box.x + item.box.width) : (item) => item.box.x;
|
|
157
|
-
return readingRows(items).flatMap((row) => [...row].sort((a, b) => along(a) - along(b) || a.box.y - b.box.y || index.get(a) - index.get(b)));
|
|
158
|
-
}
|
|
159
|
-
function readingRows(items) {
|
|
160
|
-
const index = new Map(items.map((item, i) => [item, i]));
|
|
161
|
-
const sorted = [...items].sort((a, b) => a.box.y - b.box.y || a.box.x - b.box.x || index.get(a) - index.get(b));
|
|
162
|
-
const rows2 = [];
|
|
163
|
-
for (const item of sorted) {
|
|
164
|
-
const centre = item.box.y + item.box.height / 2;
|
|
165
|
-
const row = rows2.find((candidate) => centre >= candidate.top && centre <= candidate.bottom);
|
|
166
|
-
if (row) {
|
|
167
|
-
row.items.push(item);
|
|
168
|
-
row.top = Math.min(row.top, item.box.y);
|
|
169
|
-
row.bottom = Math.max(row.bottom, item.box.y + item.box.height);
|
|
170
|
-
} else rows2.push({ top: item.box.y, bottom: item.box.y + item.box.height, items: [item] });
|
|
171
|
-
}
|
|
172
|
-
rows2.sort((a, b) => a.top - b.top);
|
|
173
|
-
return rows2.map((row) => row.items.sort((a, b) => index.get(a) - index.get(b)));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// src/direction.ts
|
|
177
|
-
var strongRtl = /[\u200F\u061C]|(?=\p{L})[\p{Script=Arabic}\p{Script=Hebrew}\p{Script=Syriac}\p{Script=Thaana}\p{Script=Nko}\p{Script=Adlam}\p{Script=Hanifi_Rohingya}\p{Script=Mandaic}\p{Script=Samaritan}\p{Script=Mende_Kikakui}\p{Script=Imperial_Aramaic}\p{Script=Phoenician}\p{Script=Kharoshthi}\p{Script=Old_South_Arabian}\p{Script=Old_North_Arabian}\p{Script=Avestan}\p{Script=Inscriptional_Parthian}\p{Script=Inscriptional_Pahlavi}\p{Script=Psalter_Pahlavi}\p{Script=Old_Turkic}\p{Script=Old_Hungarian}\p{Script=Nabataean}\p{Script=Palmyrene}\p{Script=Hatran}\p{Script=Manichaean}\p{Script=Sogdian}\p{Script=Old_Sogdian}\p{Script=Elymaic}\p{Script=Chorasmian}\p{Script=Yezidi}\p{Script=Cypriot}\p{Script=Lydian}\p{Script=Meroitic_Cursive}\p{Script=Meroitic_Hieroglyphs}\u{10F70}-\u{10FAF}\u{10D40}-\u{10D8F}]/u;
|
|
178
|
-
var strongLtr = /[\u200E\p{L}\p{Nl}]/u;
|
|
179
|
-
function paragraphDirection(text2, deckDirection) {
|
|
180
|
-
if (deckDirection !== "rtl") return "ltr";
|
|
181
|
-
let isolates = 0;
|
|
182
|
-
for (const char of String(text2 ?? "")) {
|
|
183
|
-
if (char === "\u2066" || char === "\u2067" || char === "\u2068") isolates += 1;
|
|
184
|
-
else if (char === "\u2069") isolates = Math.max(0, isolates - 1);
|
|
185
|
-
else if (isolates === 0) {
|
|
186
|
-
if (strongRtl.test(char)) return "rtl";
|
|
187
|
-
if (strongLtr.test(char)) return "ltr";
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return "rtl";
|
|
191
|
-
}
|
|
192
|
-
function physicalAlignment(alignment, direction) {
|
|
193
|
-
const value = alignment ?? "left";
|
|
194
|
-
if (direction !== "rtl" || value === "center") return value;
|
|
195
|
-
return value === "left" ? "right" : "left";
|
|
196
|
-
}
|
|
197
|
-
function paragraphDirectionAt(text2, deckDirection) {
|
|
198
|
-
if (deckDirection !== "rtl") return () => "ltr";
|
|
199
|
-
const source = String(text2 ?? ""), starts = [], directions = [];
|
|
200
|
-
let start = 0;
|
|
201
|
-
const close = (end) => {
|
|
202
|
-
starts.push(start);
|
|
203
|
-
directions.push(paragraphDirection(source.slice(start, end), "rtl"));
|
|
204
|
-
};
|
|
205
|
-
for (const match of source.matchAll(/\r\n|\r|\n/g)) {
|
|
206
|
-
close(match.index);
|
|
207
|
-
start = match.index + match[0].length;
|
|
208
|
-
}
|
|
209
|
-
close(source.length);
|
|
210
|
-
return (offset) => {
|
|
211
|
-
for (let index = starts.length - 1; index >= 0; index--) if (offset >= starts[index]) return directions[index];
|
|
212
|
-
return directions[0];
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// src/content-walk.ts
|
|
217
|
-
function isRecord2(value) {
|
|
218
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
219
|
-
}
|
|
220
|
-
function pathFor(parentPath, key) {
|
|
221
|
-
return parentPath === "/" ? `/${key}` : `${parentPath}/${key.replaceAll("~", "~0").replaceAll("/", "~1")}`;
|
|
222
|
-
}
|
|
223
|
-
var bareIdPattern = /^[a-z0-9][a-z0-9-]*$/;
|
|
224
|
-
var promotedRegionKeys = [
|
|
225
|
-
"left",
|
|
226
|
-
"center",
|
|
227
|
-
"right",
|
|
228
|
-
"left+center",
|
|
229
|
-
"center+right",
|
|
230
|
-
"left+center+right",
|
|
231
|
-
"top",
|
|
232
|
-
"middle",
|
|
233
|
-
"bottom",
|
|
234
|
-
"top+middle",
|
|
235
|
-
"middle+bottom",
|
|
236
|
-
"top+middle+bottom",
|
|
237
|
-
"top:left",
|
|
238
|
-
"top:center",
|
|
239
|
-
"top:right",
|
|
240
|
-
"top:left+center",
|
|
241
|
-
"top:center+right",
|
|
242
|
-
"top:left+center+right",
|
|
243
|
-
"middle:left",
|
|
244
|
-
"middle:center",
|
|
245
|
-
"middle:right",
|
|
246
|
-
"middle:left+center",
|
|
247
|
-
"middle:center+right",
|
|
248
|
-
"middle:left+center+right",
|
|
249
|
-
"bottom:left",
|
|
250
|
-
"bottom:center",
|
|
251
|
-
"bottom:right",
|
|
252
|
-
"bottom:left+center",
|
|
253
|
-
"bottom:center+right",
|
|
254
|
-
"bottom:left+center+right",
|
|
255
|
-
"top+middle:left",
|
|
256
|
-
"top+middle:center",
|
|
257
|
-
"top+middle:right",
|
|
258
|
-
"top+middle:left+center",
|
|
259
|
-
"top+middle:center+right",
|
|
260
|
-
"top+middle:left+center+right",
|
|
261
|
-
"middle+bottom:left",
|
|
262
|
-
"middle+bottom:center",
|
|
263
|
-
"middle+bottom:right",
|
|
264
|
-
"middle+bottom:left+center",
|
|
265
|
-
"middle+bottom:center+right",
|
|
266
|
-
"middle+bottom:left+center+right",
|
|
267
|
-
"top+middle+bottom:left",
|
|
268
|
-
"top+middle+bottom:center",
|
|
269
|
-
"top+middle+bottom:right",
|
|
270
|
-
"top+middle+bottom:left+center",
|
|
271
|
-
"top+middle+bottom:center+right",
|
|
272
|
-
"top+middle+bottom:left+center+right"
|
|
273
|
-
];
|
|
274
|
-
function visitContentPayloads(slide, slidePath, visit) {
|
|
275
|
-
const walk = (payload, path, depth, ancestors) => {
|
|
276
|
-
visit(payload, path);
|
|
277
|
-
if (!Array.isArray(payload.blocks) || depth >= MAX_COMPOSITION_DEPTH || ancestors.includes(payload)) return;
|
|
278
|
-
const nested = [...ancestors, payload];
|
|
279
|
-
const blocksPath = pathFor(path, "blocks");
|
|
280
|
-
payload.blocks.forEach((block, index) => {
|
|
281
|
-
if (isRecord2(block)) walk(block, `${blocksPath}/${index}`, depth + 1, nested);
|
|
282
|
-
});
|
|
283
|
-
};
|
|
284
|
-
if (Array.isArray(slide.blocks)) {
|
|
285
|
-
const blocksPath = pathFor(slidePath, "blocks");
|
|
286
|
-
slide.blocks.forEach((block, index) => {
|
|
287
|
-
if (isRecord2(block)) walk(block, `${blocksPath}/${index}`, 0, []);
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
for (const key of promotedRegionKeys) {
|
|
291
|
-
const region = slide[key];
|
|
292
|
-
if (isRecord2(region)) walk(region, pathFor(slidePath, key), 0, []);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// src/script-fonts.ts
|
|
297
|
-
var eastAsianScripts = /* @__PURE__ */ new Set(["Hani", "Hans", "Hant", "Hanb", "Jpan", "Kore", "Hang", "Jamo", "Hira", "Kana", "Hrkt", "Bopo", "Yiii"]);
|
|
298
|
-
var complexScripts = /* @__PURE__ */ new Set([
|
|
299
|
-
"Arab",
|
|
300
|
-
"Hebr",
|
|
301
|
-
"Syrc",
|
|
302
|
-
"Thaa",
|
|
303
|
-
"Nkoo",
|
|
304
|
-
"Adlm",
|
|
305
|
-
"Rohg",
|
|
306
|
-
"Mand",
|
|
307
|
-
"Samr",
|
|
308
|
-
"Deva",
|
|
309
|
-
"Beng",
|
|
310
|
-
"Guru",
|
|
311
|
-
"Gujr",
|
|
312
|
-
"Orya",
|
|
313
|
-
"Taml",
|
|
314
|
-
"Telu",
|
|
315
|
-
"Knda",
|
|
316
|
-
"Mlym",
|
|
317
|
-
"Sinh",
|
|
318
|
-
"Thai",
|
|
319
|
-
"Laoo",
|
|
320
|
-
"Tibt",
|
|
321
|
-
"Mymr",
|
|
322
|
-
"Khmr",
|
|
323
|
-
"Mong",
|
|
324
|
-
"Bali",
|
|
325
|
-
"Java",
|
|
326
|
-
"Lana",
|
|
327
|
-
"Tale",
|
|
328
|
-
"Talu",
|
|
329
|
-
"Cakm",
|
|
330
|
-
"Olck"
|
|
331
|
-
]);
|
|
332
|
-
var rtlScripts = /* @__PURE__ */ new Set(["Arab", "Hebr", "Syrc", "Thaa", "Nkoo", "Adlm", "Rohg", "Mand", "Samr"]);
|
|
333
|
-
var latinSlotScripts = /* @__PURE__ */ new Set(["Latn", "Cyrl", "Grek", "Zyyy", "Zzzz"]);
|
|
334
|
-
var deprecatedLanguages = { iw: "he", in: "id", ji: "yi" };
|
|
335
|
-
var matchingLanguages = { ...deprecatedLanguages, no: "nb", zsm: "ms", ku: "kmr" };
|
|
336
|
-
var likelyScripts = {
|
|
337
|
-
zh: { script: "Hans", regions: { TW: "Hant", HK: "Hant", MO: "Hant" } },
|
|
338
|
-
sr: { script: "Cyrl", regions: { ME: "Latn" } },
|
|
339
|
-
pa: { script: "Guru", regions: { PK: "Arab" } },
|
|
340
|
-
az: { script: "Latn", regions: { IR: "Arab" } },
|
|
341
|
-
uz: { script: "Latn", regions: { AF: "Arab" } },
|
|
342
|
-
bs: { script: "Latn" },
|
|
343
|
-
mn: { script: "Cyrl", regions: { CN: "Mong" } },
|
|
344
|
-
ms: { script: "Latn" }
|
|
345
|
-
};
|
|
346
|
-
var bareLanguageId = /^[a-z][a-z0-9-]*$/;
|
|
347
|
-
function scriptFontRole(script) {
|
|
348
|
-
const code = normalizeScript(script);
|
|
349
|
-
if (code && eastAsianScripts.has(code)) return "eastAsian";
|
|
350
|
-
if (code && complexScripts.has(code)) return "complexScript";
|
|
351
|
-
return "latin";
|
|
352
|
-
}
|
|
353
|
-
function normalizeScript(value) {
|
|
354
|
-
if (typeof value !== "string" || !/^[A-Za-z]{4}$/.test(value)) return void 0;
|
|
355
|
-
return value.slice(0, 1).toUpperCase() + value.slice(1).toLowerCase();
|
|
356
|
-
}
|
|
357
|
-
function text(value) {
|
|
358
|
-
return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
|
|
359
|
-
}
|
|
360
|
-
var tagSyntax = /^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/;
|
|
361
|
-
function splitTag(tag) {
|
|
362
|
-
if (!tagSyntax.test(tag)) return void 0;
|
|
363
|
-
const [first = "", ...rest] = tag.split("-");
|
|
364
|
-
const language = first.toLowerCase();
|
|
365
|
-
if (language === "und") return void 0;
|
|
366
|
-
let script;
|
|
367
|
-
let region;
|
|
368
|
-
for (const subtag of rest) {
|
|
369
|
-
if (subtag.length === 1) break;
|
|
370
|
-
if (!script && !region && /^[A-Za-z]{4}$/.test(subtag)) script = normalizeScript(subtag);
|
|
371
|
-
else if (!region && /^(?:[A-Za-z]{2}|\d{3})$/.test(subtag)) region = subtag.toUpperCase();
|
|
372
|
-
}
|
|
373
|
-
return { language, script, region };
|
|
374
|
-
}
|
|
375
|
-
function canonicalTag(tag) {
|
|
376
|
-
let extension = false;
|
|
377
|
-
return tag.split("-").map((subtag, index) => {
|
|
378
|
-
const lower = subtag.toLowerCase();
|
|
379
|
-
if (index === 0) return deprecatedLanguages[lower] ?? lower;
|
|
380
|
-
if (extension) return lower;
|
|
381
|
-
if (subtag.length === 1) {
|
|
382
|
-
extension = true;
|
|
383
|
-
return lower;
|
|
384
|
-
}
|
|
385
|
-
if (/^[A-Za-z]{4}$/.test(subtag)) return normalizeScript(subtag);
|
|
386
|
-
if (/^[A-Za-z]{2}$/.test(subtag)) return subtag.toUpperCase();
|
|
387
|
-
return lower;
|
|
388
|
-
}).join("-");
|
|
389
|
-
}
|
|
390
|
-
function vendoredScript(language, region) {
|
|
391
|
-
const entry = likelyScripts[language];
|
|
392
|
-
return entry ? (region && entry.regions?.[region]) ?? entry.script : void 0;
|
|
393
|
-
}
|
|
394
|
-
function inferScript(tag) {
|
|
395
|
-
const parts = splitTag(tag);
|
|
396
|
-
if (!parts) return void 0;
|
|
397
|
-
const known = parts.script ?? vendoredScript(matchingLanguages[parts.language] ?? parts.language, parts.region);
|
|
398
|
-
if (known) return known;
|
|
399
|
-
try {
|
|
400
|
-
return normalizeScript(new Intl.Locale(tag).maximize().script);
|
|
401
|
-
} catch {
|
|
402
|
-
return void 0;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
function inlineRecords(document, kind2) {
|
|
406
|
-
const kinds = isRecord2(document.catalogs) ? document.catalogs : {};
|
|
407
|
-
const entry = kinds[kind2];
|
|
408
|
-
return isRecord2(entry) && Array.isArray(entry.records) ? entry.records.filter(isRecord2) : [];
|
|
409
|
-
}
|
|
410
|
-
function allRecords(document, kind2) {
|
|
411
|
-
return [...inlineRecords(document, kind2), ...catalogs[kind2]];
|
|
412
|
-
}
|
|
413
|
-
function resolveReference(lookup, kind2, reference) {
|
|
414
|
-
if (typeof reference === "string") return lookup(kind2, reference);
|
|
415
|
-
if (!isRecord2(reference)) return void 0;
|
|
416
|
-
const base = typeof reference.id === "string" ? lookup(kind2, reference.id) : void 0;
|
|
417
|
-
return { ...base, ...reference };
|
|
418
|
-
}
|
|
419
|
-
function matchLanguageTag(document, tag) {
|
|
420
|
-
const wanted = splitTag(tag);
|
|
421
|
-
if (!wanted) return void 0;
|
|
422
|
-
const records = allRecords(document, "languages").filter((record5) => typeof record5.bcp47 === "string");
|
|
423
|
-
const key = canonicalTag(tag).toLowerCase();
|
|
424
|
-
const exact = records.find((record5) => canonicalTag(record5.bcp47).toLowerCase() === key);
|
|
425
|
-
if (exact) return exact;
|
|
426
|
-
const language = matchingLanguages[wanted.language] ?? wanted.language;
|
|
427
|
-
const script = wanted.script ?? vendoredScript(language, wanted.region);
|
|
428
|
-
const candidates = records.flatMap((record5) => {
|
|
429
|
-
const parts = splitTag(record5.bcp47);
|
|
430
|
-
if (!parts || (matchingLanguages[parts.language] ?? parts.language) !== language) return [];
|
|
431
|
-
const recordScript = normalizeScript(record5.script) ?? parts.script ?? vendoredScript(language, parts.region);
|
|
432
|
-
if (script && recordScript && script !== recordScript) return [];
|
|
433
|
-
return [{ record: record5, region: parts.region }];
|
|
434
|
-
});
|
|
435
|
-
return candidates.find((candidate) => wanted.region && candidate.region === wanted.region)?.record ?? candidates.find((candidate) => !candidate.region)?.record ?? candidates[0]?.record;
|
|
436
|
-
}
|
|
437
|
-
function resolveLanguage(document, lookup, reference) {
|
|
438
|
-
if (typeof reference === "string") {
|
|
439
|
-
const value = reference.trim();
|
|
440
|
-
const byId = bareLanguageId.test(value) ? lookup("languages", value) : void 0;
|
|
441
|
-
if (byId) return { record: byId, languageId: value };
|
|
442
|
-
if (/^[a-z][a-z0-9+.-]*:/i.test(value) || !splitTag(value)) return void 0;
|
|
443
|
-
const matched = matchLanguageTag(document, value);
|
|
444
|
-
if (matched) return { record: matched, authoredTag: value, languageId: text(matched.id) };
|
|
445
|
-
return inferScript(value) ? { record: {}, authoredTag: value } : void 0;
|
|
446
|
-
}
|
|
447
|
-
if (!isRecord2(reference)) return void 0;
|
|
448
|
-
const authoredTag = text(reference.bcp47);
|
|
449
|
-
if (authoredTag !== void 0 && !splitTag(authoredTag)) return void 0;
|
|
450
|
-
const base = typeof reference.id === "string" ? lookup("languages", reference.id) : authoredTag ? matchLanguageTag(document, authoredTag) : void 0;
|
|
451
|
-
const record5 = { ...base, ...reference };
|
|
452
|
-
const tag = text(record5.bcp47);
|
|
453
|
-
if (!tag || !splitTag(tag)) return void 0;
|
|
454
|
-
return { record: record5, authoredTag, authoredOoxmlLang: text(reference.ooxmlLang), languageId: text(base?.id) };
|
|
455
|
-
}
|
|
456
|
-
function pairFamilies(value) {
|
|
457
|
-
if (!isRecord2(value)) return void 0;
|
|
458
|
-
const major = text(value.major);
|
|
459
|
-
const minor = text(value.minor);
|
|
460
|
-
const heading = major ?? minor;
|
|
461
|
-
const body = minor ?? major;
|
|
462
|
-
return heading && body ? { heading, body } : void 0;
|
|
463
|
-
}
|
|
464
|
-
function schemeServesLanguage(scheme, language) {
|
|
465
|
-
const entries = Array.isArray(scheme.languages) ? scheme.languages.filter((entry) => typeof entry === "string") : [];
|
|
466
|
-
if (entries.length === 0) return true;
|
|
467
|
-
const name = text(language.name)?.toLowerCase();
|
|
468
|
-
if (!name) return false;
|
|
469
|
-
const base = name.replace(/\s*\([^)]*\)\s*$/, "");
|
|
470
|
-
return entries.some((entry) => {
|
|
471
|
-
const value = entry.trim().toLowerCase();
|
|
472
|
-
return value === name || value === base;
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
function resolveScriptFonts(input, options = {}) {
|
|
476
|
-
const document = isRecord2(input) ? input : {};
|
|
477
|
-
const lookup = (kind2, id) => inlineRecords(document, kind2).find((record6) => record6.id === id) ?? catalogs[kind2].find((record6) => record6.id === id);
|
|
478
|
-
let design = isRecord2(document.design) ? document.design : {};
|
|
479
|
-
if (options.slideIndex !== void 0) {
|
|
480
|
-
const slides = Array.isArray(document.slides) ? document.slides : [];
|
|
481
|
-
if (!Number.isInteger(options.slideIndex) || options.slideIndex < 0 || options.slideIndex >= slides.length) {
|
|
482
|
-
throw new RangeError(`slideIndex must be an integer between 0 and ${slides.length - 1}.`);
|
|
483
|
-
}
|
|
484
|
-
const slide = slides[options.slideIndex];
|
|
485
|
-
if (isRecord2(slide) && isRecord2(slide.design)) design = { ...design, ...slide.design };
|
|
486
|
-
}
|
|
487
|
-
const theme = resolveReference(lookup, "themes", design.theme ?? "minimal") ?? {};
|
|
488
|
-
const scheme = resolveFontSchemeReference(design.fontScheme ?? theme.fontScheme ?? DEFAULT_FONT_SCHEME, (id) => lookup("fontSchemes", id)).scheme;
|
|
489
|
-
const latin = resolveFontFamilies(scheme);
|
|
490
|
-
const fromOption = options.language !== void 0 ? resolveLanguage(document, lookup, options.language) : void 0;
|
|
491
|
-
const fromDocument = fromOption ? void 0 : resolveLanguage(document, lookup, document.language);
|
|
492
|
-
const requestedDefault = text(options.defaultLanguage);
|
|
493
|
-
const fallbackTag = requestedDefault && splitTag(requestedDefault) ? requestedDefault : "en-US";
|
|
494
|
-
const language = fromOption ?? fromDocument ?? resolveLanguage(document, lookup, fallbackTag) ?? { record: {}, authoredTag: fallbackTag };
|
|
495
|
-
const languageSource = fromOption ? "option" : fromDocument ? "document" : "default";
|
|
496
|
-
const record5 = language.record;
|
|
497
|
-
const bcp47 = canonicalTag(language.authoredTag ?? text(record5.bcp47) ?? fallbackTag);
|
|
498
|
-
const curated = text(record5.ooxmlLang);
|
|
499
|
-
const lang = language.authoredOoxmlLang ? canonicalTag(language.authoredOoxmlLang) : language.authoredTag && splitTag(language.authoredTag)?.region ? canonicalTag(language.authoredTag) : curated ? canonicalTag(curated) : bcp47;
|
|
500
|
-
const parts = splitTag(bcp47);
|
|
501
|
-
const script = normalizeScript(record5.script) ?? (language.languageId ? parts?.script ?? (parts ? vendoredScript(matchingLanguages[parts.language] ?? parts.language, parts.region) : void 0) ?? normalizeScript(matchLanguageTag({}, bcp47)?.script) : inferScript(bcp47)) ?? "Zzzz";
|
|
502
|
-
const scriptRole = scriptFontRole(script);
|
|
503
|
-
const declaredDirection = record5.direction;
|
|
504
|
-
const direction = declaredDirection === "rtl" || declaredDirection === "ltr" ? declaredDirection : rtlScripts.has(script) ? "rtl" : "ltr";
|
|
505
|
-
const app = options.app ?? "PowerPoint";
|
|
506
|
-
const preferred = app === "Google Slides" ? record5.googleFontScheme : record5.fontScheme;
|
|
507
|
-
const alternate = app === "Google Slides" ? record5.fontScheme : record5.googleFontScheme;
|
|
508
|
-
const languageScheme = resolveReference(lookup, "fontSchemes", preferred) ?? resolveReference(lookup, "fontSchemes", alternate);
|
|
509
|
-
const languageFamilies = pairFamilies(languageScheme);
|
|
510
|
-
const schemeAdmitsLanguage = schemeServesLanguage(scheme, record5);
|
|
511
|
-
const slot = (role, family) => {
|
|
512
|
-
const explicit = pairFamilies(scheme[role]);
|
|
513
|
-
if (explicit) return { ...explicit, source: "fontScheme" };
|
|
514
|
-
if (scheme.languageFamily === family && schemeAdmitsLanguage) return { ...latin, source: "schemeFamily" };
|
|
515
|
-
if (scriptRole === role && languageFamilies) return { ...languageFamilies, source: "language" };
|
|
516
|
-
return { heading: latin.heading, body: latin.body, source: "latin" };
|
|
517
|
-
};
|
|
518
|
-
const eastAsian = slot("eastAsian", "ea");
|
|
519
|
-
const complexScript = slot("complexScript", "cs");
|
|
520
|
-
const heading = { latin: latin.heading, eastAsian: eastAsian.heading, complexScript: complexScript.heading };
|
|
521
|
-
const body = { latin: latin.body, eastAsian: eastAsian.body, complexScript: complexScript.body };
|
|
522
|
-
let supplement;
|
|
523
|
-
if (!latinSlotScripts.has(script)) {
|
|
524
|
-
const families = scriptRole === "latin" ? languageFamilies : (scriptRole === "eastAsian" ? eastAsian : complexScript).source === "latin" ? void 0 : { heading: heading[scriptRole], body: body[scriptRole] };
|
|
525
|
-
if (families) supplement = { script: script === "Kore" ? "Hang" : script, heading: families.heading, body: families.body };
|
|
526
|
-
}
|
|
527
|
-
return {
|
|
528
|
-
...body,
|
|
529
|
-
lang,
|
|
530
|
-
bcp47,
|
|
531
|
-
...language.languageId ? { languageId: language.languageId } : {},
|
|
532
|
-
languageSource,
|
|
533
|
-
script,
|
|
534
|
-
scriptRole,
|
|
535
|
-
direction,
|
|
536
|
-
rtl: direction === "rtl",
|
|
537
|
-
heading,
|
|
538
|
-
body,
|
|
539
|
-
...supplement ? { supplement } : {},
|
|
540
|
-
sources: { eastAsian: eastAsian.source, complexScript: complexScript.source }
|
|
541
|
-
};
|
|
542
|
-
}
|
|
543
|
-
function resolveSlideDirection(presentation, slideIndex) {
|
|
544
|
-
if (!isRecord2(presentation) || presentation.language === void 0) return "ltr";
|
|
545
|
-
try {
|
|
546
|
-
return resolveScriptFonts(presentation, Number.isInteger(slideIndex) ? { slideIndex } : {}).direction;
|
|
547
|
-
} catch {
|
|
548
|
-
try {
|
|
549
|
-
return resolveScriptFonts(presentation).direction;
|
|
550
|
-
} catch {
|
|
551
|
-
return "ltr";
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// src/numbering.ts
|
|
557
|
-
var NUMBERING_STYLES = ["arabic", "roman-upper", "roman-lower", "alpha-upper", "alpha-lower"];
|
|
558
|
-
var NUMBERING_SUFFIXES = ["period", "paren", "paren-both"];
|
|
559
|
-
var MAX_NUMBERING_VALUE = 32767;
|
|
560
|
-
var MAX_ROMAN_VALUE = 3999;
|
|
561
|
-
var MAX_NUMBERING_LEVELS = 9;
|
|
562
|
-
var styleSet = new Set(NUMBERING_STYLES);
|
|
563
|
-
var suffixSet = new Set(NUMBERING_SUFFIXES);
|
|
564
|
-
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
565
|
-
function resolveEntry(entry, label) {
|
|
566
|
-
if (typeof entry === "string") {
|
|
567
|
-
if (!styleSet.has(entry)) throw new RangeError(`${label} must be one of ${NUMBERING_STYLES.join(", ")}.`);
|
|
568
|
-
return { style: entry, start: 1, suffix: "period" };
|
|
569
|
-
}
|
|
570
|
-
if (!isObject(entry)) throw new TypeError(`${label} must be a style name or a numbering object.`);
|
|
571
|
-
const { style = "arabic", start = 1, suffix = "period" } = entry;
|
|
572
|
-
if (typeof style !== "string" || !styleSet.has(style)) throw new RangeError(`${label}.style must be one of ${NUMBERING_STYLES.join(", ")}.`);
|
|
573
|
-
if (typeof suffix !== "string" || !suffixSet.has(suffix)) throw new RangeError(`${label}.suffix must be one of ${NUMBERING_SUFFIXES.join(", ")}.`);
|
|
574
|
-
if (typeof start !== "number" || !Number.isInteger(start) || start < 1 || start > MAX_NUMBERING_VALUE) throw new RangeError(`${label}.start must be an integer from 1 to ${MAX_NUMBERING_VALUE}.`);
|
|
575
|
-
return { style, start, suffix };
|
|
576
|
-
}
|
|
577
|
-
function resolveNumbering(input) {
|
|
578
|
-
if (Array.isArray(input)) {
|
|
579
|
-
if (!input.length || input.length > MAX_NUMBERING_LEVELS) throw new RangeError(`numbering must list from 1 to ${MAX_NUMBERING_LEVELS} levels.`);
|
|
580
|
-
return input.map((entry, index) => resolveEntry(entry, `numbering[${index}]`));
|
|
581
|
-
}
|
|
582
|
-
return [resolveEntry(input, "numbering")];
|
|
583
|
-
}
|
|
584
|
-
function numberingAtLevel(levels, level) {
|
|
585
|
-
return levels[Math.min(Math.max(0, level), levels.length - 1)];
|
|
586
|
-
}
|
|
587
|
-
var ROMAN = [[1e3, "m"], [900, "cm"], [500, "d"], [400, "cd"], [100, "c"], [90, "xc"], [50, "l"], [40, "xl"], [10, "x"], [9, "ix"], [5, "v"], [4, "iv"], [1, "i"]];
|
|
588
|
-
function bareNumber(value, style) {
|
|
589
|
-
if (style === "arabic") return String(value);
|
|
590
|
-
if (style === "roman-upper" || style === "roman-lower") {
|
|
591
|
-
if (value > MAX_ROMAN_VALUE) return null;
|
|
592
|
-
let rest = value, out = "";
|
|
593
|
-
for (const [amount, glyphs] of ROMAN) while (rest >= amount) {
|
|
594
|
-
out += glyphs;
|
|
595
|
-
rest -= amount;
|
|
596
|
-
}
|
|
597
|
-
return style === "roman-upper" ? out.toUpperCase() : out;
|
|
598
|
-
}
|
|
599
|
-
const letter = String.fromCharCode(97 + (value - 1) % 26).repeat(Math.floor((value - 1) / 26) + 1);
|
|
600
|
-
return style === "alpha-upper" ? letter.toUpperCase() : letter;
|
|
601
|
-
}
|
|
602
|
-
function numberingStyleDraws(style, value) {
|
|
603
|
-
return bareNumber(value, style) !== null;
|
|
604
|
-
}
|
|
605
|
-
function formatListNumber(value, style = "arabic", suffix = "period") {
|
|
606
|
-
if (!Number.isInteger(value) || value < 1) throw new RangeError("A list number must be an integer of at least 1.");
|
|
607
|
-
if (!styleSet.has(style)) throw new RangeError(`Unknown numbering style '${String(style)}'.`);
|
|
608
|
-
if (!suffixSet.has(suffix)) throw new RangeError(`Unknown numbering suffix '${String(suffix)}'.`);
|
|
609
|
-
const bare = bareNumber(value, style) ?? String(value);
|
|
610
|
-
return suffix === "period" ? `${bare}.` : suffix === "paren" ? `${bare})` : `(${bare})`;
|
|
611
|
-
}
|
|
612
|
-
function listNumbers(items, numbering) {
|
|
613
|
-
const levels = resolveNumbering(numbering);
|
|
614
|
-
const counters = [];
|
|
615
|
-
return items.map((item, index) => {
|
|
616
|
-
const object = isObject(item) ? item : void 0;
|
|
617
|
-
const level = object?.level === void 0 ? 0 : object.level;
|
|
618
|
-
if (typeof level !== "number" || !Number.isInteger(level) || level < 0) throw new RangeError("List levels must be nonnegative integers.");
|
|
619
|
-
const own = object?.start;
|
|
620
|
-
if (own !== void 0 && (typeof own !== "number" || !Number.isInteger(own) || own < 1 || own > MAX_NUMBERING_VALUE)) throw new RangeError(`A list item start must be an integer from 1 to ${MAX_NUMBERING_VALUE}.`);
|
|
621
|
-
const { style: authored, start, suffix } = numberingAtLevel(levels, level);
|
|
622
|
-
const previous = counters[level];
|
|
623
|
-
const value = own ?? (previous === void 0 ? start : previous + 1);
|
|
624
|
-
counters[level] = value;
|
|
625
|
-
counters.length = level + 1;
|
|
626
|
-
const adapted = !numberingStyleDraws(authored, value);
|
|
627
|
-
const style = adapted ? "arabic" : authored;
|
|
628
|
-
return { index, level, value, style, suffix, text: formatListNumber(value, style, suffix), ...adapted ? { adapted: "roman-range" } : {} };
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
function sliceNumberedItems(items, numbering, from, to = items.length) {
|
|
632
|
-
const whole = listNumbers(items, numbering);
|
|
633
|
-
const result = items.slice(from, to);
|
|
634
|
-
for (let index = 0; index < result.length; index++) {
|
|
635
|
-
const wanted = whole[from + index].value;
|
|
636
|
-
if (listNumbers(result.slice(0, index + 1), numbering)[index].value === wanted) continue;
|
|
637
|
-
const item = result[index];
|
|
638
|
-
const object = isObject(item) ? item : { text: item };
|
|
639
|
-
result[index] = { ...object, start: wanted };
|
|
640
|
-
}
|
|
641
|
-
return result;
|
|
642
|
-
}
|
|
643
|
-
function numberingFindings(payload) {
|
|
644
|
-
const errors = [], warnings = [];
|
|
645
|
-
const lists = ["items", "bullets"].filter((key) => Array.isArray(payload[key]));
|
|
646
|
-
if (Object.hasOwn(payload, "numbering")) {
|
|
647
|
-
if (!Object.hasOwn(payload, "items") && !Object.hasOwn(payload, "bullets")) {
|
|
648
|
-
errors.push({ key: "numbering", message: "numbering applies to 'items' or 'bullets'; this payload has neither", params: {} });
|
|
649
|
-
} else {
|
|
650
|
-
for (const key of lists) {
|
|
651
|
-
let numbers;
|
|
652
|
-
try {
|
|
653
|
-
numbers = listNumbers(payload[key], payload.numbering);
|
|
654
|
-
} catch {
|
|
655
|
-
continue;
|
|
656
|
-
}
|
|
657
|
-
const largest = numbers.reduce((max, number) => Math.max(max, number.value), 0);
|
|
658
|
-
if (largest > MAX_NUMBERING_VALUE) errors.push({ key, message: `numbering counts up to ${largest}, past the native limit of ${MAX_NUMBERING_VALUE}`, params: { largest, limit: MAX_NUMBERING_VALUE } });
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
} else {
|
|
662
|
-
for (const key of lists) {
|
|
663
|
-
if (payload[key].some((item) => isObject(item) && item.start !== void 0)) {
|
|
664
|
-
warnings.push({ key, message: `'start' on ${key} entries has no effect without a 'numbering' field on the payload`, params: {} });
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
return { errors, warnings };
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
// src/furniture-fields.ts
|
|
672
|
-
var DEFAULT_FURNITURE_DATE_FORMAT = "M/d/yyyy";
|
|
673
|
-
var DEFAULT_SLIDE_NUMBER_FORMAT = "{current}";
|
|
674
|
-
var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
675
|
-
var weekdays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
676
|
-
var tokenWidths = { y: [1, 2, 4], M: [1, 2, 3, 4], d: [1, 2], E: [1, 2, 3, 4] };
|
|
677
|
-
function parseFurnitureDateFormat(pattern) {
|
|
678
|
-
if (!pattern) return "dateFormat must not be empty.";
|
|
679
|
-
const tokens = [];
|
|
680
|
-
const literal = (text2) => {
|
|
681
|
-
const last = tokens[tokens.length - 1];
|
|
682
|
-
if (last?.kind === "literal") last.text += text2;
|
|
683
|
-
else tokens.push({ kind: "literal", text: text2 });
|
|
684
|
-
};
|
|
685
|
-
for (let index = 0; index < pattern.length; ) {
|
|
686
|
-
const character = pattern.charAt(index);
|
|
687
|
-
if (character === "'") {
|
|
688
|
-
if (pattern[index + 1] === "'") {
|
|
689
|
-
literal("'");
|
|
690
|
-
index += 2;
|
|
691
|
-
continue;
|
|
692
|
-
}
|
|
693
|
-
let end = index + 1, text2 = "";
|
|
694
|
-
for (; ; ) {
|
|
695
|
-
if (end >= pattern.length) return "dateFormat has an unterminated quoted literal.";
|
|
696
|
-
if (pattern[end] === "'") {
|
|
697
|
-
if (pattern[end + 1] === "'") {
|
|
698
|
-
text2 += "'";
|
|
699
|
-
end += 2;
|
|
700
|
-
continue;
|
|
701
|
-
}
|
|
702
|
-
break;
|
|
703
|
-
}
|
|
704
|
-
text2 += pattern[end];
|
|
705
|
-
end++;
|
|
706
|
-
}
|
|
707
|
-
literal(text2);
|
|
708
|
-
index = end + 1;
|
|
709
|
-
continue;
|
|
710
|
-
}
|
|
711
|
-
if (/[A-Za-z]/.test(character)) {
|
|
712
|
-
let end = index;
|
|
713
|
-
while (pattern[end] === character) end++;
|
|
714
|
-
const width = end - index;
|
|
715
|
-
if (!tokenWidths[character]?.includes(width)) return `dateFormat token '${pattern.slice(index, end)}' is not supported; use yyyy, yy, MMMM, MMM, MM, M, dd, d, EEEE or EEE, and quote literal letters.`;
|
|
716
|
-
tokens.push({ kind: "field", letter: character, width });
|
|
717
|
-
index = end;
|
|
718
|
-
continue;
|
|
719
|
-
}
|
|
720
|
-
literal(character);
|
|
721
|
-
index++;
|
|
722
|
-
}
|
|
723
|
-
return tokens;
|
|
724
|
-
}
|
|
725
|
-
function parseIsoDate(value) {
|
|
726
|
-
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
727
|
-
if (!match) return null;
|
|
728
|
-
const year = Number(match[1]), month = Number(match[2]), day = Number(match[3]);
|
|
729
|
-
const date = new Date(Date.UTC(2e3, month - 1, day));
|
|
730
|
-
date.setUTCFullYear(year);
|
|
731
|
-
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) return null;
|
|
732
|
-
return { year, month, day };
|
|
733
|
-
}
|
|
734
|
-
function formatFurnitureDate(iso, pattern) {
|
|
735
|
-
const date = parseIsoDate(iso);
|
|
736
|
-
if (!date) return { error: `'${iso}' is not an ISO YYYY-MM-DD calendar date.` };
|
|
737
|
-
const tokens = parseFurnitureDateFormat(pattern);
|
|
738
|
-
if (typeof tokens === "string") return { error: tokens };
|
|
739
|
-
const utc = new Date(Date.UTC(2e3, date.month - 1, date.day));
|
|
740
|
-
utc.setUTCFullYear(date.year);
|
|
741
|
-
const pad = (value, width) => String(value).padStart(width, "0");
|
|
742
|
-
let text2 = "";
|
|
743
|
-
for (const token of tokens) {
|
|
744
|
-
if (token.kind === "literal") {
|
|
745
|
-
text2 += token.text;
|
|
746
|
-
continue;
|
|
747
|
-
}
|
|
748
|
-
const { letter, width } = token;
|
|
749
|
-
if (letter === "y") text2 += width === 2 ? pad(date.year % 100, 2) : width === 4 ? pad(date.year, 4) : String(date.year);
|
|
750
|
-
else if (letter === "M") {
|
|
751
|
-
const name = months[date.month - 1] ?? "";
|
|
752
|
-
text2 += width >= 3 ? width === 4 ? name : name.slice(0, 3) : pad(date.month, width);
|
|
753
|
-
} else if (letter === "d") text2 += pad(date.day, width);
|
|
754
|
-
else {
|
|
755
|
-
const name = weekdays[utc.getUTCDay()] ?? "";
|
|
756
|
-
text2 += width === 4 ? name : name.slice(0, 3);
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
return { text: text2 };
|
|
760
|
-
}
|
|
761
|
-
function formatSlideNumber(format, current, total) {
|
|
762
|
-
if (!format.includes("{current}")) return { error: "slideNumberFormat must contain {current}." };
|
|
763
|
-
if (format.includes("{total}") && total === void 0) return { error: "slideNumberFormat uses {total}, which needs the rendered slide count." };
|
|
764
|
-
let text2 = "";
|
|
765
|
-
const fields2 = [];
|
|
766
|
-
for (const piece of format.split(/(\{current\}|\{total\})/)) {
|
|
767
|
-
if (piece === "{current}") {
|
|
768
|
-
const value = String(current);
|
|
769
|
-
fields2.push({ type: "slideNumber", start: text2.length, end: text2.length + value.length });
|
|
770
|
-
text2 += value;
|
|
771
|
-
} else if (piece === "{total}") text2 += String(total);
|
|
772
|
-
else text2 += piece;
|
|
773
|
-
}
|
|
774
|
-
return { text: text2, fields: fields2 };
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
// src/color.ts
|
|
778
|
-
var schemeColorSlots = /* @__PURE__ */ new Set([
|
|
779
|
-
"accent1",
|
|
780
|
-
"accent2",
|
|
781
|
-
"accent3",
|
|
782
|
-
"accent4",
|
|
783
|
-
"accent5",
|
|
784
|
-
"accent6",
|
|
785
|
-
"dark1",
|
|
786
|
-
"dark2",
|
|
787
|
-
"light1",
|
|
788
|
-
"light2",
|
|
789
|
-
"hyperlink",
|
|
790
|
-
"followedHyperlink"
|
|
791
|
-
]);
|
|
792
|
-
var schemeColorRoles = /* @__PURE__ */ new Set([
|
|
793
|
-
"primary",
|
|
794
|
-
"secondary",
|
|
795
|
-
"accent",
|
|
796
|
-
"background",
|
|
797
|
-
"surface",
|
|
798
|
-
"text",
|
|
799
|
-
"textSecondary"
|
|
800
|
-
]);
|
|
801
|
-
var defaultRoleSlots = {
|
|
802
|
-
primary: "accent1",
|
|
803
|
-
secondary: "accent2",
|
|
804
|
-
accent: "accent3",
|
|
805
|
-
background: "light1",
|
|
806
|
-
surface: "light2",
|
|
807
|
-
text: "dark1",
|
|
808
|
-
textSecondary: "dark2"
|
|
809
|
-
};
|
|
810
|
-
function normalizeHexColor(value) {
|
|
811
|
-
if (typeof value !== "string") return void 0;
|
|
812
|
-
let hex = value.trim();
|
|
813
|
-
if (/^#[0-9a-f]{3}$/i.test(hex)) hex = `#${[...hex.slice(1)].map((char) => char + char).join("")}`;
|
|
814
|
-
if (/^#[0-9a-f]{6}([0-9a-f]{2})?$/i.test(hex)) return hex.length === 9 ? `#${hex.slice(1, 7).toUpperCase()}` : hex.toUpperCase();
|
|
815
|
-
return void 0;
|
|
816
|
-
}
|
|
817
|
-
function schemeSlot(colorScheme, slot, fallback) {
|
|
818
|
-
return normalizeHexColor(colorScheme[slot]) ?? fallback;
|
|
819
|
-
}
|
|
820
|
-
function resolveVariable(variables, id, fallback) {
|
|
821
|
-
const entry = variables[id];
|
|
822
|
-
if (typeof entry === "string") return normalizeHexColor(entry) ?? fallback;
|
|
823
|
-
if (entry && typeof entry === "object" && entry.type === "color") {
|
|
824
|
-
return normalizeHexColor(entry.value) ?? fallback;
|
|
825
|
-
}
|
|
826
|
-
return fallback;
|
|
827
|
-
}
|
|
828
|
-
function resolveColorRef(reference, options) {
|
|
829
|
-
const { colorScheme, roles, variables = {}, fallback } = options;
|
|
830
|
-
const literal = normalizeHexColor(reference);
|
|
831
|
-
if (literal) return literal;
|
|
832
|
-
if (reference.startsWith("var:")) {
|
|
833
|
-
const id = reference.slice("var:".length);
|
|
834
|
-
if (/^[a-z][a-z0-9-]*$/.test(id)) return resolveVariable(variables, id, fallback);
|
|
835
|
-
return fallback;
|
|
836
|
-
}
|
|
837
|
-
if (schemeColorRoles.has(reference)) {
|
|
838
|
-
const roleKey = reference;
|
|
839
|
-
const fromRoles = roles?.[roleKey];
|
|
840
|
-
if (fromRoles) return normalizeHexColor(fromRoles) ?? fallback;
|
|
841
|
-
const fromScheme = normalizeHexColor(colorScheme[reference]);
|
|
842
|
-
if (fromScheme) return fromScheme;
|
|
843
|
-
const slot = defaultRoleSlots[reference];
|
|
844
|
-
return slot ? schemeSlot(colorScheme, slot, fallback) : fallback;
|
|
845
|
-
}
|
|
846
|
-
if (schemeColorSlots.has(reference)) return schemeSlot(colorScheme, reference, fallback);
|
|
847
|
-
return fallback;
|
|
848
|
-
}
|
|
849
|
-
function luminance(value) {
|
|
850
|
-
if (typeof value !== "string") return void 0;
|
|
851
|
-
let hex = value.trim();
|
|
852
|
-
if (/^#[0-9a-f]{3}$/i.test(hex)) hex = `#${[...hex.slice(1)].map((c) => c + c).join("")}`;
|
|
853
|
-
if (/^#[0-9a-f]{6}ff$/i.test(hex)) hex = hex.slice(0, 7);
|
|
854
|
-
if (!/^#[0-9a-f]{6}$/i.test(hex)) return void 0;
|
|
855
|
-
return [0.2126, 0.7152, 0.0722].reduce((sum, weight, index) => {
|
|
856
|
-
const offset = index * 2 + 1;
|
|
857
|
-
const n = Number.parseInt(hex.slice(offset, offset + 2), 16) / 255;
|
|
858
|
-
return sum + weight * (n <= 0.04045 ? n / 12.92 : ((n + 0.055) / 1.055) ** 2.4);
|
|
859
|
-
}, 0);
|
|
860
|
-
}
|
|
861
|
-
function colorContrast(foreground, background) {
|
|
862
|
-
const a = luminance(foreground), b = luminance(background);
|
|
863
|
-
return a === void 0 || b === void 0 ? void 0 : (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);
|
|
864
|
-
}
|
|
865
|
-
function textColorForFill(fill, preferred) {
|
|
866
|
-
const background = luminance(fill), contrast = colorContrast(preferred, fill);
|
|
867
|
-
if (background === void 0 || contrast === void 0 || contrast >= 4.5) return preferred;
|
|
868
|
-
return (background + 0.05) / 0.05 >= 1.05 / (background + 0.05) ? "#000000" : "#FFFFFF";
|
|
869
|
-
}
|
|
870
|
-
function chartColorForFill(fill, preferred) {
|
|
871
|
-
const contrast = colorContrast(preferred, fill);
|
|
872
|
-
if (contrast === void 0 || contrast >= 3) return preferred;
|
|
873
|
-
let hex = preferred.trim();
|
|
874
|
-
if (hex.length === 4) hex = `#${[...hex.slice(1)].map((c) => c + c).join("")}`;
|
|
875
|
-
const channels = [1, 3, 5].map((offset) => Number.parseInt(hex.slice(offset, offset + 2), 16));
|
|
876
|
-
for (let step = 1; step <= 255; step++) {
|
|
877
|
-
const candidates = [0, 255].map((target) => {
|
|
878
|
-
const color = `#${channels.map((channel) => Math.round(channel + (target - channel) * step / 255).toString(16).padStart(2, "0")).join("")}`.toUpperCase();
|
|
879
|
-
return { color, contrast: colorContrast(color, fill) ?? 0 };
|
|
880
|
-
}).sort((a, b) => b.contrast - a.contrast);
|
|
881
|
-
const best = candidates[0];
|
|
882
|
-
if (best && best.contrast >= 3) return best.color;
|
|
883
|
-
}
|
|
884
|
-
return textColorForFill(fill, preferred);
|
|
885
|
-
}
|
|
886
|
-
var CHART_SERIES_MIN_LIGHTNESS_STEP = 11;
|
|
887
|
-
var CHART_SERIES_MIN_DIFFERENCE = 12;
|
|
888
|
-
var D65 = [0.95047, 1, 1.08883];
|
|
889
|
-
function opaqueChannels(value) {
|
|
890
|
-
if (luminance(value) === void 0) return void 0;
|
|
891
|
-
let hex = value.trim();
|
|
892
|
-
if (hex.length === 4) hex = `#${[...hex.slice(1)].map((c) => c + c).join("")}`;
|
|
893
|
-
return [1, 3, 5].map((offset) => Number.parseInt(hex.slice(offset, offset + 2), 16));
|
|
894
|
-
}
|
|
895
|
-
var toLinear = (channel) => {
|
|
896
|
-
const n = channel / 255;
|
|
897
|
-
return n <= 0.04045 ? n / 12.92 : ((n + 0.055) / 1.055) ** 2.4;
|
|
898
|
-
};
|
|
899
|
-
var fromLinear = (value) => 255 * (value <= 31308e-7 ? value * 12.92 : 1.055 * value ** (1 / 2.4) - 0.055);
|
|
900
|
-
function labOf(channels) {
|
|
901
|
-
const [r, g, b] = channels.map(toLinear);
|
|
902
|
-
const f = (t) => t > 216 / 24389 ? Math.cbrt(t) : (24389 / 27 * t + 16) / 116;
|
|
903
|
-
const fx = f((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / D65[0]), fy = f(0.2126729 * r + 0.7151522 * g + 0.072175 * b), fz = f((0.0193339 * r + 0.119192 * g + 0.9503041 * b) / D65[2]);
|
|
904
|
-
return [116 * fy - 16, 500 * (fx - fy), 200 * (fy - fz)];
|
|
905
|
-
}
|
|
906
|
-
function labToLinear([l, a, b]) {
|
|
907
|
-
const fy = (l + 16) / 116, fx = fy + a / 500, fz = fy - b / 200;
|
|
908
|
-
const inverse = (t) => t ** 3 > 216 / 24389 ? t ** 3 : (116 * t - 16) / (24389 / 27);
|
|
909
|
-
const x = inverse(fx) * D65[0], y = l > 8 ? fy ** 3 : l / (24389 / 27), z = inverse(fz) * D65[2];
|
|
910
|
-
const rgb = [
|
|
911
|
-
3.2404542 * x - 1.5371385 * y - 0.4985314 * z,
|
|
912
|
-
-0.969266 * x + 1.8760108 * y + 0.041556 * z,
|
|
913
|
-
0.0556434 * x - 0.2040259 * y + 1.0572252 * z
|
|
914
|
-
];
|
|
915
|
-
return rgb.every((channel) => channel >= -5e-4 && channel <= 1.0005) ? rgb : void 0;
|
|
916
|
-
}
|
|
917
|
-
var hexOfChannels = (channels) => `#${channels.map((channel) => Math.max(0, Math.min(255, Math.round(channel))).toString(16).padStart(2, "0")).join("").toUpperCase()}`;
|
|
918
|
-
function labWithLightness(l, a, b) {
|
|
919
|
-
const chroma = Math.hypot(a, b), hue = Math.atan2(b, a);
|
|
920
|
-
const at2 = (scale) => labToLinear([l, Math.cos(hue) * chroma * scale, Math.sin(hue) * chroma * scale]);
|
|
921
|
-
let linear = at2(1);
|
|
922
|
-
if (!linear) {
|
|
923
|
-
let low = 0, high = 1;
|
|
924
|
-
for (let step = 0; step < 24; step++) {
|
|
925
|
-
const mid = (low + high) / 2;
|
|
926
|
-
if (at2(mid)) low = mid;
|
|
927
|
-
else high = mid;
|
|
928
|
-
}
|
|
929
|
-
linear = at2(low) ?? [0, 0, 0];
|
|
930
|
-
}
|
|
931
|
-
return hexOfChannels(linear.map((channel) => fromLinear(Math.max(0, Math.min(1, channel)))));
|
|
932
|
-
}
|
|
933
|
-
function labDifference(a, b) {
|
|
934
|
-
return Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
|
|
935
|
-
}
|
|
936
|
-
var CHART_SERIES_SHORTFALL_WEIGHT = 8;
|
|
937
|
-
function chartPaletteForFill(fill, palette) {
|
|
938
|
-
const opaqueFill = colorContrast(fill, fill) !== void 0;
|
|
939
|
-
const preferredLab = palette.map((color) => {
|
|
940
|
-
const channels = opaqueChannels(color);
|
|
941
|
-
return channels ? labOf(channels) : void 0;
|
|
942
|
-
});
|
|
943
|
-
const adjusted = palette.map((color) => chartColorForFill(fill, color));
|
|
944
|
-
const kept = palette.map((color, index) => opaqueFill && preferredLab[index] !== void 0 && adjusted[index] === color);
|
|
945
|
-
const placed = palette.map((_, index) => kept[index] || !opaqueFill || !preferredLab[index] ? adjusted[index] : void 0);
|
|
946
|
-
const placedLab = placed.map((color) => {
|
|
947
|
-
const channels = color === void 0 ? void 0 : opaqueChannels(color);
|
|
948
|
-
return channels ? labOf(channels) : void 0;
|
|
949
|
-
});
|
|
950
|
-
palette.forEach((_, index) => {
|
|
951
|
-
const wanted = preferredLab[index], base = adjusted[index];
|
|
952
|
-
if (placed[index] !== void 0 || !wanted) return;
|
|
953
|
-
const baseLab = labOf(opaqueChannels(base));
|
|
954
|
-
const needs = palette.flatMap((_2, other) => {
|
|
955
|
-
const before = preferredLab[other], lab = placedLab[other];
|
|
956
|
-
return other !== index && before && lab && placed[other] !== void 0 ? [{ lab, adjacent: Math.abs(other - index) === 1, wide: Math.min(labDifference(before, wanted), CHART_SERIES_MIN_DIFFERENCE), step: Math.min(Math.abs(before[0] - wanted[0]), CHART_SERIES_MIN_LIGHTNESS_STEP) }] : [];
|
|
957
|
-
});
|
|
958
|
-
const shortfall = (candidate) => needs.reduce((worst, need) => {
|
|
959
|
-
const lightness = Math.abs(need.lab[0] - candidate[0]) - need.step, difference = labDifference(need.lab, candidate) - need.wide;
|
|
960
|
-
return need.adjacent ? { near: Math.min(worst.near, difference, lightness), far: worst.far } : { near: Math.min(worst.near, difference), far: Math.min(worst.far, lightness) };
|
|
961
|
-
}, { near: Infinity, far: Infinity });
|
|
962
|
-
const deficit = (value) => Math.max(0, -value - 1e-9);
|
|
963
|
-
const cost = (candidate) => candidate.moved + CHART_SERIES_SHORTFALL_WEIGHT * deficit(candidate.near) + deficit(candidate.far);
|
|
964
|
-
const start = shortfall(baseLab);
|
|
965
|
-
if (deficit(start.near) === 0 && deficit(start.far) === 0) {
|
|
966
|
-
placed[index] = base;
|
|
967
|
-
placedLab[index] = baseLab;
|
|
968
|
-
return;
|
|
969
|
-
}
|
|
970
|
-
let best = { color: base, lab: baseLab, cost: cost({ moved: 0, ...start }), moved: 0 };
|
|
971
|
-
for (let step = 0; step <= 400; step++) {
|
|
972
|
-
const color = labWithLightness(step / 4, wanted[1], wanted[2]), contrast = colorContrast(color, fill);
|
|
973
|
-
if (contrast === void 0 || contrast < 3) continue;
|
|
974
|
-
const lab = labOf(opaqueChannels(color)), moved = Math.abs(lab[0] - baseLab[0]), total = cost({ moved, ...shortfall(lab) });
|
|
975
|
-
if (total < best.cost - 1e-9 || Math.abs(total - best.cost) <= 1e-9 && moved < best.moved) best = { color, lab, cost: total, moved };
|
|
976
|
-
}
|
|
977
|
-
placed[index] = best.color;
|
|
978
|
-
placedLab[index] = best.lab;
|
|
979
|
-
});
|
|
980
|
-
return placed;
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
// src/annotations.ts
|
|
984
|
-
var CITATION_MARKER_SCALE = 2 / 3;
|
|
985
|
-
var CITATION_MARKER_RAISE = 0.3;
|
|
986
|
-
var CAPTION_FONT_RATIO = 0.6;
|
|
987
|
-
var CAPTION_MAX_RATIO = 0.35;
|
|
988
|
-
var FOOTNOTE_MAX_RATIO = 0.35;
|
|
989
|
-
var CAPTIONABLE_FIELDS = Object.freeze(["image", "chart", "table", "video"]);
|
|
990
|
-
var record2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
991
|
-
var isRecord3 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
992
|
-
var round = (value) => Math.round(value * 1e6) / 1e6 || value;
|
|
993
|
-
var regionKey = /^(top|middle|bottom|left|center|right)([+:]|$)/;
|
|
994
|
-
var TEXT_FIELDS = ["text", "items", "bullets"];
|
|
995
|
-
function annotationText(value) {
|
|
996
|
-
if (typeof value === "string") return value;
|
|
997
|
-
if (Array.isArray(value)) return value.map((run) => typeof run === "string" ? run : record2(run).text ?? "").join("");
|
|
998
|
-
if (isRecord3(value) && value.text !== void 0) return annotationText(value.text);
|
|
999
|
-
return value === void 0 || value === null ? "" : String(value);
|
|
1000
|
-
}
|
|
1001
|
-
function captionSettings(value) {
|
|
1002
|
-
if (value === void 0 || value === null) return void 0;
|
|
1003
|
-
if (typeof value === "string" || Array.isArray(value)) return { text: value, position: "below", align: "left" };
|
|
1004
|
-
if (!isRecord3(value) || value.text === void 0) return void 0;
|
|
1005
|
-
return { text: value.text, position: value.position === "above" ? "above" : "below", align: value.align === "center" || value.align === "right" ? value.align : "left" };
|
|
1006
|
-
}
|
|
1007
|
-
function walkCitationRuns(slide, basePath, visit) {
|
|
1008
|
-
const runs = (value2, path) => {
|
|
1009
|
-
if (!Array.isArray(value2)) return;
|
|
1010
|
-
value2.forEach((run, index) => {
|
|
1011
|
-
if (!isRecord3(run) || typeof run.text !== "string") return;
|
|
1012
|
-
const cite = run.cite === void 0 ? [] : Array.isArray(run.cite) ? run.cite.filter((id) => typeof id === "string") : typeof run.cite === "string" ? [run.cite] : [];
|
|
1013
|
-
const footnote = typeof run.footnote === "string" || Array.isArray(run.footnote) ? run.footnote : void 0;
|
|
1014
|
-
if (!cite.length && footnote === void 0) return;
|
|
1015
|
-
visit({ path: `${path}.${index}`, run, cite, footnote });
|
|
1016
|
-
});
|
|
1017
|
-
};
|
|
1018
|
-
const list = (items, path) => {
|
|
1019
|
-
if (!Array.isArray(items)) return;
|
|
1020
|
-
items.forEach((item, index) => {
|
|
1021
|
-
if (Array.isArray(item)) runs(item, `${path}.${index}`);
|
|
1022
|
-
else if (isRecord3(item)) {
|
|
1023
|
-
runs(item.text, `${path}.${index}.text`);
|
|
1024
|
-
runs(item.description, `${path}.${index}.description`);
|
|
1025
|
-
}
|
|
1026
|
-
});
|
|
1027
|
-
};
|
|
1028
|
-
const host = (node, path, depth) => {
|
|
1029
|
-
const payload = record2(node);
|
|
1030
|
-
if (Array.isArray(payload.blocks)) {
|
|
1031
|
-
if (depth > 32) return;
|
|
1032
|
-
payload.blocks.forEach((block, index) => {
|
|
1033
|
-
host(block, `${path}.blocks.${index}`, depth + 1);
|
|
1034
|
-
});
|
|
1035
|
-
return;
|
|
1036
|
-
}
|
|
1037
|
-
for (const field of TEXT_FIELDS) {
|
|
1038
|
-
if (payload[field] === void 0) continue;
|
|
1039
|
-
if (field === "text") runs(payload.text, `${path}.text`);
|
|
1040
|
-
else list(payload[field], `${path}.${field}`);
|
|
1041
|
-
}
|
|
1042
|
-
};
|
|
1043
|
-
const value = record2(slide);
|
|
1044
|
-
const regions = Object.keys(value).filter((key) => regionKey.test(key)).sort();
|
|
1045
|
-
if (regions.length) {
|
|
1046
|
-
for (const key of regions) host(value[key], `${basePath}.${key}`, 0);
|
|
1047
|
-
return;
|
|
1048
|
-
}
|
|
1049
|
-
if (Array.isArray(value.blocks)) {
|
|
1050
|
-
value.blocks.forEach((block, index) => {
|
|
1051
|
-
host(block, `${basePath}.blocks.${index}`, 0);
|
|
1052
|
-
});
|
|
1053
|
-
return;
|
|
1054
|
-
}
|
|
1055
|
-
host(value, basePath, 0);
|
|
1056
|
-
}
|
|
1057
|
-
var citationMarkerText = (numbers) => numbers.join(",");
|
|
1058
|
-
function referenceList(presentation) {
|
|
1059
|
-
const list = record2(presentation).references;
|
|
1060
|
-
return Array.isArray(list) ? list.filter((item) => isRecord3(item) && typeof item.id === "string") : [];
|
|
1061
|
-
}
|
|
1062
|
-
function numberSlide(slide, slideIndex, references, numbering) {
|
|
1063
|
-
const markers = /* @__PURE__ */ new Map(), marked = [], used = /* @__PURE__ */ new Map();
|
|
1064
|
-
walkCitationRuns(slide, `slides.${slideIndex}`, (entry) => {
|
|
1065
|
-
const numbers = [];
|
|
1066
|
-
for (const id of entry.cite) {
|
|
1067
|
-
let note = numbering.byId.get(id);
|
|
1068
|
-
if (!note) {
|
|
1069
|
-
const index = references.findIndex((item) => item.id === id), reference = references[index];
|
|
1070
|
-
note = { number: numbering.notes.length + 1, kind: "reference", id, text: reference?.text ?? id, sourcePath: index >= 0 ? `references.${index}` : entry.path, resolved: !!reference, ...typeof reference?.url === "string" ? { url: reference.url } : {} };
|
|
1071
|
-
numbering.notes.push(note);
|
|
1072
|
-
numbering.byId.set(id, note);
|
|
1073
|
-
}
|
|
1074
|
-
if (!numbers.includes(note.number)) numbers.push(note.number);
|
|
1075
|
-
used.set(note.number, note);
|
|
1076
|
-
}
|
|
1077
|
-
if (entry.footnote !== void 0) {
|
|
1078
|
-
const note = { number: numbering.notes.length + 1, kind: "footnote", text: entry.footnote, sourcePath: entry.path, resolved: true };
|
|
1079
|
-
numbering.notes.push(note);
|
|
1080
|
-
numbers.push(note.number);
|
|
1081
|
-
used.set(note.number, note);
|
|
1082
|
-
}
|
|
1083
|
-
const text2 = citationMarkerText(numbers);
|
|
1084
|
-
markers.set(entry.path, text2);
|
|
1085
|
-
marked.push({ path: entry.path, numbers, text: text2 });
|
|
1086
|
-
});
|
|
1087
|
-
if (!marked.length) return void 0;
|
|
1088
|
-
return { markers, marked, notes: [...used.values()].sort((a, b) => a.number - b.number) };
|
|
1089
|
-
}
|
|
1090
|
-
function collectCitations(presentation) {
|
|
1091
|
-
const references = referenceList(presentation), numbering = { notes: [], byId: /* @__PURE__ */ new Map() }, slides = /* @__PURE__ */ new Map();
|
|
1092
|
-
const list = record2(presentation).slides;
|
|
1093
|
-
if (Array.isArray(list)) list.forEach((slide, index) => {
|
|
1094
|
-
const result = numberSlide(slide, index, references, numbering);
|
|
1095
|
-
if (result) slides.set(index, result);
|
|
1096
|
-
});
|
|
1097
|
-
const cited = new Set(numbering.notes.filter((note) => note.kind === "reference").map((note) => note.id));
|
|
1098
|
-
return { notes: numbering.notes, references: numbering.notes.filter((note) => note.kind === "reference"), slides, unused: references.map((item) => item.id).filter((id) => !cited.has(id)) };
|
|
1099
|
-
}
|
|
1100
|
-
function slideCitations(slide, slideIndex, presentation) {
|
|
1101
|
-
const references = referenceList(presentation), numbering = { notes: [], byId: /* @__PURE__ */ new Map() };
|
|
1102
|
-
const list = record2(presentation).slides;
|
|
1103
|
-
if (Array.isArray(list)) for (let index = 0; index < Math.min(slideIndex, list.length); index += 1) numberSlide(list[index], index, references, numbering);
|
|
1104
|
-
return numberSlide(slide, slideIndex, references, numbering);
|
|
1105
|
-
}
|
|
1106
|
-
var fitHeight = (fit) => Math.max(fit.placement?.height ?? 0, "richLines" in fit ? fit.height : fit.lines.length * fit.lineHeight);
|
|
1107
|
-
function layoutCaption(value, box, path, options) {
|
|
1108
|
-
const settings = captionSettings(value);
|
|
1109
|
-
if (!settings) return void 0;
|
|
1110
|
-
const fontSize = Math.max(options.minFontSize, CAPTION_FONT_RATIO * 25 * options.scale), gap = fontSize * 0.4;
|
|
1111
|
-
const maxHeight = Math.max(fontSize * 1.22, box.height * CAPTION_MAX_RATIO);
|
|
1112
|
-
const diagnostics = [];
|
|
1113
|
-
const probe = options.fit(settings.text, { x: box.x, y: box.y, width: box.width, height: maxHeight }, fontSize, fontSize, path, settings.align);
|
|
1114
|
-
const height = Math.min(maxHeight, Math.max(fontSize * 1.22, fitHeight(probe)));
|
|
1115
|
-
const captionBox = { x: box.x, y: settings.position === "above" ? box.y : box.y + box.height - height, width: box.width, height };
|
|
1116
|
-
const fit = options.fit(settings.text, captionBox, fontSize, fontSize, path, settings.align);
|
|
1117
|
-
const overflow = fit.overflow || fitHeight(fit) > height + 0.01;
|
|
1118
|
-
if (overflow) diagnostics.push({ code: "text-overflow", path, message: "The caption does not fit its band at the minimum font size; shorten it or give the block more space." });
|
|
1119
|
-
const mediaHeight = Math.max(options.scale, box.height - height - gap);
|
|
1120
|
-
const mediaBox = { x: box.x, y: settings.position === "above" ? box.y + height + gap : box.y, width: box.width, height: mediaHeight };
|
|
1121
|
-
for (const target of [captionBox, mediaBox]) for (const key of ["x", "y", "width", "height"]) target[key] = round(target[key]);
|
|
1122
|
-
return { path, value, text: settings.text, position: settings.position, alignment: settings.align, box: captionBox, mediaBox, fit, textStyle: options.textStyle(path), fontSize, overflow, diagnostics };
|
|
1123
|
-
}
|
|
1124
|
-
function layoutFootnotes(citations, options) {
|
|
1125
|
-
const fontSize = Math.max(options.minFontSize, 13 * options.scale), thickness = Math.max(1, round(options.scale));
|
|
1126
|
-
const gap = fontSize * 0.5, spacing = fontSize * 0.25, diagnostics = [];
|
|
1127
|
-
const width = Math.max(options.scale, options.width);
|
|
1128
|
-
const measured = citations.notes.map((note) => {
|
|
1129
|
-
const value = typeof note.text === "string" ? `${note.number} ${note.text}` : [`${note.number} `, ...note.text];
|
|
1130
|
-
const probe = options.fit(value, { x: options.x, y: 0, width, height: Number.MAX_SAFE_INTEGER }, fontSize, fontSize, note.sourcePath, "left");
|
|
1131
|
-
return { note, value, height: Math.max(fontSize * 1.22, fitHeight(probe)) };
|
|
1132
|
-
});
|
|
1133
|
-
const total = thickness + gap + measured.reduce((sum, item) => sum + item.height, 0) + spacing * Math.max(0, measured.length - 1);
|
|
1134
|
-
const height = round(Math.min(total, Math.max(fontSize * 1.22 + thickness + gap, options.maxHeight)));
|
|
1135
|
-
const top = round(options.bottom - height), box = { x: round(options.x), y: top, width: round(width), height };
|
|
1136
|
-
let cursor = top + thickness + gap, overflow = false;
|
|
1137
|
-
const entries = measured.map((item, index) => {
|
|
1138
|
-
const entryBox = { x: box.x, y: round(cursor), width: box.width, height: round(item.height) };
|
|
1139
|
-
const fit = options.fit(item.value, entryBox, fontSize, fontSize, item.note.sourcePath, "left");
|
|
1140
|
-
const exceeds = fit.overflow || entryBox.y + entryBox.height > options.bottom + 0.01;
|
|
1141
|
-
if (exceeds) {
|
|
1142
|
-
overflow = true;
|
|
1143
|
-
diagnostics.push({ code: "text-overflow", path: item.note.sourcePath, message: `Footnote ${item.note.number} does not fit the slide's footnote area; shorten the note or split the slide.` });
|
|
1144
|
-
}
|
|
1145
|
-
cursor += item.height + (index < measured.length - 1 ? spacing : 0);
|
|
1146
|
-
return { number: item.note.number, kind: item.note.kind, ...item.note.id !== void 0 ? { id: item.note.id } : {}, sourcePath: item.note.sourcePath, value: item.value, text: item.note.text, box: entryBox, fit, textStyle: options.textStyle(item.note.sourcePath), ...item.note.url !== void 0 ? { url: item.note.url } : {}, overflow: exceeds };
|
|
1147
|
-
});
|
|
1148
|
-
for (const note of citations.notes) if (!note.resolved) diagnostics.push({ code: "unresolved-content", path: note.sourcePath, message: `Reference '${note.id}' is not in the presentation's references list; its id is listed instead.` });
|
|
1149
|
-
return { algorithm: "footnote-area-v1", path: options.path, box, rule: { x: box.x, y: top, width: box.width, thickness }, entries, markers: citations.marked, fontSize, overflow, diagnostics };
|
|
1150
|
-
}
|
|
1151
|
-
function referencesSlide(presentation, options = {}) {
|
|
1152
|
-
const title = options.title ?? "References";
|
|
1153
|
-
const { references } = collectCitations(presentation);
|
|
1154
|
-
if (!references.length) return { title };
|
|
1155
|
-
const items = references.map((note) => {
|
|
1156
|
-
const label = `${note.number}. `;
|
|
1157
|
-
if (typeof note.text === "string" && note.url === void 0) return `${label}${note.text}`;
|
|
1158
|
-
const runs = [label, ...typeof note.text === "string" ? [note.text] : note.text];
|
|
1159
|
-
if (note.url !== void 0) runs.push(" ", { text: note.url, link: note.url });
|
|
1160
|
-
return runs;
|
|
1161
|
-
});
|
|
1162
|
-
return { title, items };
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
41
|
// src/generated/catalog-ids.ts
|
|
1166
42
|
var catalogIds = {
|
|
1167
43
|
audiences: [
|
|
@@ -1659,211 +535,2315 @@ var catalogIds = {
|
|
|
1659
535
|
"khmer-ui"
|
|
1660
536
|
]
|
|
1661
537
|
};
|
|
1662
|
-
var deprecatedCatalogIds = {
|
|
1663
|
-
"audiences/executives": "executive",
|
|
1664
|
-
"audiences/investors": "investor",
|
|
1665
|
-
"audiences/customers": "customer",
|
|
1666
|
-
"audiences/sales-team": "sales",
|
|
1667
|
-
"audiences/marketing-team": "marketing",
|
|
1668
|
-
"audiences/regulators": "regulatory",
|
|
1669
|
-
"chartTypes/100pct-bullet-bar": "100pct-stacked-bar-3x",
|
|
1670
|
-
"chartTypes/100pct-bullet-bar-2x": "100pct-stacked-bar-3x",
|
|
1671
|
-
"chartTypes/100pct-bullet-bar-3x": "100pct-stacked-bar-3x",
|
|
1672
|
-
"chartTypes/100pct-bullet-column": "100pct-stacked-column-3x",
|
|
1673
|
-
"chartTypes/100pct-bullet-column-2x": "100pct-stacked-column-3x",
|
|
1674
|
-
"chartTypes/100pct-bullet-column-3x": "100pct-stacked-column-3x",
|
|
1675
|
-
"chartTypes/100pct-progress-bar": "100pct-stacked-bar-3x",
|
|
1676
|
-
"chartTypes/100pct-stacked-area-2x": "100pct-stacked-area-3x",
|
|
1677
|
-
"chartTypes/100pct-stacked-bar-2x": "100pct-stacked-bar-3x",
|
|
1678
|
-
"chartTypes/100pct-stacked-column-2x": "100pct-stacked-column-3x",
|
|
1679
|
-
"chartTypes/australia": "world",
|
|
1680
|
-
"chartTypes/box-and-whisker-2x": "box-and-whisker",
|
|
1681
|
-
"chartTypes/box-and-whisker-3x": "box-and-whisker",
|
|
1682
|
-
"chartTypes/bullet-bar": "bar",
|
|
1683
|
-
"chartTypes/bullet-bar-2x": "bar",
|
|
1684
|
-
"chartTypes/bullet-bar-3x": "bar",
|
|
1685
|
-
"chartTypes/bullet-column": "column",
|
|
1686
|
-
"chartTypes/bullet-column-2x": "column",
|
|
1687
|
-
"chartTypes/bullet-column-3x": "column",
|
|
1688
|
-
"chartTypes/canada": "world",
|
|
1689
|
-
"chartTypes/clustered-bar-2x": "bar",
|
|
1690
|
-
"chartTypes/clustered-column": "column",
|
|
1691
|
-
"chartTypes/dot-plot": "scatter",
|
|
1692
|
-
"chartTypes/dot-plot-2x": "scatter",
|
|
1693
|
-
"chartTypes/dot-plot-3x": "scatter",
|
|
1694
|
-
"chartTypes/dot-plot-4x": "scatter",
|
|
1695
|
-
"chartTypes/dot-plot-5x": "scatter",
|
|
1696
|
-
"chartTypes/dot-plot-6x": "scatter",
|
|
1697
|
-
"chartTypes/dumbbell": "scatter",
|
|
1698
|
-
"chartTypes/line-2x": "line",
|
|
1699
|
-
"chartTypes/line-3x": "line",
|
|
1700
|
-
"chartTypes/line-with-high-low": "line",
|
|
1701
|
-
"chartTypes/line-with-high-low-and-markers": "line-with-markers",
|
|
1702
|
-
"chartTypes/line-with-markers-2x": "line-with-markers",
|
|
1703
|
-
"chartTypes/line-with-markers-3x": "line-with-markers",
|
|
1704
|
-
"chartTypes/sparkline": "line",
|
|
1705
|
-
"chartTypes/sparkline-2x": "line",
|
|
1706
|
-
"chartTypes/sparkline-3x": "line",
|
|
1707
|
-
"chartTypes/sparkline-4x": "line",
|
|
1708
|
-
"chartTypes/sparkline-5x": "line",
|
|
1709
|
-
"chartTypes/sparkline-6x": "line",
|
|
1710
|
-
"chartTypes/stacked-area-2x": "stacked-area-3x",
|
|
1711
|
-
"chartTypes/stacked-bar-2x": "stacked-bar-3x",
|
|
1712
|
-
"chartTypes/stacked-column-2x": "stacked-column-3x",
|
|
1713
|
-
"chartTypes/stacked-line-2x": "stacked-line-3x",
|
|
1714
|
-
"chartTypes/stacked-line-with-markers-2x": "stacked-line-with-markers-3x",
|
|
1715
|
-
"chartTypes/treemap-2x": "treemap",
|
|
1716
|
-
"chartTypes/treemap-3x": "treemap",
|
|
1717
|
-
"chartTypes/united-kingdom": "world",
|
|
1718
|
-
"chartTypes/united-states": "world"
|
|
538
|
+
var deprecatedCatalogIds = {
|
|
539
|
+
"audiences/executives": "executive",
|
|
540
|
+
"audiences/investors": "investor",
|
|
541
|
+
"audiences/customers": "customer",
|
|
542
|
+
"audiences/sales-team": "sales",
|
|
543
|
+
"audiences/marketing-team": "marketing",
|
|
544
|
+
"audiences/regulators": "regulatory",
|
|
545
|
+
"chartTypes/100pct-bullet-bar": "100pct-stacked-bar-3x",
|
|
546
|
+
"chartTypes/100pct-bullet-bar-2x": "100pct-stacked-bar-3x",
|
|
547
|
+
"chartTypes/100pct-bullet-bar-3x": "100pct-stacked-bar-3x",
|
|
548
|
+
"chartTypes/100pct-bullet-column": "100pct-stacked-column-3x",
|
|
549
|
+
"chartTypes/100pct-bullet-column-2x": "100pct-stacked-column-3x",
|
|
550
|
+
"chartTypes/100pct-bullet-column-3x": "100pct-stacked-column-3x",
|
|
551
|
+
"chartTypes/100pct-progress-bar": "100pct-stacked-bar-3x",
|
|
552
|
+
"chartTypes/100pct-stacked-area-2x": "100pct-stacked-area-3x",
|
|
553
|
+
"chartTypes/100pct-stacked-bar-2x": "100pct-stacked-bar-3x",
|
|
554
|
+
"chartTypes/100pct-stacked-column-2x": "100pct-stacked-column-3x",
|
|
555
|
+
"chartTypes/australia": "world",
|
|
556
|
+
"chartTypes/box-and-whisker-2x": "box-and-whisker",
|
|
557
|
+
"chartTypes/box-and-whisker-3x": "box-and-whisker",
|
|
558
|
+
"chartTypes/bullet-bar": "bar",
|
|
559
|
+
"chartTypes/bullet-bar-2x": "bar",
|
|
560
|
+
"chartTypes/bullet-bar-3x": "bar",
|
|
561
|
+
"chartTypes/bullet-column": "column",
|
|
562
|
+
"chartTypes/bullet-column-2x": "column",
|
|
563
|
+
"chartTypes/bullet-column-3x": "column",
|
|
564
|
+
"chartTypes/canada": "world",
|
|
565
|
+
"chartTypes/clustered-bar-2x": "bar",
|
|
566
|
+
"chartTypes/clustered-column": "column",
|
|
567
|
+
"chartTypes/dot-plot": "scatter",
|
|
568
|
+
"chartTypes/dot-plot-2x": "scatter",
|
|
569
|
+
"chartTypes/dot-plot-3x": "scatter",
|
|
570
|
+
"chartTypes/dot-plot-4x": "scatter",
|
|
571
|
+
"chartTypes/dot-plot-5x": "scatter",
|
|
572
|
+
"chartTypes/dot-plot-6x": "scatter",
|
|
573
|
+
"chartTypes/dumbbell": "scatter",
|
|
574
|
+
"chartTypes/line-2x": "line",
|
|
575
|
+
"chartTypes/line-3x": "line",
|
|
576
|
+
"chartTypes/line-with-high-low": "line",
|
|
577
|
+
"chartTypes/line-with-high-low-and-markers": "line-with-markers",
|
|
578
|
+
"chartTypes/line-with-markers-2x": "line-with-markers",
|
|
579
|
+
"chartTypes/line-with-markers-3x": "line-with-markers",
|
|
580
|
+
"chartTypes/sparkline": "line",
|
|
581
|
+
"chartTypes/sparkline-2x": "line",
|
|
582
|
+
"chartTypes/sparkline-3x": "line",
|
|
583
|
+
"chartTypes/sparkline-4x": "line",
|
|
584
|
+
"chartTypes/sparkline-5x": "line",
|
|
585
|
+
"chartTypes/sparkline-6x": "line",
|
|
586
|
+
"chartTypes/stacked-area-2x": "stacked-area-3x",
|
|
587
|
+
"chartTypes/stacked-bar-2x": "stacked-bar-3x",
|
|
588
|
+
"chartTypes/stacked-column-2x": "stacked-column-3x",
|
|
589
|
+
"chartTypes/stacked-line-2x": "stacked-line-3x",
|
|
590
|
+
"chartTypes/stacked-line-with-markers-2x": "stacked-line-with-markers-3x",
|
|
591
|
+
"chartTypes/treemap-2x": "treemap",
|
|
592
|
+
"chartTypes/treemap-3x": "treemap",
|
|
593
|
+
"chartTypes/united-kingdom": "world",
|
|
594
|
+
"chartTypes/united-states": "world"
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
// src/chart-options.ts
|
|
598
|
+
var END_POSITIONS = ["center", "inside-end", "inside-base", "outside-end"];
|
|
599
|
+
var STACKED_POSITIONS = ["center", "inside-end", "inside-base"];
|
|
600
|
+
var POINT_POSITIONS = ["above", "below", "left", "right", "center"];
|
|
601
|
+
var PIE_POSITIONS = ["center", "inside-end", "outside-end"];
|
|
602
|
+
var NONE = [];
|
|
603
|
+
var VALUE_CATEGORY = ["category", "value"];
|
|
604
|
+
var ALL_CONTENT = ["category", "value", "percent"];
|
|
605
|
+
var categoryKinds = /* @__PURE__ */ new Set(["bar", "line", "area", "scatter", "histogram", "pareto", "box", "waterfall", "funnel"]);
|
|
606
|
+
var valueKinds = /* @__PURE__ */ new Set(["bar", "line", "area", "scatter", "histogram", "pareto", "box", "waterfall"]);
|
|
607
|
+
var legendKinds = /* @__PURE__ */ new Set(["bar", "line", "area", "pie", "doughnut", "scatter", "radar", "box"]);
|
|
608
|
+
function labelSupport(target) {
|
|
609
|
+
const none = { supported: true, content: VALUE_CATEGORY, positions: NONE, defaultPosition: null, defaultOn: false };
|
|
610
|
+
switch (target.kind) {
|
|
611
|
+
case "bar":
|
|
612
|
+
return target.stacked ? { ...none, positions: STACKED_POSITIONS, defaultPosition: "center" } : { ...none, positions: END_POSITIONS, defaultPosition: "outside-end" };
|
|
613
|
+
case "line":
|
|
614
|
+
case "scatter":
|
|
615
|
+
return { ...none, positions: POINT_POSITIONS, defaultPosition: "above" };
|
|
616
|
+
case "pie":
|
|
617
|
+
return { ...none, content: ALL_CONTENT, positions: PIE_POSITIONS, defaultPosition: "outside-end" };
|
|
618
|
+
case "doughnut":
|
|
619
|
+
return { ...none, content: ALL_CONTENT };
|
|
620
|
+
// The funnel labels its bars with values and the treemap its tiles with category names by default.
|
|
621
|
+
case "funnel":
|
|
622
|
+
case "treemap":
|
|
623
|
+
return { ...none, defaultOn: true };
|
|
624
|
+
case "area":
|
|
625
|
+
case "radar":
|
|
626
|
+
return none;
|
|
627
|
+
case "histogram":
|
|
628
|
+
case "pareto":
|
|
629
|
+
case "waterfall":
|
|
630
|
+
return { ...none, positions: END_POSITIONS, defaultPosition: "outside-end" };
|
|
631
|
+
default:
|
|
632
|
+
return { ...none, supported: false, content: [] };
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
function chartOptionSupport(target) {
|
|
636
|
+
return {
|
|
637
|
+
axisTitles: { category: categoryKinds.has(target.kind), value: valueKinds.has(target.kind) },
|
|
638
|
+
legend: legendKinds.has(target.kind),
|
|
639
|
+
dataLabels: labelSupport(target)
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
var KEPT = {
|
|
643
|
+
column: { kind: "bar" },
|
|
644
|
+
"stacked-column-3x": { kind: "bar", stacked: true },
|
|
645
|
+
"100pct-stacked-column-3x": { kind: "bar", stacked: true },
|
|
646
|
+
bar: { kind: "bar" },
|
|
647
|
+
"stacked-bar-3x": { kind: "bar", stacked: true },
|
|
648
|
+
"100pct-stacked-bar-3x": { kind: "bar", stacked: true },
|
|
649
|
+
line: { kind: "line" },
|
|
650
|
+
"line-with-markers": { kind: "line" },
|
|
651
|
+
"stacked-line-3x": { kind: "line", stacked: true },
|
|
652
|
+
"stacked-line-with-markers-3x": { kind: "line", stacked: true },
|
|
653
|
+
area: { kind: "area" },
|
|
654
|
+
"stacked-area-3x": { kind: "area", stacked: true },
|
|
655
|
+
"100pct-stacked-area-3x": { kind: "area", stacked: true },
|
|
656
|
+
pie: { kind: "pie" },
|
|
657
|
+
doughnut: { kind: "doughnut" },
|
|
658
|
+
scatter: { kind: "scatter" },
|
|
659
|
+
radar: { kind: "radar" },
|
|
660
|
+
"radar-with-markers": { kind: "radar" },
|
|
661
|
+
"filled-radar": { kind: "radar" },
|
|
662
|
+
treemap: { kind: "treemap" },
|
|
663
|
+
histogram: { kind: "histogram" },
|
|
664
|
+
pareto: { kind: "pareto" },
|
|
665
|
+
"box-and-whisker": { kind: "box" },
|
|
666
|
+
waterfall: { kind: "waterfall" },
|
|
667
|
+
funnel: { kind: "funnel" },
|
|
668
|
+
world: { kind: "map" }
|
|
669
|
+
};
|
|
670
|
+
function chartOptionTarget(typeId) {
|
|
671
|
+
if (typeof typeId !== "string") return void 0;
|
|
672
|
+
const raw = typeId.trim().toLowerCase();
|
|
673
|
+
const id = raw === "donut" ? "doughnut" : raw;
|
|
674
|
+
const kept = deprecatedCatalogIds[`chartTypes/${id}`] ?? id;
|
|
675
|
+
const target = Object.hasOwn(KEPT, kept) ? KEPT[kept] : void 0;
|
|
676
|
+
return target ? { ...target } : void 0;
|
|
677
|
+
}
|
|
678
|
+
var record2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
679
|
+
var LEGEND_POSITIONS = /* @__PURE__ */ new Set(["none", "top", "bottom", "left", "right"]);
|
|
680
|
+
var CONTENT_ORDER = ["category", "value", "percent"];
|
|
681
|
+
var POSITIONS = /* @__PURE__ */ new Set(["auto", "center", "inside-end", "inside-base", "outside-end", "above", "below", "left", "right"]);
|
|
682
|
+
var DEFAULT_CHART_LABEL_SEPARATOR = ", ";
|
|
683
|
+
function resolveChartOptions(chart, target) {
|
|
684
|
+
const result = { active: false, axisTitles: {}, diagnostics: [] };
|
|
685
|
+
if (!record2(chart)) return result;
|
|
686
|
+
const support = target ? chartOptionSupport(target) : void 0;
|
|
687
|
+
const adapt = (option, reason, message) => result.diagnostics.push({ code: "chart-option-adapted", option, reason, message });
|
|
688
|
+
const kind2 = target?.kind ?? "unknown";
|
|
689
|
+
if (record2(chart.axisTitles)) {
|
|
690
|
+
for (const axis of ["category", "value"]) {
|
|
691
|
+
const raw = chart.axisTitles[axis];
|
|
692
|
+
if (typeof raw !== "string" || !raw.trim()) continue;
|
|
693
|
+
if (support && !support.axisTitles[axis]) {
|
|
694
|
+
adapt(`axisTitles.${axis}`, "unsupported-type", `A '${kind2}' chart has no ${axis} axis to title; the ${axis} axis title is not drawn or exported.`);
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
result.axisTitles[axis] = raw.trim();
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
if (typeof chart.legend === "string" && LEGEND_POSITIONS.has(chart.legend)) {
|
|
701
|
+
if (support && !support.legend) adapt("legend", "unsupported-type", `A '${kind2}' chart has no legend option; the legend setting is ignored.`);
|
|
702
|
+
else result.legend = chart.legend;
|
|
703
|
+
}
|
|
704
|
+
const labels = chart.dataLabels;
|
|
705
|
+
if (labels === false && support?.dataLabels.defaultOn) result.dataLabelsOff = true;
|
|
706
|
+
if (labels === true || record2(labels)) {
|
|
707
|
+
const options = record2(labels) ? labels : {};
|
|
708
|
+
if (support && !support.dataLabels.supported) {
|
|
709
|
+
adapt("dataLabels", "unsupported-type", `A '${kind2}' chart has no data label option; data labels are not drawn or exported.`);
|
|
710
|
+
} else {
|
|
711
|
+
const allowed = support?.dataLabels.content ?? ALL_CONTENT;
|
|
712
|
+
const requested = Array.isArray(options.content) ? options.content.filter((part) => typeof part === "string" && CONTENT_ORDER.includes(part)) : ["value"];
|
|
713
|
+
const content = CONTENT_ORDER.filter((part) => requested.includes(part) && allowed.includes(part));
|
|
714
|
+
for (const part of CONTENT_ORDER) {
|
|
715
|
+
if (requested.includes(part) && !allowed.includes(part)) adapt("dataLabels.content", "unsupported-content", `A '${kind2}' chart cannot label '${part}'${part === "percent" ? " (percent exists only on pie and doughnut charts)" : ""}; it is dropped from the data labels.`);
|
|
716
|
+
}
|
|
717
|
+
if (!content.length) content.push("value");
|
|
718
|
+
const positions = support?.dataLabels.positions ?? NONE;
|
|
719
|
+
let position = support?.dataLabels.defaultPosition ?? null;
|
|
720
|
+
if (typeof options.position === "string" && POSITIONS.has(options.position) && options.position !== "auto") {
|
|
721
|
+
if (!support || positions.includes(options.position)) position = options.position;
|
|
722
|
+
else adapt("dataLabels.position", "unsupported-position", `A '${kind2}'${target?.stacked ? " stacked" : ""} chart cannot place data labels '${options.position}'; the type's default position is used.`);
|
|
723
|
+
}
|
|
724
|
+
let separator = typeof options.separator === "string" ? options.separator : DEFAULT_CHART_LABEL_SEPARATOR;
|
|
725
|
+
if (/[\r\n]/.test(separator)) {
|
|
726
|
+
separator = separator.replace(/[\r\n]+/g, " ");
|
|
727
|
+
adapt("dataLabels.separator", "unsupported-content", "A data label is one line; the line break in the separator is replaced with a space.");
|
|
728
|
+
}
|
|
729
|
+
result.dataLabels = { content, position, separator };
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
result.active = result.legend !== void 0 || result.dataLabels !== void 0 || result.dataLabelsOff === true || result.axisTitles.category !== void 0 || result.axisTitles.value !== void 0;
|
|
733
|
+
return result;
|
|
734
|
+
}
|
|
735
|
+
function formatChartLabelNumber(value) {
|
|
736
|
+
return String(Number(value.toPrecision(12)));
|
|
737
|
+
}
|
|
738
|
+
function formatChartLabelPercent(share) {
|
|
739
|
+
return `${Math.round(Number((share * 100).toPrecision(12)))}%`;
|
|
740
|
+
}
|
|
741
|
+
function chartLabelText(parts, content, separator = DEFAULT_CHART_LABEL_SEPARATOR) {
|
|
742
|
+
return CONTENT_ORDER.filter((part) => content.includes(part) && parts[part] !== void 0).map((part) => parts[part]).join(separator);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// src/content-walk.ts
|
|
746
|
+
function isRecord(value) {
|
|
747
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
748
|
+
}
|
|
749
|
+
function pathFor(parentPath, key) {
|
|
750
|
+
return parentPath === "/" ? `/${key}` : `${parentPath}/${key.replaceAll("~", "~0").replaceAll("/", "~1")}`;
|
|
751
|
+
}
|
|
752
|
+
var bareIdPattern = /^[a-z0-9][a-z0-9-]*$/;
|
|
753
|
+
var promotedRegionKeys = [
|
|
754
|
+
"left",
|
|
755
|
+
"center",
|
|
756
|
+
"right",
|
|
757
|
+
"left+center",
|
|
758
|
+
"center+right",
|
|
759
|
+
"left+center+right",
|
|
760
|
+
"top",
|
|
761
|
+
"middle",
|
|
762
|
+
"bottom",
|
|
763
|
+
"top+middle",
|
|
764
|
+
"middle+bottom",
|
|
765
|
+
"top+middle+bottom",
|
|
766
|
+
"top:left",
|
|
767
|
+
"top:center",
|
|
768
|
+
"top:right",
|
|
769
|
+
"top:left+center",
|
|
770
|
+
"top:center+right",
|
|
771
|
+
"top:left+center+right",
|
|
772
|
+
"middle:left",
|
|
773
|
+
"middle:center",
|
|
774
|
+
"middle:right",
|
|
775
|
+
"middle:left+center",
|
|
776
|
+
"middle:center+right",
|
|
777
|
+
"middle:left+center+right",
|
|
778
|
+
"bottom:left",
|
|
779
|
+
"bottom:center",
|
|
780
|
+
"bottom:right",
|
|
781
|
+
"bottom:left+center",
|
|
782
|
+
"bottom:center+right",
|
|
783
|
+
"bottom:left+center+right",
|
|
784
|
+
"top+middle:left",
|
|
785
|
+
"top+middle:center",
|
|
786
|
+
"top+middle:right",
|
|
787
|
+
"top+middle:left+center",
|
|
788
|
+
"top+middle:center+right",
|
|
789
|
+
"top+middle:left+center+right",
|
|
790
|
+
"middle+bottom:left",
|
|
791
|
+
"middle+bottom:center",
|
|
792
|
+
"middle+bottom:right",
|
|
793
|
+
"middle+bottom:left+center",
|
|
794
|
+
"middle+bottom:center+right",
|
|
795
|
+
"middle+bottom:left+center+right",
|
|
796
|
+
"top+middle+bottom:left",
|
|
797
|
+
"top+middle+bottom:center",
|
|
798
|
+
"top+middle+bottom:right",
|
|
799
|
+
"top+middle+bottom:left+center",
|
|
800
|
+
"top+middle+bottom:center+right",
|
|
801
|
+
"top+middle+bottom:left+center+right"
|
|
802
|
+
];
|
|
803
|
+
function visitContentPayloads(slide, slidePath, visit) {
|
|
804
|
+
const walk2 = (payload, path, depth, ancestors) => {
|
|
805
|
+
visit(payload, path);
|
|
806
|
+
if (!Array.isArray(payload.blocks) || depth >= MAX_COMPOSITION_DEPTH || ancestors.includes(payload)) return;
|
|
807
|
+
const nested = [...ancestors, payload];
|
|
808
|
+
const blocksPath = pathFor(path, "blocks");
|
|
809
|
+
payload.blocks.forEach((block, index) => {
|
|
810
|
+
if (isRecord(block)) walk2(block, `${blocksPath}/${index}`, depth + 1, nested);
|
|
811
|
+
});
|
|
812
|
+
};
|
|
813
|
+
if (Array.isArray(slide.blocks)) {
|
|
814
|
+
const blocksPath = pathFor(slidePath, "blocks");
|
|
815
|
+
slide.blocks.forEach((block, index) => {
|
|
816
|
+
if (isRecord(block)) walk2(block, `${blocksPath}/${index}`, 0, []);
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
for (const key of promotedRegionKeys) {
|
|
820
|
+
const region = slide[key];
|
|
821
|
+
if (isRecord(region)) walk2(region, pathFor(slidePath, key), 0, []);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// src/furniture-fields.ts
|
|
826
|
+
var DEFAULT_FURNITURE_DATE_FORMAT = "M/d/yyyy";
|
|
827
|
+
var DEFAULT_SLIDE_NUMBER_FORMAT = "{current}";
|
|
828
|
+
var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
829
|
+
var weekdays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
830
|
+
var tokenWidths = { y: [1, 2, 4], M: [1, 2, 3, 4], d: [1, 2], E: [1, 2, 3, 4] };
|
|
831
|
+
function parseFurnitureDateFormat(pattern) {
|
|
832
|
+
if (!pattern) return "dateFormat must not be empty.";
|
|
833
|
+
const tokens = [];
|
|
834
|
+
const literal = (text2) => {
|
|
835
|
+
const last = tokens[tokens.length - 1];
|
|
836
|
+
if (last?.kind === "literal") last.text += text2;
|
|
837
|
+
else tokens.push({ kind: "literal", text: text2 });
|
|
838
|
+
};
|
|
839
|
+
for (let index = 0; index < pattern.length; ) {
|
|
840
|
+
const character = pattern.charAt(index);
|
|
841
|
+
if (character === "'") {
|
|
842
|
+
if (pattern[index + 1] === "'") {
|
|
843
|
+
literal("'");
|
|
844
|
+
index += 2;
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
let end = index + 1, text2 = "";
|
|
848
|
+
for (; ; ) {
|
|
849
|
+
if (end >= pattern.length) return "dateFormat has an unterminated quoted literal.";
|
|
850
|
+
if (pattern[end] === "'") {
|
|
851
|
+
if (pattern[end + 1] === "'") {
|
|
852
|
+
text2 += "'";
|
|
853
|
+
end += 2;
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
break;
|
|
857
|
+
}
|
|
858
|
+
text2 += pattern[end];
|
|
859
|
+
end++;
|
|
860
|
+
}
|
|
861
|
+
literal(text2);
|
|
862
|
+
index = end + 1;
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
if (/[A-Za-z]/.test(character)) {
|
|
866
|
+
let end = index;
|
|
867
|
+
while (pattern[end] === character) end++;
|
|
868
|
+
const width = end - index;
|
|
869
|
+
if (!tokenWidths[character]?.includes(width)) return `dateFormat token '${pattern.slice(index, end)}' is not supported; use yyyy, yy, MMMM, MMM, MM, M, dd, d, EEEE or EEE, and quote literal letters.`;
|
|
870
|
+
tokens.push({ kind: "field", letter: character, width });
|
|
871
|
+
index = end;
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
literal(character);
|
|
875
|
+
index++;
|
|
876
|
+
}
|
|
877
|
+
return tokens;
|
|
878
|
+
}
|
|
879
|
+
function parseIsoDate(value) {
|
|
880
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
881
|
+
if (!match) return null;
|
|
882
|
+
const year = Number(match[1]), month = Number(match[2]), day = Number(match[3]);
|
|
883
|
+
const date = new Date(Date.UTC(2e3, month - 1, day));
|
|
884
|
+
date.setUTCFullYear(year);
|
|
885
|
+
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) return null;
|
|
886
|
+
return { year, month, day };
|
|
887
|
+
}
|
|
888
|
+
function formatFurnitureDate(iso, pattern) {
|
|
889
|
+
const date = parseIsoDate(iso);
|
|
890
|
+
if (!date) return { error: `'${iso}' is not an ISO YYYY-MM-DD calendar date.` };
|
|
891
|
+
const tokens = parseFurnitureDateFormat(pattern);
|
|
892
|
+
if (typeof tokens === "string") return { error: tokens };
|
|
893
|
+
const utc = new Date(Date.UTC(2e3, date.month - 1, date.day));
|
|
894
|
+
utc.setUTCFullYear(date.year);
|
|
895
|
+
const pad = (value, width) => String(value).padStart(width, "0");
|
|
896
|
+
let text2 = "";
|
|
897
|
+
for (const token of tokens) {
|
|
898
|
+
if (token.kind === "literal") {
|
|
899
|
+
text2 += token.text;
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
const { letter, width } = token;
|
|
903
|
+
if (letter === "y") text2 += width === 2 ? pad(date.year % 100, 2) : width === 4 ? pad(date.year, 4) : String(date.year);
|
|
904
|
+
else if (letter === "M") {
|
|
905
|
+
const name = months[date.month - 1] ?? "";
|
|
906
|
+
text2 += width >= 3 ? width === 4 ? name : name.slice(0, 3) : pad(date.month, width);
|
|
907
|
+
} else if (letter === "d") text2 += pad(date.day, width);
|
|
908
|
+
else {
|
|
909
|
+
const name = weekdays[utc.getUTCDay()] ?? "";
|
|
910
|
+
text2 += width === 4 ? name : name.slice(0, 3);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return { text: text2 };
|
|
914
|
+
}
|
|
915
|
+
function formatSlideNumber(format, current, total) {
|
|
916
|
+
if (!format.includes("{current}")) return { error: "slideNumberFormat must contain {current}." };
|
|
917
|
+
if (format.includes("{total}") && total === void 0) return { error: "slideNumberFormat uses {total}, which needs the rendered slide count." };
|
|
918
|
+
let text2 = "";
|
|
919
|
+
const fields2 = [];
|
|
920
|
+
for (const piece of format.split(/(\{current\}|\{total\})/)) {
|
|
921
|
+
if (piece === "{current}") {
|
|
922
|
+
const value = String(current);
|
|
923
|
+
fields2.push({ type: "slideNumber", start: text2.length, end: text2.length + value.length });
|
|
924
|
+
text2 += value;
|
|
925
|
+
} else if (piece === "{total}") text2 += String(total);
|
|
926
|
+
else text2 += piece;
|
|
927
|
+
}
|
|
928
|
+
return { text: text2, fields: fields2 };
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// src/variables.ts
|
|
932
|
+
var VARIABLE_KINDS = ["color", "text", "number", "date", "image", "url", "list"];
|
|
933
|
+
var DEFAULT_VARIABLE_DATE_FORMAT = "MMMM d, yyyy";
|
|
934
|
+
var OPFVariableError = class extends Error {
|
|
935
|
+
diagnostics;
|
|
936
|
+
constructor(diagnostics) {
|
|
937
|
+
const first = diagnostics.find((entry) => entry.severity === "error") ?? diagnostics[0];
|
|
938
|
+
super(first ? `OPF variables: ${first.message}` : "OPF variables failed");
|
|
939
|
+
this.name = "OPFVariableError";
|
|
940
|
+
this.diagnostics = diagnostics;
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
var idPattern = /^[a-z][a-z0-9-]*$/;
|
|
944
|
+
var referencePattern = /^var:([a-z][a-z0-9-]*)$/;
|
|
945
|
+
var hexPattern = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
|
946
|
+
var urlPattern = /^(?:https?:\/\/|mailto:|tel:)\S+$/;
|
|
947
|
+
var datePrefixPattern = /^(\d{4}-\d{2}-\d{2})(?:[T ].*)?$/;
|
|
948
|
+
var decimalPattern = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
|
|
949
|
+
var tokenPattern = /\\\{\{|\{\{\s*([a-z][a-z0-9-]*)\s*(?:\|([^{}]*))?\}\}/g;
|
|
950
|
+
var SAMPLES = {
|
|
951
|
+
color: "#000000",
|
|
952
|
+
text: "Sample text",
|
|
953
|
+
number: 0,
|
|
954
|
+
date: "2000-01-01",
|
|
955
|
+
image: "https://example.invalid/sample.png",
|
|
956
|
+
url: "https://example.invalid/",
|
|
957
|
+
list: ["Sample item"]
|
|
958
|
+
};
|
|
959
|
+
var hasOwn = (value, key) => Object.hasOwn(value, key);
|
|
960
|
+
var clone = (value) => value === void 0 ? value : JSON.parse(JSON.stringify(value));
|
|
961
|
+
function isTemplate(presentation) {
|
|
962
|
+
return isRecord(presentation) && presentation.template === true;
|
|
963
|
+
}
|
|
964
|
+
function isKind(value) {
|
|
965
|
+
return typeof value === "string" && VARIABLE_KINDS.includes(value);
|
|
966
|
+
}
|
|
967
|
+
function coerceVariableValue(kind2, raw) {
|
|
968
|
+
const fail = (message) => ({ ok: false, message });
|
|
969
|
+
switch (kind2) {
|
|
970
|
+
case "color":
|
|
971
|
+
return typeof raw === "string" && hexPattern.test(raw.trim()) ? { ok: true, value: raw.trim() } : fail("expected a hex color such as #0F4C81");
|
|
972
|
+
case "text": {
|
|
973
|
+
if (typeof raw === "string") return { ok: true, value: raw };
|
|
974
|
+
if (typeof raw === "number" && Number.isFinite(raw)) return { ok: true, value: String(raw) };
|
|
975
|
+
if (typeof raw === "boolean") return { ok: true, value: String(raw) };
|
|
976
|
+
if (Array.isArray(raw) && raw.every((run) => typeof run === "string" || isRecord(run) && typeof run.text === "string")) return { ok: true, value: clone(raw) };
|
|
977
|
+
return fail("expected text: a string or an array of text runs");
|
|
978
|
+
}
|
|
979
|
+
case "number": {
|
|
980
|
+
if (typeof raw === "number" && Number.isFinite(raw)) return { ok: true, value: raw };
|
|
981
|
+
if (typeof raw === "string" && decimalPattern.test(raw.trim())) {
|
|
982
|
+
const value = Number(raw.trim());
|
|
983
|
+
if (Number.isFinite(value)) return { ok: true, value };
|
|
984
|
+
}
|
|
985
|
+
return fail("expected a finite number");
|
|
986
|
+
}
|
|
987
|
+
case "date": {
|
|
988
|
+
if (typeof raw === "string") {
|
|
989
|
+
const match = datePrefixPattern.exec(raw.trim());
|
|
990
|
+
if (match?.[1] && parseIsoDate(match[1])) return { ok: true, value: match[1] };
|
|
991
|
+
}
|
|
992
|
+
return fail("expected an ISO calendar date (YYYY-MM-DD)");
|
|
993
|
+
}
|
|
994
|
+
case "image": {
|
|
995
|
+
if (typeof raw === "string" && raw.trim()) return { ok: true, value: raw.trim() };
|
|
996
|
+
if (isRecord(raw) && typeof raw.src === "string" && raw.src.trim()) return { ok: true, value: clone(raw) };
|
|
997
|
+
return fail("expected an image source: a string or an object with src");
|
|
998
|
+
}
|
|
999
|
+
case "url":
|
|
1000
|
+
return typeof raw === "string" && urlPattern.test(raw.trim()) ? { ok: true, value: raw.trim() } : fail("expected an http, https, mailto or tel link");
|
|
1001
|
+
case "list": {
|
|
1002
|
+
if (typeof raw === "string") {
|
|
1003
|
+
const entries = raw.split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean);
|
|
1004
|
+
return { ok: true, value: entries };
|
|
1005
|
+
}
|
|
1006
|
+
if (Array.isArray(raw) && raw.every((entry) => typeof entry === "string" || typeof entry === "number" && Number.isFinite(entry))) {
|
|
1007
|
+
return { ok: true, value: raw.map(String) };
|
|
1008
|
+
}
|
|
1009
|
+
return fail("expected a list of strings");
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
function isProvided(kind2, raw) {
|
|
1014
|
+
if (raw === void 0 || raw === null) return false;
|
|
1015
|
+
if (typeof raw === "string" && raw.trim() === "" && kind2 !== "text") return false;
|
|
1016
|
+
return true;
|
|
1017
|
+
}
|
|
1018
|
+
function variableDeclarations(presentation) {
|
|
1019
|
+
if (!isRecord(presentation) || !isRecord(presentation.variables)) return [];
|
|
1020
|
+
const out = [];
|
|
1021
|
+
for (const [id, entry] of Object.entries(presentation.variables)) {
|
|
1022
|
+
if (!idPattern.test(id)) continue;
|
|
1023
|
+
const path = pathFor("/variables", id);
|
|
1024
|
+
if (typeof entry === "string") {
|
|
1025
|
+
if (hexPattern.test(entry)) out.push({ id, kind: "color", path, required: true, value: entry });
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1028
|
+
if (!isRecord(entry) || !isKind(entry.type)) continue;
|
|
1029
|
+
const kind2 = entry.type;
|
|
1030
|
+
const declaration = { id, kind: kind2, path, required: entry.required !== false };
|
|
1031
|
+
const value = entry.value !== void 0 ? coerceVariableValue(kind2, entry.value) : void 0;
|
|
1032
|
+
if (value?.ok) declaration.value = value.value;
|
|
1033
|
+
const example = entry.example !== void 0 ? coerceVariableValue(kind2, entry.example) : void 0;
|
|
1034
|
+
if (example?.ok) declaration.example = example.value;
|
|
1035
|
+
if (typeof entry.format === "string") declaration.format = entry.format;
|
|
1036
|
+
if (typeof entry.label === "string") declaration.label = entry.label;
|
|
1037
|
+
if (typeof entry.description === "string") declaration.description = entry.description;
|
|
1038
|
+
out.push(declaration);
|
|
1039
|
+
}
|
|
1040
|
+
return out;
|
|
1041
|
+
}
|
|
1042
|
+
function hasContentVariables(presentation) {
|
|
1043
|
+
return isTemplate(presentation) || variableDeclarations(presentation).some((declaration) => declaration.kind !== "color");
|
|
1044
|
+
}
|
|
1045
|
+
var POSITIVE_ZERO = "0";
|
|
1046
|
+
function formatVariableNumber(value, pattern) {
|
|
1047
|
+
if (!Number.isFinite(value)) return { error: `'${String(value)}' is not a finite number.` };
|
|
1048
|
+
if (pattern === void 0 || pattern === "") return { text: String(value) };
|
|
1049
|
+
const match = /^([^#0,.]*)((?:[#0,]*)(?:\.[#0]+)?)([\s\S]*)$/.exec(pattern);
|
|
1050
|
+
const numeric = match?.[2] ?? "";
|
|
1051
|
+
if (!match || !/[#0]/.test(numeric)) return { error: `Number format '${pattern}' needs a numeric part of '#' and '0' characters.` };
|
|
1052
|
+
const prefix = match[1] ?? "";
|
|
1053
|
+
const suffix = match[3] ?? "";
|
|
1054
|
+
const [integerPattern = "", decimalPattern2 = ""] = numeric.split(".");
|
|
1055
|
+
const grouping = integerPattern.includes(",");
|
|
1056
|
+
const minInteger = [...integerPattern].filter((char) => char === "0").length;
|
|
1057
|
+
const minDecimals = [...decimalPattern2].filter((char) => char === "0").length;
|
|
1058
|
+
const maxDecimals = decimalPattern2.length;
|
|
1059
|
+
const scaled = prefix.includes("%") || suffix.includes("%") ? value * 100 : value;
|
|
1060
|
+
const absolute = Math.abs(scaled);
|
|
1061
|
+
let fixed;
|
|
1062
|
+
const shifted = Number(`${absolute}e${maxDecimals}`);
|
|
1063
|
+
if (absolute >= 1e21) fixed = `${BigInt(absolute).toString()}${maxDecimals ? `.${"0".repeat(maxDecimals)}` : ""}`;
|
|
1064
|
+
else if (/e/i.test(String(absolute)) || !Number.isSafeInteger(Math.round(shifted))) fixed = absolute.toFixed(maxDecimals);
|
|
1065
|
+
else {
|
|
1066
|
+
const digits = String(Math.round(shifted)).padStart(maxDecimals + 1, "0");
|
|
1067
|
+
fixed = maxDecimals ? `${digits.slice(0, -maxDecimals)}.${digits.slice(-maxDecimals)}` : digits;
|
|
1068
|
+
}
|
|
1069
|
+
let [integer = POSITIVE_ZERO, decimals = ""] = fixed.split(".");
|
|
1070
|
+
while (decimals.length > minDecimals && decimals.endsWith("0")) decimals = decimals.slice(0, -1);
|
|
1071
|
+
integer = integer.replace(/^0+(?=\d)/, "");
|
|
1072
|
+
if (integer === POSITIVE_ZERO && minInteger === 0 && decimals) integer = "";
|
|
1073
|
+
integer = integer.padStart(minInteger, "0");
|
|
1074
|
+
if (grouping) integer = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
1075
|
+
const body = `${integer}${decimals ? `.${decimals}` : ""}` || POSITIVE_ZERO;
|
|
1076
|
+
const negative = scaled < 0 && /[1-9]/.test(body);
|
|
1077
|
+
return { text: `${negative ? "-" : ""}${prefix}${body}${suffix}` };
|
|
1078
|
+
}
|
|
1079
|
+
function diag(context, entry, dedupeKey) {
|
|
1080
|
+
if (dedupeKey) {
|
|
1081
|
+
if (context.seen.has(dedupeKey)) return;
|
|
1082
|
+
context.seen.add(dedupeKey);
|
|
1083
|
+
}
|
|
1084
|
+
context.diagnostics.push(entry);
|
|
1085
|
+
}
|
|
1086
|
+
function sampleValue(declaration) {
|
|
1087
|
+
return declaration.example !== void 0 ? declaration.example : declaration.value !== void 0 ? declaration.value : SAMPLES[declaration.kind];
|
|
1088
|
+
}
|
|
1089
|
+
function plainText(value) {
|
|
1090
|
+
if (typeof value === "string") return value;
|
|
1091
|
+
if (Array.isArray(value)) return value.map((run) => typeof run === "string" ? run : isRecord(run) && typeof run.text === "string" ? run.text : "").join("");
|
|
1092
|
+
return "";
|
|
1093
|
+
}
|
|
1094
|
+
function inlineText(effective, format, path, context) {
|
|
1095
|
+
const { declaration } = effective;
|
|
1096
|
+
const value = effective.status === "unfilled" ? sampleValue(declaration) : effective.value;
|
|
1097
|
+
switch (declaration.kind) {
|
|
1098
|
+
case "color":
|
|
1099
|
+
case "url":
|
|
1100
|
+
return String(value);
|
|
1101
|
+
case "text":
|
|
1102
|
+
if (Array.isArray(value)) {
|
|
1103
|
+
diag(context, { code: "variable-rich-flattened", severity: "info", path, id: declaration.id, message: `Rich text variable '${declaration.id}' is inserted as plain text inside a string; reference it whole as 'var:${declaration.id}' to keep its formatting.` }, `flat:${declaration.id}:${path}`);
|
|
1104
|
+
}
|
|
1105
|
+
return plainText(value);
|
|
1106
|
+
case "number": {
|
|
1107
|
+
const pattern = (format ?? declaration.format)?.trim();
|
|
1108
|
+
const formatted = formatVariableNumber(value, pattern === "" ? void 0 : pattern);
|
|
1109
|
+
if ("error" in formatted) {
|
|
1110
|
+
diag(context, { code: "variable-format", severity: "error", path, id: declaration.id, message: formatted.error }, `fmt:${declaration.id}:${format ?? ""}`);
|
|
1111
|
+
return String(value);
|
|
1112
|
+
}
|
|
1113
|
+
return formatted.text;
|
|
1114
|
+
}
|
|
1115
|
+
case "date": {
|
|
1116
|
+
const pattern = (format ?? declaration.format)?.trim() || DEFAULT_VARIABLE_DATE_FORMAT;
|
|
1117
|
+
const formatted = formatFurnitureDate(String(value), pattern);
|
|
1118
|
+
if ("error" in formatted) {
|
|
1119
|
+
diag(context, { code: "variable-format", severity: "error", path, id: declaration.id, message: formatted.error }, `fmt:${declaration.id}:${format ?? ""}`);
|
|
1120
|
+
return String(value);
|
|
1121
|
+
}
|
|
1122
|
+
return formatted.text;
|
|
1123
|
+
}
|
|
1124
|
+
case "image":
|
|
1125
|
+
return isRecord(value) ? String(value.src) : String(value);
|
|
1126
|
+
case "list":
|
|
1127
|
+
return value.join(format ?? ", ");
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
function interpolate(text2, path, context) {
|
|
1131
|
+
let changed = false;
|
|
1132
|
+
const value = text2.replace(tokenPattern, (match, id, format) => {
|
|
1133
|
+
if (id === void 0) {
|
|
1134
|
+
if (context.unescape) {
|
|
1135
|
+
changed = true;
|
|
1136
|
+
return "{{";
|
|
1137
|
+
}
|
|
1138
|
+
return match;
|
|
1139
|
+
}
|
|
1140
|
+
context.uses.push({ id, path, form: "token" });
|
|
1141
|
+
const effective = context.effective.get(id);
|
|
1142
|
+
if (!effective) {
|
|
1143
|
+
diag(context, { code: "variable-unknown", severity: "warning", path, id, message: `'{{${id}}}' names no declared variable; declare '${id}' in the top-level variables map, or write '\\{{' for literal braces.` }, `unk:${id}:${path}`);
|
|
1144
|
+
return match;
|
|
1145
|
+
}
|
|
1146
|
+
if (effective.status === "unfilled" && !context.shape) {
|
|
1147
|
+
if (!effective.declaration.required) {
|
|
1148
|
+
changed = true;
|
|
1149
|
+
return "";
|
|
1150
|
+
}
|
|
1151
|
+
return match;
|
|
1152
|
+
}
|
|
1153
|
+
const result = inlineText(effective, effective.declaration.kind === "list" ? format : format?.trim(), path, context);
|
|
1154
|
+
if (result === void 0) return match;
|
|
1155
|
+
changed = true;
|
|
1156
|
+
return result;
|
|
1157
|
+
});
|
|
1158
|
+
return { value, changed };
|
|
1159
|
+
}
|
|
1160
|
+
var OMIT = /* @__PURE__ */ Symbol("omit");
|
|
1161
|
+
var Splice = class {
|
|
1162
|
+
constructor(values) {
|
|
1163
|
+
this.values = values;
|
|
1164
|
+
}
|
|
1165
|
+
values;
|
|
1166
|
+
};
|
|
1167
|
+
function reduceString(text2, path, parentIsArray, context) {
|
|
1168
|
+
const reference = referencePattern.exec(text2);
|
|
1169
|
+
if (reference) {
|
|
1170
|
+
const id = reference[1];
|
|
1171
|
+
const effective = context.effective.get(id);
|
|
1172
|
+
if (effective && effective.declaration.kind !== "color") {
|
|
1173
|
+
context.uses.push({ id, path, form: "reference" });
|
|
1174
|
+
if (effective.status === "unfilled" && !context.shape) {
|
|
1175
|
+
return effective.declaration.required ? { kind: "keep" } : { kind: "omit" };
|
|
1176
|
+
}
|
|
1177
|
+
const value = effective.status === "unfilled" ? sampleValue(effective.declaration) : effective.value;
|
|
1178
|
+
if (effective.declaration.kind === "list" && parentIsArray) {
|
|
1179
|
+
return context.shape ? { kind: "replace", value: value[0] ?? "" } : { kind: "splice", values: clone(value) };
|
|
1180
|
+
}
|
|
1181
|
+
return { kind: "replace", value: clone(value) };
|
|
1182
|
+
}
|
|
1183
|
+
if (effective) {
|
|
1184
|
+
context.uses.push({ id, path, form: "reference" });
|
|
1185
|
+
return { kind: "keep" };
|
|
1186
|
+
}
|
|
1187
|
+
return { kind: "keep" };
|
|
1188
|
+
}
|
|
1189
|
+
if (!text2.includes("{{")) return { kind: "keep" };
|
|
1190
|
+
const result = interpolate(text2, path, context);
|
|
1191
|
+
return result.changed ? { kind: "replace", value: result.value } : { kind: "keep" };
|
|
1192
|
+
}
|
|
1193
|
+
var skippedKeys = /* @__PURE__ */ new Set(["extensions"]);
|
|
1194
|
+
var skippedRootKeys = /* @__PURE__ */ new Set(["variables", "catalogs", "$schema", "template"]);
|
|
1195
|
+
function walk(value, path, parentIsArray, context) {
|
|
1196
|
+
if (typeof value === "string") {
|
|
1197
|
+
const reduced = reduceString(value, path, parentIsArray, context);
|
|
1198
|
+
if (reduced.kind === "keep") return value;
|
|
1199
|
+
if (reduced.kind === "omit") return OMIT;
|
|
1200
|
+
if (reduced.kind === "splice") return new Splice(reduced.values);
|
|
1201
|
+
return reduced.value;
|
|
1202
|
+
}
|
|
1203
|
+
if (Array.isArray(value)) {
|
|
1204
|
+
let changed = false;
|
|
1205
|
+
const out = [];
|
|
1206
|
+
value.forEach((entry, index) => {
|
|
1207
|
+
const next = walk(entry, `${path}/${index}`, true, context);
|
|
1208
|
+
if (next === OMIT) {
|
|
1209
|
+
changed = true;
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
if (next instanceof Splice) {
|
|
1213
|
+
changed = true;
|
|
1214
|
+
out.push(...next.values);
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
if (next !== entry) changed = true;
|
|
1218
|
+
out.push(next);
|
|
1219
|
+
});
|
|
1220
|
+
return changed ? out : value;
|
|
1221
|
+
}
|
|
1222
|
+
if (isRecord(value)) {
|
|
1223
|
+
let changed = false;
|
|
1224
|
+
const entries = [];
|
|
1225
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1226
|
+
if (skippedKeys.has(key) || path === "" && skippedRootKeys.has(key)) {
|
|
1227
|
+
entries.push([key, entry]);
|
|
1228
|
+
continue;
|
|
1229
|
+
}
|
|
1230
|
+
const next = walk(entry, pathFor(path || "/", key), false, context);
|
|
1231
|
+
if (next === OMIT) {
|
|
1232
|
+
changed = true;
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
if (next !== entry) changed = true;
|
|
1236
|
+
entries.push([key, next]);
|
|
1237
|
+
}
|
|
1238
|
+
return changed ? Object.fromEntries(entries) : value;
|
|
1239
|
+
}
|
|
1240
|
+
return value;
|
|
1241
|
+
}
|
|
1242
|
+
function plan(presentation, values, options) {
|
|
1243
|
+
const declarations = variableDeclarations(presentation);
|
|
1244
|
+
const template = options.template ?? isTemplate(presentation);
|
|
1245
|
+
const lenient = template || options.partial === true;
|
|
1246
|
+
const context = {
|
|
1247
|
+
effective: /* @__PURE__ */ new Map(),
|
|
1248
|
+
diagnostics: [],
|
|
1249
|
+
uses: [],
|
|
1250
|
+
unescape: false,
|
|
1251
|
+
shape: options.shape === true,
|
|
1252
|
+
content: hasContentVariables(presentation) || template,
|
|
1253
|
+
seen: /* @__PURE__ */ new Set()
|
|
1254
|
+
};
|
|
1255
|
+
const unfilled = [];
|
|
1256
|
+
const examplesUsed = [];
|
|
1257
|
+
const providedColors = /* @__PURE__ */ new Map();
|
|
1258
|
+
const declared = new Set(declarations.map((declaration) => declaration.id));
|
|
1259
|
+
for (const key of Object.keys(values)) {
|
|
1260
|
+
if (!declared.has(key)) {
|
|
1261
|
+
context.diagnostics.push({ code: "variable-unknown-value", severity: "warning", path: "/variables", id: key, message: `A value was supplied for '${key}', which no variable declares.` });
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
for (const declaration of declarations) {
|
|
1265
|
+
const raw = hasOwn(values, declaration.id) ? values[declaration.id] : void 0;
|
|
1266
|
+
let effective;
|
|
1267
|
+
if (isProvided(declaration.kind, raw)) {
|
|
1268
|
+
const coerced = coerceVariableValue(declaration.kind, raw);
|
|
1269
|
+
if (coerced.ok) {
|
|
1270
|
+
effective = { declaration, status: "filled", value: coerced.value, provided: true };
|
|
1271
|
+
if (declaration.kind === "color") providedColors.set(declaration.id, coerced.value);
|
|
1272
|
+
} else {
|
|
1273
|
+
context.diagnostics.push({ code: "variable-invalid-value", severity: "error", path: declaration.path, id: declaration.id, message: `Value for '${declaration.id}' (${declaration.kind}): ${coerced.message}.` });
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
if (!effective && declaration.value !== void 0) effective = { declaration, status: "filled", value: declaration.value, provided: false };
|
|
1277
|
+
if (!effective && options.examples && declaration.example !== void 0) {
|
|
1278
|
+
effective = { declaration, status: "example", value: declaration.example, provided: false };
|
|
1279
|
+
examplesUsed.push(declaration.id);
|
|
1280
|
+
context.diagnostics.push({ code: "variable-example-used", severity: "info", path: declaration.path, id: declaration.id, message: `'${declaration.id}' is unfilled; its example is used.` });
|
|
1281
|
+
if (declaration.kind === "color") providedColors.set(declaration.id, declaration.example);
|
|
1282
|
+
}
|
|
1283
|
+
if (!effective) {
|
|
1284
|
+
effective = { declaration, status: "unfilled", provided: false };
|
|
1285
|
+
if (declaration.required) {
|
|
1286
|
+
unfilled.push(declaration.id);
|
|
1287
|
+
context.diagnostics.push({
|
|
1288
|
+
code: "variable-unfilled",
|
|
1289
|
+
severity: lenient ? "info" : "error",
|
|
1290
|
+
path: declaration.path,
|
|
1291
|
+
id: declaration.id,
|
|
1292
|
+
message: lenient ? `'${declaration.id}' (${declaration.kind}) is not filled yet.` : `Required variable '${declaration.id}' (${declaration.kind}) has no value. Provide one, or mark the document as a template ("template": true).`
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
context.effective.set(declaration.id, effective);
|
|
1297
|
+
}
|
|
1298
|
+
return { context, declarations, unfilled, examplesUsed, providedColors };
|
|
1299
|
+
}
|
|
1300
|
+
function rebuildVariables(presentation, built, keepAll) {
|
|
1301
|
+
const source = presentation.variables;
|
|
1302
|
+
if (!isRecord(source)) return void 0;
|
|
1303
|
+
const out = [];
|
|
1304
|
+
for (const [id, entry] of Object.entries(source)) {
|
|
1305
|
+
const effective = built.context.effective.get(id);
|
|
1306
|
+
if (!effective) {
|
|
1307
|
+
out.push([id, entry]);
|
|
1308
|
+
continue;
|
|
1309
|
+
}
|
|
1310
|
+
const { declaration } = effective;
|
|
1311
|
+
if (declaration.kind !== "color") {
|
|
1312
|
+
if (keepAll || effective.status === "unfilled" && declaration.required) out.push([id, entry]);
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
const color = built.providedColors.get(id);
|
|
1316
|
+
if (color === void 0) {
|
|
1317
|
+
out.push([id, entry]);
|
|
1318
|
+
continue;
|
|
1319
|
+
}
|
|
1320
|
+
const base = isRecord(entry) ? entry : {};
|
|
1321
|
+
const { example: _example, required: _required, label: _label, ...rest } = base;
|
|
1322
|
+
out.push([id, { ...rest, type: "color", value: color }]);
|
|
1323
|
+
}
|
|
1324
|
+
return out.length ? Object.fromEntries(out) : void 0;
|
|
1325
|
+
}
|
|
1326
|
+
function finish(presentation, built, options) {
|
|
1327
|
+
const { context } = built;
|
|
1328
|
+
const complete = built.unfilled.length === 0;
|
|
1329
|
+
context.unescape = complete && context.content && !context.shape;
|
|
1330
|
+
const walked = context.content ? walk(presentation, "", false, context) : presentation;
|
|
1331
|
+
const result = { ...walked };
|
|
1332
|
+
const variables = rebuildVariables(presentation, built, context.shape);
|
|
1333
|
+
if (variables) result.variables = variables;
|
|
1334
|
+
else delete result.variables;
|
|
1335
|
+
if (!context.shape && complete && hasOwn(result, "template")) delete result.template;
|
|
1336
|
+
if (context.content) {
|
|
1337
|
+
const used = new Set(context.uses.map((use) => use.id));
|
|
1338
|
+
for (const declaration of built.declarations) {
|
|
1339
|
+
if (declaration.kind !== "color" && !used.has(declaration.id)) {
|
|
1340
|
+
context.diagnostics.push({ code: "variable-unused", severity: "warning", path: declaration.path, id: declaration.id, message: `Variable '${declaration.id}' is declared but never used; reference it as '{{${declaration.id}}}' or 'var:${declaration.id}'.` });
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
const diagnostics = context.diagnostics;
|
|
1345
|
+
if (options.strict && diagnostics.some((entry) => entry.severity === "error")) throw new OPFVariableError(diagnostics);
|
|
1346
|
+
return { presentation: result, diagnostics, unfilled: built.unfilled, examplesUsed: built.examplesUsed, complete };
|
|
1347
|
+
}
|
|
1348
|
+
function resolveVariables(presentation, values = {}, options = {}) {
|
|
1349
|
+
if (!isRecord(presentation)) throw new TypeError("resolveVariables expects a presentation object.");
|
|
1350
|
+
if (!isRecord(values)) throw new TypeError("resolveVariables values must be an object keyed by variable id.");
|
|
1351
|
+
const built = plan(presentation, values, options);
|
|
1352
|
+
if (!built.context.content && built.providedColors.size === 0 && built.context.diagnostics.length === 0) {
|
|
1353
|
+
return { presentation, diagnostics: [], unfilled: built.unfilled, examplesUsed: [], complete: built.unfilled.length === 0 };
|
|
1354
|
+
}
|
|
1355
|
+
return finish(presentation, built, options);
|
|
1356
|
+
}
|
|
1357
|
+
function instantiateForValidation(presentation, values = {}, template) {
|
|
1358
|
+
const options = { template, shape: true, partial: true };
|
|
1359
|
+
return finish(presentation, plan(presentation, values, options), options);
|
|
1360
|
+
}
|
|
1361
|
+
function listVariables(presentation, values = {}) {
|
|
1362
|
+
if (!isRecord(presentation)) return [];
|
|
1363
|
+
const built = plan(presentation, isRecord(values) ? values : {}, { examples: false, partial: true });
|
|
1364
|
+
built.context.shape = true;
|
|
1365
|
+
built.context.content = true;
|
|
1366
|
+
walk(presentation, "", false, built.context);
|
|
1367
|
+
return built.declarations.map((declaration) => {
|
|
1368
|
+
const effective = built.context.effective.get(declaration.id);
|
|
1369
|
+
return {
|
|
1370
|
+
...declaration,
|
|
1371
|
+
...effective?.status === "filled" && effective.provided ? { value: effective.value } : {},
|
|
1372
|
+
filled: effective?.status === "filled",
|
|
1373
|
+
uses: built.context.uses.filter((use) => use.id === declaration.id)
|
|
1374
|
+
};
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// src/chart-data.ts
|
|
1379
|
+
var record3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1380
|
+
var own = (value, key) => Object.hasOwn(value, key);
|
|
1381
|
+
var segment = (key) => String(key).replaceAll("~", "~0").replaceAll("/", "~1");
|
|
1382
|
+
var at = (base, ...keys) => `${base}${keys.map((key) => `/${segment(key)}`).join("")}`;
|
|
1383
|
+
var STRICT_DECIMAL = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
|
|
1384
|
+
function chartNumber(value) {
|
|
1385
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
1386
|
+
if (typeof value !== "string") return null;
|
|
1387
|
+
const text2 = value.trim();
|
|
1388
|
+
if (!STRICT_DECIMAL.test(text2)) return null;
|
|
1389
|
+
const number = Number(text2);
|
|
1390
|
+
if (!Number.isFinite(number) || /^-?\d+$/.test(text2) && !Number.isSafeInteger(number)) return null;
|
|
1391
|
+
return number;
|
|
1392
|
+
}
|
|
1393
|
+
function numberFormatError(format) {
|
|
1394
|
+
if (format === void 0) return void 0;
|
|
1395
|
+
if (typeof format !== "string" || format === "") return 'A number format is a non-empty pattern such as "#,##0" or "0.0%".';
|
|
1396
|
+
const result = formatVariableNumber(0, format);
|
|
1397
|
+
return "error" in result ? result.error : void 0;
|
|
1398
|
+
}
|
|
1399
|
+
function formatDataNumber(value, format) {
|
|
1400
|
+
if (format === void 0 || format === "" || !Number.isFinite(value)) return String(value);
|
|
1401
|
+
const result = formatVariableNumber(value, format);
|
|
1402
|
+
return "text" in result ? result.text : String(value);
|
|
1403
|
+
}
|
|
1404
|
+
function formatParts(format) {
|
|
1405
|
+
const match = /^([^#0,.]*)((?:[#0,]*)(?:\.[#0]+)?)([\s\S]*)$/.exec(format);
|
|
1406
|
+
const numeric = match?.[2] ?? "";
|
|
1407
|
+
if (!match || !/[#0]/.test(numeric)) return void 0;
|
|
1408
|
+
const [integer = "", decimals = ""] = numeric.split(".");
|
|
1409
|
+
return { prefix: match[1] ?? "", integer, decimals, suffix: match[3] ?? "" };
|
|
1410
|
+
}
|
|
1411
|
+
var EXCEL_SAFE = /* @__PURE__ */ new Set([..."$-+():!^&'~{}<>= "]);
|
|
1412
|
+
function excelLiteral(text2, percent) {
|
|
1413
|
+
let out = "";
|
|
1414
|
+
let quoted = "";
|
|
1415
|
+
const flush = () => {
|
|
1416
|
+
if (quoted) {
|
|
1417
|
+
out += `"${quoted}"`;
|
|
1418
|
+
quoted = "";
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
1421
|
+
for (const char of text2) {
|
|
1422
|
+
if (char === "%" && !percent.used) {
|
|
1423
|
+
flush();
|
|
1424
|
+
out += "%";
|
|
1425
|
+
percent.used = true;
|
|
1426
|
+
continue;
|
|
1427
|
+
}
|
|
1428
|
+
if (char === '"') {
|
|
1429
|
+
flush();
|
|
1430
|
+
out += '\\"';
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1433
|
+
if (EXCEL_SAFE.has(char) && quoted === "") {
|
|
1434
|
+
out += char;
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
quoted += char;
|
|
1438
|
+
}
|
|
1439
|
+
flush();
|
|
1440
|
+
return out;
|
|
1441
|
+
}
|
|
1442
|
+
function excelInteger(integer) {
|
|
1443
|
+
const grouping = integer.includes(",");
|
|
1444
|
+
const digits = integer.replaceAll(",", "");
|
|
1445
|
+
if (!grouping) {
|
|
1446
|
+
const zeros2 = [...digits].filter((char) => char === "0").length;
|
|
1447
|
+
return "#".repeat(digits.length - zeros2) + "0".repeat(zeros2);
|
|
1448
|
+
}
|
|
1449
|
+
const zeros = [...digits].filter((char) => char === "0").length;
|
|
1450
|
+
const width = Math.max(4, digits.length);
|
|
1451
|
+
const placeholders = "#".repeat(width - zeros) + "0".repeat(zeros);
|
|
1452
|
+
return placeholders.replace(/\B(?=(.{3})+$)/g, ",");
|
|
1453
|
+
}
|
|
1454
|
+
function excelNumberFormat(format) {
|
|
1455
|
+
if (format === void 0 || format === "") return "General";
|
|
1456
|
+
const parts = formatParts(format);
|
|
1457
|
+
if (!parts) return "General";
|
|
1458
|
+
const percent = { used: false };
|
|
1459
|
+
const minDecimals = [...parts.decimals].filter((char) => char === "0").length;
|
|
1460
|
+
const decimals = parts.decimals ? `.${"0".repeat(minDecimals)}${"#".repeat(parts.decimals.length - minDecimals)}` : "";
|
|
1461
|
+
const prefix = excelLiteral(parts.prefix, percent);
|
|
1462
|
+
const body = `${excelInteger(parts.integer)}${decimals}`;
|
|
1463
|
+
return `${prefix}${body}${excelLiteral(parts.suffix, percent)}`;
|
|
1464
|
+
}
|
|
1465
|
+
function excelTokens(code) {
|
|
1466
|
+
const tokens = [];
|
|
1467
|
+
for (let i = 0; i < code.length; i++) {
|
|
1468
|
+
const char = code[i];
|
|
1469
|
+
if (char === '"') {
|
|
1470
|
+
const end = code.indexOf('"', i + 1);
|
|
1471
|
+
if (end < 0) return void 0;
|
|
1472
|
+
tokens.push({ kind: "literal", text: code.slice(i + 1, end), quoted: true });
|
|
1473
|
+
i = end;
|
|
1474
|
+
} else if (char === "\\") {
|
|
1475
|
+
if (i + 1 >= code.length) return void 0;
|
|
1476
|
+
tokens.push({ kind: "literal", text: code[i + 1], quoted: true });
|
|
1477
|
+
i++;
|
|
1478
|
+
} else if (char === "[") {
|
|
1479
|
+
const end = code.indexOf("]", i + 1);
|
|
1480
|
+
if (end < 0) return void 0;
|
|
1481
|
+
const currency = /^\$([^-\]]*)(?:-[0-9A-Fa-f]+)?$/.exec(code.slice(i + 1, end));
|
|
1482
|
+
if (!currency) return void 0;
|
|
1483
|
+
tokens.push({ kind: "literal", text: currency[1] ?? "", quoted: true });
|
|
1484
|
+
i = end;
|
|
1485
|
+
} else if (char === "%") tokens.push({ kind: "percent" });
|
|
1486
|
+
else if ("#0,.".includes(char)) tokens.push({ kind: "placeholder", text: char });
|
|
1487
|
+
else if (EXCEL_SAFE.has(char)) tokens.push({ kind: "literal", text: char, quoted: false });
|
|
1488
|
+
else return void 0;
|
|
1489
|
+
}
|
|
1490
|
+
return tokens;
|
|
1491
|
+
}
|
|
1492
|
+
function numberFormatFromExcel(code) {
|
|
1493
|
+
if (typeof code !== "string") return void 0;
|
|
1494
|
+
const trimmed = code.trim();
|
|
1495
|
+
if (trimmed === "" || /^general$/i.test(trimmed)) return void 0;
|
|
1496
|
+
const tokens = excelTokens(code);
|
|
1497
|
+
if (!tokens) return void 0;
|
|
1498
|
+
const first = tokens.findIndex((token) => token.kind === "placeholder");
|
|
1499
|
+
if (first < 0) return void 0;
|
|
1500
|
+
let last = first;
|
|
1501
|
+
while (last + 1 < tokens.length && tokens[last + 1].kind === "placeholder") last++;
|
|
1502
|
+
if (tokens.slice(last + 1).some((token) => token.kind === "placeholder")) return void 0;
|
|
1503
|
+
const numeric = tokens.slice(first, last + 1).map((token) => token.text).join("");
|
|
1504
|
+
const match = /^([#0,]*)(?:\.([#0]+))?$/.exec(numeric);
|
|
1505
|
+
if (!match || !/[#0]/.test(numeric)) return void 0;
|
|
1506
|
+
const integer = match[1] ?? "";
|
|
1507
|
+
if (/^,|,$/.test(integer) || integer.includes(",,")) return void 0;
|
|
1508
|
+
if (/,/.test(integer) && !/[#0]/.test(integer)) return void 0;
|
|
1509
|
+
if (/0[#,]*#/.test(integer) || /#0/.test(match[2] ?? "")) return void 0;
|
|
1510
|
+
const percentTokens = tokens.filter((token) => token.kind === "percent").length;
|
|
1511
|
+
const literalText = (list) => list.map((token) => token.kind === "percent" ? "%" : token.kind === "literal" ? token.text : "").join("");
|
|
1512
|
+
const quotedPercent = tokens.some((token) => token.kind === "literal" && token.quoted && token.text.includes("%"));
|
|
1513
|
+
if (percentTokens > 1 || percentTokens === 0 && quotedPercent) return void 0;
|
|
1514
|
+
const prefix = literalText(tokens.slice(0, first));
|
|
1515
|
+
const suffix = literalText(tokens.slice(last + 1));
|
|
1516
|
+
const format = `${prefix}${numeric}${suffix}`;
|
|
1517
|
+
const parts = formatParts(format);
|
|
1518
|
+
if (!parts || parts.prefix !== prefix || parts.suffix !== suffix) return void 0;
|
|
1519
|
+
return format;
|
|
1520
|
+
}
|
|
1521
|
+
function columnName(column) {
|
|
1522
|
+
if (typeof column === "string") return column;
|
|
1523
|
+
if (record3(column) && typeof column.name === "string") return column.name;
|
|
1524
|
+
return void 0;
|
|
1525
|
+
}
|
|
1526
|
+
function columnFormat(column) {
|
|
1527
|
+
return record3(column) && typeof column.format === "string" ? column.format : void 0;
|
|
1528
|
+
}
|
|
1529
|
+
function datasetsOf(document) {
|
|
1530
|
+
return record3(document) && record3(document.datasets) ? document.datasets : {};
|
|
1531
|
+
}
|
|
1532
|
+
function isDatasetRef(value) {
|
|
1533
|
+
return record3(value) && typeof value.dataset === "string";
|
|
1534
|
+
}
|
|
1535
|
+
function selectFields(columns2, fields2, fieldsPath, columnsPath, diagnostics) {
|
|
1536
|
+
const names = columns2.map((column) => columnName(column) ?? "");
|
|
1537
|
+
if (!Array.isArray(fields2)) return { names, formats: columns2.map(columnFormat), columns: columns2, indices: names.map((_, index) => index) };
|
|
1538
|
+
duplicateColumns(names, columnsPath, diagnostics);
|
|
1539
|
+
const indices = [];
|
|
1540
|
+
fields2.forEach((field, index) => {
|
|
1541
|
+
const found = typeof field === "string" ? names.indexOf(field) : -1;
|
|
1542
|
+
if (found < 0) diagnostics.push({ code: "dataset-field-unknown", severity: "error", path: at(fieldsPath, index), message: `field ${JSON.stringify(field)} is not a column of the dataset; use one of ${names.map((name) => JSON.stringify(name)).join(", ")}` });
|
|
1543
|
+
else indices.push(found);
|
|
1544
|
+
});
|
|
1545
|
+
return { names: indices.map((index) => names[index]), formats: indices.map((index) => columnFormat(columns2[index])), columns: indices.map((index) => columns2[index]), indices };
|
|
1546
|
+
}
|
|
1547
|
+
function duplicateColumns(names, columnsPath, diagnostics) {
|
|
1548
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1549
|
+
names.forEach((name, index) => {
|
|
1550
|
+
if (seen.has(name)) diagnostics.push({ code: "data-column-duplicate", severity: "error", path: at(columnsPath, index), message: `column name ${JSON.stringify(name)} is used by an earlier column; column names must be unique where fields or a chart mapping address them` });
|
|
1551
|
+
seen.add(name);
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
function formatDiagnostics(columns2, columnsPath, diagnostics) {
|
|
1555
|
+
columns2.forEach((column, index) => {
|
|
1556
|
+
if (!record3(column) || column.format === void 0) return;
|
|
1557
|
+
const error = numberFormatError(column.format);
|
|
1558
|
+
if (error) diagnostics.push({ code: "number-format-invalid", severity: "error", path: at(columnsPath, index, "format"), message: error });
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
function datasetDiagnostics(document) {
|
|
1562
|
+
const diagnostics = [];
|
|
1563
|
+
for (const [id, dataset] of Object.entries(datasetsOf(document))) {
|
|
1564
|
+
if (!record3(dataset) || !Array.isArray(dataset.columns)) continue;
|
|
1565
|
+
const columnsPath = at("/datasets", id, "columns");
|
|
1566
|
+
duplicateColumns(dataset.columns.map((column) => columnName(column) ?? ""), columnsPath, diagnostics);
|
|
1567
|
+
formatDiagnostics(dataset.columns, columnsPath, diagnostics);
|
|
1568
|
+
}
|
|
1569
|
+
return diagnostics;
|
|
1570
|
+
}
|
|
1571
|
+
function inlineColumn(column) {
|
|
1572
|
+
const name = columnName(column) ?? "";
|
|
1573
|
+
const format = columnFormat(column);
|
|
1574
|
+
return format === void 0 ? name : { name, format };
|
|
1575
|
+
}
|
|
1576
|
+
function inlinedData(ref, datasets) {
|
|
1577
|
+
const id = ref.dataset;
|
|
1578
|
+
const dataset = own(datasets, id) ? datasets[id] : void 0;
|
|
1579
|
+
if (!record3(dataset) || !Array.isArray(dataset.columns) || !Array.isArray(dataset.rows)) return void 0;
|
|
1580
|
+
const diagnostics = [];
|
|
1581
|
+
const selection = selectFields(dataset.columns, ref.fields, "", "", diagnostics);
|
|
1582
|
+
if (diagnostics.some((entry) => entry.code === "dataset-field-unknown")) return void 0;
|
|
1583
|
+
const rows2 = dataset.rows.map((row) => selection.indices.map((index) => Array.isArray(row) && index < row.length ? structuredClone(row[index]) : null));
|
|
1584
|
+
return { columns: selection.columns.map(inlineColumn), rows: rows2, ...record3(dataset.source) ? { source: structuredClone(dataset.source) } : {} };
|
|
1585
|
+
}
|
|
1586
|
+
function inlineDatasets(document) {
|
|
1587
|
+
if (!record3(document)) return document;
|
|
1588
|
+
const copy = structuredClone(document);
|
|
1589
|
+
const datasets = datasetsOf(copy);
|
|
1590
|
+
if (!Object.keys(datasets).length) return copy;
|
|
1591
|
+
visitDataPayloads(copy, (payload) => {
|
|
1592
|
+
if (record3(payload.chart) && isDatasetRef(payload.chart.data)) {
|
|
1593
|
+
const inline = inlinedData(payload.chart.data, datasets);
|
|
1594
|
+
if (inline) payload.chart.data = inline;
|
|
1595
|
+
}
|
|
1596
|
+
if (record3(payload.table) && isDatasetRef(payload.table)) payload.table = inlineTable(payload.table, datasets) ?? payload.table;
|
|
1597
|
+
});
|
|
1598
|
+
return copy;
|
|
1599
|
+
}
|
|
1600
|
+
function inlineTable(table, datasets) {
|
|
1601
|
+
const inline = inlinedData(table, datasets);
|
|
1602
|
+
if (!inline) return void 0;
|
|
1603
|
+
const { dataset: _dataset, fields: _fields, ...rest } = table;
|
|
1604
|
+
return { ...rest, columns: inline.columns, rows: inline.rows };
|
|
1605
|
+
}
|
|
1606
|
+
function inlineTableData(table, document) {
|
|
1607
|
+
if (!record3(table) || !isDatasetRef(table)) return table;
|
|
1608
|
+
return inlineTable(table, datasetsOf(document)) ?? table;
|
|
1609
|
+
}
|
|
1610
|
+
function inlineChartData(chart, document) {
|
|
1611
|
+
if (!record3(chart) || !isDatasetRef(chart.data)) return chart;
|
|
1612
|
+
const inline = inlinedData(chart.data, datasetsOf(document));
|
|
1613
|
+
return inline ? { ...chart, data: inline } : chart;
|
|
1614
|
+
}
|
|
1615
|
+
function visitDataPayloads(document, visit) {
|
|
1616
|
+
if (!Array.isArray(document.slides)) return;
|
|
1617
|
+
const walk2 = (payload, depth, ancestors) => {
|
|
1618
|
+
visit(payload);
|
|
1619
|
+
if (depth >= 32 || ancestors.includes(payload) || !Array.isArray(payload.blocks)) return;
|
|
1620
|
+
const nested = [...ancestors, payload];
|
|
1621
|
+
for (const block of payload.blocks) if (record3(block)) walk2(block, depth + 1, nested);
|
|
1622
|
+
};
|
|
1623
|
+
for (const slide of document.slides) {
|
|
1624
|
+
if (!record3(slide)) continue;
|
|
1625
|
+
walk2(slide, 0, []);
|
|
1626
|
+
for (const [key, value] of Object.entries(slide)) if (record3(value) && /^(top|middle|bottom|left|center|right)([+:]|$)/.test(key)) walk2(value, 1, [slide]);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
function unusedDatasets(document) {
|
|
1630
|
+
const datasets = datasetsOf(document);
|
|
1631
|
+
const ids = Object.keys(datasets);
|
|
1632
|
+
if (!ids.length || !record3(document)) return [];
|
|
1633
|
+
const used = /* @__PURE__ */ new Set();
|
|
1634
|
+
visitDataPayloads(document, (payload) => {
|
|
1635
|
+
if (record3(payload.chart) && isDatasetRef(payload.chart.data)) used.add(payload.chart.data.dataset);
|
|
1636
|
+
if (record3(payload.table) && isDatasetRef(payload.table)) used.add(payload.table.dataset);
|
|
1637
|
+
});
|
|
1638
|
+
return ids.filter((id) => !used.has(id));
|
|
1639
|
+
}
|
|
1640
|
+
function isXYChartType(type) {
|
|
1641
|
+
return chartOptionTarget(type)?.kind === "scatter";
|
|
1642
|
+
}
|
|
1643
|
+
var VARIABLE_REFERENCE = /^var:([a-z][a-z0-9-]*)$/;
|
|
1644
|
+
function numberVariable(document, cell) {
|
|
1645
|
+
if (typeof cell !== "string") return false;
|
|
1646
|
+
const id = VARIABLE_REFERENCE.exec(cell)?.[1];
|
|
1647
|
+
if (!id || !record3(document) || !record3(document.variables)) return false;
|
|
1648
|
+
const variable = document.variables[id];
|
|
1649
|
+
return record3(variable) && variable.type === "number";
|
|
1650
|
+
}
|
|
1651
|
+
function resolveChartData(chart, document, options = {}) {
|
|
1652
|
+
const base = options.path ?? "";
|
|
1653
|
+
const diagnostics = [];
|
|
1654
|
+
const fail = (reason, message) => ({ ok: false, reason, message, diagnostics });
|
|
1655
|
+
const data = record3(chart) ? chart.data : void 0;
|
|
1656
|
+
if (!record3(data)) return fail("no-columns", "The chart has no data.");
|
|
1657
|
+
let columns2;
|
|
1658
|
+
let rows2;
|
|
1659
|
+
let columnsPath;
|
|
1660
|
+
let rowsPath;
|
|
1661
|
+
let source;
|
|
1662
|
+
let datasetId;
|
|
1663
|
+
let selected;
|
|
1664
|
+
if (isDatasetRef(data)) {
|
|
1665
|
+
datasetId = data.dataset;
|
|
1666
|
+
const datasets = datasetsOf(document);
|
|
1667
|
+
const dataset = own(datasets, datasetId) ? datasets[datasetId] : void 0;
|
|
1668
|
+
if (!record3(dataset) || !Array.isArray(dataset.columns) || !Array.isArray(dataset.rows)) {
|
|
1669
|
+
diagnostics.push({ code: "dataset-unknown", severity: "error", path: at(base, "data", "dataset"), message: `unknown dataset '${datasetId}'; add it to the top-level datasets map` });
|
|
1670
|
+
return fail("dataset-unknown", `The chart references dataset '${datasetId}', which the document does not hold.`);
|
|
1671
|
+
}
|
|
1672
|
+
columnsPath = at("/datasets", datasetId, "columns");
|
|
1673
|
+
rowsPath = at("/datasets", datasetId, "rows");
|
|
1674
|
+
formatDiagnostics(dataset.columns, columnsPath, diagnostics);
|
|
1675
|
+
const selection = selectFields(dataset.columns, data.fields, at(base, "data", "fields"), columnsPath, diagnostics);
|
|
1676
|
+
columns2 = selection.columns;
|
|
1677
|
+
selected = selection.indices;
|
|
1678
|
+
rows2 = dataset.rows;
|
|
1679
|
+
if (record3(dataset.source)) source = dataset.source;
|
|
1680
|
+
} else if (typeof data.src === "string") {
|
|
1681
|
+
diagnostics.push({ code: "chart-data-source-unresolved", severity: "warning", path: at(base, "data", "src"), message: `chart data source '${data.src}' is not loaded by any engine; the preview and export draw a placeholder. Import the data inline (columns and rows, with a 'source' for provenance) or reference a dataset` });
|
|
1682
|
+
return fail("data-not-inline", "The chart reads an external data source, which no engine resolves.");
|
|
1683
|
+
} else {
|
|
1684
|
+
if (!Array.isArray(data.columns)) return fail("no-columns", "The chart data has no columns.");
|
|
1685
|
+
columns2 = data.columns;
|
|
1686
|
+
rows2 = Array.isArray(data.rows) ? data.rows : [];
|
|
1687
|
+
columnsPath = at(base, "data", "columns");
|
|
1688
|
+
rowsPath = at(base, "data", "rows");
|
|
1689
|
+
formatDiagnostics(columns2, columnsPath, diagnostics);
|
|
1690
|
+
if (record3(data.source)) source = data.source;
|
|
1691
|
+
}
|
|
1692
|
+
if (!columns2.length) return fail("no-columns", "The chart data has no columns.");
|
|
1693
|
+
const names = columns2.map((column) => columnName(column) ?? "");
|
|
1694
|
+
const formats = columns2.map(columnFormat);
|
|
1695
|
+
const mapping = record3(chart) && record3(chart.mapping) ? chart.mapping : void 0;
|
|
1696
|
+
if (mapping && !selected) duplicateColumns(names, columnsPath, diagnostics);
|
|
1697
|
+
const mappingPath = at(base, "mapping");
|
|
1698
|
+
const xy = isXYChartType(record3(chart) ? chart.type : void 0);
|
|
1699
|
+
const known = (name, path) => {
|
|
1700
|
+
if (name === void 0) return void 0;
|
|
1701
|
+
const index = typeof name === "string" ? names.indexOf(name) : -1;
|
|
1702
|
+
if (index < 0) {
|
|
1703
|
+
diagnostics.push({ code: "chart-mapping-unknown-column", severity: "error", path, message: `mapping names column ${JSON.stringify(name)}, which the chart data does not have; use one of ${names.map((entry) => JSON.stringify(entry)).join(", ")}` });
|
|
1704
|
+
return void 0;
|
|
1705
|
+
}
|
|
1706
|
+
return index;
|
|
1707
|
+
};
|
|
1708
|
+
const category = known(mapping?.category, at(mappingPath, "category")) ?? 0;
|
|
1709
|
+
let x;
|
|
1710
|
+
if (xy) {
|
|
1711
|
+
let mapped = known(mapping?.x, at(mappingPath, "x"));
|
|
1712
|
+
if (mapped === category) {
|
|
1713
|
+
diagnostics.push({ code: "chart-mapping-adapted", severity: "warning", path: at(mappingPath, "x"), message: `the X column ${JSON.stringify(names[category])} is also the category column; the default X column is used` });
|
|
1714
|
+
mapped = void 0;
|
|
1715
|
+
}
|
|
1716
|
+
x = mapped ?? (names.length > 2 ? category === 1 ? 0 : 1 : void 0);
|
|
1717
|
+
if (x !== void 0 && (x >= names.length || x === category)) x = void 0;
|
|
1718
|
+
} else if (mapping?.x !== void 0) {
|
|
1719
|
+
diagnostics.push({ code: "chart-mapping-adapted", severity: "warning", path: at(mappingPath, "x"), message: `a '${String(record3(chart) ? chart.type : "")}' chart has no X value axis; mapping.x is ignored` });
|
|
1720
|
+
}
|
|
1721
|
+
let series;
|
|
1722
|
+
if (Array.isArray(mapping?.series)) {
|
|
1723
|
+
series = [];
|
|
1724
|
+
mapping.series.forEach((name, index) => {
|
|
1725
|
+
const path = at(mappingPath, "series", index);
|
|
1726
|
+
const found = known(name, path);
|
|
1727
|
+
if (found === void 0) return;
|
|
1728
|
+
if (found === category || found === x) {
|
|
1729
|
+
diagnostics.push({ code: "chart-mapping-adapted", severity: "warning", path, message: `series ${JSON.stringify(name)} is the ${found === category ? "category" : "X"} column; it is not plotted as a series` });
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
if (series.includes(found)) {
|
|
1733
|
+
diagnostics.push({ code: "chart-mapping-adapted", severity: "warning", path, message: `series ${JSON.stringify(name)} is listed twice; it is plotted once` });
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
series.push(found);
|
|
1737
|
+
});
|
|
1738
|
+
} else series = names.map((_, index) => index).filter((index) => index !== category && index !== x);
|
|
1739
|
+
if (x !== void 0 && !series.length) {
|
|
1740
|
+
diagnostics.push({ code: "chart-mapping-adapted", severity: "warning", path: at(mappingPath, mapping?.x !== void 0 ? "x" : "series"), message: `no series is left beside the X column ${JSON.stringify(names[x])}; it is plotted as the series against row numbers` });
|
|
1741
|
+
series = [x];
|
|
1742
|
+
x = void 0;
|
|
1743
|
+
}
|
|
1744
|
+
const order = [category, ...x === void 0 ? [] : [x], ...series];
|
|
1745
|
+
if (!rows2.length) return fail("no-rows", "The chart data has no rows.");
|
|
1746
|
+
const lone = names.length === 1 && !diagnostics.some((entry) => entry.code === "dataset-field-unknown");
|
|
1747
|
+
const out = rows2.map((row, rowIndex) => order.map((column, position) => {
|
|
1748
|
+
const sourceIndex = selected ? selected[column] : column;
|
|
1749
|
+
const cell = Array.isArray(row) && sourceIndex < row.length ? row[sourceIndex] : null;
|
|
1750
|
+
if (position === 0 && !lone) return cell === void 0 ? null : cell;
|
|
1751
|
+
const number = chartNumber(cell);
|
|
1752
|
+
if (number === null && cell !== null && cell !== void 0 && cell !== "" && !numberVariable(document, cell)) {
|
|
1753
|
+
diagnostics.push({ code: "chart-value-not-numeric", severity: "warning", path: at(rowsPath, rowIndex, sourceIndex), message: `chart value ${JSON.stringify(cell)} is not a number; it is plotted as a gap. Write numbers as plain decimals (12, -3.5, 1e6) and put units, currency and percent in the column format` });
|
|
1754
|
+
}
|
|
1755
|
+
return number;
|
|
1756
|
+
}));
|
|
1757
|
+
return {
|
|
1758
|
+
ok: true,
|
|
1759
|
+
columns: order.map((index) => names[index]),
|
|
1760
|
+
hasX: x !== void 0,
|
|
1761
|
+
formats: order.map((index) => formats[index]),
|
|
1762
|
+
rows: out,
|
|
1763
|
+
...source ? { source } : {},
|
|
1764
|
+
...datasetId !== void 0 ? { dataset: datasetId } : {},
|
|
1765
|
+
diagnostics
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
var DISPLAY_NUMBER = /^(-?)([$€£¥]?)((?:[1-9]\d{0,2}(?:,\d{3})+)|0|[1-9]\d*)(?:\.(\d+))?(%?)$/;
|
|
1769
|
+
function chartNumberFixes(chart, document, options) {
|
|
1770
|
+
const fixes = /* @__PURE__ */ new Map();
|
|
1771
|
+
const resolved = resolveChartData(chart, document, options);
|
|
1772
|
+
if (!resolved.ok) return fixes;
|
|
1773
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
1774
|
+
for (const diagnostic of resolved.diagnostics) {
|
|
1775
|
+
if (diagnostic.code !== "chart-value-not-numeric") continue;
|
|
1776
|
+
candidates.add(Number(diagnostic.path.slice(diagnostic.path.lastIndexOf("/") + 1)));
|
|
1777
|
+
}
|
|
1778
|
+
if (!candidates.size) return fixes;
|
|
1779
|
+
const data = chart.data;
|
|
1780
|
+
let columns2;
|
|
1781
|
+
let rows2;
|
|
1782
|
+
let columnsPath;
|
|
1783
|
+
let rowsPath;
|
|
1784
|
+
if (isDatasetRef(data)) {
|
|
1785
|
+
const dataset = datasetsOf(document)[data.dataset];
|
|
1786
|
+
columns2 = dataset.columns;
|
|
1787
|
+
rows2 = dataset.rows;
|
|
1788
|
+
columnsPath = at("/datasets", data.dataset, "columns");
|
|
1789
|
+
rowsPath = at("/datasets", data.dataset, "rows");
|
|
1790
|
+
} else {
|
|
1791
|
+
columns2 = data.columns;
|
|
1792
|
+
rows2 = data.rows;
|
|
1793
|
+
columnsPath = at(options.path ?? "", "data", "columns");
|
|
1794
|
+
rowsPath = at(options.path ?? "", "data", "rows");
|
|
1795
|
+
}
|
|
1796
|
+
for (const column of [...candidates].sort((a, b) => a - b)) {
|
|
1797
|
+
const header = columns2[column];
|
|
1798
|
+
const name = columnName(header);
|
|
1799
|
+
if (name === void 0 || columnFormat(header) !== void 0) continue;
|
|
1800
|
+
const fix = columnFix(rows2, column, rowsPath);
|
|
1801
|
+
if (!fix) continue;
|
|
1802
|
+
fixes.set(column, {
|
|
1803
|
+
patches: [...fix.patches, { op: "replace", path: at(columnsPath, column), value: record3(header) ? { ...header, format: fix.format } : { name, format: fix.format } }],
|
|
1804
|
+
column,
|
|
1805
|
+
name,
|
|
1806
|
+
format: fix.format
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
return fixes;
|
|
1810
|
+
}
|
|
1811
|
+
function columnFix(rows2, column, rowsPath) {
|
|
1812
|
+
const cells = [];
|
|
1813
|
+
let prefix;
|
|
1814
|
+
let suffix;
|
|
1815
|
+
let grouping = false;
|
|
1816
|
+
let wide = false;
|
|
1817
|
+
const decimals = [];
|
|
1818
|
+
for (const [index, row] of rows2.entries()) {
|
|
1819
|
+
const cell = Array.isArray(row) && column < row.length ? row[column] : null;
|
|
1820
|
+
if (cell === null || cell === void 0 || cell === "") continue;
|
|
1821
|
+
const patch = chartNumber(cell) === null;
|
|
1822
|
+
if (typeof cell !== "number" && typeof cell !== "string") return void 0;
|
|
1823
|
+
const text2 = typeof cell === "number" ? String(cell) : cell.trim();
|
|
1824
|
+
const match = DISPLAY_NUMBER.exec(text2);
|
|
1825
|
+
if (!match) return void 0;
|
|
1826
|
+
const [, minus = "", currency = "", integer = "", fraction = "", percent = ""] = match;
|
|
1827
|
+
if ((prefix ?? currency) !== currency || (suffix ?? percent) !== percent) return void 0;
|
|
1828
|
+
prefix = currency;
|
|
1829
|
+
suffix = percent;
|
|
1830
|
+
if (integer.includes(",")) grouping = true;
|
|
1831
|
+
else if (integer.length > 3) wide = true;
|
|
1832
|
+
decimals.push(fraction);
|
|
1833
|
+
const digits = `${minus}${integer.replaceAll(",", "")}${fraction ? `.${fraction}` : ""}`;
|
|
1834
|
+
cells.push({ row: index, text: text2, value: Number(percent ? `${digits}e-2` : digits), patch });
|
|
1835
|
+
}
|
|
1836
|
+
if (!cells.some((cell) => cell.patch) || grouping && wide) return void 0;
|
|
1837
|
+
const fewest = Math.min(...decimals.map((fraction) => fraction.length));
|
|
1838
|
+
const most = Math.max(...decimals.map((fraction) => fraction.length));
|
|
1839
|
+
if (decimals.some((fraction) => fraction.length > fewest && fraction.endsWith("0"))) return void 0;
|
|
1840
|
+
const format = `${prefix ?? ""}${grouping ? "#,##0" : "0"}${most ? `.${"0".repeat(fewest)}${"#".repeat(most - fewest)}` : ""}${suffix ?? ""}`;
|
|
1841
|
+
if (cells.some((cell) => !Number.isFinite(cell.value) || formatDataNumber(cell.value, format) !== cell.text)) return void 0;
|
|
1842
|
+
return { format, patches: cells.filter((cell) => cell.patch).map((cell) => ({ op: "replace", path: at(rowsPath, cell.row, column), value: cell.value })) };
|
|
1843
|
+
}
|
|
1844
|
+
function suggestChartNumberFix(chart, document, options = {}) {
|
|
1845
|
+
const fixes = chartNumberFixes(chart, document, options);
|
|
1846
|
+
if (options.column === void 0) return fixes.values().next().value;
|
|
1847
|
+
for (const fix of fixes.values()) if (fix.column === options.column || fix.name === options.column) return fix;
|
|
1848
|
+
return void 0;
|
|
1849
|
+
}
|
|
1850
|
+
function chartNumberFixesByCell(chart, document, options) {
|
|
1851
|
+
const byCell = /* @__PURE__ */ new Map();
|
|
1852
|
+
for (const fix of chartNumberFixes(chart, document, options).values()) for (const patch of fix.patches.slice(0, -1)) byCell.set(patch.path, fix);
|
|
1853
|
+
return byCell;
|
|
1854
|
+
}
|
|
1855
|
+
function cellFormat(cell) {
|
|
1856
|
+
return record3(cell) && own(cell, "value") && typeof cell.format === "string" ? cell.format : void 0;
|
|
1857
|
+
}
|
|
1858
|
+
function resolveTableData(table, document, options = {}) {
|
|
1859
|
+
const base = options.path ?? "";
|
|
1860
|
+
const diagnostics = [];
|
|
1861
|
+
if (!record3(table)) return { rows: [], formats: [], diagnostics };
|
|
1862
|
+
if (isDatasetRef(table)) {
|
|
1863
|
+
const id = table.dataset;
|
|
1864
|
+
const datasets = datasetsOf(document);
|
|
1865
|
+
const dataset = own(datasets, id) ? datasets[id] : void 0;
|
|
1866
|
+
if (!record3(dataset) || !Array.isArray(dataset.columns) || !Array.isArray(dataset.rows)) {
|
|
1867
|
+
diagnostics.push({ code: "dataset-unknown", severity: "error", path: at(base, "dataset"), message: `unknown dataset '${id}'; add it to the top-level datasets map` });
|
|
1868
|
+
return { rows: [], formats: [], dataset: id, diagnostics };
|
|
1869
|
+
}
|
|
1870
|
+
const columnsPath = at("/datasets", id, "columns");
|
|
1871
|
+
formatDiagnostics(dataset.columns, columnsPath, diagnostics);
|
|
1872
|
+
const selection = selectFields(dataset.columns, table.fields, at(base, "fields"), columnsPath, diagnostics);
|
|
1873
|
+
const rows3 = dataset.rows.map((row) => selection.indices.map((index) => Array.isArray(row) && index < row.length ? row[index] : null));
|
|
1874
|
+
return { columns: selection.columns, rows: rows3, formats: selection.formats, dataset: id, diagnostics };
|
|
1875
|
+
}
|
|
1876
|
+
const columns2 = Array.isArray(table.columns) ? table.columns : void 0;
|
|
1877
|
+
const rows2 = Array.isArray(table.rows) ? table.rows.filter(Array.isArray) : [];
|
|
1878
|
+
const width = Math.max(columns2?.length ?? 0, ...rows2.map((row) => row.length), 0);
|
|
1879
|
+
const formats = Array.from({ length: width }, (_, index) => {
|
|
1880
|
+
const header = columns2?.[index];
|
|
1881
|
+
return columnFormat(header) ?? cellFormat(header);
|
|
1882
|
+
});
|
|
1883
|
+
const check = (cell, path) => {
|
|
1884
|
+
if (!record3(cell) || cell.format === void 0) return;
|
|
1885
|
+
const error = numberFormatError(cell.format);
|
|
1886
|
+
if (error) diagnostics.push({ code: "number-format-invalid", severity: "error", path: at(path, "format"), message: error });
|
|
1887
|
+
};
|
|
1888
|
+
columns2?.forEach((header, index) => {
|
|
1889
|
+
check(header, at(base, "columns", index));
|
|
1890
|
+
});
|
|
1891
|
+
if (Array.isArray(table.rows)) table.rows.forEach((row, rowIndex) => {
|
|
1892
|
+
if (Array.isArray(row)) row.forEach((cell, index) => {
|
|
1893
|
+
check(cell, at(base, "rows", rowIndex, index));
|
|
1894
|
+
});
|
|
1895
|
+
});
|
|
1896
|
+
return { ...columns2 ? { columns: columns2 } : {}, rows: rows2, formats, diagnostics };
|
|
1897
|
+
}
|
|
1898
|
+
function tableCellDisplayValue(cell, columnFormat2) {
|
|
1899
|
+
if (record3(cell) && own(cell, "value")) {
|
|
1900
|
+
const styled = cell;
|
|
1901
|
+
const format = typeof styled.format === "string" ? styled.format : columnFormat2;
|
|
1902
|
+
if (typeof styled.value !== "number" || format === void 0 || numberFormatError(format)) return cell;
|
|
1903
|
+
return { ...styled, value: formatDataNumber(styled.value, format) };
|
|
1904
|
+
}
|
|
1905
|
+
if (typeof cell !== "number" || columnFormat2 === void 0 || numberFormatError(columnFormat2)) return cell;
|
|
1906
|
+
return formatDataNumber(cell, columnFormat2);
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
// src/image-aspect.ts
|
|
1910
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1911
|
+
function sourceOf(value) {
|
|
1912
|
+
if (typeof value === "string") return value;
|
|
1913
|
+
return isRecord2(value) && typeof value.src === "string" ? value.src : void 0;
|
|
1914
|
+
}
|
|
1915
|
+
var PREFIX_BYTES = 64 * 1024;
|
|
1916
|
+
var JPEG_PREFIX_BYTES = 1024 * 1024;
|
|
1917
|
+
function dataBytes(uri, limit) {
|
|
1918
|
+
const match = /^data:([^;,]*)((?:;[^;,]*)*),/i.exec(uri);
|
|
1919
|
+
if (!match) return void 0;
|
|
1920
|
+
const type = (match[1] ?? "").toLowerCase(), start = match[0].length;
|
|
1921
|
+
try {
|
|
1922
|
+
if (/;base64/i.test(match[2] ?? "")) {
|
|
1923
|
+
const chars = Math.ceil(limit / 3) * 4, text2 = uri.slice(start, start + chars + chars / 8).replace(/\s+/g, "").slice(0, chars);
|
|
1924
|
+
return { type, bytes: Uint8Array.from(atob(text2.slice(0, text2.length - text2.length % 4)), (char) => char.charCodeAt(0)) };
|
|
1925
|
+
}
|
|
1926
|
+
const prefix = uri.slice(start, start + limit).replace(/%[0-9a-f]?$/i, "");
|
|
1927
|
+
return { type, bytes: new TextEncoder().encode(decodeURIComponent(prefix)) };
|
|
1928
|
+
} catch {
|
|
1929
|
+
return void 0;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
var at2 = (b, i) => b[i] ?? 0;
|
|
1933
|
+
var u16be = (b, i) => at2(b, i) << 8 | at2(b, i + 1);
|
|
1934
|
+
var u32be = (b, i) => (at2(b, i) << 24 | at2(b, i + 1) << 16 | at2(b, i + 2) << 8 | at2(b, i + 3)) >>> 0;
|
|
1935
|
+
var u16le = (b, i) => at2(b, i) | at2(b, i + 1) << 8;
|
|
1936
|
+
var u24le = (b, i) => at2(b, i) | at2(b, i + 1) << 8 | at2(b, i + 2) << 16;
|
|
1937
|
+
var ascii = (b, i, n) => String.fromCharCode(...b.subarray(i, i + n));
|
|
1938
|
+
function rasterDimensions(b) {
|
|
1939
|
+
if (b.length >= 24 && u32be(b, 0) === 2303741511 && ascii(b, 12, 4) === "IHDR") return { width: u32be(b, 16), height: u32be(b, 20) };
|
|
1940
|
+
if (b.length >= 10 && (ascii(b, 0, 6) === "GIF87a" || ascii(b, 0, 6) === "GIF89a")) return { width: u16le(b, 6), height: u16le(b, 8) };
|
|
1941
|
+
if (b.length >= 4 && b[0] === 255 && b[1] === 216) {
|
|
1942
|
+
let i = 2;
|
|
1943
|
+
while (i + 9 < b.length) {
|
|
1944
|
+
if (at2(b, i) !== 255) {
|
|
1945
|
+
i++;
|
|
1946
|
+
continue;
|
|
1947
|
+
}
|
|
1948
|
+
const marker = at2(b, i + 1);
|
|
1949
|
+
if (marker === 255) {
|
|
1950
|
+
i++;
|
|
1951
|
+
continue;
|
|
1952
|
+
}
|
|
1953
|
+
if (marker === 216 || marker === 1 || marker >= 208 && marker <= 215) {
|
|
1954
|
+
i += 2;
|
|
1955
|
+
continue;
|
|
1956
|
+
}
|
|
1957
|
+
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) return { height: u16be(b, i + 5), width: u16be(b, i + 7) };
|
|
1958
|
+
i += 2 + u16be(b, i + 2);
|
|
1959
|
+
}
|
|
1960
|
+
return void 0;
|
|
1961
|
+
}
|
|
1962
|
+
if (b.length >= 30 && ascii(b, 0, 4) === "RIFF" && ascii(b, 8, 4) === "WEBP") {
|
|
1963
|
+
const chunk = ascii(b, 12, 4);
|
|
1964
|
+
if (chunk === "VP8X") return { width: u24le(b, 24) + 1, height: u24le(b, 27) + 1 };
|
|
1965
|
+
if (chunk === "VP8L" && at2(b, 20) === 47) {
|
|
1966
|
+
const bits = u16le(b, 21) | u16le(b, 23) << 16;
|
|
1967
|
+
return { width: (bits & 16383) + 1, height: (bits >>> 14 & 16383) + 1 };
|
|
1968
|
+
}
|
|
1969
|
+
if (chunk === "VP8 ") return { width: u16le(b, 26) & 16383, height: u16le(b, 28) & 16383 };
|
|
1970
|
+
}
|
|
1971
|
+
return void 0;
|
|
1972
|
+
}
|
|
1973
|
+
function svgDimensions(text2) {
|
|
1974
|
+
const root = /<svg\b([^>]*)>/i.exec(text2)?.[1];
|
|
1975
|
+
if (root === void 0) return void 0;
|
|
1976
|
+
const attribute = (name) => new RegExp(`\\s${name}\\s*=\\s*["']([^"']*)["']`, "i").exec(root)?.[1];
|
|
1977
|
+
const length = (value) => {
|
|
1978
|
+
const match = value && /^\s*([0-9]*\.?[0-9]+)\s*(px|pt|in|cm|mm|em)?\s*$/i.exec(value);
|
|
1979
|
+
return match ? Number(match[1]) : void 0;
|
|
1980
|
+
};
|
|
1981
|
+
const width = length(attribute("width")), height = length(attribute("height"));
|
|
1982
|
+
if (width && height) return { width, height };
|
|
1983
|
+
const box = attribute("viewBox")?.trim().split(/[\s,]+/).map(Number);
|
|
1984
|
+
if (box && box.length === 4 && box.every(Number.isFinite)) {
|
|
1985
|
+
const [, , width2 = 0, height2 = 0] = box;
|
|
1986
|
+
if (width2 > 0 && height2 > 0) return { width: width2, height: height2 };
|
|
1987
|
+
}
|
|
1988
|
+
return void 0;
|
|
1989
|
+
}
|
|
1990
|
+
var cache = /* @__PURE__ */ new Map();
|
|
1991
|
+
var CACHE_ENTRIES = 64;
|
|
1992
|
+
function readDimensions(source) {
|
|
1993
|
+
const first = dataBytes(source, PREFIX_BYTES);
|
|
1994
|
+
if (!first) return void 0;
|
|
1995
|
+
let dimensions;
|
|
1996
|
+
if (first.type.startsWith("image/svg")) dimensions = svgDimensions(new TextDecoder().decode(first.bytes));
|
|
1997
|
+
else {
|
|
1998
|
+
dimensions = rasterDimensions(first.bytes);
|
|
1999
|
+
if (!dimensions && first.bytes.length >= 2 && first.bytes[0] === 255 && first.bytes[1] === 216) {
|
|
2000
|
+
const more = dataBytes(source, JPEG_PREFIX_BYTES);
|
|
2001
|
+
if (more) dimensions = rasterDimensions(more.bytes);
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
return dimensions && dimensions.width > 0 && dimensions.height > 0 ? dimensions : void 0;
|
|
2005
|
+
}
|
|
2006
|
+
function intrinsicImageSize(value, assets) {
|
|
2007
|
+
let source = sourceOf(value);
|
|
2008
|
+
for (let hop = 0; source?.startsWith("asset:") && hop < 8; hop++) source = sourceOf(isRecord2(assets) ? assets[source.slice(6)] : void 0);
|
|
2009
|
+
if (!source?.startsWith("data:")) return void 0;
|
|
2010
|
+
if (!cache.has(source)) {
|
|
2011
|
+
if (cache.size >= CACHE_ENTRIES) cache.delete(cache.keys().next().value);
|
|
2012
|
+
cache.set(source, readDimensions(source));
|
|
2013
|
+
}
|
|
2014
|
+
const size = cache.get(source);
|
|
2015
|
+
return size ? { ...size, svg: /^data:image\/svg/i.test(source) } : void 0;
|
|
2016
|
+
}
|
|
2017
|
+
function intrinsicImageAspect(value, assets) {
|
|
2018
|
+
const size = intrinsicImageSize(value, assets);
|
|
2019
|
+
return size ? size.width / size.height : void 0;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
// src/reading-order.ts
|
|
2023
|
+
function visualReadingOrder(items, direction = "ltr") {
|
|
2024
|
+
const index = new Map(items.map((item, i) => [item, i]));
|
|
2025
|
+
const along = direction === "rtl" ? (item) => -(item.box.x + item.box.width) : (item) => item.box.x;
|
|
2026
|
+
return readingRows(items).flatMap((row) => [...row].sort((a, b) => along(a) - along(b) || a.box.y - b.box.y || index.get(a) - index.get(b)));
|
|
2027
|
+
}
|
|
2028
|
+
function readingRows(items) {
|
|
2029
|
+
const index = new Map(items.map((item, i) => [item, i]));
|
|
2030
|
+
const sorted = [...items].sort((a, b) => a.box.y - b.box.y || a.box.x - b.box.x || index.get(a) - index.get(b));
|
|
2031
|
+
const rows2 = [];
|
|
2032
|
+
for (const item of sorted) {
|
|
2033
|
+
const centre = item.box.y + item.box.height / 2;
|
|
2034
|
+
const row = rows2.find((candidate) => centre >= candidate.top && centre <= candidate.bottom);
|
|
2035
|
+
if (row) {
|
|
2036
|
+
row.items.push(item);
|
|
2037
|
+
row.top = Math.min(row.top, item.box.y);
|
|
2038
|
+
row.bottom = Math.max(row.bottom, item.box.y + item.box.height);
|
|
2039
|
+
} else rows2.push({ top: item.box.y, bottom: item.box.y + item.box.height, items: [item] });
|
|
2040
|
+
}
|
|
2041
|
+
rows2.sort((a, b) => a.top - b.top);
|
|
2042
|
+
return rows2.map((row) => row.items.sort((a, b) => index.get(a) - index.get(b)));
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
// src/direction.ts
|
|
2046
|
+
var strongRtl = /[\u200F\u061C]|(?=\p{L})[\p{Script=Arabic}\p{Script=Hebrew}\p{Script=Syriac}\p{Script=Thaana}\p{Script=Nko}\p{Script=Adlam}\p{Script=Hanifi_Rohingya}\p{Script=Mandaic}\p{Script=Samaritan}\p{Script=Mende_Kikakui}\p{Script=Imperial_Aramaic}\p{Script=Phoenician}\p{Script=Kharoshthi}\p{Script=Old_South_Arabian}\p{Script=Old_North_Arabian}\p{Script=Avestan}\p{Script=Inscriptional_Parthian}\p{Script=Inscriptional_Pahlavi}\p{Script=Psalter_Pahlavi}\p{Script=Old_Turkic}\p{Script=Old_Hungarian}\p{Script=Nabataean}\p{Script=Palmyrene}\p{Script=Hatran}\p{Script=Manichaean}\p{Script=Sogdian}\p{Script=Old_Sogdian}\p{Script=Elymaic}\p{Script=Chorasmian}\p{Script=Yezidi}\p{Script=Cypriot}\p{Script=Lydian}\p{Script=Meroitic_Cursive}\p{Script=Meroitic_Hieroglyphs}\u{10F70}-\u{10FAF}\u{10D40}-\u{10D8F}]/u;
|
|
2047
|
+
var strongLtr = /[\u200E\p{L}\p{Nl}]/u;
|
|
2048
|
+
function paragraphDirection(text2, deckDirection) {
|
|
2049
|
+
if (deckDirection !== "rtl") return "ltr";
|
|
2050
|
+
let isolates = 0;
|
|
2051
|
+
for (const char of String(text2 ?? "")) {
|
|
2052
|
+
if (char === "\u2066" || char === "\u2067" || char === "\u2068") isolates += 1;
|
|
2053
|
+
else if (char === "\u2069") isolates = Math.max(0, isolates - 1);
|
|
2054
|
+
else if (isolates === 0) {
|
|
2055
|
+
if (strongRtl.test(char)) return "rtl";
|
|
2056
|
+
if (strongLtr.test(char)) return "ltr";
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
return "rtl";
|
|
2060
|
+
}
|
|
2061
|
+
function physicalAlignment(alignment, direction) {
|
|
2062
|
+
const value = alignment ?? "left";
|
|
2063
|
+
if (direction !== "rtl" || value === "center") return value;
|
|
2064
|
+
return value === "left" ? "right" : "left";
|
|
2065
|
+
}
|
|
2066
|
+
function paragraphDirectionAt(text2, deckDirection) {
|
|
2067
|
+
if (deckDirection !== "rtl") return () => "ltr";
|
|
2068
|
+
const source = String(text2 ?? ""), starts = [], directions = [];
|
|
2069
|
+
let start = 0;
|
|
2070
|
+
const close = (end) => {
|
|
2071
|
+
starts.push(start);
|
|
2072
|
+
directions.push(paragraphDirection(source.slice(start, end), "rtl"));
|
|
2073
|
+
};
|
|
2074
|
+
for (const match of source.matchAll(/\r\n|\r|\n/g)) {
|
|
2075
|
+
close(match.index);
|
|
2076
|
+
start = match.index + match[0].length;
|
|
2077
|
+
}
|
|
2078
|
+
close(source.length);
|
|
2079
|
+
return (offset) => {
|
|
2080
|
+
for (let index = starts.length - 1; index >= 0; index--) if (offset >= starts[index]) return directions[index];
|
|
2081
|
+
return directions[0];
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
// src/script-fonts.ts
|
|
2086
|
+
var eastAsianScripts = /* @__PURE__ */ new Set(["Hani", "Hans", "Hant", "Hanb", "Jpan", "Kore", "Hang", "Jamo", "Hira", "Kana", "Hrkt", "Bopo", "Yiii"]);
|
|
2087
|
+
var complexScripts = /* @__PURE__ */ new Set([
|
|
2088
|
+
"Arab",
|
|
2089
|
+
"Hebr",
|
|
2090
|
+
"Syrc",
|
|
2091
|
+
"Thaa",
|
|
2092
|
+
"Nkoo",
|
|
2093
|
+
"Adlm",
|
|
2094
|
+
"Rohg",
|
|
2095
|
+
"Mand",
|
|
2096
|
+
"Samr",
|
|
2097
|
+
"Deva",
|
|
2098
|
+
"Beng",
|
|
2099
|
+
"Guru",
|
|
2100
|
+
"Gujr",
|
|
2101
|
+
"Orya",
|
|
2102
|
+
"Taml",
|
|
2103
|
+
"Telu",
|
|
2104
|
+
"Knda",
|
|
2105
|
+
"Mlym",
|
|
2106
|
+
"Sinh",
|
|
2107
|
+
"Thai",
|
|
2108
|
+
"Laoo",
|
|
2109
|
+
"Tibt",
|
|
2110
|
+
"Mymr",
|
|
2111
|
+
"Khmr",
|
|
2112
|
+
"Mong",
|
|
2113
|
+
"Bali",
|
|
2114
|
+
"Java",
|
|
2115
|
+
"Lana",
|
|
2116
|
+
"Tale",
|
|
2117
|
+
"Talu",
|
|
2118
|
+
"Cakm",
|
|
2119
|
+
"Olck"
|
|
2120
|
+
]);
|
|
2121
|
+
var rtlScripts = /* @__PURE__ */ new Set(["Arab", "Hebr", "Syrc", "Thaa", "Nkoo", "Adlm", "Rohg", "Mand", "Samr"]);
|
|
2122
|
+
var latinSlotScripts = /* @__PURE__ */ new Set(["Latn", "Cyrl", "Grek", "Zyyy", "Zzzz"]);
|
|
2123
|
+
var deprecatedLanguages = { iw: "he", in: "id", ji: "yi" };
|
|
2124
|
+
var matchingLanguages = { ...deprecatedLanguages, no: "nb", zsm: "ms", ku: "kmr" };
|
|
2125
|
+
var likelyScripts = {
|
|
2126
|
+
zh: { script: "Hans", regions: { TW: "Hant", HK: "Hant", MO: "Hant" } },
|
|
2127
|
+
sr: { script: "Cyrl", regions: { ME: "Latn" } },
|
|
2128
|
+
pa: { script: "Guru", regions: { PK: "Arab" } },
|
|
2129
|
+
az: { script: "Latn", regions: { IR: "Arab" } },
|
|
2130
|
+
uz: { script: "Latn", regions: { AF: "Arab" } },
|
|
2131
|
+
bs: { script: "Latn" },
|
|
2132
|
+
mn: { script: "Cyrl", regions: { CN: "Mong" } },
|
|
2133
|
+
ms: { script: "Latn" }
|
|
1719
2134
|
};
|
|
2135
|
+
var bareLanguageId = /^[a-z][a-z0-9-]*$/;
|
|
2136
|
+
function scriptFontRole(script) {
|
|
2137
|
+
const code = normalizeScript(script);
|
|
2138
|
+
if (code && eastAsianScripts.has(code)) return "eastAsian";
|
|
2139
|
+
if (code && complexScripts.has(code)) return "complexScript";
|
|
2140
|
+
return "latin";
|
|
2141
|
+
}
|
|
2142
|
+
function normalizeScript(value) {
|
|
2143
|
+
if (typeof value !== "string" || !/^[A-Za-z]{4}$/.test(value)) return void 0;
|
|
2144
|
+
return value.slice(0, 1).toUpperCase() + value.slice(1).toLowerCase();
|
|
2145
|
+
}
|
|
2146
|
+
function text(value) {
|
|
2147
|
+
return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
|
|
2148
|
+
}
|
|
2149
|
+
var tagSyntax = /^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/;
|
|
2150
|
+
function splitTag(tag) {
|
|
2151
|
+
if (!tagSyntax.test(tag)) return void 0;
|
|
2152
|
+
const [first = "", ...rest] = tag.split("-");
|
|
2153
|
+
const language = first.toLowerCase();
|
|
2154
|
+
if (language === "und") return void 0;
|
|
2155
|
+
let script;
|
|
2156
|
+
let region;
|
|
2157
|
+
for (const subtag of rest) {
|
|
2158
|
+
if (subtag.length === 1) break;
|
|
2159
|
+
if (!script && !region && /^[A-Za-z]{4}$/.test(subtag)) script = normalizeScript(subtag);
|
|
2160
|
+
else if (!region && /^(?:[A-Za-z]{2}|\d{3})$/.test(subtag)) region = subtag.toUpperCase();
|
|
2161
|
+
}
|
|
2162
|
+
return { language, script, region };
|
|
2163
|
+
}
|
|
2164
|
+
function canonicalTag(tag) {
|
|
2165
|
+
let extension = false;
|
|
2166
|
+
return tag.split("-").map((subtag, index) => {
|
|
2167
|
+
const lower = subtag.toLowerCase();
|
|
2168
|
+
if (index === 0) return deprecatedLanguages[lower] ?? lower;
|
|
2169
|
+
if (extension) return lower;
|
|
2170
|
+
if (subtag.length === 1) {
|
|
2171
|
+
extension = true;
|
|
2172
|
+
return lower;
|
|
2173
|
+
}
|
|
2174
|
+
if (/^[A-Za-z]{4}$/.test(subtag)) return normalizeScript(subtag);
|
|
2175
|
+
if (/^[A-Za-z]{2}$/.test(subtag)) return subtag.toUpperCase();
|
|
2176
|
+
return lower;
|
|
2177
|
+
}).join("-");
|
|
2178
|
+
}
|
|
2179
|
+
function vendoredScript(language, region) {
|
|
2180
|
+
const entry = likelyScripts[language];
|
|
2181
|
+
return entry ? (region && entry.regions?.[region]) ?? entry.script : void 0;
|
|
2182
|
+
}
|
|
2183
|
+
function inferScript(tag) {
|
|
2184
|
+
const parts = splitTag(tag);
|
|
2185
|
+
if (!parts) return void 0;
|
|
2186
|
+
const known = parts.script ?? vendoredScript(matchingLanguages[parts.language] ?? parts.language, parts.region);
|
|
2187
|
+
if (known) return known;
|
|
2188
|
+
try {
|
|
2189
|
+
return normalizeScript(new Intl.Locale(tag).maximize().script);
|
|
2190
|
+
} catch {
|
|
2191
|
+
return void 0;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
function inlineRecords(document, kind2) {
|
|
2195
|
+
const kinds = isRecord(document.catalogs) ? document.catalogs : {};
|
|
2196
|
+
const entry = kinds[kind2];
|
|
2197
|
+
return isRecord(entry) && Array.isArray(entry.records) ? entry.records.filter(isRecord) : [];
|
|
2198
|
+
}
|
|
2199
|
+
function allRecords(document, kind2) {
|
|
2200
|
+
return [...inlineRecords(document, kind2), ...catalogs[kind2]];
|
|
2201
|
+
}
|
|
2202
|
+
function resolveReference(lookup, kind2, reference) {
|
|
2203
|
+
if (typeof reference === "string") return lookup(kind2, reference);
|
|
2204
|
+
if (!isRecord(reference)) return void 0;
|
|
2205
|
+
const base = typeof reference.id === "string" ? lookup(kind2, reference.id) : void 0;
|
|
2206
|
+
return { ...base, ...reference };
|
|
2207
|
+
}
|
|
2208
|
+
function matchLanguageTag(document, tag) {
|
|
2209
|
+
const wanted = splitTag(tag);
|
|
2210
|
+
if (!wanted) return void 0;
|
|
2211
|
+
const records = allRecords(document, "languages").filter((record6) => typeof record6.bcp47 === "string");
|
|
2212
|
+
const key = canonicalTag(tag).toLowerCase();
|
|
2213
|
+
const exact = records.find((record6) => canonicalTag(record6.bcp47).toLowerCase() === key);
|
|
2214
|
+
if (exact) return exact;
|
|
2215
|
+
const language = matchingLanguages[wanted.language] ?? wanted.language;
|
|
2216
|
+
const script = wanted.script ?? vendoredScript(language, wanted.region);
|
|
2217
|
+
const candidates = records.flatMap((record6) => {
|
|
2218
|
+
const parts = splitTag(record6.bcp47);
|
|
2219
|
+
if (!parts || (matchingLanguages[parts.language] ?? parts.language) !== language) return [];
|
|
2220
|
+
const recordScript = normalizeScript(record6.script) ?? parts.script ?? vendoredScript(language, parts.region);
|
|
2221
|
+
if (script && recordScript && script !== recordScript) return [];
|
|
2222
|
+
return [{ record: record6, region: parts.region }];
|
|
2223
|
+
});
|
|
2224
|
+
return candidates.find((candidate) => wanted.region && candidate.region === wanted.region)?.record ?? candidates.find((candidate) => !candidate.region)?.record ?? candidates[0]?.record;
|
|
2225
|
+
}
|
|
2226
|
+
function resolveLanguage(document, lookup, reference) {
|
|
2227
|
+
if (typeof reference === "string") {
|
|
2228
|
+
const value = reference.trim();
|
|
2229
|
+
const byId = bareLanguageId.test(value) ? lookup("languages", value) : void 0;
|
|
2230
|
+
if (byId) return { record: byId, languageId: value };
|
|
2231
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(value) || !splitTag(value)) return void 0;
|
|
2232
|
+
const matched = matchLanguageTag(document, value);
|
|
2233
|
+
if (matched) return { record: matched, authoredTag: value, languageId: text(matched.id) };
|
|
2234
|
+
return inferScript(value) ? { record: {}, authoredTag: value } : void 0;
|
|
2235
|
+
}
|
|
2236
|
+
if (!isRecord(reference)) return void 0;
|
|
2237
|
+
const authoredTag = text(reference.bcp47);
|
|
2238
|
+
if (authoredTag !== void 0 && !splitTag(authoredTag)) return void 0;
|
|
2239
|
+
const base = typeof reference.id === "string" ? lookup("languages", reference.id) : authoredTag ? matchLanguageTag(document, authoredTag) : void 0;
|
|
2240
|
+
const record6 = { ...base, ...reference };
|
|
2241
|
+
const tag = text(record6.bcp47);
|
|
2242
|
+
if (!tag || !splitTag(tag)) return void 0;
|
|
2243
|
+
return { record: record6, authoredTag, authoredOoxmlLang: text(reference.ooxmlLang), languageId: text(base?.id) };
|
|
2244
|
+
}
|
|
2245
|
+
function pairFamilies(value) {
|
|
2246
|
+
if (!isRecord(value)) return void 0;
|
|
2247
|
+
const major = text(value.major);
|
|
2248
|
+
const minor = text(value.minor);
|
|
2249
|
+
const heading = major ?? minor;
|
|
2250
|
+
const body = minor ?? major;
|
|
2251
|
+
return heading && body ? { heading, body } : void 0;
|
|
2252
|
+
}
|
|
2253
|
+
function schemeServesLanguage(scheme, language) {
|
|
2254
|
+
const entries = Array.isArray(scheme.languages) ? scheme.languages.filter((entry) => typeof entry === "string") : [];
|
|
2255
|
+
if (entries.length === 0) return true;
|
|
2256
|
+
const name = text(language.name)?.toLowerCase();
|
|
2257
|
+
if (!name) return false;
|
|
2258
|
+
const base = name.replace(/\s*\([^)]*\)\s*$/, "");
|
|
2259
|
+
return entries.some((entry) => {
|
|
2260
|
+
const value = entry.trim().toLowerCase();
|
|
2261
|
+
return value === name || value === base;
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
function resolveScriptFonts(input, options = {}) {
|
|
2265
|
+
const document = isRecord(input) ? input : {};
|
|
2266
|
+
const lookup = (kind2, id) => inlineRecords(document, kind2).find((record7) => record7.id === id) ?? catalogs[kind2].find((record7) => record7.id === id);
|
|
2267
|
+
let design = isRecord(document.design) ? document.design : {};
|
|
2268
|
+
if (options.slideIndex !== void 0) {
|
|
2269
|
+
const slides = Array.isArray(document.slides) ? document.slides : [];
|
|
2270
|
+
if (!Number.isInteger(options.slideIndex) || options.slideIndex < 0 || options.slideIndex >= slides.length) {
|
|
2271
|
+
throw new RangeError(`slideIndex must be an integer between 0 and ${slides.length - 1}.`);
|
|
2272
|
+
}
|
|
2273
|
+
const slide = slides[options.slideIndex];
|
|
2274
|
+
if (isRecord(slide) && isRecord(slide.design)) design = { ...design, ...slide.design };
|
|
2275
|
+
}
|
|
2276
|
+
const theme = resolveReference(lookup, "themes", design.theme ?? "minimal") ?? {};
|
|
2277
|
+
const scheme = resolveFontSchemeReference(design.fontScheme ?? theme.fontScheme ?? DEFAULT_FONT_SCHEME, (id) => lookup("fontSchemes", id)).scheme;
|
|
2278
|
+
const latin = resolveFontFamilies(scheme);
|
|
2279
|
+
const fromOption = options.language !== void 0 ? resolveLanguage(document, lookup, options.language) : void 0;
|
|
2280
|
+
const fromDocument = fromOption ? void 0 : resolveLanguage(document, lookup, document.language);
|
|
2281
|
+
const requestedDefault = text(options.defaultLanguage);
|
|
2282
|
+
const fallbackTag = requestedDefault && splitTag(requestedDefault) ? requestedDefault : "en-US";
|
|
2283
|
+
const language = fromOption ?? fromDocument ?? resolveLanguage(document, lookup, fallbackTag) ?? { record: {}, authoredTag: fallbackTag };
|
|
2284
|
+
const languageSource = fromOption ? "option" : fromDocument ? "document" : "default";
|
|
2285
|
+
const record6 = language.record;
|
|
2286
|
+
const bcp47 = canonicalTag(language.authoredTag ?? text(record6.bcp47) ?? fallbackTag);
|
|
2287
|
+
const curated = text(record6.ooxmlLang);
|
|
2288
|
+
const lang = language.authoredOoxmlLang ? canonicalTag(language.authoredOoxmlLang) : language.authoredTag && splitTag(language.authoredTag)?.region ? canonicalTag(language.authoredTag) : curated ? canonicalTag(curated) : bcp47;
|
|
2289
|
+
const parts = splitTag(bcp47);
|
|
2290
|
+
const script = normalizeScript(record6.script) ?? (language.languageId ? parts?.script ?? (parts ? vendoredScript(matchingLanguages[parts.language] ?? parts.language, parts.region) : void 0) ?? normalizeScript(matchLanguageTag({}, bcp47)?.script) : inferScript(bcp47)) ?? "Zzzz";
|
|
2291
|
+
const scriptRole = scriptFontRole(script);
|
|
2292
|
+
const declaredDirection = record6.direction;
|
|
2293
|
+
const direction = declaredDirection === "rtl" || declaredDirection === "ltr" ? declaredDirection : rtlScripts.has(script) ? "rtl" : "ltr";
|
|
2294
|
+
const app = options.app ?? "PowerPoint";
|
|
2295
|
+
const preferred = app === "Google Slides" ? record6.googleFontScheme : record6.fontScheme;
|
|
2296
|
+
const alternate = app === "Google Slides" ? record6.fontScheme : record6.googleFontScheme;
|
|
2297
|
+
const languageScheme = resolveReference(lookup, "fontSchemes", preferred) ?? resolveReference(lookup, "fontSchemes", alternate);
|
|
2298
|
+
const languageFamilies = pairFamilies(languageScheme);
|
|
2299
|
+
const schemeAdmitsLanguage = schemeServesLanguage(scheme, record6);
|
|
2300
|
+
const slot = (role, family) => {
|
|
2301
|
+
const explicit = pairFamilies(scheme[role]);
|
|
2302
|
+
if (explicit) return { ...explicit, source: "fontScheme" };
|
|
2303
|
+
if (scheme.languageFamily === family && schemeAdmitsLanguage) return { ...latin, source: "schemeFamily" };
|
|
2304
|
+
if (scriptRole === role && languageFamilies) return { ...languageFamilies, source: "language" };
|
|
2305
|
+
return { heading: latin.heading, body: latin.body, source: "latin" };
|
|
2306
|
+
};
|
|
2307
|
+
const eastAsian = slot("eastAsian", "ea");
|
|
2308
|
+
const complexScript = slot("complexScript", "cs");
|
|
2309
|
+
const heading = { latin: latin.heading, eastAsian: eastAsian.heading, complexScript: complexScript.heading };
|
|
2310
|
+
const body = { latin: latin.body, eastAsian: eastAsian.body, complexScript: complexScript.body };
|
|
2311
|
+
let supplement;
|
|
2312
|
+
if (!latinSlotScripts.has(script)) {
|
|
2313
|
+
const chosen = complexScript.source === "fontScheme" || complexScript.source === "schemeFamily" ? complexScript : void 0;
|
|
2314
|
+
const families = scriptRole === "latin" ? chosen ? { heading: chosen.heading, body: chosen.body } : languageFamilies : (scriptRole === "eastAsian" ? eastAsian : complexScript).source === "latin" ? void 0 : { heading: heading[scriptRole], body: body[scriptRole] };
|
|
2315
|
+
if (families) supplement = { script: script === "Kore" ? "Hang" : script, heading: families.heading, body: families.body };
|
|
2316
|
+
}
|
|
2317
|
+
return {
|
|
2318
|
+
...body,
|
|
2319
|
+
lang,
|
|
2320
|
+
bcp47,
|
|
2321
|
+
...language.languageId ? { languageId: language.languageId } : {},
|
|
2322
|
+
languageSource,
|
|
2323
|
+
script,
|
|
2324
|
+
scriptRole,
|
|
2325
|
+
direction,
|
|
2326
|
+
rtl: direction === "rtl",
|
|
2327
|
+
heading,
|
|
2328
|
+
body,
|
|
2329
|
+
...supplement ? { supplement } : {},
|
|
2330
|
+
sources: { eastAsian: eastAsian.source, complexScript: complexScript.source }
|
|
2331
|
+
};
|
|
2332
|
+
}
|
|
2333
|
+
function resolveSlideDirection(presentation, slideIndex) {
|
|
2334
|
+
if (!isRecord(presentation) || presentation.language === void 0) return "ltr";
|
|
2335
|
+
try {
|
|
2336
|
+
return resolveScriptFonts(presentation, Number.isInteger(slideIndex) ? { slideIndex } : {}).direction;
|
|
2337
|
+
} catch {
|
|
2338
|
+
try {
|
|
2339
|
+
return resolveScriptFonts(presentation).direction;
|
|
2340
|
+
} catch {
|
|
2341
|
+
return "ltr";
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
// src/numbering.ts
|
|
2347
|
+
var NUMBERING_STYLES = ["arabic", "roman-upper", "roman-lower", "alpha-upper", "alpha-lower"];
|
|
2348
|
+
var NUMBERING_SUFFIXES = ["period", "paren", "paren-both"];
|
|
2349
|
+
var MAX_NUMBERING_VALUE = 32767;
|
|
2350
|
+
var MAX_ROMAN_VALUE = 3999;
|
|
2351
|
+
var MAX_NUMBERING_LEVELS = 9;
|
|
2352
|
+
var styleSet = new Set(NUMBERING_STYLES);
|
|
2353
|
+
var suffixSet = new Set(NUMBERING_SUFFIXES);
|
|
2354
|
+
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2355
|
+
function resolveEntry(entry, label) {
|
|
2356
|
+
if (typeof entry === "string") {
|
|
2357
|
+
if (!styleSet.has(entry)) throw new RangeError(`${label} must be one of ${NUMBERING_STYLES.join(", ")}.`);
|
|
2358
|
+
return { style: entry, start: 1, suffix: "period" };
|
|
2359
|
+
}
|
|
2360
|
+
if (!isObject(entry)) throw new TypeError(`${label} must be a style name or a numbering object.`);
|
|
2361
|
+
const { style = "arabic", start = 1, suffix = "period" } = entry;
|
|
2362
|
+
if (typeof style !== "string" || !styleSet.has(style)) throw new RangeError(`${label}.style must be one of ${NUMBERING_STYLES.join(", ")}.`);
|
|
2363
|
+
if (typeof suffix !== "string" || !suffixSet.has(suffix)) throw new RangeError(`${label}.suffix must be one of ${NUMBERING_SUFFIXES.join(", ")}.`);
|
|
2364
|
+
if (typeof start !== "number" || !Number.isInteger(start) || start < 1 || start > MAX_NUMBERING_VALUE) throw new RangeError(`${label}.start must be an integer from 1 to ${MAX_NUMBERING_VALUE}.`);
|
|
2365
|
+
return { style, start, suffix };
|
|
2366
|
+
}
|
|
2367
|
+
function resolveNumbering(input) {
|
|
2368
|
+
if (Array.isArray(input)) {
|
|
2369
|
+
if (!input.length || input.length > MAX_NUMBERING_LEVELS) throw new RangeError(`numbering must list from 1 to ${MAX_NUMBERING_LEVELS} levels.`);
|
|
2370
|
+
return input.map((entry, index) => resolveEntry(entry, `numbering[${index}]`));
|
|
2371
|
+
}
|
|
2372
|
+
return [resolveEntry(input, "numbering")];
|
|
2373
|
+
}
|
|
2374
|
+
function numberingAtLevel(levels, level) {
|
|
2375
|
+
return levels[Math.min(Math.max(0, level), levels.length - 1)];
|
|
2376
|
+
}
|
|
2377
|
+
var ROMAN = [[1e3, "m"], [900, "cm"], [500, "d"], [400, "cd"], [100, "c"], [90, "xc"], [50, "l"], [40, "xl"], [10, "x"], [9, "ix"], [5, "v"], [4, "iv"], [1, "i"]];
|
|
2378
|
+
function bareNumber(value, style) {
|
|
2379
|
+
if (style === "arabic") return String(value);
|
|
2380
|
+
if (style === "roman-upper" || style === "roman-lower") {
|
|
2381
|
+
if (value > MAX_ROMAN_VALUE) return null;
|
|
2382
|
+
let rest = value, out = "";
|
|
2383
|
+
for (const [amount, glyphs] of ROMAN) while (rest >= amount) {
|
|
2384
|
+
out += glyphs;
|
|
2385
|
+
rest -= amount;
|
|
2386
|
+
}
|
|
2387
|
+
return style === "roman-upper" ? out.toUpperCase() : out;
|
|
2388
|
+
}
|
|
2389
|
+
const letter = String.fromCharCode(97 + (value - 1) % 26).repeat(Math.floor((value - 1) / 26) + 1);
|
|
2390
|
+
return style === "alpha-upper" ? letter.toUpperCase() : letter;
|
|
2391
|
+
}
|
|
2392
|
+
function numberingStyleDraws(style, value) {
|
|
2393
|
+
return bareNumber(value, style) !== null;
|
|
2394
|
+
}
|
|
2395
|
+
function formatListNumber(value, style = "arabic", suffix = "period") {
|
|
2396
|
+
if (!Number.isInteger(value) || value < 1) throw new RangeError("A list number must be an integer of at least 1.");
|
|
2397
|
+
if (!styleSet.has(style)) throw new RangeError(`Unknown numbering style '${String(style)}'.`);
|
|
2398
|
+
if (!suffixSet.has(suffix)) throw new RangeError(`Unknown numbering suffix '${String(suffix)}'.`);
|
|
2399
|
+
const bare = bareNumber(value, style) ?? String(value);
|
|
2400
|
+
return suffix === "period" ? `${bare}.` : suffix === "paren" ? `${bare})` : `(${bare})`;
|
|
2401
|
+
}
|
|
2402
|
+
function listNumbers(items, numbering) {
|
|
2403
|
+
const levels = resolveNumbering(numbering);
|
|
2404
|
+
const counters = [];
|
|
2405
|
+
return items.map((item, index) => {
|
|
2406
|
+
const object = isObject(item) ? item : void 0;
|
|
2407
|
+
const level = object?.level === void 0 ? 0 : object.level;
|
|
2408
|
+
if (typeof level !== "number" || !Number.isInteger(level) || level < 0) throw new RangeError("List levels must be nonnegative integers.");
|
|
2409
|
+
const own2 = object?.start;
|
|
2410
|
+
if (own2 !== void 0 && (typeof own2 !== "number" || !Number.isInteger(own2) || own2 < 1 || own2 > MAX_NUMBERING_VALUE)) throw new RangeError(`A list item start must be an integer from 1 to ${MAX_NUMBERING_VALUE}.`);
|
|
2411
|
+
const { style: authored, start, suffix } = numberingAtLevel(levels, level);
|
|
2412
|
+
const previous = counters[level];
|
|
2413
|
+
const value = own2 ?? (previous === void 0 ? start : previous + 1);
|
|
2414
|
+
counters[level] = value;
|
|
2415
|
+
counters.length = level + 1;
|
|
2416
|
+
const adapted = !numberingStyleDraws(authored, value);
|
|
2417
|
+
const style = adapted ? "arabic" : authored;
|
|
2418
|
+
return { index, level, value, style, suffix, text: formatListNumber(value, style, suffix), ...adapted ? { adapted: "roman-range" } : {} };
|
|
2419
|
+
});
|
|
2420
|
+
}
|
|
2421
|
+
function sliceNumberedItems(items, numbering, from, to = items.length) {
|
|
2422
|
+
const whole = listNumbers(items, numbering);
|
|
2423
|
+
const result = items.slice(from, to);
|
|
2424
|
+
for (let index = 0; index < result.length; index++) {
|
|
2425
|
+
const wanted = whole[from + index].value;
|
|
2426
|
+
if (listNumbers(result.slice(0, index + 1), numbering)[index].value === wanted) continue;
|
|
2427
|
+
const item = result[index];
|
|
2428
|
+
const object = isObject(item) ? item : { text: item };
|
|
2429
|
+
result[index] = { ...object, start: wanted };
|
|
2430
|
+
}
|
|
2431
|
+
return result;
|
|
2432
|
+
}
|
|
2433
|
+
function numberingFindings(payload) {
|
|
2434
|
+
const errors = [], warnings = [];
|
|
2435
|
+
const lists = ["items", "bullets"].filter((key) => Array.isArray(payload[key]));
|
|
2436
|
+
if (Object.hasOwn(payload, "numbering")) {
|
|
2437
|
+
if (!Object.hasOwn(payload, "items") && !Object.hasOwn(payload, "bullets")) {
|
|
2438
|
+
errors.push({ key: "numbering", message: "numbering applies to 'items' or 'bullets'; this payload has neither", params: {} });
|
|
2439
|
+
} else {
|
|
2440
|
+
for (const key of lists) {
|
|
2441
|
+
let numbers;
|
|
2442
|
+
try {
|
|
2443
|
+
numbers = listNumbers(payload[key], payload.numbering);
|
|
2444
|
+
} catch {
|
|
2445
|
+
continue;
|
|
2446
|
+
}
|
|
2447
|
+
const largest = numbers.reduce((max, number) => Math.max(max, number.value), 0);
|
|
2448
|
+
if (largest > MAX_NUMBERING_VALUE) errors.push({ key, message: `numbering counts up to ${largest}, past the native limit of ${MAX_NUMBERING_VALUE}`, params: { largest, limit: MAX_NUMBERING_VALUE } });
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
} else {
|
|
2452
|
+
for (const key of lists) {
|
|
2453
|
+
if (payload[key].some((item) => isObject(item) && item.start !== void 0)) {
|
|
2454
|
+
warnings.push({ key, message: `'start' on ${key} entries has no effect without a 'numbering' field on the payload`, params: {} });
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
return { errors, warnings };
|
|
2459
|
+
}
|
|
1720
2460
|
|
|
1721
|
-
// src/
|
|
1722
|
-
var
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
2461
|
+
// src/color.ts
|
|
2462
|
+
var schemeColorSlots = /* @__PURE__ */ new Set([
|
|
2463
|
+
"accent1",
|
|
2464
|
+
"accent2",
|
|
2465
|
+
"accent3",
|
|
2466
|
+
"accent4",
|
|
2467
|
+
"accent5",
|
|
2468
|
+
"accent6",
|
|
2469
|
+
"dark1",
|
|
2470
|
+
"dark2",
|
|
2471
|
+
"light1",
|
|
2472
|
+
"light2",
|
|
2473
|
+
"hyperlink",
|
|
2474
|
+
"followedHyperlink"
|
|
2475
|
+
]);
|
|
2476
|
+
var schemeColorRoles = /* @__PURE__ */ new Set([
|
|
2477
|
+
"primary",
|
|
2478
|
+
"secondary",
|
|
2479
|
+
"accent",
|
|
2480
|
+
"background",
|
|
2481
|
+
"surface",
|
|
2482
|
+
"text",
|
|
2483
|
+
"textSecondary"
|
|
2484
|
+
]);
|
|
2485
|
+
var defaultRoleSlots = {
|
|
2486
|
+
primary: "accent1",
|
|
2487
|
+
secondary: "accent2",
|
|
2488
|
+
accent: "accent3",
|
|
2489
|
+
background: "light1",
|
|
2490
|
+
surface: "light2",
|
|
2491
|
+
text: "dark1",
|
|
2492
|
+
textSecondary: "dark2"
|
|
2493
|
+
};
|
|
2494
|
+
function normalizeHexColor(value) {
|
|
2495
|
+
if (typeof value !== "string") return void 0;
|
|
2496
|
+
let hex = value.trim();
|
|
2497
|
+
if (/^#[0-9a-f]{3}$/i.test(hex)) hex = `#${[...hex.slice(1)].map((char) => char + char).join("")}`;
|
|
2498
|
+
if (/^#[0-9a-f]{6}([0-9a-f]{2})?$/i.test(hex)) return hex.length === 9 ? `#${hex.slice(1, 7).toUpperCase()}` : hex.toUpperCase();
|
|
2499
|
+
return void 0;
|
|
2500
|
+
}
|
|
2501
|
+
function schemeSlot(colorScheme, slot, fallback) {
|
|
2502
|
+
return normalizeHexColor(colorScheme[slot]) ?? fallback;
|
|
2503
|
+
}
|
|
2504
|
+
function resolveVariable(variables, id, fallback) {
|
|
2505
|
+
const entry = variables[id];
|
|
2506
|
+
if (typeof entry === "string") return normalizeHexColor(entry) ?? fallback;
|
|
2507
|
+
if (entry && typeof entry === "object" && entry.type === "color") {
|
|
2508
|
+
return normalizeHexColor(entry.value) ?? fallback;
|
|
1757
2509
|
}
|
|
2510
|
+
return fallback;
|
|
1758
2511
|
}
|
|
1759
|
-
function
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
2512
|
+
function resolveColorRef(reference, options) {
|
|
2513
|
+
const { colorScheme, roles, variables = {}, fallback } = options;
|
|
2514
|
+
const literal = normalizeHexColor(reference);
|
|
2515
|
+
if (literal) return literal;
|
|
2516
|
+
if (reference.startsWith("var:")) {
|
|
2517
|
+
const id = reference.slice("var:".length);
|
|
2518
|
+
if (/^[a-z][a-z0-9-]*$/.test(id)) return resolveVariable(variables, id, fallback);
|
|
2519
|
+
return fallback;
|
|
2520
|
+
}
|
|
2521
|
+
if (schemeColorRoles.has(reference)) {
|
|
2522
|
+
const roleKey = reference;
|
|
2523
|
+
const fromRoles = roles?.[roleKey];
|
|
2524
|
+
if (fromRoles) return normalizeHexColor(fromRoles) ?? fallback;
|
|
2525
|
+
const fromScheme = normalizeHexColor(colorScheme[reference]);
|
|
2526
|
+
if (fromScheme) return fromScheme;
|
|
2527
|
+
const slot = defaultRoleSlots[reference];
|
|
2528
|
+
return slot ? schemeSlot(colorScheme, slot, fallback) : fallback;
|
|
2529
|
+
}
|
|
2530
|
+
if (schemeColorSlots.has(reference)) return schemeSlot(colorScheme, reference, fallback);
|
|
2531
|
+
return fallback;
|
|
2532
|
+
}
|
|
2533
|
+
function luminance(value) {
|
|
2534
|
+
if (typeof value !== "string") return void 0;
|
|
2535
|
+
let hex = value.trim();
|
|
2536
|
+
if (/^#[0-9a-f]{3}$/i.test(hex)) hex = `#${[...hex.slice(1)].map((c) => c + c).join("")}`;
|
|
2537
|
+
if (/^#[0-9a-f]{6}ff$/i.test(hex)) hex = hex.slice(0, 7);
|
|
2538
|
+
if (!/^#[0-9a-f]{6}$/i.test(hex)) return void 0;
|
|
2539
|
+
return [0.2126, 0.7152, 0.0722].reduce((sum, weight, index) => {
|
|
2540
|
+
const offset = index * 2 + 1;
|
|
2541
|
+
const n = Number.parseInt(hex.slice(offset, offset + 2), 16) / 255;
|
|
2542
|
+
return sum + weight * (n <= 0.04045 ? n / 12.92 : ((n + 0.055) / 1.055) ** 2.4);
|
|
2543
|
+
}, 0);
|
|
2544
|
+
}
|
|
2545
|
+
function colorContrast(foreground, background) {
|
|
2546
|
+
const a = luminance(foreground), b = luminance(background);
|
|
2547
|
+
return a === void 0 || b === void 0 ? void 0 : (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);
|
|
2548
|
+
}
|
|
2549
|
+
function textColorForFill(fill, preferred) {
|
|
2550
|
+
const background = luminance(fill), contrast = colorContrast(preferred, fill);
|
|
2551
|
+
if (background === void 0 || contrast === void 0 || contrast >= 4.5) return preferred;
|
|
2552
|
+
return (background + 0.05) / 0.05 >= 1.05 / (background + 0.05) ? "#000000" : "#FFFFFF";
|
|
2553
|
+
}
|
|
2554
|
+
function chartColorForFill(fill, preferred) {
|
|
2555
|
+
const contrast = colorContrast(preferred, fill);
|
|
2556
|
+
if (contrast === void 0 || contrast >= 3) return preferred;
|
|
2557
|
+
let hex = preferred.trim();
|
|
2558
|
+
if (hex.length === 4) hex = `#${[...hex.slice(1)].map((c) => c + c).join("")}`;
|
|
2559
|
+
const channels = [1, 3, 5].map((offset) => Number.parseInt(hex.slice(offset, offset + 2), 16));
|
|
2560
|
+
for (let step = 1; step <= 255; step++) {
|
|
2561
|
+
const candidates = [0, 255].map((target) => {
|
|
2562
|
+
const color = `#${channels.map((channel) => Math.round(channel + (target - channel) * step / 255).toString(16).padStart(2, "0")).join("")}`.toUpperCase();
|
|
2563
|
+
return { color, contrast: colorContrast(color, fill) ?? 0 };
|
|
2564
|
+
}).sort((a, b) => b.contrast - a.contrast);
|
|
2565
|
+
const best = candidates[0];
|
|
2566
|
+
if (best && best.contrast >= 3) return best.color;
|
|
2567
|
+
}
|
|
2568
|
+
return textColorForFill(fill, preferred);
|
|
2569
|
+
}
|
|
2570
|
+
var CHART_SERIES_MIN_LIGHTNESS_STEP = 11;
|
|
2571
|
+
var CHART_SERIES_MIN_DIFFERENCE = 12;
|
|
2572
|
+
var D65 = [0.95047, 1, 1.08883];
|
|
2573
|
+
function opaqueChannels(value) {
|
|
2574
|
+
if (luminance(value) === void 0) return void 0;
|
|
2575
|
+
let hex = value.trim();
|
|
2576
|
+
if (hex.length === 4) hex = `#${[...hex.slice(1)].map((c) => c + c).join("")}`;
|
|
2577
|
+
return [1, 3, 5].map((offset) => Number.parseInt(hex.slice(offset, offset + 2), 16));
|
|
2578
|
+
}
|
|
2579
|
+
var toLinear = (channel) => {
|
|
2580
|
+
const n = channel / 255;
|
|
2581
|
+
return n <= 0.04045 ? n / 12.92 : ((n + 0.055) / 1.055) ** 2.4;
|
|
2582
|
+
};
|
|
2583
|
+
var fromLinear = (value) => 255 * (value <= 31308e-7 ? value * 12.92 : 1.055 * value ** (1 / 2.4) - 0.055);
|
|
2584
|
+
function labOf(channels) {
|
|
2585
|
+
const [r, g, b] = channels.map(toLinear);
|
|
2586
|
+
const f = (t) => t > 216 / 24389 ? Math.cbrt(t) : (24389 / 27 * t + 16) / 116;
|
|
2587
|
+
const fx = f((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / D65[0]), fy = f(0.2126729 * r + 0.7151522 * g + 0.072175 * b), fz = f((0.0193339 * r + 0.119192 * g + 0.9503041 * b) / D65[2]);
|
|
2588
|
+
return [116 * fy - 16, 500 * (fx - fy), 200 * (fy - fz)];
|
|
2589
|
+
}
|
|
2590
|
+
function labToLinear([l, a, b]) {
|
|
2591
|
+
const fy = (l + 16) / 116, fx = fy + a / 500, fz = fy - b / 200;
|
|
2592
|
+
const inverse = (t) => t ** 3 > 216 / 24389 ? t ** 3 : (116 * t - 16) / (24389 / 27);
|
|
2593
|
+
const x = inverse(fx) * D65[0], y = l > 8 ? fy ** 3 : l / (24389 / 27), z = inverse(fz) * D65[2];
|
|
2594
|
+
const rgb = [
|
|
2595
|
+
3.2404542 * x - 1.5371385 * y - 0.4985314 * z,
|
|
2596
|
+
-0.969266 * x + 1.8760108 * y + 0.041556 * z,
|
|
2597
|
+
0.0556434 * x - 0.2040259 * y + 1.0572252 * z
|
|
2598
|
+
];
|
|
2599
|
+
return rgb.every((channel) => channel >= -5e-4 && channel <= 1.0005) ? rgb : void 0;
|
|
2600
|
+
}
|
|
2601
|
+
var hexOfChannels = (channels) => `#${channels.map((channel) => Math.max(0, Math.min(255, Math.round(channel))).toString(16).padStart(2, "0")).join("").toUpperCase()}`;
|
|
2602
|
+
function labWithLightness(l, a, b) {
|
|
2603
|
+
const chroma = Math.hypot(a, b), hue = Math.atan2(b, a);
|
|
2604
|
+
const at3 = (scale) => labToLinear([l, Math.cos(hue) * chroma * scale, Math.sin(hue) * chroma * scale]);
|
|
2605
|
+
let linear = at3(1);
|
|
2606
|
+
if (!linear) {
|
|
2607
|
+
let low = 0, high = 1;
|
|
2608
|
+
for (let step = 0; step < 24; step++) {
|
|
2609
|
+
const mid = (low + high) / 2;
|
|
2610
|
+
if (at3(mid)) low = mid;
|
|
2611
|
+
else high = mid;
|
|
2612
|
+
}
|
|
2613
|
+
linear = at3(low) ?? [0, 0, 0];
|
|
2614
|
+
}
|
|
2615
|
+
return hexOfChannels(linear.map((channel) => fromLinear(Math.max(0, Math.min(1, channel)))));
|
|
2616
|
+
}
|
|
2617
|
+
function labDifference(a, b) {
|
|
2618
|
+
return Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
|
|
2619
|
+
}
|
|
2620
|
+
var CHART_SERIES_SHORTFALL_WEIGHT = 8;
|
|
2621
|
+
function chartPaletteForFill(fill, palette) {
|
|
2622
|
+
const opaqueFill = colorContrast(fill, fill) !== void 0;
|
|
2623
|
+
const preferredLab = palette.map((color) => {
|
|
2624
|
+
const channels = opaqueChannels(color);
|
|
2625
|
+
return channels ? labOf(channels) : void 0;
|
|
2626
|
+
});
|
|
2627
|
+
const adjusted = palette.map((color) => chartColorForFill(fill, color));
|
|
2628
|
+
const kept = palette.map((color, index) => opaqueFill && preferredLab[index] !== void 0 && adjusted[index] === color);
|
|
2629
|
+
const placed = palette.map((_, index) => kept[index] || !opaqueFill || !preferredLab[index] ? adjusted[index] : void 0);
|
|
2630
|
+
const placedLab = placed.map((color) => {
|
|
2631
|
+
const channels = color === void 0 ? void 0 : opaqueChannels(color);
|
|
2632
|
+
return channels ? labOf(channels) : void 0;
|
|
2633
|
+
});
|
|
2634
|
+
palette.forEach((_, index) => {
|
|
2635
|
+
const wanted = preferredLab[index], base = adjusted[index];
|
|
2636
|
+
if (placed[index] !== void 0 || !wanted) return;
|
|
2637
|
+
const baseLab = labOf(opaqueChannels(base));
|
|
2638
|
+
const needs = palette.flatMap((_2, other) => {
|
|
2639
|
+
const before = preferredLab[other], lab = placedLab[other];
|
|
2640
|
+
return other !== index && before && lab && placed[other] !== void 0 ? [{ lab, adjacent: Math.abs(other - index) === 1, wide: Math.min(labDifference(before, wanted), CHART_SERIES_MIN_DIFFERENCE), step: Math.min(Math.abs(before[0] - wanted[0]), CHART_SERIES_MIN_LIGHTNESS_STEP) }] : [];
|
|
2641
|
+
});
|
|
2642
|
+
const shortfall = (candidate) => needs.reduce((worst, need) => {
|
|
2643
|
+
const lightness = Math.abs(need.lab[0] - candidate[0]) - need.step, difference = labDifference(need.lab, candidate) - need.wide;
|
|
2644
|
+
return need.adjacent ? { near: Math.min(worst.near, difference, lightness), far: worst.far } : { near: Math.min(worst.near, difference), far: Math.min(worst.far, lightness) };
|
|
2645
|
+
}, { near: Infinity, far: Infinity });
|
|
2646
|
+
const deficit = (value) => Math.max(0, -value - 1e-9);
|
|
2647
|
+
const cost = (candidate) => candidate.moved + CHART_SERIES_SHORTFALL_WEIGHT * deficit(candidate.near) + deficit(candidate.far);
|
|
2648
|
+
const start = shortfall(baseLab);
|
|
2649
|
+
if (deficit(start.near) === 0 && deficit(start.far) === 0) {
|
|
2650
|
+
placed[index] = base;
|
|
2651
|
+
placedLab[index] = baseLab;
|
|
2652
|
+
return;
|
|
2653
|
+
}
|
|
2654
|
+
let best = { color: base, lab: baseLab, cost: cost({ moved: 0, ...start }), moved: 0 };
|
|
2655
|
+
for (let step = 0; step <= 400; step++) {
|
|
2656
|
+
const color = labWithLightness(step / 4, wanted[1], wanted[2]), contrast = colorContrast(color, fill);
|
|
2657
|
+
if (contrast === void 0 || contrast < 3) continue;
|
|
2658
|
+
const lab = labOf(opaqueChannels(color)), moved = Math.abs(lab[0] - baseLab[0]), total = cost({ moved, ...shortfall(lab) });
|
|
2659
|
+
if (total < best.cost - 1e-9 || Math.abs(total - best.cost) <= 1e-9 && moved < best.moved) best = { color, lab, cost: total, moved };
|
|
2660
|
+
}
|
|
2661
|
+
placed[index] = best.color;
|
|
2662
|
+
placedLab[index] = best.lab;
|
|
2663
|
+
});
|
|
2664
|
+
return placed;
|
|
1765
2665
|
}
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
"radar-with-markers": { kind: "radar" },
|
|
1785
|
-
"filled-radar": { kind: "radar" },
|
|
1786
|
-
treemap: { kind: "treemap" },
|
|
1787
|
-
histogram: { kind: "histogram" },
|
|
1788
|
-
pareto: { kind: "pareto" },
|
|
1789
|
-
"box-and-whisker": { kind: "box" },
|
|
1790
|
-
waterfall: { kind: "waterfall" },
|
|
1791
|
-
funnel: { kind: "funnel" },
|
|
1792
|
-
world: { kind: "map" }
|
|
1793
|
-
};
|
|
1794
|
-
function chartOptionTarget(typeId) {
|
|
1795
|
-
if (typeof typeId !== "string") return void 0;
|
|
1796
|
-
const raw = typeId.trim().toLowerCase();
|
|
1797
|
-
const id = raw === "donut" ? "doughnut" : raw;
|
|
1798
|
-
const kept = deprecatedCatalogIds[`chartTypes/${id}`] ?? id;
|
|
1799
|
-
const target = Object.hasOwn(KEPT, kept) ? KEPT[kept] : void 0;
|
|
1800
|
-
return target ? { ...target } : void 0;
|
|
2666
|
+
|
|
2667
|
+
// src/annotations.ts
|
|
2668
|
+
var CITATION_MARKER_SCALE = 2 / 3;
|
|
2669
|
+
var CITATION_MARKER_RAISE = 0.3;
|
|
2670
|
+
var CAPTION_FONT_RATIO = 0.6;
|
|
2671
|
+
var CAPTION_MAX_RATIO = 0.35;
|
|
2672
|
+
var FOOTNOTE_MAX_RATIO = 0.35;
|
|
2673
|
+
var CAPTIONABLE_FIELDS = Object.freeze(["image", "chart", "table", "video"]);
|
|
2674
|
+
var record4 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2675
|
+
var isRecord3 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
2676
|
+
var round = (value) => Math.round(value * 1e6) / 1e6 || value;
|
|
2677
|
+
var regionKey = /^(top|middle|bottom|left|center|right)([+:]|$)/;
|
|
2678
|
+
var TEXT_FIELDS = ["text", "items", "bullets"];
|
|
2679
|
+
function annotationText(value) {
|
|
2680
|
+
if (typeof value === "string") return value;
|
|
2681
|
+
if (Array.isArray(value)) return value.map((run) => typeof run === "string" ? run : record4(run).text ?? "").join("");
|
|
2682
|
+
if (isRecord3(value) && value.text !== void 0) return annotationText(value.text);
|
|
2683
|
+
return value === void 0 || value === null ? "" : String(value);
|
|
1801
2684
|
}
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
2685
|
+
function captionSettings(value) {
|
|
2686
|
+
if (value === void 0 || value === null) return void 0;
|
|
2687
|
+
if (typeof value === "string" || Array.isArray(value)) return { text: value, position: "below", align: "left" };
|
|
2688
|
+
if (!isRecord3(value) || value.text === void 0) return void 0;
|
|
2689
|
+
return { text: value.text, position: value.position === "above" ? "above" : "below", align: value.align === "center" || value.align === "right" ? value.align : "left" };
|
|
2690
|
+
}
|
|
2691
|
+
function walkCitationRuns(slide, basePath, visit) {
|
|
2692
|
+
const runs = (value2, path) => {
|
|
2693
|
+
if (!Array.isArray(value2)) return;
|
|
2694
|
+
value2.forEach((run, index) => {
|
|
2695
|
+
if (!isRecord3(run) || typeof run.text !== "string") return;
|
|
2696
|
+
const cite = run.cite === void 0 ? [] : Array.isArray(run.cite) ? run.cite.filter((id) => typeof id === "string") : typeof run.cite === "string" ? [run.cite] : [];
|
|
2697
|
+
const footnote = typeof run.footnote === "string" || Array.isArray(run.footnote) ? run.footnote : void 0;
|
|
2698
|
+
if (!cite.length && footnote === void 0) return;
|
|
2699
|
+
visit({ path: `${path}.${index}`, run, cite, footnote });
|
|
2700
|
+
});
|
|
2701
|
+
};
|
|
2702
|
+
const list = (items, path) => {
|
|
2703
|
+
if (!Array.isArray(items)) return;
|
|
2704
|
+
items.forEach((item, index) => {
|
|
2705
|
+
if (Array.isArray(item)) runs(item, `${path}.${index}`);
|
|
2706
|
+
else if (isRecord3(item)) {
|
|
2707
|
+
runs(item.text, `${path}.${index}.text`);
|
|
2708
|
+
runs(item.description, `${path}.${index}.description`);
|
|
1820
2709
|
}
|
|
1821
|
-
|
|
2710
|
+
});
|
|
2711
|
+
};
|
|
2712
|
+
const host = (node, path, depth) => {
|
|
2713
|
+
const payload = record4(node);
|
|
2714
|
+
if (Array.isArray(payload.blocks)) {
|
|
2715
|
+
if (depth > 32) return;
|
|
2716
|
+
payload.blocks.forEach((block, index) => {
|
|
2717
|
+
host(block, `${path}.blocks.${index}`, depth + 1);
|
|
2718
|
+
});
|
|
2719
|
+
return;
|
|
1822
2720
|
}
|
|
2721
|
+
for (const field of TEXT_FIELDS) {
|
|
2722
|
+
if (payload[field] === void 0) continue;
|
|
2723
|
+
if (field === "text") runs(payload.text, `${path}.text`);
|
|
2724
|
+
else list(payload[field], `${path}.${field}`);
|
|
2725
|
+
}
|
|
2726
|
+
};
|
|
2727
|
+
const value = record4(slide);
|
|
2728
|
+
const regions = Object.keys(value).filter((key) => regionKey.test(key)).sort();
|
|
2729
|
+
if (regions.length) {
|
|
2730
|
+
for (const key of regions) host(value[key], `${basePath}.${key}`, 0);
|
|
2731
|
+
return;
|
|
1823
2732
|
}
|
|
1824
|
-
if (
|
|
1825
|
-
|
|
1826
|
-
|
|
2733
|
+
if (Array.isArray(value.blocks)) {
|
|
2734
|
+
value.blocks.forEach((block, index) => {
|
|
2735
|
+
host(block, `${basePath}.blocks.${index}`, 0);
|
|
2736
|
+
});
|
|
2737
|
+
return;
|
|
1827
2738
|
}
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
if (!
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
else adapt("dataLabels.position", "unsupported-position", `A '${kind2}'${target?.stacked ? " stacked" : ""} chart cannot place data labels '${options.position}'; the type's default position is used.`);
|
|
1847
|
-
}
|
|
1848
|
-
let separator = typeof options.separator === "string" ? options.separator : DEFAULT_CHART_LABEL_SEPARATOR;
|
|
1849
|
-
if (/[\r\n]/.test(separator)) {
|
|
1850
|
-
separator = separator.replace(/[\r\n]+/g, " ");
|
|
1851
|
-
adapt("dataLabels.separator", "unsupported-content", "A data label is one line; the line break in the separator is replaced with a space.");
|
|
2739
|
+
host(value, basePath, 0);
|
|
2740
|
+
}
|
|
2741
|
+
var citationMarkerText = (numbers) => numbers.join(",");
|
|
2742
|
+
function referenceList(presentation) {
|
|
2743
|
+
const list = record4(presentation).references;
|
|
2744
|
+
return Array.isArray(list) ? list.filter((item) => isRecord3(item) && typeof item.id === "string") : [];
|
|
2745
|
+
}
|
|
2746
|
+
function numberSlide(slide, slideIndex, references, numbering) {
|
|
2747
|
+
const markers = /* @__PURE__ */ new Map(), marked = [], used = /* @__PURE__ */ new Map();
|
|
2748
|
+
walkCitationRuns(slide, `slides.${slideIndex}`, (entry) => {
|
|
2749
|
+
const numbers = [];
|
|
2750
|
+
for (const id of entry.cite) {
|
|
2751
|
+
let note = numbering.byId.get(id);
|
|
2752
|
+
if (!note) {
|
|
2753
|
+
const index = references.findIndex((item) => item.id === id), reference = references[index];
|
|
2754
|
+
note = { number: numbering.notes.length + 1, kind: "reference", id, text: reference?.text ?? id, sourcePath: index >= 0 ? `references.${index}` : entry.path, resolved: !!reference, ...typeof reference?.url === "string" ? { url: reference.url } : {} };
|
|
2755
|
+
numbering.notes.push(note);
|
|
2756
|
+
numbering.byId.set(id, note);
|
|
1852
2757
|
}
|
|
1853
|
-
|
|
2758
|
+
if (!numbers.includes(note.number)) numbers.push(note.number);
|
|
2759
|
+
used.set(note.number, note);
|
|
1854
2760
|
}
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
2761
|
+
if (entry.footnote !== void 0) {
|
|
2762
|
+
const note = { number: numbering.notes.length + 1, kind: "footnote", text: entry.footnote, sourcePath: entry.path, resolved: true };
|
|
2763
|
+
numbering.notes.push(note);
|
|
2764
|
+
numbers.push(note.number);
|
|
2765
|
+
used.set(note.number, note);
|
|
2766
|
+
}
|
|
2767
|
+
const text2 = citationMarkerText(numbers);
|
|
2768
|
+
markers.set(entry.path, text2);
|
|
2769
|
+
marked.push({ path: entry.path, numbers, text: text2 });
|
|
2770
|
+
});
|
|
2771
|
+
if (!marked.length) return void 0;
|
|
2772
|
+
return { markers, marked, notes: [...used.values()].sort((a, b) => a.number - b.number) };
|
|
1858
2773
|
}
|
|
1859
|
-
function
|
|
1860
|
-
|
|
2774
|
+
function collectCitations(presentation) {
|
|
2775
|
+
const references = referenceList(presentation), numbering = { notes: [], byId: /* @__PURE__ */ new Map() }, slides = /* @__PURE__ */ new Map();
|
|
2776
|
+
const list = record4(presentation).slides;
|
|
2777
|
+
if (Array.isArray(list)) list.forEach((slide, index) => {
|
|
2778
|
+
const result = numberSlide(slide, index, references, numbering);
|
|
2779
|
+
if (result) slides.set(index, result);
|
|
2780
|
+
});
|
|
2781
|
+
const cited = new Set(numbering.notes.filter((note) => note.kind === "reference").map((note) => note.id));
|
|
2782
|
+
return { notes: numbering.notes, references: numbering.notes.filter((note) => note.kind === "reference"), slides, unused: references.map((item) => item.id).filter((id) => !cited.has(id)) };
|
|
1861
2783
|
}
|
|
1862
|
-
function
|
|
1863
|
-
|
|
2784
|
+
function slideCitations(slide, slideIndex, presentation) {
|
|
2785
|
+
const references = referenceList(presentation), numbering = { notes: [], byId: /* @__PURE__ */ new Map() };
|
|
2786
|
+
const list = record4(presentation).slides;
|
|
2787
|
+
if (Array.isArray(list)) for (let index = 0; index < Math.min(slideIndex, list.length); index += 1) numberSlide(list[index], index, references, numbering);
|
|
2788
|
+
return numberSlide(slide, slideIndex, references, numbering);
|
|
1864
2789
|
}
|
|
1865
|
-
|
|
1866
|
-
|
|
2790
|
+
var fitHeight = (fit) => Math.max(fit.placement?.height ?? 0, "richLines" in fit ? fit.height : fit.lines.length * fit.lineHeight);
|
|
2791
|
+
function layoutCaption(value, box, path, options) {
|
|
2792
|
+
const settings = captionSettings(value);
|
|
2793
|
+
if (!settings) return void 0;
|
|
2794
|
+
const fontSize = Math.max(options.minFontSize, CAPTION_FONT_RATIO * 25 * options.scale), gap = fontSize * 0.4;
|
|
2795
|
+
const maxHeight = Math.max(fontSize * 1.22, box.height * CAPTION_MAX_RATIO);
|
|
2796
|
+
const diagnostics = [];
|
|
2797
|
+
const probe = options.fit(settings.text, { x: box.x, y: box.y, width: box.width, height: maxHeight }, fontSize, fontSize, path, settings.align);
|
|
2798
|
+
const height = Math.min(maxHeight, Math.max(fontSize * 1.22, fitHeight(probe)));
|
|
2799
|
+
const captionBox = { x: box.x, y: settings.position === "above" ? box.y : box.y + box.height - height, width: box.width, height };
|
|
2800
|
+
const fit = options.fit(settings.text, captionBox, fontSize, fontSize, path, settings.align);
|
|
2801
|
+
const overflow = fit.overflow || fitHeight(fit) > height + 0.01;
|
|
2802
|
+
if (overflow) diagnostics.push({ code: "text-overflow", path, message: "The caption does not fit its band at the minimum font size; shorten it or give the block more space." });
|
|
2803
|
+
const mediaHeight = Math.max(options.scale, box.height - height - gap);
|
|
2804
|
+
const mediaBox = { x: box.x, y: settings.position === "above" ? box.y + height + gap : box.y, width: box.width, height: mediaHeight };
|
|
2805
|
+
for (const target of [captionBox, mediaBox]) for (const key of ["x", "y", "width", "height"]) target[key] = round(target[key]);
|
|
2806
|
+
return { path, value, text: settings.text, position: settings.position, alignment: settings.align, box: captionBox, mediaBox, fit, textStyle: options.textStyle(path), fontSize, overflow, diagnostics };
|
|
2807
|
+
}
|
|
2808
|
+
function layoutFootnotes(citations, options) {
|
|
2809
|
+
const fontSize = Math.max(options.minFontSize, 13 * options.scale), thickness = Math.max(1, round(options.scale));
|
|
2810
|
+
const gap = fontSize * 0.5, spacing = fontSize * 0.25, diagnostics = [];
|
|
2811
|
+
const width = Math.max(options.scale, options.width);
|
|
2812
|
+
const measured = citations.notes.map((note) => {
|
|
2813
|
+
const value = typeof note.text === "string" ? `${note.number} ${note.text}` : [`${note.number} `, ...note.text];
|
|
2814
|
+
const probe = options.fit(value, { x: options.x, y: 0, width, height: Number.MAX_SAFE_INTEGER }, fontSize, fontSize, note.sourcePath, "left");
|
|
2815
|
+
return { note, value, height: Math.max(fontSize * 1.22, fitHeight(probe)) };
|
|
2816
|
+
});
|
|
2817
|
+
const total = thickness + gap + measured.reduce((sum, item) => sum + item.height, 0) + spacing * Math.max(0, measured.length - 1);
|
|
2818
|
+
const height = round(Math.min(total, Math.max(fontSize * 1.22 + thickness + gap, options.maxHeight)));
|
|
2819
|
+
const top = round(options.bottom - height), box = { x: round(options.x), y: top, width: round(width), height };
|
|
2820
|
+
let cursor = top + thickness + gap, overflow = false;
|
|
2821
|
+
const entries = measured.map((item, index) => {
|
|
2822
|
+
const entryBox = { x: box.x, y: round(cursor), width: box.width, height: round(item.height) };
|
|
2823
|
+
const fit = options.fit(item.value, entryBox, fontSize, fontSize, item.note.sourcePath, "left");
|
|
2824
|
+
const exceeds = fit.overflow || entryBox.y + entryBox.height > options.bottom + 0.01;
|
|
2825
|
+
if (exceeds) {
|
|
2826
|
+
overflow = true;
|
|
2827
|
+
diagnostics.push({ code: "text-overflow", path: item.note.sourcePath, message: `Footnote ${item.note.number} does not fit the slide's footnote area; shorten the note or split the slide.` });
|
|
2828
|
+
}
|
|
2829
|
+
cursor += item.height + (index < measured.length - 1 ? spacing : 0);
|
|
2830
|
+
return { number: item.note.number, kind: item.note.kind, ...item.note.id !== void 0 ? { id: item.note.id } : {}, sourcePath: item.note.sourcePath, value: item.value, text: item.note.text, box: entryBox, fit, textStyle: options.textStyle(item.note.sourcePath), ...item.note.url !== void 0 ? { url: item.note.url } : {}, overflow: exceeds };
|
|
2831
|
+
});
|
|
2832
|
+
for (const note of citations.notes) if (!note.resolved) diagnostics.push({ code: "unresolved-content", path: note.sourcePath, message: `Reference '${note.id}' is not in the presentation's references list; its id is listed instead.` });
|
|
2833
|
+
return { algorithm: "footnote-area-v1", path: options.path, box, rule: { x: box.x, y: top, width: box.width, thickness }, entries, markers: citations.marked, fontSize, overflow, diagnostics };
|
|
2834
|
+
}
|
|
2835
|
+
function referencesSlide(presentation, options = {}) {
|
|
2836
|
+
const title = options.title ?? "References";
|
|
2837
|
+
const { references } = collectCitations(presentation);
|
|
2838
|
+
if (!references.length) return { title };
|
|
2839
|
+
const items = references.map((note) => {
|
|
2840
|
+
const label = `${note.number}. `;
|
|
2841
|
+
if (typeof note.text === "string" && note.url === void 0) return `${label}${note.text}`;
|
|
2842
|
+
const runs = [label, ...typeof note.text === "string" ? [note.text] : note.text];
|
|
2843
|
+
if (note.url !== void 0) runs.push(" ", { text: note.url, link: note.url });
|
|
2844
|
+
return runs;
|
|
2845
|
+
});
|
|
2846
|
+
return { title, items };
|
|
1867
2847
|
}
|
|
1868
2848
|
|
|
1869
2849
|
// src/composition.ts
|
|
@@ -1875,14 +2855,14 @@ function resolveFontSchemeReference(reference, lookup, path = "design.fontScheme
|
|
|
1875
2855
|
const overrides = typeof reference === "object" && reference !== null && !Array.isArray(reference) ? reference : void 0;
|
|
1876
2856
|
const id = typeof reference === "string" ? reference : typeof overrides?.id === "string" ? overrides.id : void 0;
|
|
1877
2857
|
const found = id === void 0 ? void 0 : lookup(id);
|
|
1878
|
-
const base =
|
|
2858
|
+
const base = record5(found ?? lookup(DEFAULT_FONT_SCHEME));
|
|
1879
2859
|
const scheme = overrides ? { ...base, ...overrides } : { ...base };
|
|
1880
2860
|
if (id === void 0 || found !== void 0) return { scheme };
|
|
1881
2861
|
return { scheme, diagnostic: { code: "unresolved-font-scheme", path, id, fallback: DEFAULT_FONT_SCHEME, message: `Font scheme '${id}' is not in the inline or bundled catalogs; using the default font scheme '${DEFAULT_FONT_SCHEME}'.` } };
|
|
1882
2862
|
}
|
|
1883
2863
|
function resolveFontFamilies(input) {
|
|
1884
|
-
const scheme =
|
|
1885
|
-
const family = (value) => typeof value === "string" ? value :
|
|
2864
|
+
const scheme = record5(input);
|
|
2865
|
+
const family = (value) => typeof value === "string" ? value : record5(value).family;
|
|
1886
2866
|
const accent = family(scheme.accent);
|
|
1887
2867
|
return {
|
|
1888
2868
|
heading: family(scheme.heading) ?? scheme.major ?? scheme.minor ?? DEFAULT_FONT_FAMILIES.heading,
|
|
@@ -1968,12 +2948,12 @@ var headings = /* @__PURE__ */ new Set(["title", "subtitle", "tag"]);
|
|
|
1968
2948
|
var COVER_LAYOUT_IDS = /* @__PURE__ */ new Set(["title", "title-subtitle"]);
|
|
1969
2949
|
var rows = ["top", "middle", "bottom"];
|
|
1970
2950
|
var columns = ["left", "center", "right"];
|
|
1971
|
-
var
|
|
2951
|
+
var record5 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1972
2952
|
var kind = (field) => field === "items" ? "list" : field === "bullets" ? "text" : field;
|
|
1973
2953
|
var round2 = (value) => Math.round(value * 1e6) / 1e6 || value;
|
|
1974
2954
|
var SLIDE_IMAGE_POSITIONS = ["background", "top", "bottom", "left", "right"];
|
|
1975
2955
|
var SLIDE_IMAGE_BAND = 0.5;
|
|
1976
|
-
var assetSource = (value) => typeof value === "string" ? value :
|
|
2956
|
+
var assetSource = (value) => typeof value === "string" ? value : record5(value).src;
|
|
1977
2957
|
var finite = (value, min, max) => typeof value === "number" && Number.isFinite(value) && value >= min && value <= max ? value : void 0;
|
|
1978
2958
|
var pathNumber = (value) => String(round2(value));
|
|
1979
2959
|
var SLIDE_IMAGE_SHAPES = { rectangle: "rect", rounded: "roundRect", circle: "ellipse", hexagon: "hexagon" };
|
|
@@ -1997,11 +2977,11 @@ function slideImageShape(kind2, box, cornerRadius = 1 / 6) {
|
|
|
1997
2977
|
return { kind: "rectangle", preset: "rect", adjust: {}, path: rect };
|
|
1998
2978
|
}
|
|
1999
2979
|
function resolveSlideImage(slide, layout, presentation, width, height, path, padding, scale, diagnostics, rtl = false) {
|
|
2000
|
-
const
|
|
2001
|
-
const local =
|
|
2002
|
-
const configured = local ?
|
|
2980
|
+
const own2 = record5(slide.design), deck = record5(record5(presentation).design);
|
|
2981
|
+
const local = own2.slideImage !== void 0;
|
|
2982
|
+
const configured = local ? own2.slideImage : deck.slideImage;
|
|
2003
2983
|
if (!configured || typeof configured !== "object" && typeof configured !== "string") return void 0;
|
|
2004
|
-
const treatment = typeof configured === "object" && !Array.isArray(configured) && "position" in configured ?
|
|
2984
|
+
const treatment = typeof configured === "object" && !Array.isArray(configured) && "position" in configured ? record5(configured) : void 0;
|
|
2005
2985
|
const alignment = typeof layout.slideImageAlignment === "string" ? layout.slideImageAlignment.toLowerCase() : void 0;
|
|
2006
2986
|
const authoredPosition = treatment ? treatment.position : SLIDE_IMAGE_POSITIONS.find((value2) => value2 === alignment) ?? "background";
|
|
2007
2987
|
if (!SLIDE_IMAGE_POSITIONS.includes(authoredPosition)) return void 0;
|
|
@@ -2015,7 +2995,7 @@ function resolveSlideImage(slide, layout, presentation, width, height, path, pad
|
|
|
2015
2995
|
const source = assetSource(value);
|
|
2016
2996
|
if (typeof source !== "string" || !source) return void 0;
|
|
2017
2997
|
const t = treatment ?? {};
|
|
2018
|
-
const fill = t.fill === "crop" || t.fill === "fit" ? t.fill : (
|
|
2998
|
+
const fill = t.fill === "crop" || t.fill === "fit" ? t.fill : (own2.imageFill ?? deck.imageFill) === "fit" ? "fit" : "crop";
|
|
2019
2999
|
const share = finite(t.size, 0.1, 0.9) ?? SLIDE_IMAGE_BAND;
|
|
2020
3000
|
const band = { width: round2(width * share), height: round2(height * share) };
|
|
2021
3001
|
const region = position === "left" ? { x: 0, y: 0, width: band.width, height } : position === "right" ? { x: round2(width - band.width), y: 0, width: band.width, height } : position === "top" ? { x: 0, y: 0, width, height: band.height } : position === "bottom" ? { x: 0, y: round2(height - band.height), width, height: band.height } : { x: 0, y: 0, width, height };
|
|
@@ -2040,13 +3020,13 @@ function resolveSlideImage(slide, layout, presentation, width, height, path, pad
|
|
|
2040
3020
|
shape: slideImageShape(kind2, box, finite(t.cornerRadius, 0, 0.5))
|
|
2041
3021
|
};
|
|
2042
3022
|
if (typeof t.alt === "string") result.alt = t.alt;
|
|
2043
|
-
const border =
|
|
3023
|
+
const border = record5(t.border), borderWidth = finite(border.width, 0, 64);
|
|
2044
3024
|
if (border.color !== void 0 && borderWidth) result.border = { color: border.color, width: round2(borderWidth * scale) };
|
|
2045
3025
|
const opacity = finite(t.opacity, 0, 1);
|
|
2046
3026
|
if (opacity !== void 0 && opacity < 1) result.opacity = opacity;
|
|
2047
3027
|
if (t.recolor === "grayscale") result.recolor = { type: "grayscale" };
|
|
2048
|
-
else if (
|
|
2049
|
-
const overlay =
|
|
3028
|
+
else if (record5(t.recolor).dark !== void 0 && record5(t.recolor).light !== void 0) result.recolor = { type: "duotone", dark: t.recolor.dark, light: t.recolor.light };
|
|
3029
|
+
const overlay = record5(t.overlay), overlayOpacity = finite(overlay.opacity, 0, 1);
|
|
2050
3030
|
if (overlay.color !== void 0 && overlayOpacity !== void 0) {
|
|
2051
3031
|
const edge = ["top", "bottom", "left", "right"].includes(overlay.edge) ? mirrorSide(overlay.edge) : void 0;
|
|
2052
3032
|
if (edge && kind2 !== "rectangle") diagnostics.push({ code: "unsupported-image-treatment", path: `${designPath}.overlay.edge`, message: "An edge overlay needs a rectangle frame; a band cannot follow a rounded, circular or hexagonal mask as one native shape. Remove edge or use shape rectangle." });
|
|
@@ -2075,10 +3055,10 @@ function resolveLogo(presentation, slide, options = {}) {
|
|
|
2075
3055
|
};
|
|
2076
3056
|
const pick = (value, path) => {
|
|
2077
3057
|
if (value === void 0 || value === null || value === false) return null;
|
|
2078
|
-
if (typeof value === "string" || typeof value === "object" && !Array.isArray(value) && "src" in
|
|
3058
|
+
if (typeof value === "string" || typeof value === "object" && !Array.isArray(value) && "src" in record5(value)) {
|
|
2079
3059
|
return usable(value) ? { source: value, path, variant: "default", slot } : null;
|
|
2080
3060
|
}
|
|
2081
|
-
const set =
|
|
3061
|
+
const set = record5(value);
|
|
2082
3062
|
if (!Object.keys(set).some((key) => LOGO_SET_KEYS.has(key))) return null;
|
|
2083
3063
|
for (const key of chain) {
|
|
2084
3064
|
const variant = set[key];
|
|
@@ -2086,16 +3066,16 @@ function resolveLogo(presentation, slide, options = {}) {
|
|
|
2086
3066
|
}
|
|
2087
3067
|
return null;
|
|
2088
3068
|
};
|
|
2089
|
-
const deck =
|
|
2090
|
-
const
|
|
2091
|
-
if (
|
|
2092
|
-
const shared = pick(
|
|
3069
|
+
const deck = record5(presentation), slideIndex = Number.isSafeInteger(options.slideIndex) && options.slideIndex >= 0 ? options.slideIndex : 0;
|
|
3070
|
+
const own2 = pick(record5(record5(slide).design).logo, `slides.${slideIndex}.design.logo`);
|
|
3071
|
+
if (own2) return own2;
|
|
3072
|
+
const shared = pick(record5(deck.design).logo, "design.logo");
|
|
2093
3073
|
if (shared) return shared;
|
|
2094
3074
|
const organizations = Array.isArray(deck.organization) ? deck.organization : [deck.organization];
|
|
2095
|
-
const primaryIndex = organizations.findIndex((item) =>
|
|
3075
|
+
const primaryIndex = organizations.findIndex((item) => record5(item).role === "primary");
|
|
2096
3076
|
const index = primaryIndex >= 0 ? primaryIndex : organizations.findIndex(Boolean);
|
|
2097
3077
|
if (index < 0) return null;
|
|
2098
|
-
return pick(
|
|
3078
|
+
return pick(record5(organizations[index]).logo, Array.isArray(deck.organization) ? `organization.${index}.logo` : "organization.logo");
|
|
2099
3079
|
}
|
|
2100
3080
|
var webUrl = /^https?:\/\/\S+$/i;
|
|
2101
3081
|
function resolveSocialProfile(platform, value, records = [], owner = "organization") {
|
|
@@ -2113,8 +3093,8 @@ function resolveSocialProfile(platform, value, records = [], owner = "organizati
|
|
|
2113
3093
|
return { text: display(url), href: encodeURI(url), resolved: true };
|
|
2114
3094
|
}
|
|
2115
3095
|
function layoutFurniture(input, options = {}) {
|
|
2116
|
-
const slide =
|
|
2117
|
-
const settings = { ...
|
|
3096
|
+
const slide = record5(input), width = options.width ?? 1280, height = options.height ?? 720, scale = Math.min(width, height) / 720;
|
|
3097
|
+
const settings = { ...record5(record5(options.layout).composition), ...record5(slide.composition) };
|
|
2118
3098
|
assertComposition(settings);
|
|
2119
3099
|
const furnitureRtl = (options.direction ?? resolveSlideDirection(options.presentation, options.slideIndex)) === "rtl";
|
|
2120
3100
|
const minimum = snapFontSizeUp((settings.minFontSize ?? 16) * scale), size = gridFontSize(13 * scale, minimum), padding = (options.textRasterPadding ?? 2) * scale;
|
|
@@ -2126,14 +3106,14 @@ function layoutFurniture(input, options = {}) {
|
|
|
2126
3106
|
if (options.date !== void 0 && (typeof options.date !== "string" || !parseIsoDate(options.date))) throw new RangeError("The furniture date option must be an ISO YYYY-MM-DD calendar date.");
|
|
2127
3107
|
const outlines = options.textMeasurement?.outlineBounds !== void 0, parts = [], diagnostics = [];
|
|
2128
3108
|
const sourceRoot = `slides.${options.slideIndex ?? 0}`, organizations = Array.isArray(options.presentation?.organization) ? options.presentation.organization : [options.presentation?.organization];
|
|
2129
|
-
const primaryIndex = organizations.findIndex((item) =>
|
|
2130
|
-
const organization =
|
|
2131
|
-
const inlinePlatforms =
|
|
3109
|
+
const primaryIndex = organizations.findIndex((item) => record5(item).role === "primary"), organizationIndex = primaryIndex >= 0 ? primaryIndex : organizations.findIndex(Boolean);
|
|
3110
|
+
const organization = record5(organizations[organizationIndex]), organizationRoot = Array.isArray(options.presentation?.organization) ? `organization.${organizationIndex}` : "organization", organizationPath = `${organizationRoot}.name`;
|
|
3111
|
+
const inlinePlatforms = record5(record5(options.presentation?.catalogs).socialPlatforms).records, platformRecords = [...Array.isArray(inlinePlatforms) ? inlinePlatforms : [], ...options.socialPlatforms ?? []];
|
|
2132
3112
|
const fontFamily = options.fonts?.body ?? "sans-serif";
|
|
2133
3113
|
let headerBottom = 0, footerTop = height, configured = false;
|
|
2134
3114
|
const error = (path, message, code = "text-overflow") => diagnostics.push({ code, path, message });
|
|
2135
3115
|
for (const kind2 of ["header", "footer"]) {
|
|
2136
|
-
const local =
|
|
3116
|
+
const local = record5(slide.design)[kind2] !== void 0, value = local ? record5(slide.design)[kind2] : options.presentation?.design?.[kind2];
|
|
2137
3117
|
if (value === void 0 || value === false) continue;
|
|
2138
3118
|
const root = local ? `${sourceRoot}.design.${kind2}` : `design.${kind2}`;
|
|
2139
3119
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("Header/footer content must be an object or false.");
|
|
@@ -2141,7 +3121,7 @@ function layoutFurniture(input, options = {}) {
|
|
|
2141
3121
|
const zones = [];
|
|
2142
3122
|
for (const [index, zone] of ["left", "center", "right"].entries()) {
|
|
2143
3123
|
const side = furnitureRtl && zone !== "center" ? zone === "left" ? "right" : "left" : zone;
|
|
2144
|
-
const content =
|
|
3124
|
+
const content = record5(record5(value)[zone]), path = `${root}.${zone}`, x = width * (0.07 + (furnitureRtl ? 2 - index : index) * 0.3), zoneWidth = width * 0.26, zoneParts = [];
|
|
2145
3125
|
let y = 0;
|
|
2146
3126
|
const add = (field, text2, generated = false, sourcePath, extras = {}) => {
|
|
2147
3127
|
if (text2 === void 0) return;
|
|
@@ -2151,10 +3131,10 @@ function layoutFurniture(input, options = {}) {
|
|
|
2151
3131
|
const ink = fit.sourceLines.map((line, index2) => {
|
|
2152
3132
|
let outline = null;
|
|
2153
3133
|
if (outlines) {
|
|
2154
|
-
for (const
|
|
2155
|
-
const bounds = measureTextOutline(text2.slice(
|
|
3134
|
+
for (const segment2 of line.segments) if (segment2.kind === "text") {
|
|
3135
|
+
const bounds = measureTextOutline(text2.slice(segment2.start, segment2.end), size, style, options.textMeasurement);
|
|
2156
3136
|
if (bounds) {
|
|
2157
|
-
const next = { ...bounds, x: bounds.x +
|
|
3137
|
+
const next = { ...bounds, x: bounds.x + segment2.x };
|
|
2158
3138
|
if (!outline) outline = next;
|
|
2159
3139
|
else {
|
|
2160
3140
|
const right = Math.max(outline.x + outline.width, next.x + next.width), bottom = Math.max(outline.y + outline.height, next.y + next.height);
|
|
@@ -2178,7 +3158,7 @@ function layoutFurniture(input, options = {}) {
|
|
|
2178
3158
|
y += box.height;
|
|
2179
3159
|
};
|
|
2180
3160
|
const imageBox = (source) => {
|
|
2181
|
-
const imageHeight = Math.max(32 * scale, Math.min(height * 0.05, 72 * scale)), imageWidth = Math.min(zoneWidth, imageHeight * (intrinsicImageAspect(source,
|
|
3161
|
+
const imageHeight = Math.max(32 * scale, Math.min(height * 0.05, 72 * scale)), imageWidth = Math.min(zoneWidth, imageHeight * (intrinsicImageAspect(source, record5(options.presentation).assets) ?? 1));
|
|
2182
3162
|
return { x: side === "left" ? x : side === "center" ? x + (zoneWidth - imageWidth) / 2 : x + zoneWidth - imageWidth, y, width: imageWidth, height: imageHeight };
|
|
2183
3163
|
};
|
|
2184
3164
|
if (content.logo === true) {
|
|
@@ -2200,7 +3180,7 @@ function layoutFurniture(input, options = {}) {
|
|
|
2200
3180
|
else error(`${path}.organization`, "Generated organization name needs a named organization in the presentation.", "unresolved-content");
|
|
2201
3181
|
}
|
|
2202
3182
|
if (content.socials === true) {
|
|
2203
|
-
const links = Object.entries(
|
|
3183
|
+
const links = Object.entries(record5(organization.socials)).filter(([, value2]) => typeof value2 === "string" && value2.trim()).map(([platform, value2]) => ({ platform, sourcePath: `${organizationRoot}.socials.${platform}`, ...resolveSocialProfile(platform, value2, platformRecords, "organization") }));
|
|
2204
3184
|
if (links.length) add("socials", links.map((link) => link.text).join("\n"), true, `${organizationRoot}.socials`, { links });
|
|
2205
3185
|
else error(`${path}.socials`, "Generated social profiles need a primary organization with socials.", "unresolved-content");
|
|
2206
3186
|
}
|
|
@@ -2443,11 +3423,11 @@ function layoutMetric(value, box, options = {}) {
|
|
|
2443
3423
|
const gap = 8 * scale, primaryGap = 12 * scale;
|
|
2444
3424
|
const lineInk = (part, fit) => fit.sourceLines.map((line, index) => {
|
|
2445
3425
|
let outline = null;
|
|
2446
|
-
for (const
|
|
2447
|
-
if (
|
|
2448
|
-
const bounds = measureTextOutline(part.text.slice(
|
|
3426
|
+
for (const segment2 of line.segments) {
|
|
3427
|
+
if (segment2.kind !== "text") continue;
|
|
3428
|
+
const bounds = measureTextOutline(part.text.slice(segment2.start, segment2.end), fit.fontSize, part.style, options.textMeasurement);
|
|
2449
3429
|
if (!bounds) continue;
|
|
2450
|
-
const next = { ...bounds, x:
|
|
3430
|
+
const next = { ...bounds, x: segment2.x + bounds.x };
|
|
2451
3431
|
if (!outline) outline = next;
|
|
2452
3432
|
else {
|
|
2453
3433
|
const right = Math.max(outline.x + outline.width, next.x + next.width), bottom = Math.max(outline.y + outline.height, next.y + next.height);
|
|
@@ -2602,13 +3582,13 @@ function layoutTimeline(value, box, options = {}) {
|
|
|
2602
3582
|
const ink = fit.sourceLines.map((line, index) => {
|
|
2603
3583
|
let left = Infinity, top = Infinity, right = -Infinity, bottom = -Infinity, hasInk = false;
|
|
2604
3584
|
if (outlines) {
|
|
2605
|
-
for (const
|
|
2606
|
-
const outline = measureTextOutline(part.text.slice(
|
|
3585
|
+
for (const segment2 of line.segments) if (segment2.kind === "text") {
|
|
3586
|
+
const outline = measureTextOutline(part.text.slice(segment2.start, segment2.end), size, part.style, options.textMeasurement);
|
|
2607
3587
|
if (outline) {
|
|
2608
3588
|
hasInk = true;
|
|
2609
|
-
left = Math.min(left, outline.x +
|
|
3589
|
+
left = Math.min(left, outline.x + segment2.x);
|
|
2610
3590
|
top = Math.min(top, outline.y);
|
|
2611
|
-
right = Math.max(right, outline.x +
|
|
3591
|
+
right = Math.max(right, outline.x + segment2.x + outline.width);
|
|
2612
3592
|
bottom = Math.max(bottom, outline.y + outline.height);
|
|
2613
3593
|
}
|
|
2614
3594
|
}
|
|
@@ -2701,21 +3681,21 @@ function fitSourceText(text2, box, size, minimum, step, measure, prose = false,
|
|
|
2701
3681
|
const layout = (fontSize) => {
|
|
2702
3682
|
const tabWidth = measure(" ", fontSize) * 4;
|
|
2703
3683
|
if (!Number.isFinite(tabWidth) || text2.includes(" ") && tabWidth <= 0) throw new RangeError("Text tabs require a positive finite measured space advance.");
|
|
2704
|
-
const measureLine = (start2, end2,
|
|
3684
|
+
const measureLine = (start2, end2, record6 = false) => {
|
|
2705
3685
|
let x = 0, offset = start2;
|
|
2706
3686
|
const segments = [];
|
|
2707
|
-
for (const [index,
|
|
3687
|
+
for (const [index, segment2] of text2.slice(start2, end2).split(" ").entries()) {
|
|
2708
3688
|
if (index) {
|
|
2709
3689
|
const next = (Math.floor(x / tabWidth + 1e-9) + 1) * tabWidth;
|
|
2710
|
-
if (
|
|
3690
|
+
if (record6) segments.push({ kind: "tab", start: offset, end: offset + 1, x, width: next - x });
|
|
2711
3691
|
x = next;
|
|
2712
3692
|
offset++;
|
|
2713
3693
|
}
|
|
2714
|
-
if (
|
|
2715
|
-
const advance = measure(
|
|
2716
|
-
if (
|
|
3694
|
+
if (segment2) {
|
|
3695
|
+
const advance = measure(segment2, fontSize);
|
|
3696
|
+
if (record6) segments.push({ kind: "text", start: offset, end: offset + segment2.length, x, width: advance });
|
|
2717
3697
|
x += advance;
|
|
2718
|
-
offset +=
|
|
3698
|
+
offset += segment2.length;
|
|
2719
3699
|
}
|
|
2720
3700
|
}
|
|
2721
3701
|
return { width: x, segments };
|
|
@@ -2757,9 +3737,9 @@ function fitSourceText(text2, box, size, minimum, step, measure, prose = false,
|
|
|
2757
3737
|
end = b;
|
|
2758
3738
|
continue;
|
|
2759
3739
|
}
|
|
2760
|
-
for (const
|
|
2761
|
-
const next = a +
|
|
2762
|
-
if (end > start && width(start, next) > box.width + 0.01) push(a +
|
|
3740
|
+
for (const segment2 of textSegments.segment(token[0])) {
|
|
3741
|
+
const next = a + segment2.index + segment2.segment.length;
|
|
3742
|
+
if (end > start && width(start, next) > box.width + 0.01) push(a + segment2.index, a + segment2.index, "soft");
|
|
2763
3743
|
end = next;
|
|
2764
3744
|
}
|
|
2765
3745
|
}
|
|
@@ -2897,7 +3877,7 @@ function richTextLayouter(input, box, requestedSize, options, minimum = 0) {
|
|
|
2897
3877
|
const size = script ? paint(normalSize * 0.7) : paint(normalSize), baselineShift = entry.run.superscript ? -normalSize * 0.35 : entry.run.subscript ? normalSize * 0.2 : 0;
|
|
2898
3878
|
const measure = textWidthMeasurer(entry.style, options.textMeasurement);
|
|
2899
3879
|
let cursor = 0;
|
|
2900
|
-
for (const [index,
|
|
3880
|
+
for (const [index, segment2] of text2.split(" ").entries()) {
|
|
2901
3881
|
if (index) {
|
|
2902
3882
|
const tabStart = a + cursor, tabWidth = measure(" ", size) * 4;
|
|
2903
3883
|
if (!Number.isFinite(tabWidth) || tabWidth <= 0) throw new RangeError("Rich text tabs require a positive finite measured space advance.");
|
|
@@ -2906,11 +3886,11 @@ function richTextLayouter(input, box, requestedSize, options, minimum = 0) {
|
|
|
2906
3886
|
x = next;
|
|
2907
3887
|
cursor++;
|
|
2908
3888
|
}
|
|
2909
|
-
if (
|
|
2910
|
-
const width2 = measure(
|
|
2911
|
-
result.push({ text:
|
|
3889
|
+
if (segment2) {
|
|
3890
|
+
const width2 = measure(segment2, size), segmentStart = a + cursor;
|
|
3891
|
+
result.push({ text: segment2, runIndex: entry.runIndex, start: segmentStart - entry.start, end: segmentStart - entry.start + segment2.length, x, width: width2, fontSize: size, baselineShift, style: entry.style, run: entry.run });
|
|
2912
3892
|
x += width2;
|
|
2913
|
-
cursor +=
|
|
3893
|
+
cursor += segment2.length;
|
|
2914
3894
|
}
|
|
2915
3895
|
}
|
|
2916
3896
|
if (entry.marker && b === entry.end) {
|
|
@@ -2953,8 +3933,8 @@ function richTextLayouter(input, box, requestedSize, options, minimum = 0) {
|
|
|
2953
3933
|
lineEnd = end;
|
|
2954
3934
|
continue;
|
|
2955
3935
|
}
|
|
2956
|
-
for (const { segment, index } of textSegments.segment(match[0])) {
|
|
2957
|
-
const next = start + index +
|
|
3936
|
+
for (const { segment: segment2, index } of textSegments.segment(match[0])) {
|
|
3937
|
+
const next = start + index + segment2.length;
|
|
2958
3938
|
if (lineEnd > lineStart && width(lineStart, next) > box.width + WRAP_TOLERANCE) push(start + index);
|
|
2959
3939
|
lineEnd = next;
|
|
2960
3940
|
}
|
|
@@ -3069,19 +4049,26 @@ function fitList(input, box, requestedSize = 25, minFontSize = 16, options = { s
|
|
|
3069
4049
|
function contentText(field, value) {
|
|
3070
4050
|
if (field === "text" || headings.has(field)) return flatten(value);
|
|
3071
4051
|
if (field === "items" || field === "bullets") return (Array.isArray(value) ? value : [value]).map((item) => {
|
|
3072
|
-
const entry =
|
|
4052
|
+
const entry = record5(item);
|
|
3073
4053
|
return `\u2022 ${flatten(item)}${entry.description ? `
|
|
3074
4054
|
${flatten(entry.description)}` : ""}`;
|
|
3075
4055
|
}).join("\n");
|
|
3076
|
-
if (field === "code") return flatten(
|
|
3077
|
-
if (field === "quote") return flatten(
|
|
4056
|
+
if (field === "code") return flatten(record5(value).source ?? value);
|
|
4057
|
+
if (field === "quote") return flatten(record5(value).text ?? value);
|
|
3078
4058
|
return void 0;
|
|
3079
4059
|
}
|
|
3080
4060
|
function layoutTable(value, box, options = {}) {
|
|
3081
4061
|
const scale = options.scale ?? 1, minimum = snapFontSizeUp((options.minFontSize ?? 16) * scale), requested = gridFontSize(15 * scale, minimum);
|
|
3082
4062
|
if (![box.x, box.y, box.width, box.height, scale, minimum].every(Number.isFinite) || box.width <= 0 || box.height <= 0 || scale <= 0 || minimum <= 0) throw new RangeError("Table dimensions, scale and font sizes must be finite and positive.");
|
|
3083
|
-
const
|
|
4063
|
+
const inline = inlineTableData(value, options.presentation);
|
|
4064
|
+
const grid = tableGrid(inline, options.path ?? "table");
|
|
3084
4065
|
if (grid.issues.length) throw new RangeError(`${grid.issues[0].path}: ${grid.issues[0].message}`);
|
|
4066
|
+
const formats = resolveTableData(inline).formats;
|
|
4067
|
+
for (const row of grid.rows) for (const cell of row) {
|
|
4068
|
+
if (cell.header) continue;
|
|
4069
|
+
const shown = tableCellDisplayValue(cell.input, formats[cell.column]);
|
|
4070
|
+
if (shown !== cell.input) cell.value = typeof shown === "object" && shown !== null && !Array.isArray(shown) ? shown.value : shown;
|
|
4071
|
+
}
|
|
3085
4072
|
const columnCount = grid.columnCount, cellWidth = box.width / columnCount;
|
|
3086
4073
|
const cells = grid.rows.flat().map((cell) => {
|
|
3087
4074
|
const padding = { top: 8, right: 10, bottom: 4, left: 10, ...cell.style.padding };
|
|
@@ -3132,13 +4119,13 @@ function layoutTable(value, box, options = {}) {
|
|
|
3132
4119
|
return { rows: rows2, columnCount, height: sum(heights), overflow };
|
|
3133
4120
|
}
|
|
3134
4121
|
function tableOverflows(value, box, scale, settings, options, path) {
|
|
3135
|
-
return box.width <= 0 || box.height <= 0 || layoutTable(value, box, { scale, minFontSize: settings.minFontSize, fontFamily: options.fonts?.body, textMeasurement: options.textMeasurement, path }).overflow;
|
|
4122
|
+
return box.width <= 0 || box.height <= 0 || layoutTable(value, box, { scale, minFontSize: settings.minFontSize, fontFamily: options.fonts?.body, textMeasurement: options.textMeasurement, path, presentation: options.presentation }).overflow;
|
|
3136
4123
|
}
|
|
3137
4124
|
function flatten(value) {
|
|
3138
4125
|
if (value == null) return "";
|
|
3139
4126
|
if (typeof value !== "object") return String(value);
|
|
3140
4127
|
if (Array.isArray(value)) return value.map(flatten).join("");
|
|
3141
|
-
const item =
|
|
4128
|
+
const item = record5(value);
|
|
3142
4129
|
return flatten(item.text ?? item.value ?? item.source ?? JSON.stringify(value));
|
|
3143
4130
|
}
|
|
3144
4131
|
function regionParts(key) {
|
|
@@ -3194,14 +4181,14 @@ function assertComposition(value) {
|
|
|
3194
4181
|
if (value.overflow !== void 0 && !["warn", "error"].includes(value.overflow)) throw new RangeError("Invalid composition.overflow.");
|
|
3195
4182
|
}
|
|
3196
4183
|
function composeSlide(input, options = {}) {
|
|
3197
|
-
const slide =
|
|
4184
|
+
const slide = record5(input), layout = record5(options.layout);
|
|
3198
4185
|
const width = options.width ?? 1280, height = options.height ?? 720;
|
|
3199
4186
|
if (![width, height].every((value) => Number.isFinite(value) && value > 0)) throw new RangeError("Canvas dimensions must be finite and positive.");
|
|
3200
4187
|
const scale = Math.min(width, height) / 720;
|
|
3201
4188
|
const deckDirection = options.direction ?? resolveSlideDirection(options.presentation, options.slideIndex);
|
|
3202
4189
|
const rtl = deckDirection === "rtl", textDirection = rtl ? "rtl" : void 0;
|
|
3203
|
-
const hasCards =
|
|
3204
|
-
const composition = { ...
|
|
4190
|
+
const hasCards = record5(slide.design).contentBox ?? options.contentBox ?? false;
|
|
4191
|
+
const composition = { ...record5(layout.composition), ...record5(slide.composition) };
|
|
3205
4192
|
assertComposition(composition);
|
|
3206
4193
|
const padding = (composition.padding ?? 0.08) * Math.min(width, height);
|
|
3207
4194
|
const gap = (composition.gap ?? 1 / 30) * Math.min(width, height);
|
|
@@ -3209,13 +4196,13 @@ function composeSlide(input, options = {}) {
|
|
|
3209
4196
|
const rasterPadding = (options.textRasterPadding ?? 1) * scale;
|
|
3210
4197
|
if (!Number.isFinite(rasterPadding) || rasterPadding < 0) throw new RangeError("Text raster padding must be finite and nonnegative.");
|
|
3211
4198
|
let coverGroup = false;
|
|
3212
|
-
const alignmentFor = (field) => (field === "title" || coverGroup && (field === "tag" || field === "subtitle") &&
|
|
4199
|
+
const alignmentFor = (field) => (field === "title" || coverGroup && (field === "tag" || field === "subtitle") && record5(slide.design).contentAlignment === void 0 ? record5(slide.design).titleAlignment ?? options.titleAlignment : record5(slide.design).contentAlignment ?? options.contentAlignment) ?? "left";
|
|
3213
4200
|
const styleFor = (field, path2) => resolveTextStyle({
|
|
3214
4201
|
fontFamily: (field === "title" ? options.fonts?.heading : field === "code" ? options.fonts?.code : field === "tag" ? options.fonts?.accent ?? options.fonts?.body : options.fonts?.body) ?? (field === "code" ? "monospace" : "sans-serif"),
|
|
3215
4202
|
fontWeight: field === "title" ? 700 : 400,
|
|
3216
4203
|
path: path2
|
|
3217
4204
|
}, options.textMeasurement);
|
|
3218
|
-
const slideDesign =
|
|
4205
|
+
const slideDesign = record5(slide.design), deckDesign = record5(record5(options.presentation).design);
|
|
3219
4206
|
const designHint = (field) => slideDesign[field] !== void 0 ? { value: slideDesign[field], path: `slides.${options.slideIndex ?? 0}.design.${field}` } : deckDesign[field] !== void 0 ? { value: deckDesign[field], path: `design.${field}` } : void 0;
|
|
3220
4207
|
const listBullet = designHint("listBullet");
|
|
3221
4208
|
const bulletImage = listBullet?.value === "image" ? (() => {
|
|
@@ -3255,11 +4242,11 @@ function composeSlide(input, options = {}) {
|
|
|
3255
4242
|
return { width: line.width, y: line.y, baseline: line.baseline, height: line.height, outline };
|
|
3256
4243
|
}) : fit.sourceLines.map((line, index) => {
|
|
3257
4244
|
let outline = null;
|
|
3258
|
-
for (const
|
|
3259
|
-
if (
|
|
3260
|
-
const bounds = measureTextOutline(text2.slice(
|
|
4245
|
+
for (const segment2 of line.segments) {
|
|
4246
|
+
if (segment2.kind === "tab") continue;
|
|
4247
|
+
const bounds = measureTextOutline(text2.slice(segment2.start, segment2.end), fontSize, style, options.textMeasurement);
|
|
3261
4248
|
if (!bounds) continue;
|
|
3262
|
-
const next = { ...bounds, x:
|
|
4249
|
+
const next = { ...bounds, x: segment2.x + bounds.x };
|
|
3263
4250
|
if (!outline) outline = next;
|
|
3264
4251
|
else {
|
|
3265
4252
|
const right = Math.max(outline.x + outline.width, next.x + next.width), bottom = Math.max(outline.y + outline.height, next.y + next.height);
|
|
@@ -3304,7 +4291,7 @@ function composeSlide(input, options = {}) {
|
|
|
3304
4291
|
const regions = Object.keys(slide).filter((key) => regionParts(key)).sort();
|
|
3305
4292
|
const emptyPayload = (value) => value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
|
|
3306
4293
|
const emptyHost = (host) => emptyPayload(host.blocks) && fields.every((field) => emptyPayload(host[field]));
|
|
3307
|
-
const hasBodyPayload = regions.some((key) => !emptyHost(
|
|
4294
|
+
const hasBodyPayload = regions.some((key) => !emptyHost(record5(slide[key]))) || !emptyPayload(slide.blocks) || fields.some((field) => !emptyPayload(slide[field]));
|
|
3308
4295
|
const isCover = !hasBodyPayload && (headingOnlyLayout || !layout.id && !layoutPlaceholders.some((placeholder) => !headings.has(placeholder.type ?? "")));
|
|
3309
4296
|
coverGroup = isCover;
|
|
3310
4297
|
let logo;
|
|
@@ -3347,7 +4334,7 @@ function composeSlide(input, options = {}) {
|
|
|
3347
4334
|
const collect = (host, basePath, depth = 0, ancestors = []) => {
|
|
3348
4335
|
if (Array.isArray(host.blocks)) {
|
|
3349
4336
|
if (depth >= MAX_COMPOSITION_DEPTH || ancestors.includes(host)) throw new RangeError(`Content groups must be acyclic and nest at most ${MAX_COMPOSITION_DEPTH} levels.`);
|
|
3350
|
-
const settings =
|
|
4337
|
+
const settings = record5(host.composition);
|
|
3351
4338
|
assertComposition(settings);
|
|
3352
4339
|
return [{
|
|
3353
4340
|
field: "blocks",
|
|
@@ -3356,27 +4343,28 @@ function composeSlide(input, options = {}) {
|
|
|
3356
4343
|
path: basePath,
|
|
3357
4344
|
payload: host,
|
|
3358
4345
|
composition: settings,
|
|
3359
|
-
children: host.blocks.flatMap((block, index) => collect(
|
|
4346
|
+
children: host.blocks.flatMap((block, index) => collect(record5(block), `${basePath}.blocks.${index}`, depth + 1, [...ancestors, host]))
|
|
3360
4347
|
}];
|
|
3361
4348
|
}
|
|
3362
4349
|
const captioned = host.caption !== void 0 ? fields.filter((field) => host[field] !== void 0 && CAPTIONABLE_FIELDS.includes(field)) : [];
|
|
4350
|
+
const fieldValue = (field) => field === "table" ? inlineTableData(host[field], options.presentation) : field === "chart" ? inlineChartData(host[field], options.presentation) : host[field];
|
|
3363
4351
|
return fields.filter((field) => host[field] !== void 0).map((field) => ({
|
|
3364
4352
|
field,
|
|
3365
4353
|
type: host.type ?? kind(field),
|
|
3366
|
-
value:
|
|
4354
|
+
value: fieldValue(field),
|
|
3367
4355
|
path: `${basePath}.${field}`,
|
|
3368
|
-
payload: { type: host.type ?? kind(field), [field]:
|
|
4356
|
+
payload: { type: host.type ?? kind(field), [field]: fieldValue(field), ...host.numbering !== void 0 && (field === "items" || field === "bullets") ? { numbering: host.numbering } : {} },
|
|
3369
4357
|
...captioned.length === 1 && captioned[0] === field ? { caption: host.caption, captionPath: `${basePath}.caption` } : {}
|
|
3370
4358
|
}));
|
|
3371
4359
|
};
|
|
3372
4360
|
const regionsInReadingOrder = visualReadingOrder(regions.map((key) => ({ key, box: regionBox(regionParts(key), contentBox, gap) }))).map((entry) => entry.key);
|
|
3373
|
-
const pending = regions.length ? regionsInReadingOrder.flatMap((key) => collect(
|
|
4361
|
+
const pending = regions.length ? regionsInReadingOrder.flatMap((key) => collect(record5(slide[key]), `${path}.${key}`).map((item) => ({ ...item, region: regionParts(key) }))) : Array.isArray(slide.blocks) ? slide.blocks.flatMap((block, index) => collect(record5(block), `${path}.blocks.${index}`)) : collect(slideImage?.replacesContent ? { ...slide, image: void 0 } : slide, path);
|
|
3374
4362
|
const groups = [], flows = [];
|
|
3375
4363
|
const decisions = options.explain ? [] : void 0;
|
|
3376
|
-
const inheritedSettings = (parent,
|
|
4364
|
+
const inheritedSettings = (parent, own2 = {}) => ({
|
|
3377
4365
|
minFontSize: parent.minFontSize,
|
|
3378
|
-
...
|
|
3379
|
-
overflow: parent.overflow === "error" ? "error" :
|
|
4366
|
+
...own2,
|
|
4367
|
+
overflow: parent.overflow === "error" ? "error" : own2.overflow ?? parent.overflow
|
|
3380
4368
|
});
|
|
3381
4369
|
const inset = (box, settings) => {
|
|
3382
4370
|
const amount = (settings.padding ?? 0) * Math.min(box.width, box.height);
|
|
@@ -3467,10 +4455,10 @@ function composeSlide(input, options = {}) {
|
|
|
3467
4455
|
};
|
|
3468
4456
|
const scoreNode = (node, box, settings, penalties) => {
|
|
3469
4457
|
if (!node.children) return leafScore(node, box, settings, penalties);
|
|
3470
|
-
const
|
|
3471
|
-
const cols = defaultColumns(node.children.length, area2,
|
|
3472
|
-
const boxes = gridBoxes(node.children.length, area2, cols, (
|
|
3473
|
-
return node.children.reduce((score, child, index) => score + scoreNode(child, boxes[index],
|
|
4458
|
+
const own2 = inheritedSettings(settings, node.composition), area2 = inset(box, own2);
|
|
4459
|
+
const cols = defaultColumns(node.children.length, area2, own2);
|
|
4460
|
+
const boxes = gridBoxes(node.children.length, area2, cols, (own2.gap ?? 1 / 30) * Math.min(box.width, box.height), own2.weights ?? [], modeFor(own2) === "column");
|
|
4461
|
+
return node.children.reduce((score, child, index) => score + scoreNode(child, boxes[index], own2, penalties), 0);
|
|
3474
4462
|
};
|
|
3475
4463
|
const arrange = (nodes, area2, settings, reserved = 0, gapOverride, containerPath = path, recorded = true) => {
|
|
3476
4464
|
const count = Math.max(nodes.length, reserved);
|
|
@@ -3508,9 +4496,9 @@ function composeSlide(input, options = {}) {
|
|
|
3508
4496
|
nodes.forEach((node, index) => {
|
|
3509
4497
|
let box = node.region ? mirrored(regionBox(node.region, area2, actualGap)) : boxes[index];
|
|
3510
4498
|
if (node.children) {
|
|
3511
|
-
const
|
|
3512
|
-
if (!node.synthetic) groups.push({ path: node.path, box, contentBox: inner, composition:
|
|
3513
|
-
arrange(node.children, inner,
|
|
4499
|
+
const own2 = inheritedSettings(settings, node.composition), inner = inset(box, own2);
|
|
4500
|
+
if (!node.synthetic) groups.push({ path: node.path, box, contentBox: inner, composition: own2 });
|
|
4501
|
+
arrange(node.children, inner, own2, 0, (own2.gap ?? 1 / 30) * Math.min(box.width, box.height), node.path, !node.synthetic);
|
|
3514
4502
|
} else {
|
|
3515
4503
|
const frameBox = hasCards ? acceptedBox(box) : void 0;
|
|
3516
4504
|
box = payloadBox(box);
|
|
@@ -3554,7 +4542,7 @@ function composeSlide(input, options = {}) {
|
|
|
3554
4542
|
const picture = placeholders.findIndex((p) => p.type === "picture");
|
|
3555
4543
|
if (picture >= 0) placeholders.splice(picture, 1);
|
|
3556
4544
|
}
|
|
3557
|
-
const ownMode =
|
|
4545
|
+
const ownMode = record5(slide.composition).mode;
|
|
3558
4546
|
const direction = designHint("contentDirection")?.value;
|
|
3559
4547
|
const directionMode = direction === "vertical" ? "column" : direction === "horizontal" ? "row" : void 0;
|
|
3560
4548
|
const layoutDirectionMode = layout.slideLayoutDirection === "Vertical" ? "column" : layout.slideLayoutDirection === "Horizontal" ? "row" : "auto";
|
|
@@ -3619,7 +4607,7 @@ function resolveCanvasDimensions(input) {
|
|
|
3619
4607
|
letter: [11, 8.5],
|
|
3620
4608
|
a4: [11.69, 8.27]
|
|
3621
4609
|
};
|
|
3622
|
-
const value =
|
|
4610
|
+
const value = record5(input);
|
|
3623
4611
|
const preset = presets[typeof input === "string" ? input : value.preset] ?? presets.widescreen;
|
|
3624
4612
|
const width = (value.widthInches ?? preset[0]) * 96;
|
|
3625
4613
|
const height = (value.heightInches ?? preset[1]) * 96;
|
|
@@ -3627,4 +4615,4 @@ function resolveCanvasDimensions(input) {
|
|
|
3627
4615
|
return { width, height };
|
|
3628
4616
|
}
|
|
3629
4617
|
|
|
3630
|
-
export { CAPTIONABLE_FIELDS, CAPTION_FONT_RATIO, CAPTION_MAX_RATIO, CHART_SERIES_MIN_DIFFERENCE, CHART_SERIES_MIN_LIGHTNESS_STEP, CITATION_MARKER_RAISE, CITATION_MARKER_SCALE, DEFAULT_CHART_LABEL_SEPARATOR, DEFAULT_FONT_SCHEME, DEFAULT_FURNITURE_DATE_FORMAT, DEFAULT_SLIDE_NUMBER_FORMAT, FONT_SIZE_GRID_PER_PX, FOOTNOTE_MAX_RATIO, MAX_COMPOSITION_DEPTH, MAX_NUMBERING_LEVELS, MAX_NUMBERING_VALUE, MAX_ROMAN_VALUE, NUMBERING_STYLES, NUMBERING_SUFFIXES, OPFCompositionError, PICTURE_BULLET_SCALE, annotationText, bareIdPattern, captionSettings, catalogIds, chartColorForFill, chartLabelText, chartOptionSupport, chartOptionTarget, chartPaletteForFill, citationMarkerText, collectCitations, colorContrast, composeSlide, deprecatedCatalogIds, fitList, fitRichText, fitText, formatChartLabelNumber, formatChartLabelPercent, formatFurnitureDate, formatListNumber, formatSlideNumber, intrinsicImageSize,
|
|
4618
|
+
export { CAPTIONABLE_FIELDS, CAPTION_FONT_RATIO, CAPTION_MAX_RATIO, CHART_SERIES_MIN_DIFFERENCE, CHART_SERIES_MIN_LIGHTNESS_STEP, CITATION_MARKER_RAISE, CITATION_MARKER_SCALE, DEFAULT_CHART_LABEL_SEPARATOR, DEFAULT_FONT_SCHEME, DEFAULT_FURNITURE_DATE_FORMAT, DEFAULT_SLIDE_NUMBER_FORMAT, DEFAULT_VARIABLE_DATE_FORMAT, FONT_SIZE_GRID_PER_PX, FOOTNOTE_MAX_RATIO, MAX_COMPOSITION_DEPTH, MAX_NUMBERING_LEVELS, MAX_NUMBERING_VALUE, MAX_ROMAN_VALUE, NUMBERING_STYLES, NUMBERING_SUFFIXES, OPFCompositionError, OPFVariableError, PICTURE_BULLET_SCALE, VARIABLE_KINDS, annotationText, bareIdPattern, captionSettings, catalogIds, chartColorForFill, chartLabelText, chartNumber, chartNumberFixesByCell, chartOptionSupport, chartOptionTarget, chartPaletteForFill, citationMarkerText, coerceVariableValue, collectCitations, colorContrast, composeSlide, datasetDiagnostics, deprecatedCatalogIds, excelNumberFormat, fitList, fitRichText, fitText, formatChartLabelNumber, formatChartLabelPercent, formatDataNumber, formatFurnitureDate, formatListNumber, formatSlideNumber, formatVariableNumber, hasContentVariables, inlineChartData, inlineDatasets, inlineTableData, instantiateForValidation, intrinsicImageSize, isDatasetRef, isRecord, isTemplate, isXYChartType, layoutCaption, layoutCode, layoutFootnotes, layoutFurniture, layoutMetric, layoutQuote, layoutTable, layoutTimeline, listNumbers, listVariables, measureText, measureTextOutline, normalizeHexColor, numberFormatError, numberFormatFromExcel, numberingAtLevel, numberingFindings, numberingStyleDraws, paragraphDirection, paragraphDirectionAt, pathFor, physicalAlignment, placeTextLines, promotedRegionKeys, readingRows, referencesSlide, resolveCanvasDimensions, resolveChartData, resolveChartOptions, resolveColorRef, resolveFontFamilies, resolveFontSchemeReference, resolveLogo, resolveNumbering, resolveScriptFonts, resolveSlideDirection, resolveSocialProfile, resolveTableData, resolveTextStyle, resolveVariables, scriptFontRole, sliceNumberedItems, slideCitations, slideImageShape, snapFontSizeDown, snapFontSizeUp, suggestChartNumberFix, tableCellDisplayValue, tableGrid, tableRowBoundaries, textColorForFill, textWidthMeasurer, unusedDatasets, variableDeclarations, visitContentPayloads, visualReadingOrder, walkCitationRuns, wrapText };
|