@openpresentation/opf 0.11.4 → 0.12.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 +16 -0
- package/dist/audit.d.ts +156 -0
- package/dist/audit.js +7 -0
- package/dist/{catalogs-CUClB3nl.d.ts → catalogs-u6TEeMcz.d.ts} +1 -1
- package/dist/catalogs.d.ts +2 -2
- package/dist/catalogs.js +2 -1
- package/dist/{chunk-NSVVQ52O.js → chunk-32DIB5BN.js} +8 -6
- package/dist/chunk-4FWCY4ZV.js +198 -0
- package/dist/{chunk-3H2U7FDR.js → chunk-6TSDIPSH.js} +1861 -247
- package/dist/chunk-FYLJVF7X.js +18448 -0
- package/dist/{chunk-HJL64ETN.js → chunk-GJKJL4YY.js} +14 -0
- package/dist/chunk-JENJZR3F.js +1690 -0
- package/dist/{chunk-3OJN7QAW.js → chunk-JVTN3HPP.js} +18 -4
- package/dist/{chunk-KJKASEPE.js → chunk-RVN5C2FX.js} +737 -26
- package/dist/chunk-S5W34SIJ.js +1 -0
- package/dist/chunk-S667QI3M.js +261 -0
- package/dist/chunk-UQKJWHSS.js +1332 -0
- package/dist/{chunk-RXNFDGPC.js → chunk-WXSPETQ6.js} +311 -40
- package/dist/{composition-Dl-d9FRj.d.ts → composition-DWbCiMwF.d.ts} +559 -10
- package/dist/composition.d.ts +1 -1
- package/dist/composition.js +2 -1
- package/dist/convert.d.ts +261 -0
- package/dist/convert.js +1107 -0
- package/dist/diff.d.ts +145 -0
- package/dist/diff.js +604 -0
- package/dist/docs.js +68 -14
- package/dist/examples.d.ts +1 -1
- package/dist/font-policy.d.ts +15 -0
- package/dist/font-policy.js +1 -1
- package/dist/format.d.ts +20 -0
- package/dist/format.js +83 -0
- package/dist/index.d.ts +173 -24
- package/dist/index.js +926 -262
- package/dist/lint.d.ts +4 -4
- package/dist/lint.js +6 -5
- package/dist/markdown.d.ts +74 -0
- package/dist/markdown.js +1941 -0
- package/dist/pagination.d.ts +1 -1
- package/dist/pagination.js +6 -5
- package/dist/patch.d.ts +99 -0
- package/dist/patch.js +6 -0
- package/dist/{presentation-j5PVrqfV.d.ts → presentation-DRx4hpNZ.d.ts} +451 -16
- package/dist/repo-readme.js +1 -1
- package/dist/{schemas-X_NniU4A.d.ts → schemas-BSjlG8-R.d.ts} +28 -0
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.js +1 -1
- package/dist/spec/reference/font-policy.json +311 -40
- package/dist/spec/reference/font-policy.schema.json +40 -0
- package/dist/spec/reference/symbol-font-encodings.json +18354 -0
- package/dist/spec/reference/symbol-font-encodings.schema.json +156 -0
- package/dist/spec/schemas/opf.schema.json +591 -22
- package/dist/spec-files.d.ts +1 -1
- package/dist/spec-files.js +1 -1
- package/dist/symbol-font-encodings.d.ts +128 -0
- package/dist/symbol-font-encodings.js +1 -0
- package/dist/types.d.ts +4 -4
- package/dist/validator-bu396ffT.d.ts +172 -0
- package/dist/validator.d.ts +4 -36
- package/dist/validator.js +5 -4
- package/package.json +31 -2
- package/dist/chunk-FDCWQHWO.js +0 -1366
- /package/dist/{chunk-AEUSVEUY.js → chunk-3ZGSWEHK.js} +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { catalogs } from './chunk-3ZGSWEHK.js';
|
|
2
|
+
|
|
1
3
|
// src/table.ts
|
|
2
4
|
var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
3
5
|
function tableGrid(value, path = "table") {
|
|
@@ -49,8 +51,8 @@ function dataBytes(uri, limit) {
|
|
|
49
51
|
const type = (match[1] ?? "").toLowerCase(), start = match[0].length;
|
|
50
52
|
try {
|
|
51
53
|
if (/;base64/i.test(match[2] ?? "")) {
|
|
52
|
-
const chars = Math.ceil(limit / 3) * 4,
|
|
53
|
-
return { type, bytes: Uint8Array.from(atob(
|
|
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)) };
|
|
54
56
|
}
|
|
55
57
|
const prefix = uri.slice(start, start + limit).replace(/%[0-9a-f]?$/i, "");
|
|
56
58
|
return { type, bytes: new TextEncoder().encode(decodeURIComponent(prefix)) };
|
|
@@ -99,8 +101,8 @@ function rasterDimensions(b) {
|
|
|
99
101
|
}
|
|
100
102
|
return void 0;
|
|
101
103
|
}
|
|
102
|
-
function svgDimensions(
|
|
103
|
-
const root = /<svg\b([^>]*)>/i.exec(
|
|
104
|
+
function svgDimensions(text2) {
|
|
105
|
+
const root = /<svg\b([^>]*)>/i.exec(text2)?.[1];
|
|
104
106
|
if (root === void 0) return void 0;
|
|
105
107
|
const attribute = (name) => new RegExp(`\\s${name}\\s*=\\s*["']([^"']*)["']`, "i").exec(root)?.[1];
|
|
106
108
|
const length = (value) => {
|
|
@@ -118,7 +120,7 @@ function svgDimensions(text) {
|
|
|
118
120
|
}
|
|
119
121
|
var cache = /* @__PURE__ */ new Map();
|
|
120
122
|
var CACHE_ENTRIES = 64;
|
|
121
|
-
function
|
|
123
|
+
function readDimensions(source) {
|
|
122
124
|
const first = dataBytes(source, PREFIX_BYTES);
|
|
123
125
|
if (!first) return void 0;
|
|
124
126
|
let dimensions;
|
|
@@ -130,17 +132,540 @@ function readAspect(source) {
|
|
|
130
132
|
if (more) dimensions = rasterDimensions(more.bytes);
|
|
131
133
|
}
|
|
132
134
|
}
|
|
133
|
-
return dimensions && dimensions.width > 0 && dimensions.height > 0 ? dimensions
|
|
135
|
+
return dimensions && dimensions.width > 0 && dimensions.height > 0 ? dimensions : void 0;
|
|
134
136
|
}
|
|
135
|
-
function
|
|
137
|
+
function intrinsicImageSize(value, assets) {
|
|
136
138
|
let source = sourceOf(value);
|
|
137
139
|
for (let hop = 0; source?.startsWith("asset:") && hop < 8; hop++) source = sourceOf(isRecord(assets) ? assets[source.slice(6)] : void 0);
|
|
138
140
|
if (!source?.startsWith("data:")) return void 0;
|
|
139
|
-
if (cache.has(source))
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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 };
|
|
144
669
|
}
|
|
145
670
|
|
|
146
671
|
// src/furniture-fields.ts
|
|
@@ -152,10 +677,10 @@ var tokenWidths = { y: [1, 2, 4], M: [1, 2, 3, 4], d: [1, 2], E: [1, 2, 3, 4] };
|
|
|
152
677
|
function parseFurnitureDateFormat(pattern) {
|
|
153
678
|
if (!pattern) return "dateFormat must not be empty.";
|
|
154
679
|
const tokens = [];
|
|
155
|
-
const literal = (
|
|
680
|
+
const literal = (text2) => {
|
|
156
681
|
const last = tokens[tokens.length - 1];
|
|
157
|
-
if (last?.kind === "literal") last.text +=
|
|
158
|
-
else tokens.push({ kind: "literal", text });
|
|
682
|
+
if (last?.kind === "literal") last.text += text2;
|
|
683
|
+
else tokens.push({ kind: "literal", text: text2 });
|
|
159
684
|
};
|
|
160
685
|
for (let index = 0; index < pattern.length; ) {
|
|
161
686
|
const character = pattern.charAt(index);
|
|
@@ -165,21 +690,21 @@ function parseFurnitureDateFormat(pattern) {
|
|
|
165
690
|
index += 2;
|
|
166
691
|
continue;
|
|
167
692
|
}
|
|
168
|
-
let end = index + 1,
|
|
693
|
+
let end = index + 1, text2 = "";
|
|
169
694
|
for (; ; ) {
|
|
170
695
|
if (end >= pattern.length) return "dateFormat has an unterminated quoted literal.";
|
|
171
696
|
if (pattern[end] === "'") {
|
|
172
697
|
if (pattern[end + 1] === "'") {
|
|
173
|
-
|
|
698
|
+
text2 += "'";
|
|
174
699
|
end += 2;
|
|
175
700
|
continue;
|
|
176
701
|
}
|
|
177
702
|
break;
|
|
178
703
|
}
|
|
179
|
-
|
|
704
|
+
text2 += pattern[end];
|
|
180
705
|
end++;
|
|
181
706
|
}
|
|
182
|
-
literal(
|
|
707
|
+
literal(text2);
|
|
183
708
|
index = end + 1;
|
|
184
709
|
continue;
|
|
185
710
|
}
|
|
@@ -214,39 +739,39 @@ function formatFurnitureDate(iso, pattern) {
|
|
|
214
739
|
const utc = new Date(Date.UTC(2e3, date.month - 1, date.day));
|
|
215
740
|
utc.setUTCFullYear(date.year);
|
|
216
741
|
const pad = (value, width) => String(value).padStart(width, "0");
|
|
217
|
-
let
|
|
742
|
+
let text2 = "";
|
|
218
743
|
for (const token of tokens) {
|
|
219
744
|
if (token.kind === "literal") {
|
|
220
|
-
|
|
745
|
+
text2 += token.text;
|
|
221
746
|
continue;
|
|
222
747
|
}
|
|
223
748
|
const { letter, width } = token;
|
|
224
|
-
if (letter === "y")
|
|
749
|
+
if (letter === "y") text2 += width === 2 ? pad(date.year % 100, 2) : width === 4 ? pad(date.year, 4) : String(date.year);
|
|
225
750
|
else if (letter === "M") {
|
|
226
751
|
const name = months[date.month - 1] ?? "";
|
|
227
|
-
|
|
228
|
-
} else if (letter === "d")
|
|
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);
|
|
229
754
|
else {
|
|
230
755
|
const name = weekdays[utc.getUTCDay()] ?? "";
|
|
231
|
-
|
|
756
|
+
text2 += width === 4 ? name : name.slice(0, 3);
|
|
232
757
|
}
|
|
233
758
|
}
|
|
234
|
-
return { text };
|
|
759
|
+
return { text: text2 };
|
|
235
760
|
}
|
|
236
761
|
function formatSlideNumber(format, current, total) {
|
|
237
762
|
if (!format.includes("{current}")) return { error: "slideNumberFormat must contain {current}." };
|
|
238
763
|
if (format.includes("{total}") && total === void 0) return { error: "slideNumberFormat uses {total}, which needs the rendered slide count." };
|
|
239
|
-
let
|
|
764
|
+
let text2 = "";
|
|
240
765
|
const fields2 = [];
|
|
241
766
|
for (const piece of format.split(/(\{current\}|\{total\})/)) {
|
|
242
767
|
if (piece === "{current}") {
|
|
243
768
|
const value = String(current);
|
|
244
|
-
fields2.push({ type: "slideNumber", start:
|
|
245
|
-
|
|
246
|
-
} else if (piece === "{total}")
|
|
247
|
-
else
|
|
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;
|
|
248
773
|
}
|
|
249
|
-
return { text, fields: fields2 };
|
|
774
|
+
return { text: text2, fields: fields2 };
|
|
250
775
|
}
|
|
251
776
|
|
|
252
777
|
// src/color.ts
|
|
@@ -358,6 +883,982 @@ function chartColorForFill(fill, preferred) {
|
|
|
358
883
|
}
|
|
359
884
|
return textColorForFill(fill, preferred);
|
|
360
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
|
+
// src/generated/catalog-ids.ts
|
|
1166
|
+
var catalogIds = {
|
|
1167
|
+
audiences: [
|
|
1168
|
+
"executives",
|
|
1169
|
+
"board",
|
|
1170
|
+
"engineering-team",
|
|
1171
|
+
"investors",
|
|
1172
|
+
"customers",
|
|
1173
|
+
"sales-team",
|
|
1174
|
+
"marketing-team",
|
|
1175
|
+
"all-hands",
|
|
1176
|
+
"candidates",
|
|
1177
|
+
"regulators",
|
|
1178
|
+
"executive",
|
|
1179
|
+
"investor",
|
|
1180
|
+
"technical",
|
|
1181
|
+
"sales",
|
|
1182
|
+
"marketing",
|
|
1183
|
+
"academic",
|
|
1184
|
+
"internal-team",
|
|
1185
|
+
"customer",
|
|
1186
|
+
"general-public",
|
|
1187
|
+
"media",
|
|
1188
|
+
"partner",
|
|
1189
|
+
"regulatory"
|
|
1190
|
+
],
|
|
1191
|
+
purposes: [
|
|
1192
|
+
"inform",
|
|
1193
|
+
"decide",
|
|
1194
|
+
"align",
|
|
1195
|
+
"persuade",
|
|
1196
|
+
"educate",
|
|
1197
|
+
"report",
|
|
1198
|
+
"pitch",
|
|
1199
|
+
"sell",
|
|
1200
|
+
"plan"
|
|
1201
|
+
],
|
|
1202
|
+
tones: [
|
|
1203
|
+
"formal",
|
|
1204
|
+
"casual",
|
|
1205
|
+
"inspirational",
|
|
1206
|
+
"technical",
|
|
1207
|
+
"persuasive",
|
|
1208
|
+
"authoritative",
|
|
1209
|
+
"conversational"
|
|
1210
|
+
],
|
|
1211
|
+
themes: [
|
|
1212
|
+
"minimal",
|
|
1213
|
+
"classic",
|
|
1214
|
+
"dark",
|
|
1215
|
+
"bold"
|
|
1216
|
+
],
|
|
1217
|
+
layouts: [
|
|
1218
|
+
"blank",
|
|
1219
|
+
"chart-1x",
|
|
1220
|
+
"chart-2x",
|
|
1221
|
+
"chart-3x",
|
|
1222
|
+
"code-1x",
|
|
1223
|
+
"image-1x",
|
|
1224
|
+
"image-2x",
|
|
1225
|
+
"image-3x",
|
|
1226
|
+
"image-bleed",
|
|
1227
|
+
"list-1x",
|
|
1228
|
+
"list-2x",
|
|
1229
|
+
"list-3x",
|
|
1230
|
+
"list-4x",
|
|
1231
|
+
"list-5x",
|
|
1232
|
+
"list-6x",
|
|
1233
|
+
"media-1x",
|
|
1234
|
+
"number-1x",
|
|
1235
|
+
"number-2x",
|
|
1236
|
+
"number-3x",
|
|
1237
|
+
"number-4x",
|
|
1238
|
+
"number-5x",
|
|
1239
|
+
"number-6x",
|
|
1240
|
+
"quote-1x",
|
|
1241
|
+
"table-1x",
|
|
1242
|
+
"text-1x",
|
|
1243
|
+
"text-2x",
|
|
1244
|
+
"text-3x",
|
|
1245
|
+
"timeline-1x",
|
|
1246
|
+
"title",
|
|
1247
|
+
"title-subtitle",
|
|
1248
|
+
"title-slide",
|
|
1249
|
+
"two-column",
|
|
1250
|
+
"image-focus",
|
|
1251
|
+
"data-visualization",
|
|
1252
|
+
"quote-highlight",
|
|
1253
|
+
"section-divider",
|
|
1254
|
+
"agenda",
|
|
1255
|
+
"thank-you-cta",
|
|
1256
|
+
"bullet-list",
|
|
1257
|
+
"three-column",
|
|
1258
|
+
"icon-grid",
|
|
1259
|
+
"faq",
|
|
1260
|
+
"stats-metrics",
|
|
1261
|
+
"comparison",
|
|
1262
|
+
"swot-analysis",
|
|
1263
|
+
"matrix-quadrant",
|
|
1264
|
+
"funnel-pyramid",
|
|
1265
|
+
"timeline",
|
|
1266
|
+
"process-flow",
|
|
1267
|
+
"photo-grid",
|
|
1268
|
+
"before-after",
|
|
1269
|
+
"testimonial-grid",
|
|
1270
|
+
"team-bio",
|
|
1271
|
+
"pricing-table",
|
|
1272
|
+
"roadmap",
|
|
1273
|
+
"product-feature",
|
|
1274
|
+
"executive-summary",
|
|
1275
|
+
"key-takeaway",
|
|
1276
|
+
"chapter-recap",
|
|
1277
|
+
"qa-discussion",
|
|
1278
|
+
"single-message-statement",
|
|
1279
|
+
"one-column-explainer",
|
|
1280
|
+
"case-study",
|
|
1281
|
+
"pros-cons",
|
|
1282
|
+
"feature-comparison-matrix",
|
|
1283
|
+
"venn-overlap",
|
|
1284
|
+
"decision-tree",
|
|
1285
|
+
"kpi-scorecard",
|
|
1286
|
+
"dashboard",
|
|
1287
|
+
"waterfall-bridge",
|
|
1288
|
+
"benchmark-target-table",
|
|
1289
|
+
"heatmap-cohort",
|
|
1290
|
+
"risk-heatmap",
|
|
1291
|
+
"forecast-scenario",
|
|
1292
|
+
"swimlane-process",
|
|
1293
|
+
"gantt-project-plan",
|
|
1294
|
+
"circular-lifecycle",
|
|
1295
|
+
"customer-journey",
|
|
1296
|
+
"org-chart",
|
|
1297
|
+
"capability-map",
|
|
1298
|
+
"logic-tree",
|
|
1299
|
+
"hub-and-spoke",
|
|
1300
|
+
"architecture-stack",
|
|
1301
|
+
"ecosystem-map",
|
|
1302
|
+
"network-dependency-map",
|
|
1303
|
+
"wheel-radial",
|
|
1304
|
+
"geographic-map",
|
|
1305
|
+
"screenshot-walkthrough",
|
|
1306
|
+
"market-size",
|
|
1307
|
+
"persona-grid",
|
|
1308
|
+
"competitive-landscape",
|
|
1309
|
+
"business-model-canvas",
|
|
1310
|
+
"customer-logos",
|
|
1311
|
+
"executive-decision",
|
|
1312
|
+
"status-rag",
|
|
1313
|
+
"raci-matrix",
|
|
1314
|
+
"action-plan",
|
|
1315
|
+
"assumptions-dependencies",
|
|
1316
|
+
"appendix-index",
|
|
1317
|
+
"detailed-data-table"
|
|
1318
|
+
],
|
|
1319
|
+
chartTypes: [
|
|
1320
|
+
"100pct-stacked-area-3x",
|
|
1321
|
+
"100pct-stacked-bar-3x",
|
|
1322
|
+
"100pct-stacked-column-3x",
|
|
1323
|
+
"area",
|
|
1324
|
+
"bar",
|
|
1325
|
+
"box-and-whisker",
|
|
1326
|
+
"column",
|
|
1327
|
+
"doughnut",
|
|
1328
|
+
"filled-radar",
|
|
1329
|
+
"funnel",
|
|
1330
|
+
"histogram",
|
|
1331
|
+
"line",
|
|
1332
|
+
"line-with-markers",
|
|
1333
|
+
"pareto",
|
|
1334
|
+
"pie",
|
|
1335
|
+
"radar",
|
|
1336
|
+
"radar-with-markers",
|
|
1337
|
+
"scatter",
|
|
1338
|
+
"stacked-area-3x",
|
|
1339
|
+
"stacked-bar-3x",
|
|
1340
|
+
"stacked-column-3x",
|
|
1341
|
+
"stacked-line-3x",
|
|
1342
|
+
"stacked-line-with-markers-3x",
|
|
1343
|
+
"treemap",
|
|
1344
|
+
"waterfall",
|
|
1345
|
+
"world",
|
|
1346
|
+
"100pct-bullet-bar",
|
|
1347
|
+
"100pct-bullet-bar-2x",
|
|
1348
|
+
"100pct-bullet-bar-3x",
|
|
1349
|
+
"100pct-bullet-column",
|
|
1350
|
+
"100pct-bullet-column-2x",
|
|
1351
|
+
"100pct-bullet-column-3x",
|
|
1352
|
+
"100pct-progress-bar",
|
|
1353
|
+
"100pct-stacked-area-2x",
|
|
1354
|
+
"100pct-stacked-bar-2x",
|
|
1355
|
+
"100pct-stacked-column-2x",
|
|
1356
|
+
"australia",
|
|
1357
|
+
"box-and-whisker-2x",
|
|
1358
|
+
"box-and-whisker-3x",
|
|
1359
|
+
"bullet-bar",
|
|
1360
|
+
"bullet-bar-2x",
|
|
1361
|
+
"bullet-bar-3x",
|
|
1362
|
+
"bullet-column",
|
|
1363
|
+
"bullet-column-2x",
|
|
1364
|
+
"bullet-column-3x",
|
|
1365
|
+
"canada",
|
|
1366
|
+
"clustered-bar-2x",
|
|
1367
|
+
"clustered-column",
|
|
1368
|
+
"dot-plot",
|
|
1369
|
+
"dot-plot-2x",
|
|
1370
|
+
"dot-plot-3x",
|
|
1371
|
+
"dot-plot-4x",
|
|
1372
|
+
"dot-plot-5x",
|
|
1373
|
+
"dot-plot-6x",
|
|
1374
|
+
"dumbbell",
|
|
1375
|
+
"line-2x",
|
|
1376
|
+
"line-3x",
|
|
1377
|
+
"line-with-high-low",
|
|
1378
|
+
"line-with-high-low-and-markers",
|
|
1379
|
+
"line-with-markers-2x",
|
|
1380
|
+
"line-with-markers-3x",
|
|
1381
|
+
"sparkline",
|
|
1382
|
+
"sparkline-2x",
|
|
1383
|
+
"sparkline-3x",
|
|
1384
|
+
"sparkline-4x",
|
|
1385
|
+
"sparkline-5x",
|
|
1386
|
+
"sparkline-6x",
|
|
1387
|
+
"stacked-area-2x",
|
|
1388
|
+
"stacked-bar-2x",
|
|
1389
|
+
"stacked-column-2x",
|
|
1390
|
+
"stacked-line-2x",
|
|
1391
|
+
"stacked-line-with-markers-2x",
|
|
1392
|
+
"treemap-2x",
|
|
1393
|
+
"treemap-3x",
|
|
1394
|
+
"united-kingdom",
|
|
1395
|
+
"united-states"
|
|
1396
|
+
],
|
|
1397
|
+
narratives: [
|
|
1398
|
+
"problem-solution",
|
|
1399
|
+
"scqa",
|
|
1400
|
+
"pyramid-principle",
|
|
1401
|
+
"situation-complication-resolution",
|
|
1402
|
+
"what-so-what-now-what",
|
|
1403
|
+
"strategic-narrative",
|
|
1404
|
+
"golden-circle",
|
|
1405
|
+
"conference-talk",
|
|
1406
|
+
"transformation-arc",
|
|
1407
|
+
"pitch-deck",
|
|
1408
|
+
"early-startup-pitch",
|
|
1409
|
+
"venture-pitch",
|
|
1410
|
+
"persuasive-sales",
|
|
1411
|
+
"sparkline",
|
|
1412
|
+
"company-intro",
|
|
1413
|
+
"marketing-strategy",
|
|
1414
|
+
"product-launch",
|
|
1415
|
+
"strategic-advisory",
|
|
1416
|
+
"challenge-resolution",
|
|
1417
|
+
"project-proposal",
|
|
1418
|
+
"vision-roadmap",
|
|
1419
|
+
"failure-analysis",
|
|
1420
|
+
"star-method",
|
|
1421
|
+
"classic-story",
|
|
1422
|
+
"heros-journey",
|
|
1423
|
+
"business-narrative",
|
|
1424
|
+
"change-story",
|
|
1425
|
+
"rags-to-riches",
|
|
1426
|
+
"underdog-victory",
|
|
1427
|
+
"survival-story",
|
|
1428
|
+
"educate",
|
|
1429
|
+
"persuade",
|
|
1430
|
+
"reveal",
|
|
1431
|
+
"justice",
|
|
1432
|
+
"innovation",
|
|
1433
|
+
"focus",
|
|
1434
|
+
"qbr",
|
|
1435
|
+
"business-review",
|
|
1436
|
+
"board-meeting",
|
|
1437
|
+
"weekly-progress",
|
|
1438
|
+
"status-update",
|
|
1439
|
+
"performance-review",
|
|
1440
|
+
"survey-analysis",
|
|
1441
|
+
"trend-analysis",
|
|
1442
|
+
"data-story",
|
|
1443
|
+
"employee-review",
|
|
1444
|
+
"performance-improvement-plan",
|
|
1445
|
+
"capacity-planning"
|
|
1446
|
+
],
|
|
1447
|
+
socialPlatforms: [
|
|
1448
|
+
"linkedin",
|
|
1449
|
+
"x",
|
|
1450
|
+
"github",
|
|
1451
|
+
"youtube",
|
|
1452
|
+
"instagram",
|
|
1453
|
+
"facebook",
|
|
1454
|
+
"tiktok",
|
|
1455
|
+
"threads",
|
|
1456
|
+
"mastodon",
|
|
1457
|
+
"bluesky"
|
|
1458
|
+
],
|
|
1459
|
+
languages: [
|
|
1460
|
+
"afrikaans",
|
|
1461
|
+
"albanian",
|
|
1462
|
+
"amharic",
|
|
1463
|
+
"arabic",
|
|
1464
|
+
"armenian",
|
|
1465
|
+
"aymara",
|
|
1466
|
+
"azerbaijani",
|
|
1467
|
+
"bengali",
|
|
1468
|
+
"berber-latin",
|
|
1469
|
+
"bosnian-latin",
|
|
1470
|
+
"bulgarian",
|
|
1471
|
+
"catalan",
|
|
1472
|
+
"cebuano",
|
|
1473
|
+
"chinese-simplified",
|
|
1474
|
+
"chinese-traditional",
|
|
1475
|
+
"chittagonian",
|
|
1476
|
+
"croatian",
|
|
1477
|
+
"czech",
|
|
1478
|
+
"danish",
|
|
1479
|
+
"dutch",
|
|
1480
|
+
"english",
|
|
1481
|
+
"english-au",
|
|
1482
|
+
"english-ca",
|
|
1483
|
+
"english-gb",
|
|
1484
|
+
"english-in",
|
|
1485
|
+
"english-us",
|
|
1486
|
+
"estonian",
|
|
1487
|
+
"filipino",
|
|
1488
|
+
"finnish",
|
|
1489
|
+
"french",
|
|
1490
|
+
"fulfulde",
|
|
1491
|
+
"galician",
|
|
1492
|
+
"georgian",
|
|
1493
|
+
"german",
|
|
1494
|
+
"greek",
|
|
1495
|
+
"gujarati",
|
|
1496
|
+
"hausa",
|
|
1497
|
+
"hebrew",
|
|
1498
|
+
"hindi",
|
|
1499
|
+
"hungarian",
|
|
1500
|
+
"igbo",
|
|
1501
|
+
"indonesian",
|
|
1502
|
+
"italian",
|
|
1503
|
+
"japanese",
|
|
1504
|
+
"kannada",
|
|
1505
|
+
"kazakh",
|
|
1506
|
+
"khmer",
|
|
1507
|
+
"kinyarwanda",
|
|
1508
|
+
"korean",
|
|
1509
|
+
"kurmanji",
|
|
1510
|
+
"latvian",
|
|
1511
|
+
"lithuanian",
|
|
1512
|
+
"macedonian",
|
|
1513
|
+
"malagasy",
|
|
1514
|
+
"malay",
|
|
1515
|
+
"malayalam",
|
|
1516
|
+
"maori",
|
|
1517
|
+
"marathi",
|
|
1518
|
+
"mongolian",
|
|
1519
|
+
"nepali",
|
|
1520
|
+
"norwegian",
|
|
1521
|
+
"odia",
|
|
1522
|
+
"oromo",
|
|
1523
|
+
"pashto",
|
|
1524
|
+
"persian",
|
|
1525
|
+
"polish",
|
|
1526
|
+
"portuguese",
|
|
1527
|
+
"punjabi-gurmukhi",
|
|
1528
|
+
"punjabi-shahmukhi",
|
|
1529
|
+
"romanian",
|
|
1530
|
+
"russian",
|
|
1531
|
+
"serbian-cyrillic",
|
|
1532
|
+
"serbian-latin",
|
|
1533
|
+
"shona",
|
|
1534
|
+
"slovak",
|
|
1535
|
+
"slovenian",
|
|
1536
|
+
"somali",
|
|
1537
|
+
"spanish",
|
|
1538
|
+
"swahili",
|
|
1539
|
+
"swedish",
|
|
1540
|
+
"tagalog",
|
|
1541
|
+
"tajik",
|
|
1542
|
+
"tamil",
|
|
1543
|
+
"telugu",
|
|
1544
|
+
"thai",
|
|
1545
|
+
"turkish",
|
|
1546
|
+
"ukrainian",
|
|
1547
|
+
"urdu",
|
|
1548
|
+
"uzbek-latin",
|
|
1549
|
+
"vietnamese-quoc-ngu",
|
|
1550
|
+
"xhosa",
|
|
1551
|
+
"yoruba",
|
|
1552
|
+
"zulu"
|
|
1553
|
+
],
|
|
1554
|
+
colorSchemes: [
|
|
1555
|
+
"black-and-white",
|
|
1556
|
+
"bold-red",
|
|
1557
|
+
"boost",
|
|
1558
|
+
"burnt-orange",
|
|
1559
|
+
"cool-horizon",
|
|
1560
|
+
"corporate-blue",
|
|
1561
|
+
"deep-purple",
|
|
1562
|
+
"forest-green",
|
|
1563
|
+
"golden-yellow",
|
|
1564
|
+
"luxury",
|
|
1565
|
+
"pastel-red",
|
|
1566
|
+
"slate-gray",
|
|
1567
|
+
"steel-blue",
|
|
1568
|
+
"vibes"
|
|
1569
|
+
],
|
|
1570
|
+
fontSchemes: [
|
|
1571
|
+
"consolas",
|
|
1572
|
+
"courier-new",
|
|
1573
|
+
"arial",
|
|
1574
|
+
"calibri",
|
|
1575
|
+
"impact",
|
|
1576
|
+
"lucida-sans",
|
|
1577
|
+
"segoe-ui",
|
|
1578
|
+
"segoe-ui-light",
|
|
1579
|
+
"tahoma",
|
|
1580
|
+
"trebuchet-ms",
|
|
1581
|
+
"verdana",
|
|
1582
|
+
"bookman",
|
|
1583
|
+
"century-schoolbook",
|
|
1584
|
+
"constantia",
|
|
1585
|
+
"garamond",
|
|
1586
|
+
"rockwell",
|
|
1587
|
+
"times-new-roman",
|
|
1588
|
+
"aptos",
|
|
1589
|
+
"grandview",
|
|
1590
|
+
"seaford",
|
|
1591
|
+
"skeena",
|
|
1592
|
+
"tenorite",
|
|
1593
|
+
"georgia",
|
|
1594
|
+
"microsoft-yahei",
|
|
1595
|
+
"simsun",
|
|
1596
|
+
"fangsong",
|
|
1597
|
+
"yu-gothic",
|
|
1598
|
+
"pmingliu",
|
|
1599
|
+
"mingliu",
|
|
1600
|
+
"ms-mincho",
|
|
1601
|
+
"malgun-gothic",
|
|
1602
|
+
"batang",
|
|
1603
|
+
"gungsuh",
|
|
1604
|
+
"mangal",
|
|
1605
|
+
"nirmala-ui",
|
|
1606
|
+
"latha",
|
|
1607
|
+
"david",
|
|
1608
|
+
"miriam",
|
|
1609
|
+
"gisha",
|
|
1610
|
+
"vrinda",
|
|
1611
|
+
"shruti",
|
|
1612
|
+
"tunga",
|
|
1613
|
+
"kartika",
|
|
1614
|
+
"kalinga",
|
|
1615
|
+
"raavi",
|
|
1616
|
+
"gautami",
|
|
1617
|
+
"arabic-typesetting",
|
|
1618
|
+
"microsoft-jhenghei",
|
|
1619
|
+
"meiryo",
|
|
1620
|
+
"angsana-new",
|
|
1621
|
+
"aparajita",
|
|
1622
|
+
"dilleniaupc",
|
|
1623
|
+
"sakkal-majalla",
|
|
1624
|
+
"shonar-bangla",
|
|
1625
|
+
"sylfaen",
|
|
1626
|
+
"traditional-arabic",
|
|
1627
|
+
"montserrat",
|
|
1628
|
+
"open-sans",
|
|
1629
|
+
"poppins",
|
|
1630
|
+
"pt-serif",
|
|
1631
|
+
"raleway",
|
|
1632
|
+
"roboto",
|
|
1633
|
+
"noto-sans-arabic",
|
|
1634
|
+
"noto-sans-armenian",
|
|
1635
|
+
"noto-sans-bengali",
|
|
1636
|
+
"noto-sans-devangari",
|
|
1637
|
+
"noto-sans-georgian",
|
|
1638
|
+
"noto-sans-gujarati",
|
|
1639
|
+
"noto-sans-gurmukhi",
|
|
1640
|
+
"noto-sans-hebrew",
|
|
1641
|
+
"noto-sans-jp",
|
|
1642
|
+
"noto-sans-kannada",
|
|
1643
|
+
"noto-sans-kr",
|
|
1644
|
+
"noto-sans-malayalam",
|
|
1645
|
+
"noto-sans-mongolian",
|
|
1646
|
+
"noto-sans-oriya",
|
|
1647
|
+
"noto-sans-sc",
|
|
1648
|
+
"noto-sans-tamil",
|
|
1649
|
+
"noto-sans-tc",
|
|
1650
|
+
"noto-sans-telugu",
|
|
1651
|
+
"noto-sans",
|
|
1652
|
+
"noto-sans-thai",
|
|
1653
|
+
"noto-nastaliq-urdu",
|
|
1654
|
+
"noto-naksh-arabic",
|
|
1655
|
+
"noto-sans-ethiopic",
|
|
1656
|
+
"noto-sans-khmer",
|
|
1657
|
+
"nyala",
|
|
1658
|
+
"daunpenh",
|
|
1659
|
+
"khmer-ui"
|
|
1660
|
+
]
|
|
1661
|
+
};
|
|
1662
|
+
var deprecatedCatalogIds = {
|
|
1663
|
+
"chartTypes/100pct-bullet-bar": "100pct-stacked-bar-3x",
|
|
1664
|
+
"chartTypes/100pct-bullet-bar-2x": "100pct-stacked-bar-3x",
|
|
1665
|
+
"chartTypes/100pct-bullet-bar-3x": "100pct-stacked-bar-3x",
|
|
1666
|
+
"chartTypes/100pct-bullet-column": "100pct-stacked-column-3x",
|
|
1667
|
+
"chartTypes/100pct-bullet-column-2x": "100pct-stacked-column-3x",
|
|
1668
|
+
"chartTypes/100pct-bullet-column-3x": "100pct-stacked-column-3x",
|
|
1669
|
+
"chartTypes/100pct-progress-bar": "100pct-stacked-bar-3x",
|
|
1670
|
+
"chartTypes/100pct-stacked-area-2x": "100pct-stacked-area-3x",
|
|
1671
|
+
"chartTypes/100pct-stacked-bar-2x": "100pct-stacked-bar-3x",
|
|
1672
|
+
"chartTypes/100pct-stacked-column-2x": "100pct-stacked-column-3x",
|
|
1673
|
+
"chartTypes/australia": "world",
|
|
1674
|
+
"chartTypes/box-and-whisker-2x": "box-and-whisker",
|
|
1675
|
+
"chartTypes/box-and-whisker-3x": "box-and-whisker",
|
|
1676
|
+
"chartTypes/bullet-bar": "bar",
|
|
1677
|
+
"chartTypes/bullet-bar-2x": "bar",
|
|
1678
|
+
"chartTypes/bullet-bar-3x": "bar",
|
|
1679
|
+
"chartTypes/bullet-column": "column",
|
|
1680
|
+
"chartTypes/bullet-column-2x": "column",
|
|
1681
|
+
"chartTypes/bullet-column-3x": "column",
|
|
1682
|
+
"chartTypes/canada": "world",
|
|
1683
|
+
"chartTypes/clustered-bar-2x": "bar",
|
|
1684
|
+
"chartTypes/clustered-column": "column",
|
|
1685
|
+
"chartTypes/dot-plot": "scatter",
|
|
1686
|
+
"chartTypes/dot-plot-2x": "scatter",
|
|
1687
|
+
"chartTypes/dot-plot-3x": "scatter",
|
|
1688
|
+
"chartTypes/dot-plot-4x": "scatter",
|
|
1689
|
+
"chartTypes/dot-plot-5x": "scatter",
|
|
1690
|
+
"chartTypes/dot-plot-6x": "scatter",
|
|
1691
|
+
"chartTypes/dumbbell": "scatter",
|
|
1692
|
+
"chartTypes/line-2x": "line",
|
|
1693
|
+
"chartTypes/line-3x": "line",
|
|
1694
|
+
"chartTypes/line-with-high-low": "line",
|
|
1695
|
+
"chartTypes/line-with-high-low-and-markers": "line-with-markers",
|
|
1696
|
+
"chartTypes/line-with-markers-2x": "line-with-markers",
|
|
1697
|
+
"chartTypes/line-with-markers-3x": "line-with-markers",
|
|
1698
|
+
"chartTypes/sparkline": "line",
|
|
1699
|
+
"chartTypes/sparkline-2x": "line",
|
|
1700
|
+
"chartTypes/sparkline-3x": "line",
|
|
1701
|
+
"chartTypes/sparkline-4x": "line",
|
|
1702
|
+
"chartTypes/sparkline-5x": "line",
|
|
1703
|
+
"chartTypes/sparkline-6x": "line",
|
|
1704
|
+
"chartTypes/stacked-area-2x": "stacked-area-3x",
|
|
1705
|
+
"chartTypes/stacked-bar-2x": "stacked-bar-3x",
|
|
1706
|
+
"chartTypes/stacked-column-2x": "stacked-column-3x",
|
|
1707
|
+
"chartTypes/stacked-line-2x": "stacked-line-3x",
|
|
1708
|
+
"chartTypes/stacked-line-with-markers-2x": "stacked-line-with-markers-3x",
|
|
1709
|
+
"chartTypes/treemap-2x": "treemap",
|
|
1710
|
+
"chartTypes/treemap-3x": "treemap",
|
|
1711
|
+
"chartTypes/united-kingdom": "world",
|
|
1712
|
+
"chartTypes/united-states": "world"
|
|
1713
|
+
};
|
|
1714
|
+
|
|
1715
|
+
// src/chart-options.ts
|
|
1716
|
+
var END_POSITIONS = ["center", "inside-end", "inside-base", "outside-end"];
|
|
1717
|
+
var STACKED_POSITIONS = ["center", "inside-end", "inside-base"];
|
|
1718
|
+
var POINT_POSITIONS = ["above", "below", "left", "right", "center"];
|
|
1719
|
+
var PIE_POSITIONS = ["center", "inside-end", "outside-end"];
|
|
1720
|
+
var NONE = [];
|
|
1721
|
+
var VALUE_CATEGORY = ["category", "value"];
|
|
1722
|
+
var ALL_CONTENT = ["category", "value", "percent"];
|
|
1723
|
+
var categoryKinds = /* @__PURE__ */ new Set(["bar", "line", "area", "scatter", "histogram", "pareto", "box", "waterfall", "funnel"]);
|
|
1724
|
+
var valueKinds = /* @__PURE__ */ new Set(["bar", "line", "area", "scatter", "histogram", "pareto", "box", "waterfall"]);
|
|
1725
|
+
var legendKinds = /* @__PURE__ */ new Set(["bar", "line", "area", "pie", "doughnut", "scatter", "radar", "box"]);
|
|
1726
|
+
function labelSupport(target) {
|
|
1727
|
+
const none = { supported: true, content: VALUE_CATEGORY, positions: NONE, defaultPosition: null, defaultOn: false };
|
|
1728
|
+
switch (target.kind) {
|
|
1729
|
+
case "bar":
|
|
1730
|
+
return target.stacked ? { ...none, positions: STACKED_POSITIONS, defaultPosition: "center" } : { ...none, positions: END_POSITIONS, defaultPosition: "outside-end" };
|
|
1731
|
+
case "line":
|
|
1732
|
+
case "scatter":
|
|
1733
|
+
return { ...none, positions: POINT_POSITIONS, defaultPosition: "above" };
|
|
1734
|
+
case "pie":
|
|
1735
|
+
return { ...none, content: ALL_CONTENT, positions: PIE_POSITIONS, defaultPosition: "outside-end" };
|
|
1736
|
+
case "doughnut":
|
|
1737
|
+
return { ...none, content: ALL_CONTENT };
|
|
1738
|
+
// The funnel labels its bars with values and the treemap its tiles with category names by default.
|
|
1739
|
+
case "funnel":
|
|
1740
|
+
case "treemap":
|
|
1741
|
+
return { ...none, defaultOn: true };
|
|
1742
|
+
case "area":
|
|
1743
|
+
case "radar":
|
|
1744
|
+
return none;
|
|
1745
|
+
case "histogram":
|
|
1746
|
+
case "pareto":
|
|
1747
|
+
case "waterfall":
|
|
1748
|
+
return { ...none, positions: END_POSITIONS, defaultPosition: "outside-end" };
|
|
1749
|
+
default:
|
|
1750
|
+
return { ...none, supported: false, content: [] };
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
function chartOptionSupport(target) {
|
|
1754
|
+
return {
|
|
1755
|
+
axisTitles: { category: categoryKinds.has(target.kind), value: valueKinds.has(target.kind) },
|
|
1756
|
+
legend: legendKinds.has(target.kind),
|
|
1757
|
+
dataLabels: labelSupport(target)
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
var KEPT = {
|
|
1761
|
+
column: { kind: "bar" },
|
|
1762
|
+
"stacked-column-3x": { kind: "bar", stacked: true },
|
|
1763
|
+
"100pct-stacked-column-3x": { kind: "bar", stacked: true },
|
|
1764
|
+
bar: { kind: "bar" },
|
|
1765
|
+
"stacked-bar-3x": { kind: "bar", stacked: true },
|
|
1766
|
+
"100pct-stacked-bar-3x": { kind: "bar", stacked: true },
|
|
1767
|
+
line: { kind: "line" },
|
|
1768
|
+
"line-with-markers": { kind: "line" },
|
|
1769
|
+
"stacked-line-3x": { kind: "line", stacked: true },
|
|
1770
|
+
"stacked-line-with-markers-3x": { kind: "line", stacked: true },
|
|
1771
|
+
area: { kind: "area" },
|
|
1772
|
+
"stacked-area-3x": { kind: "area", stacked: true },
|
|
1773
|
+
"100pct-stacked-area-3x": { kind: "area", stacked: true },
|
|
1774
|
+
pie: { kind: "pie" },
|
|
1775
|
+
doughnut: { kind: "doughnut" },
|
|
1776
|
+
scatter: { kind: "scatter" },
|
|
1777
|
+
radar: { kind: "radar" },
|
|
1778
|
+
"radar-with-markers": { kind: "radar" },
|
|
1779
|
+
"filled-radar": { kind: "radar" },
|
|
1780
|
+
treemap: { kind: "treemap" },
|
|
1781
|
+
histogram: { kind: "histogram" },
|
|
1782
|
+
pareto: { kind: "pareto" },
|
|
1783
|
+
"box-and-whisker": { kind: "box" },
|
|
1784
|
+
waterfall: { kind: "waterfall" },
|
|
1785
|
+
funnel: { kind: "funnel" },
|
|
1786
|
+
world: { kind: "map" }
|
|
1787
|
+
};
|
|
1788
|
+
function chartOptionTarget(typeId) {
|
|
1789
|
+
if (typeof typeId !== "string") return void 0;
|
|
1790
|
+
const raw = typeId.trim().toLowerCase();
|
|
1791
|
+
const id = raw === "donut" ? "doughnut" : raw;
|
|
1792
|
+
const kept = deprecatedCatalogIds[`chartTypes/${id}`] ?? id;
|
|
1793
|
+
const target = Object.hasOwn(KEPT, kept) ? KEPT[kept] : void 0;
|
|
1794
|
+
return target ? { ...target } : void 0;
|
|
1795
|
+
}
|
|
1796
|
+
var record3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1797
|
+
var LEGEND_POSITIONS = /* @__PURE__ */ new Set(["none", "top", "bottom", "left", "right"]);
|
|
1798
|
+
var CONTENT_ORDER = ["category", "value", "percent"];
|
|
1799
|
+
var POSITIONS = /* @__PURE__ */ new Set(["auto", "center", "inside-end", "inside-base", "outside-end", "above", "below", "left", "right"]);
|
|
1800
|
+
var DEFAULT_CHART_LABEL_SEPARATOR = ", ";
|
|
1801
|
+
function resolveChartOptions(chart, target) {
|
|
1802
|
+
const result = { active: false, axisTitles: {}, diagnostics: [] };
|
|
1803
|
+
if (!record3(chart)) return result;
|
|
1804
|
+
const support = target ? chartOptionSupport(target) : void 0;
|
|
1805
|
+
const adapt = (option, reason, message) => result.diagnostics.push({ code: "chart-option-adapted", option, reason, message });
|
|
1806
|
+
const kind2 = target?.kind ?? "unknown";
|
|
1807
|
+
if (record3(chart.axisTitles)) {
|
|
1808
|
+
for (const axis of ["category", "value"]) {
|
|
1809
|
+
const raw = chart.axisTitles[axis];
|
|
1810
|
+
if (typeof raw !== "string" || !raw.trim()) continue;
|
|
1811
|
+
if (support && !support.axisTitles[axis]) {
|
|
1812
|
+
adapt(`axisTitles.${axis}`, "unsupported-type", `A '${kind2}' chart has no ${axis} axis to title; the ${axis} axis title is not drawn or exported.`);
|
|
1813
|
+
continue;
|
|
1814
|
+
}
|
|
1815
|
+
result.axisTitles[axis] = raw.trim();
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
if (typeof chart.legend === "string" && LEGEND_POSITIONS.has(chart.legend)) {
|
|
1819
|
+
if (support && !support.legend) adapt("legend", "unsupported-type", `A '${kind2}' chart has no legend option; the legend setting is ignored.`);
|
|
1820
|
+
else result.legend = chart.legend;
|
|
1821
|
+
}
|
|
1822
|
+
const labels = chart.dataLabels;
|
|
1823
|
+
if (labels === false && support?.dataLabels.defaultOn) result.dataLabelsOff = true;
|
|
1824
|
+
if (labels === true || record3(labels)) {
|
|
1825
|
+
const options = record3(labels) ? labels : {};
|
|
1826
|
+
if (support && !support.dataLabels.supported) {
|
|
1827
|
+
adapt("dataLabels", "unsupported-type", `A '${kind2}' chart has no data label option; data labels are not drawn or exported.`);
|
|
1828
|
+
} else {
|
|
1829
|
+
const allowed = support?.dataLabels.content ?? ALL_CONTENT;
|
|
1830
|
+
const requested = Array.isArray(options.content) ? options.content.filter((part) => typeof part === "string" && CONTENT_ORDER.includes(part)) : ["value"];
|
|
1831
|
+
const content = CONTENT_ORDER.filter((part) => requested.includes(part) && allowed.includes(part));
|
|
1832
|
+
for (const part of CONTENT_ORDER) {
|
|
1833
|
+
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.`);
|
|
1834
|
+
}
|
|
1835
|
+
if (!content.length) content.push("value");
|
|
1836
|
+
const positions = support?.dataLabels.positions ?? NONE;
|
|
1837
|
+
let position = support?.dataLabels.defaultPosition ?? null;
|
|
1838
|
+
if (typeof options.position === "string" && POSITIONS.has(options.position) && options.position !== "auto") {
|
|
1839
|
+
if (!support || positions.includes(options.position)) position = options.position;
|
|
1840
|
+
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.`);
|
|
1841
|
+
}
|
|
1842
|
+
let separator = typeof options.separator === "string" ? options.separator : DEFAULT_CHART_LABEL_SEPARATOR;
|
|
1843
|
+
if (/[\r\n]/.test(separator)) {
|
|
1844
|
+
separator = separator.replace(/[\r\n]+/g, " ");
|
|
1845
|
+
adapt("dataLabels.separator", "unsupported-content", "A data label is one line; the line break in the separator is replaced with a space.");
|
|
1846
|
+
}
|
|
1847
|
+
result.dataLabels = { content, position, separator };
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
result.active = result.legend !== void 0 || result.dataLabels !== void 0 || result.dataLabelsOff === true || result.axisTitles.category !== void 0 || result.axisTitles.value !== void 0;
|
|
1851
|
+
return result;
|
|
1852
|
+
}
|
|
1853
|
+
function formatChartLabelNumber(value) {
|
|
1854
|
+
return String(Number(value.toPrecision(12)));
|
|
1855
|
+
}
|
|
1856
|
+
function formatChartLabelPercent(share) {
|
|
1857
|
+
return `${Math.round(Number((share * 100).toPrecision(12)))}%`;
|
|
1858
|
+
}
|
|
1859
|
+
function chartLabelText(parts, content, separator = DEFAULT_CHART_LABEL_SEPARATOR) {
|
|
1860
|
+
return CONTENT_ORDER.filter((part) => content.includes(part) && parts[part] !== void 0).map((part) => parts[part]).join(separator);
|
|
1861
|
+
}
|
|
361
1862
|
|
|
362
1863
|
// src/composition.ts
|
|
363
1864
|
var MAX_COMPOSITION_DEPTH = 32;
|
|
@@ -368,14 +1869,14 @@ function resolveFontSchemeReference(reference, lookup, path = "design.fontScheme
|
|
|
368
1869
|
const overrides = typeof reference === "object" && reference !== null && !Array.isArray(reference) ? reference : void 0;
|
|
369
1870
|
const id = typeof reference === "string" ? reference : typeof overrides?.id === "string" ? overrides.id : void 0;
|
|
370
1871
|
const found = id === void 0 ? void 0 : lookup(id);
|
|
371
|
-
const base =
|
|
1872
|
+
const base = record4(found ?? lookup(DEFAULT_FONT_SCHEME));
|
|
372
1873
|
const scheme = overrides ? { ...base, ...overrides } : { ...base };
|
|
373
1874
|
if (id === void 0 || found !== void 0) return { scheme };
|
|
374
1875
|
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}'.` } };
|
|
375
1876
|
}
|
|
376
1877
|
function resolveFontFamilies(input) {
|
|
377
|
-
const scheme =
|
|
378
|
-
const family = (value) => typeof value === "string" ? value :
|
|
1878
|
+
const scheme = record4(input);
|
|
1879
|
+
const family = (value) => typeof value === "string" ? value : record4(value).family;
|
|
379
1880
|
const accent = family(scheme.accent);
|
|
380
1881
|
return {
|
|
381
1882
|
heading: family(scheme.heading) ?? scheme.major ?? scheme.minor ?? DEFAULT_FONT_FAMILIES.heading,
|
|
@@ -389,27 +1890,28 @@ function resolveTextStyle(style, measurement) {
|
|
|
389
1890
|
}
|
|
390
1891
|
function textWidthMeasurer(style, measurement) {
|
|
391
1892
|
if (!measurement) return measureText;
|
|
392
|
-
return (
|
|
393
|
-
const width = measurement.measure(
|
|
1893
|
+
return (text2, size) => {
|
|
1894
|
+
const width = measurement.measure(text2, size, style);
|
|
394
1895
|
if (!Number.isFinite(width) || width < 0) throw new RangeError("Text measurement must return a finite, nonnegative width.");
|
|
395
1896
|
return width;
|
|
396
1897
|
};
|
|
397
1898
|
}
|
|
398
|
-
function measureTextOutline(
|
|
1899
|
+
function measureTextOutline(text2, fontSize, style, measurement) {
|
|
399
1900
|
if (measurement?.outlineBounds === void 0) return void 0;
|
|
400
1901
|
if (typeof measurement.outlineBounds !== "function") throw new TypeError("Text outline provider must be a function.");
|
|
401
|
-
const bounds = measurement.outlineBounds(
|
|
1902
|
+
const bounds = measurement.outlineBounds(text2, fontSize, style);
|
|
402
1903
|
if (bounds === null) return null;
|
|
403
1904
|
if (!bounds || ![bounds.x, bounds.y, bounds.width, bounds.height].every(Number.isFinite) || bounds.width < 0 || bounds.height < 0) {
|
|
404
1905
|
throw new RangeError("Text outline measurement must return null or finite bounds with nonnegative dimensions.");
|
|
405
1906
|
}
|
|
406
1907
|
return { x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height };
|
|
407
1908
|
}
|
|
408
|
-
function placeTextLines(lines, box, alignment = "left", rasterPadding = 0) {
|
|
1909
|
+
function placeTextLines(lines, box, alignment = "left", rasterPadding = 0, directions) {
|
|
409
1910
|
if (!Array.isArray(lines) || !box || ![box.x, box.y, box.width, box.height, rasterPadding].every(Number.isFinite) || box.width <= 0 || box.height <= 0 || rasterPadding < 0 || !["left", "center", "right"].includes(alignment)) throw new RangeError("Text placement requires lines, finite positive dimensions, nonnegative padding and a valid alignment.");
|
|
410
|
-
const
|
|
1911
|
+
const placed = [];
|
|
411
1912
|
let shift = 0, bottom = box.y, height = 0, overflow = false;
|
|
412
|
-
for (const line of lines) {
|
|
1913
|
+
for (const [lineIndex, line] of lines.entries()) {
|
|
1914
|
+
const lineAlignment = physicalAlignment(alignment, directions?.[lineIndex]), factor = lineAlignment === "right" ? 1 : lineAlignment === "center" ? 0.5 : 0;
|
|
413
1915
|
if (!line || ![line.width, line.y, line.baseline, line.height].every(Number.isFinite) || line.width < 0 || line.height <= 0 || line.y < 0 || line.baseline < line.y) throw new RangeError("Text lines require finite coordinates, nonnegative advances and a baseline at or below their top.");
|
|
414
1916
|
const ink = line.outline;
|
|
415
1917
|
if (ink !== null && (!ink || ![ink.x, ink.y, ink.width, ink.height].every(Number.isFinite) || ink.width < 0 || ink.height < 0)) throw new RangeError("Text outlines must be null or finite coordinates with nonnegative dimensions.");
|
|
@@ -425,7 +1927,7 @@ function placeTextLines(lines, box, alignment = "left", rasterPadding = 0) {
|
|
|
425
1927
|
if (outline) bottom = outline.y + outline.height + rasterPadding;
|
|
426
1928
|
height = Math.max(height, y + line.height - box.y, outline ? bottom - box.y : 0);
|
|
427
1929
|
if (line.width > box.width + 0.01 || height > box.height + 0.01) overflow = true;
|
|
428
|
-
placed.push({ x, y, baseline, height: line.height, width: line.width, outline });
|
|
1930
|
+
placed.push({ x, y, baseline, height: line.height, width: line.width, outline, ...directions ? { alignment: lineAlignment } : {} });
|
|
429
1931
|
}
|
|
430
1932
|
return { alignment, rasterPadding, lines: placed, height, overflow };
|
|
431
1933
|
}
|
|
@@ -460,14 +1962,14 @@ var headings = /* @__PURE__ */ new Set(["title", "subtitle", "tag"]);
|
|
|
460
1962
|
var COVER_LAYOUT_IDS = /* @__PURE__ */ new Set(["title", "title-subtitle"]);
|
|
461
1963
|
var rows = ["top", "middle", "bottom"];
|
|
462
1964
|
var columns = ["left", "center", "right"];
|
|
463
|
-
var
|
|
1965
|
+
var record4 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
464
1966
|
var kind = (field) => field === "items" ? "list" : field === "bullets" ? "text" : field;
|
|
465
|
-
var
|
|
1967
|
+
var round2 = (value) => Math.round(value * 1e6) / 1e6 || value;
|
|
466
1968
|
var SLIDE_IMAGE_POSITIONS = ["background", "top", "bottom", "left", "right"];
|
|
467
1969
|
var SLIDE_IMAGE_BAND = 0.5;
|
|
468
|
-
var assetSource = (value) => typeof value === "string" ? value :
|
|
1970
|
+
var assetSource = (value) => typeof value === "string" ? value : record4(value).src;
|
|
469
1971
|
var finite = (value, min, max) => typeof value === "number" && Number.isFinite(value) && value >= min && value <= max ? value : void 0;
|
|
470
|
-
var pathNumber = (value) => String(
|
|
1972
|
+
var pathNumber = (value) => String(round2(value));
|
|
471
1973
|
var SLIDE_IMAGE_SHAPES = { rectangle: "rect", rounded: "roundRect", circle: "ellipse", hexagon: "hexagon" };
|
|
472
1974
|
function slideImageShape(kind2, box, cornerRadius = 1 / 6) {
|
|
473
1975
|
const { x, y, width: w, height: h } = box, r = x + w, b = y + h, ss = Math.min(w, h), n = pathNumber;
|
|
@@ -488,15 +1990,17 @@ function slideImageShape(kind2, box, cornerRadius = 1 / 6) {
|
|
|
488
1990
|
}
|
|
489
1991
|
return { kind: "rectangle", preset: "rect", adjust: {}, path: rect };
|
|
490
1992
|
}
|
|
491
|
-
function resolveSlideImage(slide, layout, presentation, width, height, path, padding, scale, diagnostics) {
|
|
492
|
-
const own =
|
|
1993
|
+
function resolveSlideImage(slide, layout, presentation, width, height, path, padding, scale, diagnostics, rtl = false) {
|
|
1994
|
+
const own = record4(slide.design), deck = record4(record4(presentation).design);
|
|
493
1995
|
const local = own.slideImage !== void 0;
|
|
494
1996
|
const configured = local ? own.slideImage : deck.slideImage;
|
|
495
1997
|
if (!configured || typeof configured !== "object" && typeof configured !== "string") return void 0;
|
|
496
|
-
const treatment = typeof configured === "object" && !Array.isArray(configured) && "position" in configured ?
|
|
1998
|
+
const treatment = typeof configured === "object" && !Array.isArray(configured) && "position" in configured ? record4(configured) : void 0;
|
|
497
1999
|
const alignment = typeof layout.slideImageAlignment === "string" ? layout.slideImageAlignment.toLowerCase() : void 0;
|
|
498
|
-
const
|
|
499
|
-
if (!SLIDE_IMAGE_POSITIONS.includes(
|
|
2000
|
+
const authoredPosition = treatment ? treatment.position : SLIDE_IMAGE_POSITIONS.find((value2) => value2 === alignment) ?? "background";
|
|
2001
|
+
if (!SLIDE_IMAGE_POSITIONS.includes(authoredPosition)) return void 0;
|
|
2002
|
+
const mirrorSide = (side) => !rtl ? side : side === "left" ? "right" : side === "right" ? "left" : side;
|
|
2003
|
+
const position = mirrorSide(authoredPosition);
|
|
500
2004
|
const designSource = treatment ? treatment.src : configured;
|
|
501
2005
|
const root = slide.image, sameSource = root !== void 0 && designSource !== void 0 && assetSource(root) === assetSource(designSource);
|
|
502
2006
|
if (!local && layout.slideImage !== true && !sameSource) return void 0;
|
|
@@ -507,8 +2011,8 @@ function resolveSlideImage(slide, layout, presentation, width, height, path, pad
|
|
|
507
2011
|
const t = treatment ?? {};
|
|
508
2012
|
const fill = t.fill === "crop" || t.fill === "fit" ? t.fill : (own.imageFill ?? deck.imageFill) === "fit" ? "fit" : "crop";
|
|
509
2013
|
const share = finite(t.size, 0.1, 0.9) ?? SLIDE_IMAGE_BAND;
|
|
510
|
-
const band = { width:
|
|
511
|
-
const region = position === "left" ? { x: 0, y: 0, width: band.width, height } : position === "right" ? { x:
|
|
2014
|
+
const band = { width: round2(width * share), height: round2(height * share) };
|
|
2015
|
+
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 };
|
|
512
2016
|
const designPath = local ? `${path}.design.slideImage` : "design.slideImage";
|
|
513
2017
|
const kind2 = Object.hasOwn(SLIDE_IMAGE_SHAPES, t.shape) ? t.shape : "rectangle";
|
|
514
2018
|
let box = t.inset === true ? { x: region.x + padding, y: region.y + padding, width: Math.max(scale, region.width - 2 * padding), height: Math.max(scale, region.height - 2 * padding) } : { ...region };
|
|
@@ -517,7 +2021,7 @@ function resolveSlideImage(slide, layout, presentation, width, height, path, pad
|
|
|
517
2021
|
const frameWidth = Math.min(box.width, box.height * aspect), frameHeight = frameWidth / aspect;
|
|
518
2022
|
box = { x: box.x + (box.width - frameWidth) / 2, y: box.y + (box.height - frameHeight) / 2, width: frameWidth, height: frameHeight };
|
|
519
2023
|
}
|
|
520
|
-
box = { x:
|
|
2024
|
+
box = { x: round2(box.x), y: round2(box.y), width: round2(box.width), height: round2(box.height) };
|
|
521
2025
|
const result = {
|
|
522
2026
|
path: designPath,
|
|
523
2027
|
sourcePath: replacesContent ? `${path}.image` : designPath,
|
|
@@ -530,19 +2034,19 @@ function resolveSlideImage(slide, layout, presentation, width, height, path, pad
|
|
|
530
2034
|
shape: slideImageShape(kind2, box, finite(t.cornerRadius, 0, 0.5))
|
|
531
2035
|
};
|
|
532
2036
|
if (typeof t.alt === "string") result.alt = t.alt;
|
|
533
|
-
const border =
|
|
534
|
-
if (border.color !== void 0 && borderWidth) result.border = { color: border.color, width:
|
|
2037
|
+
const border = record4(t.border), borderWidth = finite(border.width, 0, 64);
|
|
2038
|
+
if (border.color !== void 0 && borderWidth) result.border = { color: border.color, width: round2(borderWidth * scale) };
|
|
535
2039
|
const opacity = finite(t.opacity, 0, 1);
|
|
536
2040
|
if (opacity !== void 0 && opacity < 1) result.opacity = opacity;
|
|
537
2041
|
if (t.recolor === "grayscale") result.recolor = { type: "grayscale" };
|
|
538
|
-
else if (
|
|
539
|
-
const overlay =
|
|
2042
|
+
else if (record4(t.recolor).dark !== void 0 && record4(t.recolor).light !== void 0) result.recolor = { type: "duotone", dark: t.recolor.dark, light: t.recolor.light };
|
|
2043
|
+
const overlay = record4(t.overlay), overlayOpacity = finite(overlay.opacity, 0, 1);
|
|
540
2044
|
if (overlay.color !== void 0 && overlayOpacity !== void 0) {
|
|
541
|
-
const edge = ["top", "bottom", "left", "right"].includes(overlay.edge) ? overlay.edge : void 0;
|
|
2045
|
+
const edge = ["top", "bottom", "left", "right"].includes(overlay.edge) ? mirrorSide(overlay.edge) : void 0;
|
|
542
2046
|
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." });
|
|
543
2047
|
else {
|
|
544
2048
|
const part = finite(overlay.size, 0.05, 1) ?? 0.3;
|
|
545
|
-
const bandBox = !edge ? box : edge === "top" ? { ...box, height:
|
|
2049
|
+
const bandBox = !edge ? box : edge === "top" ? { ...box, height: round2(box.height * part) } : edge === "bottom" ? { ...box, y: round2(box.y + box.height * (1 - part)), height: round2(box.height * part) } : edge === "left" ? { ...box, width: round2(box.width * part) } : { ...box, x: round2(box.x + box.width * (1 - part)), width: round2(box.width * part) };
|
|
546
2050
|
result.overlay = { color: overlay.color, opacity: overlayOpacity, box: bandBox, shape: edge ? slideImageShape("rectangle", bandBox) : result.shape };
|
|
547
2051
|
}
|
|
548
2052
|
}
|
|
@@ -565,10 +2069,10 @@ function resolveLogo(presentation, slide, options = {}) {
|
|
|
565
2069
|
};
|
|
566
2070
|
const pick = (value, path) => {
|
|
567
2071
|
if (value === void 0 || value === null || value === false) return null;
|
|
568
|
-
if (typeof value === "string" || typeof value === "object" && !Array.isArray(value) && "src" in
|
|
2072
|
+
if (typeof value === "string" || typeof value === "object" && !Array.isArray(value) && "src" in record4(value)) {
|
|
569
2073
|
return usable(value) ? { source: value, path, variant: "default", slot } : null;
|
|
570
2074
|
}
|
|
571
|
-
const set =
|
|
2075
|
+
const set = record4(value);
|
|
572
2076
|
if (!Object.keys(set).some((key) => LOGO_SET_KEYS.has(key))) return null;
|
|
573
2077
|
for (const key of chain) {
|
|
574
2078
|
const variant = set[key];
|
|
@@ -576,16 +2080,16 @@ function resolveLogo(presentation, slide, options = {}) {
|
|
|
576
2080
|
}
|
|
577
2081
|
return null;
|
|
578
2082
|
};
|
|
579
|
-
const deck =
|
|
580
|
-
const own = pick(
|
|
2083
|
+
const deck = record4(presentation), slideIndex = Number.isSafeInteger(options.slideIndex) && options.slideIndex >= 0 ? options.slideIndex : 0;
|
|
2084
|
+
const own = pick(record4(record4(slide).design).logo, `slides.${slideIndex}.design.logo`);
|
|
581
2085
|
if (own) return own;
|
|
582
|
-
const shared = pick(
|
|
2086
|
+
const shared = pick(record4(deck.design).logo, "design.logo");
|
|
583
2087
|
if (shared) return shared;
|
|
584
2088
|
const organizations = Array.isArray(deck.organization) ? deck.organization : [deck.organization];
|
|
585
|
-
const primaryIndex = organizations.findIndex((item) =>
|
|
2089
|
+
const primaryIndex = organizations.findIndex((item) => record4(item).role === "primary");
|
|
586
2090
|
const index = primaryIndex >= 0 ? primaryIndex : organizations.findIndex(Boolean);
|
|
587
2091
|
if (index < 0) return null;
|
|
588
|
-
return pick(
|
|
2092
|
+
return pick(record4(organizations[index]).logo, Array.isArray(deck.organization) ? `organization.${index}.logo` : "organization.logo");
|
|
589
2093
|
}
|
|
590
2094
|
var webUrl = /^https?:\/\/\S+$/i;
|
|
591
2095
|
function resolveSocialProfile(platform, value, records = [], owner = "organization") {
|
|
@@ -603,10 +2107,11 @@ function resolveSocialProfile(platform, value, records = [], owner = "organizati
|
|
|
603
2107
|
return { text: display(url), href: encodeURI(url), resolved: true };
|
|
604
2108
|
}
|
|
605
2109
|
function layoutFurniture(input, options = {}) {
|
|
606
|
-
const slide =
|
|
607
|
-
const settings = { ...
|
|
2110
|
+
const slide = record4(input), width = options.width ?? 1280, height = options.height ?? 720, scale = Math.min(width, height) / 720;
|
|
2111
|
+
const settings = { ...record4(record4(options.layout).composition), ...record4(slide.composition) };
|
|
608
2112
|
assertComposition(settings);
|
|
609
|
-
const
|
|
2113
|
+
const furnitureRtl = (options.direction ?? resolveSlideDirection(options.presentation, options.slideIndex)) === "rtl";
|
|
2114
|
+
const minimum = snapFontSizeUp((settings.minFontSize ?? 16) * scale), size = gridFontSize(13 * scale, minimum), padding = (options.textRasterPadding ?? 2) * scale;
|
|
610
2115
|
if (![width, height, scale, minimum, padding].every(Number.isFinite) || width <= 0 || height <= 0 || minimum <= 0 || padding < 0) throw new RangeError("Furniture requires finite positive dimensions and nonnegative raster padding.");
|
|
611
2116
|
const number = options.slideNumber ?? (options.slideIndex ?? 0) + 1;
|
|
612
2117
|
if (!Number.isSafeInteger(number) || number < 1) throw new RangeError("Displayed slide number must be a positive safe integer.");
|
|
@@ -615,32 +2120,33 @@ function layoutFurniture(input, options = {}) {
|
|
|
615
2120
|
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.");
|
|
616
2121
|
const outlines = options.textMeasurement?.outlineBounds !== void 0, parts = [], diagnostics = [];
|
|
617
2122
|
const sourceRoot = `slides.${options.slideIndex ?? 0}`, organizations = Array.isArray(options.presentation?.organization) ? options.presentation.organization : [options.presentation?.organization];
|
|
618
|
-
const primaryIndex = organizations.findIndex((item) =>
|
|
619
|
-
const organization =
|
|
620
|
-
const inlinePlatforms =
|
|
2123
|
+
const primaryIndex = organizations.findIndex((item) => record4(item).role === "primary"), organizationIndex = primaryIndex >= 0 ? primaryIndex : organizations.findIndex(Boolean);
|
|
2124
|
+
const organization = record4(organizations[organizationIndex]), organizationRoot = Array.isArray(options.presentation?.organization) ? `organization.${organizationIndex}` : "organization", organizationPath = `${organizationRoot}.name`;
|
|
2125
|
+
const inlinePlatforms = record4(record4(options.presentation?.catalogs).socialPlatforms).records, platformRecords = [...Array.isArray(inlinePlatforms) ? inlinePlatforms : [], ...options.socialPlatforms ?? []];
|
|
621
2126
|
const fontFamily = options.fonts?.body ?? "sans-serif";
|
|
622
2127
|
let headerBottom = 0, footerTop = height, configured = false;
|
|
623
2128
|
const error = (path, message, code = "text-overflow") => diagnostics.push({ code, path, message });
|
|
624
2129
|
for (const kind2 of ["header", "footer"]) {
|
|
625
|
-
const local =
|
|
2130
|
+
const local = record4(slide.design)[kind2] !== void 0, value = local ? record4(slide.design)[kind2] : options.presentation?.design?.[kind2];
|
|
626
2131
|
if (value === void 0 || value === false) continue;
|
|
627
2132
|
const root = local ? `${sourceRoot}.design.${kind2}` : `design.${kind2}`;
|
|
628
2133
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("Header/footer content must be an object or false.");
|
|
629
2134
|
configured = true;
|
|
630
2135
|
const zones = [];
|
|
631
2136
|
for (const [index, zone] of ["left", "center", "right"].entries()) {
|
|
632
|
-
const
|
|
2137
|
+
const side = furnitureRtl && zone !== "center" ? zone === "left" ? "right" : "left" : zone;
|
|
2138
|
+
const content = record4(record4(value)[zone]), path = `${root}.${zone}`, x = width * (0.07 + (furnitureRtl ? 2 - index : index) * 0.3), zoneWidth = width * 0.26, zoneParts = [];
|
|
633
2139
|
let y = 0;
|
|
634
|
-
const add = (field,
|
|
635
|
-
if (
|
|
636
|
-
if (typeof
|
|
2140
|
+
const add = (field, text2, generated = false, sourcePath, extras = {}) => {
|
|
2141
|
+
if (text2 === void 0) return;
|
|
2142
|
+
if (typeof text2 !== "string") throw new TypeError(`Furniture field ${path}.${field} requires string content.`);
|
|
637
2143
|
const partPath = `${path}.${field}`, style = resolveTextStyle({ fontFamily, fontWeight: 400, italic: false, path: partPath }, options.textMeasurement);
|
|
638
|
-
const fit = fitText(
|
|
2144
|
+
const fit = fitText(text2, { width: Math.max(Number.MIN_VALUE, zoneWidth - (outlines ? 2 * padding : 0)), height: Number.MAX_VALUE }, size, size, textWidthMeasurer(style, options.textMeasurement));
|
|
639
2145
|
const ink = fit.sourceLines.map((line, index2) => {
|
|
640
2146
|
let outline = null;
|
|
641
2147
|
if (outlines) {
|
|
642
2148
|
for (const segment of line.segments) if (segment.kind === "text") {
|
|
643
|
-
const bounds = measureTextOutline(
|
|
2149
|
+
const bounds = measureTextOutline(text2.slice(segment.start, segment.end), size, style, options.textMeasurement);
|
|
644
2150
|
if (bounds) {
|
|
645
2151
|
const next = { ...bounds, x: bounds.x + segment.x };
|
|
646
2152
|
if (!outline) outline = next;
|
|
@@ -656,30 +2162,30 @@ function layoutFurniture(input, options = {}) {
|
|
|
656
2162
|
}
|
|
657
2163
|
return { width: line.width, y: index2 * fit.lineHeight, baseline: size + index2 * fit.lineHeight, height: fit.lineHeight, outline };
|
|
658
2164
|
});
|
|
659
|
-
const natural = outlines ? placeTextLines(ink, { x, y, width: zoneWidth, height: Number.MAX_VALUE },
|
|
2165
|
+
const natural = outlines ? placeTextLines(ink, { x, y, width: zoneWidth, height: Number.MAX_VALUE }, side, padding) : void 0;
|
|
660
2166
|
const partHeight = natural?.height ?? fit.sourceLines.length * fit.lineHeight;
|
|
661
2167
|
if (!Number.isFinite(partHeight)) throw new RangeError("Furniture exceeds finite layout coordinates.");
|
|
662
|
-
const box = { x, y, width: zoneWidth, height: Math.max(scale, partHeight) }, placement = outlines ? placeTextLines(ink, box,
|
|
2168
|
+
const box = { x, y, width: zoneWidth, height: Math.max(scale, partHeight) }, placement = outlines ? placeTextLines(ink, box, side, padding) : void 0;
|
|
663
2169
|
const accepted = { ...fit, ...placement ? { placement } : {}, overflow: fit.overflow || !!placement?.overflow };
|
|
664
|
-
zoneParts.push({ type: "text", kind: kind2, zone, field, path: partPath, sourcePath: sourcePath ?? (!generated ? partPath : void 0), generated, text, style, requestedFontSize:
|
|
2170
|
+
zoneParts.push({ type: "text", kind: kind2, zone, field, path: partPath, sourcePath: sourcePath ?? (!generated ? partPath : void 0), generated, text: text2, style, requestedFontSize: size, minFontSize: minimum, box, alignment: side, fit: accepted, ...extras.fields?.length ? { fields: extras.fields } : {}, ...extras.links?.length ? { links: extras.links } : {} });
|
|
665
2171
|
if (accepted.overflow) error(partPath, "Repeated text exceeds its zone at the selected readability floor; change the furniture or slide design.");
|
|
666
2172
|
y += box.height;
|
|
667
2173
|
};
|
|
668
2174
|
const imageBox = (source) => {
|
|
669
|
-
const imageHeight = Math.max(32 * scale, Math.min(height * 0.05, 72 * scale)), imageWidth = Math.min(zoneWidth, imageHeight * (intrinsicImageAspect(source,
|
|
670
|
-
return { x:
|
|
2175
|
+
const imageHeight = Math.max(32 * scale, Math.min(height * 0.05, 72 * scale)), imageWidth = Math.min(zoneWidth, imageHeight * (intrinsicImageAspect(source, record4(options.presentation).assets) ?? 1));
|
|
2176
|
+
return { x: side === "left" ? x : side === "center" ? x + (zoneWidth - imageWidth) / 2 : x + zoneWidth - imageWidth, y, width: imageWidth, height: imageHeight };
|
|
671
2177
|
};
|
|
672
2178
|
if (content.logo === true) {
|
|
673
2179
|
const resolved = resolveLogo(options.presentation, slide, { slot: "icon", onDark: options.darkBackground, slideIndex: options.slideIndex });
|
|
674
2180
|
if (resolved) {
|
|
675
2181
|
const box = imageBox(resolved.source);
|
|
676
|
-
zoneParts.push({ type: "image", kind: kind2, zone, field: "logo", path: `${path}.logo`, sourcePath: resolved.path, generated: true, image: resolved.source, box, alignment:
|
|
2182
|
+
zoneParts.push({ type: "image", kind: kind2, zone, field: "logo", path: `${path}.logo`, sourcePath: resolved.path, generated: true, image: resolved.source, box, alignment: side });
|
|
677
2183
|
y += box.height;
|
|
678
2184
|
} else error(`${path}.logo`, "Generated logo needs design.logo or a primary organization logo.", "unresolved-content");
|
|
679
2185
|
}
|
|
680
2186
|
if (content.image !== void 0) {
|
|
681
2187
|
const box = imageBox(content.image);
|
|
682
|
-
zoneParts.push({ type: "image", kind: kind2, zone, field: "image", path: `${path}.image`, sourcePath: `${path}.image`, generated: false, image: content.image, box, alignment:
|
|
2188
|
+
zoneParts.push({ type: "image", kind: kind2, zone, field: "image", path: `${path}.image`, sourcePath: `${path}.image`, generated: false, image: content.image, box, alignment: side });
|
|
683
2189
|
y += box.height;
|
|
684
2190
|
}
|
|
685
2191
|
add("text", content.text);
|
|
@@ -688,7 +2194,7 @@ function layoutFurniture(input, options = {}) {
|
|
|
688
2194
|
else error(`${path}.organization`, "Generated organization name needs a named organization in the presentation.", "unresolved-content");
|
|
689
2195
|
}
|
|
690
2196
|
if (content.socials === true) {
|
|
691
|
-
const links = Object.entries(
|
|
2197
|
+
const links = Object.entries(record4(organization.socials)).filter(([, value2]) => typeof value2 === "string" && value2.trim()).map(([platform, value2]) => ({ platform, sourcePath: `${organizationRoot}.socials.${platform}`, ...resolveSocialProfile(platform, value2, platformRecords, "organization") }));
|
|
692
2198
|
if (links.length) add("socials", links.map((link) => link.text).join("\n"), true, `${organizationRoot}.socials`, { links });
|
|
693
2199
|
else error(`${path}.socials`, "Generated social profiles need a primary organization with socials.", "unresolved-content");
|
|
694
2200
|
}
|
|
@@ -740,29 +2246,43 @@ function layoutFurniture(input, options = {}) {
|
|
|
740
2246
|
return { algorithm: "furniture-flow-v2", configured, textMeasurement: options.textMeasurement ? "provided" : "estimated", textOutlines: outlines ? "provided" : "unavailable", parts, headerBottom, footerTop, diagnostics, overflow: diagnostics.length > 0 };
|
|
741
2247
|
}
|
|
742
2248
|
var textSegments = new Intl.Segmenter("und", { granularity: "grapheme" });
|
|
743
|
-
|
|
2249
|
+
var WRAP_TOLERANCE = 0.01;
|
|
2250
|
+
var HANGING_SPACE = /[^\S\r\n\u00a0\u202f\ufeff]/u;
|
|
2251
|
+
function measureText(text2, fontSize) {
|
|
744
2252
|
let units = 0;
|
|
745
|
-
for (const character of
|
|
2253
|
+
for (const character of text2) {
|
|
746
2254
|
if (/\p{Mark}|\u200d|\ufe0f/u.test(character)) continue;
|
|
747
2255
|
units += character === " " ? 0.32 : /[A-Z0-9]/.test(character) ? 0.62 : character.codePointAt(0) > 11904 ? 1 : 0.54;
|
|
748
2256
|
}
|
|
749
2257
|
return units * fontSize;
|
|
750
2258
|
}
|
|
751
|
-
function wrapText(
|
|
752
|
-
return fitSourceText(
|
|
2259
|
+
function wrapText(text2, width, fontSize, measure = measureText) {
|
|
2260
|
+
return fitSourceText(text2, { width, height: Number.MAX_VALUE }, fontSize, fontSize, 1, measure, true, false).lines;
|
|
753
2261
|
}
|
|
754
|
-
function fitText(
|
|
2262
|
+
function fitText(text2, box, requestedSize = 25, minFontSize = 16, measure = measureText, direction) {
|
|
755
2263
|
if (![box.width, box.height, requestedSize, minFontSize].every(Number.isFinite) || box.width <= 0 || box.height <= 0 || requestedSize <= 0 || minFontSize <= 0) {
|
|
756
2264
|
throw new RangeError("Text dimensions and font sizes must be finite and positive.");
|
|
757
2265
|
}
|
|
758
|
-
return fitSourceText(
|
|
2266
|
+
return fitSourceText(text2, box, Math.max(requestedSize, minFontSize), minFontSize, 1, measure, true, true, direction);
|
|
759
2267
|
}
|
|
760
|
-
|
|
2268
|
+
var FONT_SIZE_GRID_PER_PX = 75;
|
|
2269
|
+
var FONT_GRID_EPSILON = 1e-6;
|
|
2270
|
+
function snapFontSizeDown(px) {
|
|
2271
|
+
return Math.max(px > 0 ? 1 : -Infinity, Math.floor(px * FONT_SIZE_GRID_PER_PX + FONT_GRID_EPSILON)) / FONT_SIZE_GRID_PER_PX;
|
|
2272
|
+
}
|
|
2273
|
+
function snapFontSizeUp(px) {
|
|
2274
|
+
return Math.max(px > 0 ? 1 : -Infinity, Math.ceil(px * FONT_SIZE_GRID_PER_PX - FONT_GRID_EPSILON)) / FONT_SIZE_GRID_PER_PX;
|
|
2275
|
+
}
|
|
2276
|
+
function gridFontSize(raw, minimum) {
|
|
2277
|
+
return Math.max(snapFontSizeUp(minimum), snapFontSizeDown(raw));
|
|
2278
|
+
}
|
|
2279
|
+
function fitAtSizes(layout, requested, minimum, step = 1, snap = true) {
|
|
761
2280
|
const start = Math.max(requested, minimum);
|
|
762
2281
|
if (![start, minimum, step].every((value) => Number.isFinite(value) && value > 0)) throw new RangeError("Readable font sizes and fitting steps must be finite and positive.");
|
|
763
2282
|
for (let trial = 0; trial <= 64; trial++) {
|
|
764
|
-
const
|
|
765
|
-
|
|
2283
|
+
const raw = start - trial * step, last = trial === 64 || raw <= minimum;
|
|
2284
|
+
const size = !snap ? last ? minimum : Math.max(minimum, raw) : last ? snapFontSizeUp(minimum) : gridFontSize(raw, minimum), result = layout(size);
|
|
2285
|
+
if (!result.overflow || last) return result;
|
|
766
2286
|
}
|
|
767
2287
|
throw new Error("Text fitting did not evaluate its bounded floor trial.");
|
|
768
2288
|
}
|
|
@@ -772,7 +2292,7 @@ function layoutQuote(value, box, options = {}) {
|
|
|
772
2292
|
if (!quote || Array.isArray(quote) || typeof quote.text !== "string" || [quote.attribution, quote.source].some((field) => field !== void 0 && typeof field !== "string")) {
|
|
773
2293
|
throw new TypeError("Quote content must be a string or a text object with optional string attribution/source.");
|
|
774
2294
|
}
|
|
775
|
-
const scale = options.scale ?? 1, minimum = (options.minFontSize ?? 16) * scale;
|
|
2295
|
+
const scale = options.scale ?? 1, minimum = snapFontSizeUp((options.minFontSize ?? 16) * scale);
|
|
776
2296
|
if (![box.x, box.y, box.width, box.height, scale, minimum].every(Number.isFinite) || box.width <= 0 || box.height <= 0 || scale <= 0 || minimum <= 0) {
|
|
777
2297
|
throw new RangeError("Quote dimensions, scale and minimum font size must be finite and positive.");
|
|
778
2298
|
}
|
|
@@ -780,23 +2300,23 @@ function layoutQuote(value, box, options = {}) {
|
|
|
780
2300
|
const path = options.path ?? "quote";
|
|
781
2301
|
const diagnostics = [], parts = [];
|
|
782
2302
|
const report = (reason, diagnosticPath, roles, message) => diagnostics.push({ code: "text-overflow", reason, path: diagnosticPath, parts: roles, message });
|
|
783
|
-
const source = (sourcePath,
|
|
784
|
-
const add = (role,
|
|
2303
|
+
const source = (sourcePath, text2, outputStart) => ({ path: sourcePath, start: 0, end: text2.length, outputStart, outputEnd: outputStart + text2.length });
|
|
2304
|
+
const add = (role, text2, sources, area, fontSize, fontFamily, fontWeight, partPath) => {
|
|
785
2305
|
const requestedStyle = { fontFamily, fontWeight, italic: false, path: partPath };
|
|
786
2306
|
const style = resolveTextStyle({ ...requestedStyle }, options.textMeasurement);
|
|
787
|
-
const requestedFontSize =
|
|
788
|
-
const part = { role, path: partPath, text, sources, box: area, requestedFontSize, minFontSize: minimum, requestedStyle, style };
|
|
2307
|
+
const requestedFontSize = gridFontSize(fontSize * scale, minimum);
|
|
2308
|
+
const part = { role, path: partPath, text: text2, sources, box: area, requestedFontSize, minFontSize: minimum, requestedStyle, style };
|
|
789
2309
|
parts.push(part);
|
|
790
2310
|
return part;
|
|
791
2311
|
};
|
|
792
2312
|
let footer = "";
|
|
793
2313
|
const footerSources = [];
|
|
794
2314
|
for (const field of ["attribution", "source"]) {
|
|
795
|
-
const
|
|
796
|
-
if (!
|
|
2315
|
+
const text2 = quote[field];
|
|
2316
|
+
if (!text2) continue;
|
|
797
2317
|
if (footer) footer += " - ";
|
|
798
|
-
footerSources.push(source(`${path}.${field}`,
|
|
799
|
-
footer +=
|
|
2318
|
+
footerSources.push(source(`${path}.${field}`, text2, footer.length));
|
|
2319
|
+
footer += text2;
|
|
800
2320
|
}
|
|
801
2321
|
const bodyPath = shorthand ? path : `${path}.text`;
|
|
802
2322
|
const body = add(
|
|
@@ -820,7 +2340,7 @@ function layoutQuote(value, box, options = {}) {
|
|
|
820
2340
|
path
|
|
821
2341
|
) : void 0;
|
|
822
2342
|
const usable = (area) => [area.x, area.y, area.width, area.height].every(Number.isFinite) && area.width > 0 && area.height > 0;
|
|
823
|
-
const fit = (part, area, size = part.requestedFontSize, floor = minimum) => usable(area) ? fitText(part.text, area, size, floor, textWidthMeasurer(part.style, options.textMeasurement)) : void 0;
|
|
2343
|
+
const fit = (part, area, size = part.requestedFontSize, floor = minimum) => usable(area) ? fitText(part.text, area, size, floor, textWidthMeasurer(part.style, options.textMeasurement), options.direction) : void 0;
|
|
824
2344
|
const inner = { x: box.x + 18, y: box.y + 18, width: box.width - 36, height: box.height - 36 };
|
|
825
2345
|
if (!attribution) body.fit = fit(body, body.box);
|
|
826
2346
|
else if (usable(inner) && inner.height > 18) {
|
|
@@ -877,31 +2397,31 @@ function layoutMetric(value, box, options = {}) {
|
|
|
877
2397
|
if (!metric || Array.isArray(metric) || !isValue(metric.value) || [metric.label, metric.description, metric.unit].some((field) => field !== void 0 && typeof field !== "string") || metric.delta !== void 0 && !isValue(metric.delta) || metric.trend !== void 0 && !["up", "down", "flat"].includes(metric.trend)) {
|
|
878
2398
|
throw new TypeError("Metric content requires a finite numeric or string value and schema-valid display metadata.");
|
|
879
2399
|
}
|
|
880
|
-
const scale = options.scale ?? 1, minimum = (options.minFontSize ?? 16) * scale;
|
|
2400
|
+
const scale = options.scale ?? 1, minimum = snapFontSizeUp((options.minFontSize ?? 16) * scale);
|
|
881
2401
|
const rasterPadding = (options.textRasterPadding ?? 1) * scale, hasOutlines = options.textMeasurement?.outlineBounds !== void 0;
|
|
882
2402
|
if (![box.x, box.y, box.width, box.height, box.x + box.width, box.y + box.height, scale, minimum, Math.max(76 * scale, minimum) * 1.22].every(Number.isFinite) || box.width <= 0 || box.height <= 0 || scale <= 0 || minimum <= 0) {
|
|
883
2403
|
throw new RangeError("Metric dimensions, scale and minimum font size must be finite and positive.");
|
|
884
2404
|
}
|
|
885
2405
|
if (!Number.isFinite(rasterPadding) || rasterPadding < 0) throw new RangeError("Metric raster padding must be finite and nonnegative.");
|
|
886
2406
|
if (options.overflow !== void 0 && !["warn", "error"].includes(options.overflow)) throw new RangeError("Invalid metric overflow policy.");
|
|
887
|
-
const
|
|
888
|
-
if (!["left", "center", "right"].includes(
|
|
2407
|
+
const requestedAlignment = options.align ?? "left";
|
|
2408
|
+
if (!["left", "center", "right"].includes(requestedAlignment)) throw new RangeError("Invalid metric alignment.");
|
|
889
2409
|
const sourcePath = options.path ?? "metric", parts = [], diagnostics = [];
|
|
890
2410
|
for (const role of ["value", "unit", "label", "description", "delta", "trend"]) {
|
|
891
2411
|
const sourceValue = metric[role];
|
|
892
2412
|
if (sourceValue === void 0) continue;
|
|
893
|
-
const
|
|
2413
|
+
const text2 = String(sourceValue), path = scalar ? sourcePath : `${sourcePath}.${role}`;
|
|
894
2414
|
const nominal = role === "value" ? Math.min(76 * scale, box.height * 0.28) : (role === "description" ? 20 : role === "trend" ? 18 : 23) * scale;
|
|
895
2415
|
const requestedStyle = { fontFamily: (role === "value" ? options.fonts?.heading : options.fonts?.body) ?? "sans-serif", fontWeight: role === "value" ? 800 : role === "description" ? 400 : 500, italic: false, path };
|
|
896
2416
|
const part = {
|
|
897
2417
|
role,
|
|
898
2418
|
path,
|
|
899
|
-
text,
|
|
900
|
-
sources: [{ path, value: sourceValue, start: 0, end:
|
|
901
|
-
visible: role === "value" ||
|
|
2419
|
+
text: text2,
|
|
2420
|
+
sources: [{ path, value: sourceValue, start: 0, end: text2.length }],
|
|
2421
|
+
visible: role === "value" || text2.length > 0,
|
|
902
2422
|
linePositions: [],
|
|
903
2423
|
box: { ...box, height: 0 },
|
|
904
|
-
requestedFontSize:
|
|
2424
|
+
requestedFontSize: gridFontSize(nominal, minimum),
|
|
905
2425
|
minFontSize: minimum,
|
|
906
2426
|
requestedStyle,
|
|
907
2427
|
style: resolveTextStyle({ ...requestedStyle }, options.textMeasurement)
|
|
@@ -910,6 +2430,8 @@ function layoutMetric(value, box, options = {}) {
|
|
|
910
2430
|
parts.push(part);
|
|
911
2431
|
}
|
|
912
2432
|
const primary = parts[0], metadata = parts.filter((part) => part !== primary && part.visible), unit = metadata.find((part) => part.role === "unit");
|
|
2433
|
+
const metricDirection = options.direction === "rtl" ? paragraphDirection(["label", "description", "unit"].map((role) => parts.find((part) => part.role === role)?.text).find(Boolean) ?? primary.text, "rtl") : void 0;
|
|
2434
|
+
const alignment = physicalAlignment(requestedAlignment, metricDirection);
|
|
913
2435
|
const usable = (area) => [area.x, area.y, area.width, area.height, area.x + area.width, area.y + area.height].every(Number.isFinite) && area.width > 0 && area.height > 0;
|
|
914
2436
|
const occupied = (fit) => fit.placement?.height ?? fit.lines.length * fit.lineHeight;
|
|
915
2437
|
const gap = 8 * scale, primaryGap = 12 * scale;
|
|
@@ -946,7 +2468,7 @@ function layoutMetric(value, box, options = {}) {
|
|
|
946
2468
|
const key = JSON.stringify([part.role, size, width]);
|
|
947
2469
|
let fit = cache2.get(key);
|
|
948
2470
|
if (!fit) {
|
|
949
|
-
fit = fitCodeText(part.text, { ...box, width: hasOutlines ? Math.max(Number.MIN_VALUE, width - 2 * rasterPadding) : width }, size, size, scale, textWidthMeasurer(part.style, options.textMeasurement));
|
|
2471
|
+
fit = fitCodeText(part.text, { ...box, width: hasOutlines ? Math.max(Number.MIN_VALUE, width - 2 * rasterPadding) : width }, size, size, scale, textWidthMeasurer(part.style, options.textMeasurement), options.direction);
|
|
950
2472
|
fit = place(part, fit, width);
|
|
951
2473
|
if (!Number.isFinite(occupied(fit))) throw new RangeError("Metric text layout exceeds finite coordinates.");
|
|
952
2474
|
cache2.set(key, fit);
|
|
@@ -956,7 +2478,7 @@ function layoutMetric(value, box, options = {}) {
|
|
|
956
2478
|
const fitValue = (area, singleLine) => {
|
|
957
2479
|
if (!usable(area)) return void 0;
|
|
958
2480
|
for (let step = 0; step <= 76; step++) {
|
|
959
|
-
const size =
|
|
2481
|
+
const size = gridFontSize(primary.requestedFontSize - step * scale, minimum);
|
|
960
2482
|
const natural = measure(primary, size, area.width);
|
|
961
2483
|
const fit = { ...natural, overflow: !!natural.placement?.overflow || singleLine && natural.lines.length !== 1 || occupied(natural) > area.height + 0.01 || natural.sourceLines.some((line) => line.width > area.width + 0.01) };
|
|
962
2484
|
if (!fit.overflow || size === minimum) return fit;
|
|
@@ -965,7 +2487,7 @@ function layoutMetric(value, box, options = {}) {
|
|
|
965
2487
|
let selected, attempts = 0;
|
|
966
2488
|
const reductions = Math.min(23, Math.ceil(Math.max(0, ...metadata.map((part) => (part.requestedFontSize - minimum) / scale))));
|
|
967
2489
|
for (let reduction = 0; reduction <= reductions; reduction++) {
|
|
968
|
-
const measured = new Map(metadata.map((part) => [part, measure(part,
|
|
2490
|
+
const measured = new Map(metadata.map((part) => [part, measure(part, gridFontSize(part.requestedFontSize - reduction * scale, minimum), box.width)]));
|
|
969
2491
|
const measuredUnit = unit ? measured.get(unit) : void 0;
|
|
970
2492
|
const unitWidth = unit && measuredUnit ? naturalWidth(unit, measuredUnit) : 0;
|
|
971
2493
|
const unitFit = unit && measuredUnit && unitWidth > 0 ? place(unit, measuredUnit, unitWidth) : measuredUnit;
|
|
@@ -1043,17 +2565,17 @@ function layoutTimeline(value, box, options = {}) {
|
|
|
1043
2565
|
if (!timeline || !Array.isArray(timeline.events) || !timeline.events.length || [timeline.name, timeline.description].some((field) => field !== void 0 && typeof field !== "string") || timeline.events.some((event) => !event || typeof event.what !== "string" || [event.when, event.description].some((field) => field !== void 0 && typeof field !== "string"))) {
|
|
1044
2566
|
throw new TypeError("Timeline content requires ordered events with string labels and optional string metadata.");
|
|
1045
2567
|
}
|
|
1046
|
-
const scale = options.scale ?? 1, minimum = (options.minFontSize ?? 16) * scale, padding = (options.textRasterPadding ?? 1) * scale;
|
|
2568
|
+
const scale = options.scale ?? 1, minimum = snapFontSizeUp((options.minFontSize ?? 16) * scale), padding = (options.textRasterPadding ?? 1) * scale;
|
|
1047
2569
|
if (![box.x, box.y, box.width, box.height, scale, minimum, padding].every(Number.isFinite) || box.width <= 0 || box.height <= 0 || scale <= 0 || minimum <= 0 || padding < 0) throw new RangeError("Timeline dimensions, scale and minimum must be positive, with finite nonnegative raster padding.");
|
|
1048
2570
|
if (options.overflow !== void 0 && !["warn", "error"].includes(options.overflow)) throw new RangeError("Invalid timeline overflow policy.");
|
|
1049
|
-
const outlines = options.textMeasurement?.outlineBounds !== void 0;
|
|
2571
|
+
const outlines = options.textMeasurement?.outlineBounds !== void 0, rtl = options.direction === "rtl";
|
|
1050
2572
|
if (outlines && typeof options.textMeasurement?.outlineBounds !== "function") throw new TypeError("Text outline provider must be a function.");
|
|
1051
2573
|
const path = options.path ?? "timeline", eventPath = (index) => shorthand ? `${path}.${index}` : `${path}.events.${index}`;
|
|
1052
2574
|
const fonts = resolveFontFamilies(options.fonts), source = [];
|
|
1053
|
-
const add = (role,
|
|
1054
|
-
if (
|
|
2575
|
+
const add = (role, text2, partPath, size, weight, eventIndex) => {
|
|
2576
|
+
if (text2 === void 0) return;
|
|
1055
2577
|
const requestedStyle = { fontFamily: fonts.body, fontWeight: weight, italic: false, path: partPath };
|
|
1056
|
-
source.push({ role, eventIndex, path: partPath, text, sources: [{ path: partPath, start: 0, end:
|
|
2578
|
+
source.push({ role, eventIndex, path: partPath, text: text2, sources: [{ path: partPath, start: 0, end: text2.length }], box: { ...box }, alignment: "center", requestedFontSize: gridFontSize(size * scale, minimum), minFontSize: minimum, requestedStyle, style: resolveTextStyle({ ...requestedStyle }, options.textMeasurement) });
|
|
1057
2579
|
};
|
|
1058
2580
|
add("name", timeline.name, `${path}.name`, 24, 700);
|
|
1059
2581
|
add("description", timeline.description, `${path}.description`, 18, 400);
|
|
@@ -1070,7 +2592,7 @@ function layoutTimeline(value, box, options = {}) {
|
|
|
1070
2592
|
const key = JSON.stringify([part.path, size, width, alignment]);
|
|
1071
2593
|
let measured = cache2.get(key);
|
|
1072
2594
|
if (measured) return measured;
|
|
1073
|
-
const fit = fitText(part.text, { width: Math.max(Number.MIN_VALUE, width - (outlines ? 2 * padding : 0)), height: Number.MAX_VALUE }, size, size, textWidthMeasurer(part.style, options.textMeasurement));
|
|
2595
|
+
const fit = fitText(part.text, { width: Math.max(Number.MIN_VALUE, width - (outlines ? 2 * padding : 0)), height: Number.MAX_VALUE }, size, size, textWidthMeasurer(part.style, options.textMeasurement), options.direction);
|
|
1074
2596
|
const ink = fit.sourceLines.map((line, index) => {
|
|
1075
2597
|
let left = Infinity, top = Infinity, right = -Infinity, bottom = -Infinity, hasInk = false;
|
|
1076
2598
|
if (outlines) {
|
|
@@ -1087,7 +2609,7 @@ function layoutTimeline(value, box, options = {}) {
|
|
|
1087
2609
|
}
|
|
1088
2610
|
return { width: line.width, y: index * fit.lineHeight, baseline: size + index * fit.lineHeight, height: fit.lineHeight, outline: hasInk ? { x: left, y: top, width: right - left, height: bottom - top } : null };
|
|
1089
2611
|
});
|
|
1090
|
-
const placement = outlines ? placeTextLines(ink, { x: 0, y: 0, width, height: Number.MAX_VALUE }, alignment, padding) : void 0;
|
|
2612
|
+
const placement = outlines ? placeTextLines(ink, { x: 0, y: 0, width, height: Number.MAX_VALUE }, alignment, padding, fit.directions) : void 0;
|
|
1091
2613
|
const height = placement?.height ?? fit.sourceLines.length * fit.lineHeight;
|
|
1092
2614
|
if (!Number.isFinite(height)) throw new RangeError("Timeline text exceeds finite layout coordinates.");
|
|
1093
2615
|
measured = { fit, ink, height };
|
|
@@ -1108,8 +2630,8 @@ function layoutTimeline(value, box, options = {}) {
|
|
|
1108
2630
|
score2 += Math.max(1, excess);
|
|
1109
2631
|
};
|
|
1110
2632
|
const place = (part, x, y2, width, alignment) => {
|
|
1111
|
-
const measured = measure(part,
|
|
1112
|
-
const area = { x, y: y2, width: Math.max(scale, width), height: Math.max(scale, measured.height) }, placement = outlines ? placeTextLines(measured.ink, area, alignment, padding) : void 0;
|
|
2633
|
+
const measured = measure(part, gridFontSize(part.requestedFontSize - reduction * scale, minimum), Math.max(scale, width), alignment);
|
|
2634
|
+
const area = { x, y: y2, width: Math.max(scale, width), height: Math.max(scale, measured.height) }, placement = outlines ? placeTextLines(measured.ink, area, alignment, padding, measured.fit.directions) : void 0;
|
|
1113
2635
|
const fit = { ...measured.fit, ...placement ? { placement } : {}, overflow: measured.fit.overflow || !!placement?.overflow };
|
|
1114
2636
|
const accepted = { ...part, box: area, alignment, fit };
|
|
1115
2637
|
parts.push(accepted);
|
|
@@ -1127,7 +2649,7 @@ function layoutTimeline(value, box, options = {}) {
|
|
|
1127
2649
|
const width = box.width / Math.max(2, count), step = (box.width - width) / Math.max(1, count - 1), start = count === 1 ? box.x + box.width / 2 : box.x + width / 2;
|
|
1128
2650
|
const lineY = y + Math.max(scale, available) * 0.46;
|
|
1129
2651
|
events.forEach((fields2, index) => {
|
|
1130
|
-
const x = start + index * step, top = index % 2 === 0 ? y : lineY + 24 * scale, bottom = index % 2 === 0 ? lineY - 24 * scale : box.y + box.height;
|
|
2652
|
+
const x = start + (rtl ? count - 1 - index : index) * step, top = index % 2 === 0 ? y : lineY + 24 * scale, bottom = index % 2 === 0 ? lineY - 24 * scale : box.y + box.height;
|
|
1131
2653
|
markers.push({ path: eventPath(index), eventIndex: index, x, y: lineY, radius });
|
|
1132
2654
|
let cursor = top;
|
|
1133
2655
|
for (const part of fields2) {
|
|
@@ -1137,7 +2659,7 @@ function layoutTimeline(value, box, options = {}) {
|
|
|
1137
2659
|
}
|
|
1138
2660
|
});
|
|
1139
2661
|
} else {
|
|
1140
|
-
const x = box.x + radius, textX = box.x + 24 * scale, width = box.width - 24 * scale;
|
|
2662
|
+
const x = rtl ? box.x + box.width - radius : box.x + radius, textX = rtl ? box.x : box.x + 24 * scale, width = box.width - 24 * scale;
|
|
1141
2663
|
events.forEach((fields2, index) => {
|
|
1142
2664
|
const top = y;
|
|
1143
2665
|
let first2;
|
|
@@ -1154,7 +2676,7 @@ function layoutTimeline(value, box, options = {}) {
|
|
|
1154
2676
|
diagnostics.push({ code: "text-overflow", reason: "event-space", path: marker.path, message: "Timeline marker has no usable space; increase the cell or paginate events." });
|
|
1155
2677
|
score2 += 1;
|
|
1156
2678
|
}
|
|
1157
|
-
const first = markers[0], last = markers.at(-1), connector = { x1: first.x, y1: first.y, x2: last.x, y2: last.y };
|
|
2679
|
+
const first = markers[0], last = markers.at(-1), connector = rtl ? { x1: Math.min(first.x, last.x), y1: first.y, x2: Math.max(first.x, last.x), y2: last.y } : { x1: first.x, y1: first.y, x2: last.x, y2: last.y };
|
|
1158
2680
|
const candidate = { arrangement, parts, markers, connector, diagnostics, score: score2 };
|
|
1159
2681
|
if (!selected || score2 < selected.score) selected = candidate;
|
|
1160
2682
|
if (!diagnostics.length) break;
|
|
@@ -1165,26 +2687,27 @@ function layoutTimeline(value, box, options = {}) {
|
|
|
1165
2687
|
if (result.diagnostics.length && options.overflow === "error") throw new OPFCompositionError(result.diagnostics);
|
|
1166
2688
|
return { algorithm: "timeline-flow-v1", attempts, textMeasurement: options.textMeasurement ? "provided" : "estimated", textOutlines: outlines ? "provided" : "unavailable", ...result, overflow: result.diagnostics.length > 0 };
|
|
1167
2689
|
}
|
|
1168
|
-
function fitCodeText(
|
|
1169
|
-
return fitSourceText(
|
|
2690
|
+
function fitCodeText(text2, box, size, minimum, step, measure, direction) {
|
|
2691
|
+
return fitSourceText(text2, box, size, minimum, step, measure, false, true, direction);
|
|
1170
2692
|
}
|
|
1171
|
-
function fitSourceText(
|
|
2693
|
+
function fitSourceText(text2, box, size, minimum, step, measure, prose = false, snap = true, direction) {
|
|
2694
|
+
const directionAt = direction === "rtl" ? paragraphDirectionAt(text2, "rtl") : void 0;
|
|
1172
2695
|
const layout = (fontSize) => {
|
|
1173
2696
|
const tabWidth = measure(" ", fontSize) * 4;
|
|
1174
|
-
if (!Number.isFinite(tabWidth) ||
|
|
1175
|
-
const measureLine = (start2, end2,
|
|
2697
|
+
if (!Number.isFinite(tabWidth) || text2.includes(" ") && tabWidth <= 0) throw new RangeError("Text tabs require a positive finite measured space advance.");
|
|
2698
|
+
const measureLine = (start2, end2, record5 = false) => {
|
|
1176
2699
|
let x = 0, offset = start2;
|
|
1177
2700
|
const segments = [];
|
|
1178
|
-
for (const [index, segment] of
|
|
2701
|
+
for (const [index, segment] of text2.slice(start2, end2).split(" ").entries()) {
|
|
1179
2702
|
if (index) {
|
|
1180
2703
|
const next = (Math.floor(x / tabWidth + 1e-9) + 1) * tabWidth;
|
|
1181
|
-
if (
|
|
2704
|
+
if (record5) segments.push({ kind: "tab", start: offset, end: offset + 1, x, width: next - x });
|
|
1182
2705
|
x = next;
|
|
1183
2706
|
offset++;
|
|
1184
2707
|
}
|
|
1185
2708
|
if (segment) {
|
|
1186
2709
|
const advance = measure(segment, fontSize);
|
|
1187
|
-
if (
|
|
2710
|
+
if (record5) segments.push({ kind: "text", start: offset, end: offset + segment.length, x, width: advance });
|
|
1188
2711
|
x += advance;
|
|
1189
2712
|
offset += segment.length;
|
|
1190
2713
|
}
|
|
@@ -1193,26 +2716,34 @@ function fitSourceText(text, box, size, minimum, step, measure, prose = false) {
|
|
|
1193
2716
|
};
|
|
1194
2717
|
const width = (start2, end2) => measureLine(start2, end2).width;
|
|
1195
2718
|
const sourceLines = [];
|
|
1196
|
-
let start = 0, end = 0;
|
|
2719
|
+
let start = 0, end = 0, hangFrom = -1;
|
|
1197
2720
|
const push = (last, nextStart, boundary) => {
|
|
1198
|
-
|
|
2721
|
+
const measured = measureLine(start, last, true);
|
|
2722
|
+
if (hangFrom >= 0 && hangFrom < last) measured.width = measureLine(start, hangFrom).width;
|
|
2723
|
+
hangFrom = -1;
|
|
2724
|
+
sourceLines.push({ start, end: last, nextStart, boundary, ...measured });
|
|
1199
2725
|
start = nextStart;
|
|
1200
2726
|
end = nextStart;
|
|
1201
2727
|
};
|
|
1202
2728
|
const tokens = prose ? /\r\n|\r|\n|[^\S\r\n\u00a0\u202f\ufeff]+|(?:[^\s]|\u00a0|\u202f|\ufeff)+/gu : /\r\n|\r|\n|[^\S\r\n]+|[^\s]+/gu;
|
|
1203
|
-
for (const token of
|
|
2729
|
+
for (const token of text2.matchAll(tokens)) {
|
|
1204
2730
|
if (token.index === void 0) throw new Error("Missing code token source offset.");
|
|
1205
2731
|
const a = token.index, b = a + token[0].length;
|
|
1206
2732
|
if (/^[\r\n]/.test(token[0])) {
|
|
1207
2733
|
push(a, b, "hard");
|
|
1208
2734
|
continue;
|
|
1209
2735
|
}
|
|
1210
|
-
if (width(start, b) <= box.width +
|
|
2736
|
+
if (width(start, b) <= box.width + WRAP_TOLERANCE) {
|
|
2737
|
+
end = b;
|
|
2738
|
+
continue;
|
|
2739
|
+
}
|
|
2740
|
+
if (prose && end > start && /\S/u.test(text2.slice(start, end)) && HANGING_SPACE.test(token[0][0])) {
|
|
2741
|
+
if (hangFrom < 0) hangFrom = end;
|
|
1211
2742
|
end = b;
|
|
1212
2743
|
continue;
|
|
1213
2744
|
}
|
|
1214
|
-
if (end > start && /\S/u.test(
|
|
1215
|
-
if (width(start, b) <= box.width +
|
|
2745
|
+
if (end > start && /\S/u.test(text2.slice(start, end))) push(a, a, "soft");
|
|
2746
|
+
if (width(start, b) <= box.width + WRAP_TOLERANCE) {
|
|
1216
2747
|
end = b;
|
|
1217
2748
|
continue;
|
|
1218
2749
|
}
|
|
@@ -1229,39 +2760,40 @@ function fitSourceText(text, box, size, minimum, step, measure, prose = false) {
|
|
|
1229
2760
|
push(end, end, "end");
|
|
1230
2761
|
const lineHeight = fontSize * 1.22;
|
|
1231
2762
|
return {
|
|
1232
|
-
lines: sourceLines.map((line) =>
|
|
2763
|
+
lines: sourceLines.map((line) => text2.slice(line.start, line.end)),
|
|
1233
2764
|
sourceLines,
|
|
1234
2765
|
fontSize,
|
|
1235
2766
|
lineHeight,
|
|
1236
2767
|
tabSize: 4,
|
|
1237
2768
|
tabWidth,
|
|
2769
|
+
...directionAt ? { directions: sourceLines.map((line) => directionAt(line.start)) } : {},
|
|
1238
2770
|
overflow: sourceLines.length * lineHeight > box.height + 0.01 || sourceLines.some((line) => line.width > box.width + 0.01)
|
|
1239
2771
|
};
|
|
1240
2772
|
};
|
|
1241
|
-
return fitAtSizes(layout, size, minimum, step);
|
|
2773
|
+
return fitAtSizes(layout, size, minimum, step, snap);
|
|
1242
2774
|
}
|
|
1243
2775
|
function layoutCode(value, box, options = {}) {
|
|
1244
2776
|
const shorthand = typeof value === "string", code = shorthand ? { source: value } : value;
|
|
1245
2777
|
if (!code || Array.isArray(code) || typeof code.source !== "string" || [code.language, code.filename].some((field) => field !== void 0 && typeof field !== "string")) {
|
|
1246
2778
|
throw new TypeError("Code content must be a string or source object with optional string language/filename.");
|
|
1247
2779
|
}
|
|
1248
|
-
const scale = options.scale ?? 1, minimum = (options.minFontSize ?? 16) * scale;
|
|
2780
|
+
const scale = options.scale ?? 1, minimum = snapFontSizeUp((options.minFontSize ?? 16) * scale);
|
|
1249
2781
|
if (![box.x, box.y, box.width, box.height, box.x + box.width, box.y + box.height, scale, minimum, Math.max(18 * scale, minimum) * 1.22].every(Number.isFinite) || box.width <= 0 || box.height <= 0 || scale <= 0 || minimum <= 0) {
|
|
1250
2782
|
throw new RangeError("Code dimensions, scale and minimum font size must be finite and positive.");
|
|
1251
2783
|
}
|
|
1252
2784
|
if (options.overflow !== void 0 && !["warn", "error"].includes(options.overflow)) throw new RangeError("Invalid code overflow policy.");
|
|
1253
2785
|
const path = options.path ?? "code", inner = { x: box.x + 18, y: box.y + 18, width: box.width - 36, height: box.height - 36 };
|
|
1254
2786
|
const parts = [], diagnostics = [];
|
|
1255
|
-
const add = (role,
|
|
2787
|
+
const add = (role, text2, partPath, generated = false) => {
|
|
1256
2788
|
const requestedStyle = { fontFamily: options.fonts?.code ?? "monospace", fontWeight: role === "body" ? 400 : 700, italic: false, path: partPath };
|
|
1257
2789
|
const part = {
|
|
1258
2790
|
role,
|
|
1259
2791
|
path: partPath,
|
|
1260
|
-
text,
|
|
1261
|
-
sources: generated ? [] : [{ path: partPath, start: 0, end:
|
|
2792
|
+
text: text2,
|
|
2793
|
+
sources: generated ? [] : [{ path: partPath, start: 0, end: text2.length }],
|
|
1262
2794
|
generated,
|
|
1263
2795
|
box: { ...inner },
|
|
1264
|
-
requestedFontSize:
|
|
2796
|
+
requestedFontSize: gridFontSize((role === "body" ? 18 : 14) * scale, minimum),
|
|
1265
2797
|
minFontSize: minimum,
|
|
1266
2798
|
requestedStyle,
|
|
1267
2799
|
style: resolveTextStyle({ ...requestedStyle }, options.textMeasurement)
|
|
@@ -1318,7 +2850,7 @@ function layoutCode(value, box, options = {}) {
|
|
|
1318
2850
|
}
|
|
1319
2851
|
function fitRichText(input, box, requestedSize = 25, minFontSize = 16, options = { style: { fontFamily: "sans-serif", fontWeight: 400 } }) {
|
|
1320
2852
|
if (![box.width, box.height, requestedSize, minFontSize].every((value) => Number.isFinite(value) && value > 0)) throw new RangeError("Rich text dimensions and font sizes must be finite and positive.");
|
|
1321
|
-
const layout = richTextLayouter(input, box, requestedSize, options);
|
|
2853
|
+
const layout = richTextLayouter(input, box, requestedSize, options, minFontSize);
|
|
1322
2854
|
return fitAtSizes(layout, requestedSize, richMinimum(input, requestedSize, minFontSize));
|
|
1323
2855
|
}
|
|
1324
2856
|
function richMinimum(input, requested, minimum) {
|
|
@@ -1332,7 +2864,7 @@ function richMinimum(input, requested, minimum) {
|
|
|
1332
2864
|
}
|
|
1333
2865
|
return visible ? floor : minimum;
|
|
1334
2866
|
}
|
|
1335
|
-
function richTextLayouter(input, box, requestedSize, options) {
|
|
2867
|
+
function richTextLayouter(input, box, requestedSize, options, minimum = 0) {
|
|
1336
2868
|
let offset = 0;
|
|
1337
2869
|
const source = input.map((value, runIndex) => {
|
|
1338
2870
|
const run = typeof value === "string" ? { text: value } : value;
|
|
@@ -1340,23 +2872,26 @@ function richTextLayouter(input, box, requestedSize, options) {
|
|
|
1340
2872
|
const start = offset;
|
|
1341
2873
|
offset += run.text.length;
|
|
1342
2874
|
const style = resolveTextStyle({ ...options.style, fontFamily: run.fontFamily ?? options.style.fontFamily, fontWeight: run.bold === void 0 ? options.style.fontWeight : run.bold ? 700 : 400, italic: run.italic ?? options.style.italic, path: options.style.path ? `${options.style.path}.${runIndex}` : void 0 }, options.textMeasurement);
|
|
1343
|
-
|
|
2875
|
+
const marker = options.citationMarker && options.style.path && run.text ? options.citationMarker(`${options.style.path}.${runIndex}`) : void 0;
|
|
2876
|
+
return { run, runIndex, start, end: offset, style, ...marker ? { marker } : {} };
|
|
1344
2877
|
});
|
|
1345
2878
|
const whole = source.map((entry) => entry.run.text).join("");
|
|
1346
|
-
const
|
|
1347
|
-
|
|
2879
|
+
const paint = (raw) => raw >= minimum - 1e-6 ? gridFontSize(raw, minimum) : snapFontSizeDown(raw);
|
|
2880
|
+
const directionAt = options.direction === "rtl" ? paragraphDirectionAt(whole, "rtl") : void 0;
|
|
2881
|
+
const layout = (rawFontSize) => {
|
|
2882
|
+
const fontSize = paint(rawFontSize), ratio = rawFontSize / requestedSize;
|
|
1348
2883
|
const fragments = (start, end) => {
|
|
1349
2884
|
let x = 0;
|
|
1350
2885
|
const result = [];
|
|
1351
2886
|
for (const entry of source) {
|
|
1352
2887
|
const a = Math.max(start, entry.start), b = Math.min(end, entry.end);
|
|
1353
2888
|
if (b <= a) continue;
|
|
1354
|
-
const
|
|
2889
|
+
const text2 = whole.slice(a, b), normalSize = entry.run.fontSize !== void 0 ? entry.run.fontSize * 4 / 3 * ratio : fontSize;
|
|
1355
2890
|
const script = entry.run.superscript || entry.run.subscript;
|
|
1356
|
-
const size =
|
|
2891
|
+
const size = script ? paint(normalSize * 0.7) : paint(normalSize), baselineShift = entry.run.superscript ? -normalSize * 0.35 : entry.run.subscript ? normalSize * 0.2 : 0;
|
|
1357
2892
|
const measure = textWidthMeasurer(entry.style, options.textMeasurement);
|
|
1358
2893
|
let cursor = 0;
|
|
1359
|
-
for (const [index, segment] of
|
|
2894
|
+
for (const [index, segment] of text2.split(" ").entries()) {
|
|
1360
2895
|
if (index) {
|
|
1361
2896
|
const tabStart = a + cursor, tabWidth = measure(" ", size) * 4;
|
|
1362
2897
|
if (!Number.isFinite(tabWidth) || tabWidth <= 0) throw new RangeError("Rich text tabs require a positive finite measured space advance.");
|
|
@@ -1372,13 +2907,20 @@ function richTextLayouter(input, box, requestedSize, options) {
|
|
|
1372
2907
|
cursor += segment.length;
|
|
1373
2908
|
}
|
|
1374
2909
|
}
|
|
2910
|
+
if (entry.marker && b === entry.end) {
|
|
2911
|
+
const markerSize = Math.round(normalSize * CITATION_MARKER_SCALE * FONT_SIZE_GRID_PER_PX) / FONT_SIZE_GRID_PER_PX, width2 = measure(entry.marker, markerSize);
|
|
2912
|
+
result.push({ kind: "marker", text: entry.marker, runIndex: entry.runIndex, start: entry.run.text.length, end: entry.run.text.length, x, width: width2, fontSize: markerSize, nominalSize: normalSize, baselineShift: -normalSize * CITATION_MARKER_RAISE, style: entry.style, run: entry.run });
|
|
2913
|
+
x += width2;
|
|
2914
|
+
}
|
|
1375
2915
|
}
|
|
1376
2916
|
return result;
|
|
1377
2917
|
};
|
|
1378
2918
|
const width = (start, end) => fragments(start, end).reduce((sum, fragment) => sum + fragment.width, 0);
|
|
1379
|
-
const ranges = [];
|
|
1380
|
-
let lineStart = 0, lineEnd = 0;
|
|
2919
|
+
const ranges = [], hung = /* @__PURE__ */ new Map();
|
|
2920
|
+
let lineStart = 0, lineEnd = 0, hangFrom = -1;
|
|
1381
2921
|
const push = (end) => {
|
|
2922
|
+
if (hangFrom >= 0) hung.set(ranges.length, hangFrom);
|
|
2923
|
+
hangFrom = -1;
|
|
1382
2924
|
ranges.push({ start: lineStart, end });
|
|
1383
2925
|
lineStart = end;
|
|
1384
2926
|
lineEnd = end;
|
|
@@ -1391,26 +2933,32 @@ function richTextLayouter(input, box, requestedSize, options) {
|
|
|
1391
2933
|
lineEnd = end;
|
|
1392
2934
|
continue;
|
|
1393
2935
|
}
|
|
1394
|
-
if (width(lineStart, end) <= box.width) {
|
|
2936
|
+
if (width(lineStart, end) <= box.width + WRAP_TOLERANCE) {
|
|
2937
|
+
lineEnd = end;
|
|
2938
|
+
continue;
|
|
2939
|
+
}
|
|
2940
|
+
if (HANGING_SPACE.test(match[0][0]) && lineEnd > lineStart && /\S/u.test(whole.slice(lineStart, lineEnd))) {
|
|
2941
|
+
if (hangFrom < 0) hangFrom = lineEnd;
|
|
1395
2942
|
lineEnd = end;
|
|
1396
2943
|
continue;
|
|
1397
2944
|
}
|
|
1398
2945
|
if (lineEnd > lineStart) push(start);
|
|
1399
|
-
if (width(start, end) <= box.width) {
|
|
2946
|
+
if (width(start, end) <= box.width + WRAP_TOLERANCE) {
|
|
1400
2947
|
lineEnd = end;
|
|
1401
2948
|
continue;
|
|
1402
2949
|
}
|
|
1403
2950
|
for (const { segment, index } of textSegments.segment(match[0])) {
|
|
1404
2951
|
const next = start + index + segment.length;
|
|
1405
|
-
if (lineEnd > lineStart && width(lineStart, next) > box.width) push(start + index);
|
|
2952
|
+
if (lineEnd > lineStart && width(lineStart, next) > box.width + WRAP_TOLERANCE) push(start + index);
|
|
1406
2953
|
lineEnd = next;
|
|
1407
2954
|
}
|
|
1408
2955
|
}
|
|
2956
|
+
if (hangFrom >= 0) hung.set(ranges.length, hangFrom);
|
|
1409
2957
|
ranges.push({ start: lineStart, end: lineEnd });
|
|
1410
2958
|
let y = 0;
|
|
1411
|
-
const richLines = ranges.map((range) => {
|
|
1412
|
-
const parts = fragments(range.start, range.end), ascent = Math.max(fontSize, ...parts.map((part) => part.fontSize - part.baselineShift)), descent = Math.max(fontSize * 0.22, ...parts.map((part) => part.fontSize * 0.22 + part.baselineShift));
|
|
1413
|
-
const height = ascent + descent, line = { fragments: parts, width: parts.reduce((sum, p) => sum + p.width, 0), y, baseline: y + ascent, height };
|
|
2959
|
+
const richLines = ranges.map((range, rangeIndex) => {
|
|
2960
|
+
const parts = fragments(range.start, range.end), shown = hung.get(rangeIndex) ?? range.end, ascent = Math.max(fontSize, ...parts.map((part) => part.fontSize - part.baselineShift)), descent = Math.max(fontSize * 0.22, ...parts.map((part) => part.fontSize * 0.22 + part.baselineShift));
|
|
2961
|
+
const height = ascent + descent, line = { fragments: parts, width: shown === range.end ? parts.reduce((sum, p) => sum + p.width, 0) : width(range.start, shown), y, baseline: y + ascent, height };
|
|
1414
2962
|
y += height;
|
|
1415
2963
|
return line;
|
|
1416
2964
|
});
|
|
@@ -1424,7 +2972,15 @@ function richTextLayouter(input, box, requestedSize, options) {
|
|
|
1424
2972
|
y += height;
|
|
1425
2973
|
}
|
|
1426
2974
|
}
|
|
1427
|
-
return {
|
|
2975
|
+
return {
|
|
2976
|
+
lines: ranges.map((range) => whole.slice(range.start, range.end)),
|
|
2977
|
+
fontSize,
|
|
2978
|
+
lineHeight: Math.max(fontSize * 1.22, ...richLines.map((line) => line.height)),
|
|
2979
|
+
richLines,
|
|
2980
|
+
height: y,
|
|
2981
|
+
overflow: y > box.height + 0.01 || richLines.some((line) => line.width > box.width + 0.01),
|
|
2982
|
+
...directionAt ? { directions: ranges.map((range) => directionAt(range.start)) } : {}
|
|
2983
|
+
};
|
|
1428
2984
|
};
|
|
1429
2985
|
return layout;
|
|
1430
2986
|
}
|
|
@@ -1433,36 +2989,50 @@ function fitList(input, box, requestedSize = 25, minFontSize = 16, options = { s
|
|
|
1433
2989
|
if (!Array.isArray(input) || ![box.width, box.height, requestedSize, minFontSize].every((value) => Number.isFinite(value) && value > 0)) throw new RangeError("List content, dimensions and font sizes must be valid.");
|
|
1434
2990
|
const source = input.map((value, index) => {
|
|
1435
2991
|
const object = typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1436
|
-
const
|
|
2992
|
+
const text2 = object ? object.text : value, description = object?.description, level = object?.level ?? 0;
|
|
1437
2993
|
if (!Number.isInteger(level) || level < 0) throw new RangeError("List levels must be nonnegative integers.");
|
|
1438
2994
|
const runs = (value2) => typeof value2 === "string" ? [value2] : value2;
|
|
1439
|
-
if (!Array.isArray(runs(
|
|
2995
|
+
if (!Array.isArray(runs(text2)) || description !== void 0 && !Array.isArray(runs(description))) throw new TypeError("List text and descriptions must be strings or text runs.");
|
|
1440
2996
|
const base = options.style.path ? `${options.style.path}.${index}` : void 0;
|
|
1441
|
-
|
|
2997
|
+
const plain = (value2) => typeof value2 === "string" ? value2 : value2.map((run) => typeof run === "string" ? run : run.text).join("");
|
|
2998
|
+
const direction = options.direction === "rtl" ? paragraphDirection(plain(text2), "rtl") : void 0;
|
|
2999
|
+
return { index, level, direction, value: text2, descriptionValue: description, textPath: base ? base + (object ? ".text" : "") : void 0, descriptionPath: base ? base + ".description" : void 0, runs: runs(text2), descriptionRuns: description === void 0 ? void 0 : runs(description) };
|
|
1442
3000
|
});
|
|
3001
|
+
const numbers = options.numbering === void 0 ? void 0 : listNumbers(input, options.numbering);
|
|
3002
|
+
const markerStyleFor = (item) => {
|
|
3003
|
+
const first = item.runs.map((run) => typeof run === "string" ? { text: run } : run).find((run) => run.text);
|
|
3004
|
+
return { ...options.style, path: item.textPath, fontWeight: first?.bold === void 0 ? options.style.fontWeight : first.bold ? 700 : 400, italic: first?.italic ?? options.style.italic };
|
|
3005
|
+
};
|
|
1443
3006
|
const layout = (fontSize) => {
|
|
1444
3007
|
let y = box.y, overflow = false;
|
|
1445
|
-
const entries = [], lines = [];
|
|
3008
|
+
const entries = [], lines = [], directions = [];
|
|
3009
|
+
const markerStyles = numbers ? source.map((item) => resolveTextStyle(markerStyleFor(item), options.textMeasurement)) : [];
|
|
3010
|
+
const markerWidths = numbers ? numbers.map((number, index) => textWidthMeasurer(markerStyles[index], options.textMeasurement)(number.text, fontSize)) : [];
|
|
3011
|
+
const hanging = numbers ? Math.max(fontSize * 1.1, Math.max(0, ...markerWidths) + fontSize * 0.3) : fontSize * 1.1;
|
|
1446
3012
|
for (const item of source) {
|
|
1447
|
-
const
|
|
3013
|
+
const rtl = item.direction === "rtl";
|
|
3014
|
+
const indent = hanging, offset = Math.min(box.width, item.level * indent), x = rtl ? box.x : box.x + offset + indent, width = Math.max(1, rtl ? box.width - offset - indent : box.x + box.width - x);
|
|
1448
3015
|
if (offset + indent >= box.width) overflow = true;
|
|
1449
3016
|
const textBox = { x, y, width, height: box.height };
|
|
1450
3017
|
const style = { ...options.style, path: item.textPath };
|
|
1451
|
-
const
|
|
1452
|
-
textBox.height =
|
|
1453
|
-
lines.push(...
|
|
1454
|
-
|
|
3018
|
+
const text2 = richTextLayouter(item.runs, textBox, requestedSize, { ...options, style }, minFontSize)(fontSize);
|
|
3019
|
+
textBox.height = text2.height;
|
|
3020
|
+
lines.push(...text2.lines);
|
|
3021
|
+
if (options.direction === "rtl") directions.push(...text2.directions ?? []);
|
|
3022
|
+
y += text2.height;
|
|
1455
3023
|
let description, descriptionBox;
|
|
1456
3024
|
if (item.descriptionRuns !== void 0) {
|
|
1457
3025
|
y += fontSize * 0.12;
|
|
1458
3026
|
descriptionBox = { x, y, width, height: box.height };
|
|
1459
|
-
description = richTextLayouter(item.descriptionRuns, descriptionBox, requestedSize * 0.82, { ...options, style: { ...options.style, path: item.descriptionPath } })(fontSize * 0.82);
|
|
3027
|
+
description = richTextLayouter(item.descriptionRuns, descriptionBox, requestedSize * 0.82, { ...options, style: { ...options.style, path: item.descriptionPath } }, minFontSize)(fontSize * 0.82);
|
|
1460
3028
|
descriptionBox.height = description.height;
|
|
1461
3029
|
lines.push(...description.lines);
|
|
3030
|
+
if (options.direction === "rtl") directions.push(...description.directions ?? []);
|
|
1462
3031
|
y += description.height;
|
|
1463
3032
|
}
|
|
1464
|
-
overflow ||=
|
|
1465
|
-
const
|
|
3033
|
+
overflow ||= text2.overflow || !!description?.overflow;
|
|
3034
|
+
const number = numbers?.[item.index];
|
|
3035
|
+
const marker = { text: number ? number.text : ["\u2022", "\u25E6", "\u25AA"][item.level % 3], x: rtl ? box.x + box.width - offset : box.x + offset, y: textBox.y + (text2.richLines[0]?.baseline ?? fontSize), fontSize, style: number ? markerStyles[item.index] : resolveTextStyle(style, options.textMeasurement), indent, ...number ? { number, width: markerWidths[item.index] } : {}, ...rtl ? { anchor: "end" } : {} }, side = fontSize * PICTURE_BULLET_SCALE;
|
|
1466
3036
|
entries.push({
|
|
1467
3037
|
index: item.index,
|
|
1468
3038
|
level: item.level,
|
|
@@ -1470,17 +3040,18 @@ function fitList(input, box, requestedSize = 25, minFontSize = 16, options = { s
|
|
|
1470
3040
|
descriptionValue: item.descriptionValue,
|
|
1471
3041
|
textPath: item.textPath,
|
|
1472
3042
|
descriptionPath: item.descriptionPath,
|
|
1473
|
-
text,
|
|
3043
|
+
text: text2,
|
|
1474
3044
|
textBox,
|
|
1475
3045
|
description,
|
|
1476
3046
|
descriptionBox,
|
|
1477
3047
|
marker,
|
|
1478
|
-
...
|
|
3048
|
+
...item.direction ? { direction: item.direction } : {},
|
|
3049
|
+
...options.bulletImage && !number ? { bulletImage: options.bulletImage, bulletBox: { x: rtl ? marker.x - side : marker.x, y: marker.y - side, width: side, height: side } } : {}
|
|
1479
3050
|
});
|
|
1480
3051
|
if (item.index < source.length - 1) y += fontSize * 0.28;
|
|
1481
3052
|
}
|
|
1482
3053
|
const height = y - box.y;
|
|
1483
|
-
return { listEntries: entries, lines, fontSize, lineHeight: fontSize * 1.22, height, overflow: overflow || height > box.height + 0.01 };
|
|
3054
|
+
return { listEntries: entries, lines, fontSize, lineHeight: fontSize * 1.22, height, overflow: overflow || height > box.height + 0.01, ...options.direction === "rtl" ? { directions } : {} };
|
|
1484
3055
|
};
|
|
1485
3056
|
let minimum = minFontSize;
|
|
1486
3057
|
for (const item of source) {
|
|
@@ -1492,16 +3063,16 @@ function fitList(input, box, requestedSize = 25, minFontSize = 16, options = { s
|
|
|
1492
3063
|
function contentText(field, value) {
|
|
1493
3064
|
if (field === "text" || headings.has(field)) return flatten(value);
|
|
1494
3065
|
if (field === "items" || field === "bullets") return (Array.isArray(value) ? value : [value]).map((item) => {
|
|
1495
|
-
const entry =
|
|
3066
|
+
const entry = record4(item);
|
|
1496
3067
|
return `\u2022 ${flatten(item)}${entry.description ? `
|
|
1497
3068
|
${flatten(entry.description)}` : ""}`;
|
|
1498
3069
|
}).join("\n");
|
|
1499
|
-
if (field === "code") return flatten(
|
|
1500
|
-
if (field === "quote") return flatten(
|
|
3070
|
+
if (field === "code") return flatten(record4(value).source ?? value);
|
|
3071
|
+
if (field === "quote") return flatten(record4(value).text ?? value);
|
|
1501
3072
|
return void 0;
|
|
1502
3073
|
}
|
|
1503
3074
|
function layoutTable(value, box, options = {}) {
|
|
1504
|
-
const scale = options.scale ?? 1, minimum = (options.minFontSize ?? 16) * scale, requested =
|
|
3075
|
+
const scale = options.scale ?? 1, minimum = snapFontSizeUp((options.minFontSize ?? 16) * scale), requested = gridFontSize(15 * scale, minimum);
|
|
1505
3076
|
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.");
|
|
1506
3077
|
const grid = tableGrid(value, options.path ?? "table");
|
|
1507
3078
|
if (grid.issues.length) throw new RangeError(`${grid.issues[0].path}: ${grid.issues[0].message}`);
|
|
@@ -1515,13 +3086,13 @@ function layoutTable(value, box, options = {}) {
|
|
|
1515
3086
|
const cellMinimum = (cell) => Array.isArray(cell.value) ? richMinimum(cell.value, requested, minimum) : minimum;
|
|
1516
3087
|
const fitCell = (cell, height, min) => {
|
|
1517
3088
|
const textBox = { width: Math.max(scale, cell.width), height: Math.max(scale, height) };
|
|
1518
|
-
return Array.isArray(cell.value) ? fitRichText(cell.value, textBox, requested, min, { style: cell.textStyle, textMeasurement: options.textMeasurement, uniformLineHeight: true }) : fitText(flatten(cell.value), textBox, requested, min, textWidthMeasurer(resolveTextStyle(cell.textStyle, options.textMeasurement), options.textMeasurement));
|
|
3089
|
+
return Array.isArray(cell.value) ? fitRichText(cell.value, textBox, requested, min, { style: cell.textStyle, textMeasurement: options.textMeasurement, uniformLineHeight: true, direction: options.direction }) : fitText(flatten(cell.value), textBox, requested, min, textWidthMeasurer(resolveTextStyle(cell.textStyle, options.textMeasurement), options.textMeasurement), options.direction);
|
|
1519
3090
|
};
|
|
1520
3091
|
const textHeight = (fit) => "height" in fit ? fit.height : fit.lines.length * fit.lineHeight;
|
|
1521
3092
|
const required = (cell, natural2) => {
|
|
1522
|
-
const floor = cellMinimum(cell), size = natural2 ? Math.max(requested, floor) : floor;
|
|
3093
|
+
const floor = cellMinimum(cell), size = gridFontSize(natural2 ? Math.max(requested, floor) : floor, floor);
|
|
1523
3094
|
const textBox = { width: Math.max(scale, cell.width), height: scale };
|
|
1524
|
-
const fit = Array.isArray(cell.value) ? richTextLayouter(cell.value, textBox, requested, { style: cell.textStyle, textMeasurement: options.textMeasurement, uniformLineHeight: true })(size) : fitText(flatten(cell.value), textBox, size, size, textWidthMeasurer(resolveTextStyle(cell.textStyle, options.textMeasurement), options.textMeasurement));
|
|
3095
|
+
const fit = Array.isArray(cell.value) ? richTextLayouter(cell.value, textBox, requested, { style: cell.textStyle, textMeasurement: options.textMeasurement, uniformLineHeight: true }, minimum)(size) : fitText(flatten(cell.value), textBox, size, size, textWidthMeasurer(resolveTextStyle(cell.textStyle, options.textMeasurement), options.textMeasurement));
|
|
1525
3096
|
return textHeight(fit) + (cell.padding.top + cell.padding.bottom) * scale;
|
|
1526
3097
|
};
|
|
1527
3098
|
const natural = Array(grid.rowCount).fill(0), needed = Array(grid.rowCount).fill(0);
|
|
@@ -1544,13 +3115,13 @@ function layoutTable(value, box, options = {}) {
|
|
|
1544
3115
|
let overflow = false;
|
|
1545
3116
|
const rows2 = heights.map((height, r) => ({ box: { x: box.x, y: ys[r], width: box.width, height }, cells: [] }));
|
|
1546
3117
|
for (const cell of cells) {
|
|
1547
|
-
const cellBox = { x: box.x + cell.column * cellWidth, y: ys[cell.row], width: cell.colSpan * cellWidth, height: ys[cell.row + cell.rowSpan] - ys[cell.row] };
|
|
3118
|
+
const cellBox = { x: box.x + (options.direction === "rtl" ? columnCount - cell.column - cell.colSpan : cell.column) * cellWidth, y: ys[cell.row], width: cell.colSpan * cellWidth, height: ys[cell.row + cell.rowSpan] - ys[cell.row] };
|
|
1548
3119
|
const available = cellBox.height - (cell.padding.top + cell.padding.bottom) * scale;
|
|
1549
3120
|
const fit = fitCell(cell, available, minimum), height = textHeight(fit);
|
|
1550
3121
|
const offset = cell.style.verticalAlign === "bottom" ? Math.max(0, available - height) : cell.style.verticalAlign === "middle" ? Math.max(0, (available - height) / 2) : 0;
|
|
1551
3122
|
const textBox = { x: cellBox.x + cell.padding.left * scale, y: cellBox.y + cell.padding.top * scale + offset, width: Math.max(scale, cell.width), height: Math.max(scale, available - offset) };
|
|
1552
3123
|
overflow ||= cell.width <= 0 || available <= 0 || fit.overflow;
|
|
1553
|
-
rows2[cell.row].cells.push({ value: cell.value, input: cell.input, path: cell.valuePath, sourcePath: cell.path, style: cell.style, row: cell.row, column: cell.column, rowSpan: cell.rowSpan, colSpan: cell.colSpan, header: cell.header, rich: Array.isArray(cell.value), box: cellBox, textBox, textStyle: resolveTextStyle(cell.textStyle, options.textMeasurement), fit });
|
|
3124
|
+
rows2[cell.row].cells.push({ value: cell.value, input: cell.input, path: cell.valuePath, sourcePath: cell.path, style: cell.style, row: cell.row, column: cell.column, rowSpan: cell.rowSpan, colSpan: cell.colSpan, header: cell.header, rich: Array.isArray(cell.value), box: cellBox, textBox, textStyle: resolveTextStyle(cell.textStyle, options.textMeasurement), fit, ...fit.directions ? { direction: fit.directions[0] } : {} });
|
|
1554
3125
|
}
|
|
1555
3126
|
return { rows: rows2, columnCount, height: sum(heights), overflow };
|
|
1556
3127
|
}
|
|
@@ -1561,7 +3132,7 @@ function flatten(value) {
|
|
|
1561
3132
|
if (value == null) return "";
|
|
1562
3133
|
if (typeof value !== "object") return String(value);
|
|
1563
3134
|
if (Array.isArray(value)) return value.map(flatten).join("");
|
|
1564
|
-
const item =
|
|
3135
|
+
const item = record4(value);
|
|
1565
3136
|
return flatten(item.text ?? item.value ?? item.source ?? JSON.stringify(value));
|
|
1566
3137
|
}
|
|
1567
3138
|
function regionParts(key) {
|
|
@@ -1617,26 +3188,28 @@ function assertComposition(value) {
|
|
|
1617
3188
|
if (value.overflow !== void 0 && !["warn", "error"].includes(value.overflow)) throw new RangeError("Invalid composition.overflow.");
|
|
1618
3189
|
}
|
|
1619
3190
|
function composeSlide(input, options = {}) {
|
|
1620
|
-
const slide =
|
|
3191
|
+
const slide = record4(input), layout = record4(options.layout);
|
|
1621
3192
|
const width = options.width ?? 1280, height = options.height ?? 720;
|
|
1622
3193
|
if (![width, height].every((value) => Number.isFinite(value) && value > 0)) throw new RangeError("Canvas dimensions must be finite and positive.");
|
|
1623
3194
|
const scale = Math.min(width, height) / 720;
|
|
1624
|
-
const
|
|
1625
|
-
const
|
|
3195
|
+
const deckDirection = options.direction ?? resolveSlideDirection(options.presentation, options.slideIndex);
|
|
3196
|
+
const rtl = deckDirection === "rtl", textDirection = rtl ? "rtl" : void 0;
|
|
3197
|
+
const hasCards = record4(slide.design).contentBox ?? options.contentBox ?? false;
|
|
3198
|
+
const composition = { ...record4(layout.composition), ...record4(slide.composition) };
|
|
1626
3199
|
assertComposition(composition);
|
|
1627
3200
|
const padding = (composition.padding ?? 0.08) * Math.min(width, height);
|
|
1628
3201
|
const gap = (composition.gap ?? 1 / 30) * Math.min(width, height);
|
|
1629
|
-
const minSize = (composition.minFontSize ?? 16) * scale;
|
|
3202
|
+
const minSize = snapFontSizeUp((composition.minFontSize ?? 16) * scale);
|
|
1630
3203
|
const rasterPadding = (options.textRasterPadding ?? 1) * scale;
|
|
1631
3204
|
if (!Number.isFinite(rasterPadding) || rasterPadding < 0) throw new RangeError("Text raster padding must be finite and nonnegative.");
|
|
1632
3205
|
let coverGroup = false;
|
|
1633
|
-
const alignmentFor = (field) => (field === "title" || coverGroup && (field === "tag" || field === "subtitle") &&
|
|
3206
|
+
const alignmentFor = (field) => (field === "title" || coverGroup && (field === "tag" || field === "subtitle") && record4(slide.design).contentAlignment === void 0 ? record4(slide.design).titleAlignment ?? options.titleAlignment : record4(slide.design).contentAlignment ?? options.contentAlignment) ?? "left";
|
|
1634
3207
|
const styleFor = (field, path2) => resolveTextStyle({
|
|
1635
3208
|
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"),
|
|
1636
3209
|
fontWeight: field === "title" ? 700 : 400,
|
|
1637
3210
|
path: path2
|
|
1638
3211
|
}, options.textMeasurement);
|
|
1639
|
-
const slideDesign =
|
|
3212
|
+
const slideDesign = record4(slide.design), deckDesign = record4(record4(options.presentation).design);
|
|
1640
3213
|
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;
|
|
1641
3214
|
const listBullet = designHint("listBullet");
|
|
1642
3215
|
const bulletImage = listBullet?.value === "image" ? (() => {
|
|
@@ -1644,15 +3217,18 @@ function composeSlide(input, options = {}) {
|
|
|
1644
3217
|
return resolved ? { source: resolved.source, path: resolved.path } : void 0;
|
|
1645
3218
|
})() : void 0;
|
|
1646
3219
|
const widthFor = (field, path2) => textWidthMeasurer(styleFor(field, path2), options.textMeasurement);
|
|
1647
|
-
const
|
|
3220
|
+
const citations = slideCitations(slide, options.slideIndex ?? 0, options.presentation);
|
|
3221
|
+
const citationMarker = citations ? (runPath) => citations.markers.get(runPath) : void 0;
|
|
3222
|
+
const richOptions = (style) => ({ style, textMeasurement: options.textMeasurement, ...citationMarker ? { citationMarker } : {}, ...rtl ? { direction: "rtl" } : {} });
|
|
3223
|
+
const fitPlacedText = (field, value, text2, box, size, minimum, path2, explicitAlignment) => {
|
|
1648
3224
|
const style = styleFor(field, path2), rich = field === "text" && Array.isArray(value);
|
|
1649
|
-
if (!options.textMeasurement?.outlineBounds) return rich ? fitRichText(value, box, size, minimum,
|
|
1650
|
-
const alignment = alignmentFor(field);
|
|
1651
|
-
const richLayout = rich ? richTextLayouter(value, box, size,
|
|
1652
|
-
const measure = textWidthMeasurer(style, options.textMeasurement), floor = rich ? richMinimum(value, size, minimum) : minimum, start = Math.max(size, floor);
|
|
3225
|
+
if (!options.textMeasurement?.outlineBounds) return rich ? fitRichText(value, box, size, minimum, richOptions(style)) : fitText(text2, box, size, minimum, textWidthMeasurer(style, options.textMeasurement), textDirection);
|
|
3226
|
+
const alignment = explicitAlignment ?? alignmentFor(field);
|
|
3227
|
+
const richLayout = rich ? richTextLayouter(value, box, size, richOptions(style), minimum) : void 0;
|
|
3228
|
+
const measure = textWidthMeasurer(style, options.textMeasurement), floor = snapFontSizeUp(rich ? richMinimum(value, size, minimum) : minimum), start = Math.max(size, floor);
|
|
1653
3229
|
for (let trial = 0; trial <= 64; trial++) {
|
|
1654
|
-
const
|
|
1655
|
-
const fit = richLayout ? richLayout(fontSize) : fitText(
|
|
3230
|
+
const raw = start - trial * scale, last = trial === 64 || raw <= floor, fontSize = last ? floor : gridFontSize(raw, floor);
|
|
3231
|
+
const fit = richLayout ? richLayout(fontSize) : fitText(text2, box, fontSize, fontSize, measure, textDirection);
|
|
1656
3232
|
const richLines = "richLines" in fit ? fit.richLines : void 0;
|
|
1657
3233
|
const lines = richLines ? richLines.map((line) => {
|
|
1658
3234
|
let outline = null;
|
|
@@ -1675,7 +3251,7 @@ function composeSlide(input, options = {}) {
|
|
|
1675
3251
|
let outline = null;
|
|
1676
3252
|
for (const segment of line.segments) {
|
|
1677
3253
|
if (segment.kind === "tab") continue;
|
|
1678
|
-
const bounds = measureTextOutline(
|
|
3254
|
+
const bounds = measureTextOutline(text2.slice(segment.start, segment.end), fontSize, style, options.textMeasurement);
|
|
1679
3255
|
if (!bounds) continue;
|
|
1680
3256
|
const next = { ...bounds, x: segment.x + bounds.x };
|
|
1681
3257
|
if (!outline) outline = next;
|
|
@@ -1689,15 +3265,17 @@ function composeSlide(input, options = {}) {
|
|
|
1689
3265
|
}
|
|
1690
3266
|
return { width: line.width, y: index * fit.lineHeight, baseline: fontSize + index * fit.lineHeight, height: fit.lineHeight, outline };
|
|
1691
3267
|
});
|
|
1692
|
-
const placement = placeTextLines(lines, box, alignment, rasterPadding), result = { ...fit, placement, overflow: fit.overflow || placement.overflow };
|
|
1693
|
-
if (!result.overflow ||
|
|
3268
|
+
const placement = placeTextLines(lines, box, alignment, rasterPadding, fit.directions), result = { ...fit, placement, overflow: fit.overflow || placement.overflow };
|
|
3269
|
+
if (!result.overflow || last) return result;
|
|
1694
3270
|
}
|
|
1695
3271
|
throw new Error("Text placement did not evaluate its bounded floor trial.");
|
|
1696
3272
|
};
|
|
1697
|
-
const fitContent = (field, value,
|
|
3273
|
+
const fitContent = (field, value, text2, box, size, minimum, path2, numbering) => field === "text" ? fitPlacedText(field, value, text2, box, size, minimum, path2) : field === "items" || field === "bullets" ? fitList(value, box, size, minimum, { ...richOptions(styleFor(field, path2)), ...numbering !== void 0 ? { numbering } : bulletImage ? { bulletImage } : {} }) : fitText(text2, box, size, minimum, widthFor(field, path2), field === "code" ? void 0 : textDirection);
|
|
1698
3274
|
const path = `slides.${options.slideIndex ?? 0}`;
|
|
3275
|
+
const annotationFit = (value, box, requestedSize, minFontSize, fitPath, alignment) => fitPlacedText("text", value, annotationText(value), box, requestedSize, minFontSize, fitPath, alignment);
|
|
3276
|
+
const annotationOptions = { scale, minFontSize: minSize, fit: annotationFit, textStyle: (fitPath) => styleFor("text", fitPath) };
|
|
1699
3277
|
const slideImageDiagnostics = [];
|
|
1700
|
-
const slideImage = resolveSlideImage(slide, layout, options.presentation, width, height, path, padding, scale, slideImageDiagnostics);
|
|
3278
|
+
const slideImage = resolveSlideImage(slide, layout, options.presentation, width, height, path, padding, scale, slideImageDiagnostics, rtl);
|
|
1701
3279
|
const area = { left: 0, top: 0, right: width, bottom: height };
|
|
1702
3280
|
if (slideImage?.position === "left") area.left = slideImage.region.width;
|
|
1703
3281
|
else if (slideImage?.position === "right") area.right = slideImage.region.x;
|
|
@@ -1707,14 +3285,20 @@ function composeSlide(input, options = {}) {
|
|
|
1707
3285
|
const measuredFurniture = layoutFurniture(slide, options);
|
|
1708
3286
|
const furniture = measuredFurniture.configured || measuredFurniture.diagnostics.length ? measuredFurniture : void 0;
|
|
1709
3287
|
if (furniture) diagnostics.push(...furniture.diagnostics);
|
|
1710
|
-
|
|
3288
|
+
let bodyBottom = Math.min(area.bottom - padding, furniture && furniture.footerTop < height ? furniture.footerTop - gap * 0.5 : height - padding);
|
|
1711
3289
|
const headingTop = Math.max(area.top + padding, furniture?.headerBottom ? furniture.headerBottom + gap * 0.5 : padding);
|
|
3290
|
+
let footnotes;
|
|
3291
|
+
if (citations) {
|
|
3292
|
+
footnotes = layoutFootnotes(citations, { ...annotationOptions, x: area.left + padding, width: area.right - area.left - padding * 2, bottom: bodyBottom, maxHeight: Math.max(minSize * 1.22 + 2 * scale, (bodyBottom - headingTop) * FOOTNOTE_MAX_RATIO), path });
|
|
3293
|
+
diagnostics.push(...footnotes.diagnostics);
|
|
3294
|
+
bodyBottom = footnotes.box.y - gap * 0.5;
|
|
3295
|
+
}
|
|
1712
3296
|
const layoutPlaceholders = Array.isArray(layout.placeholders) ? layout.placeholders : [];
|
|
1713
3297
|
const headingOnlyLayout = COVER_LAYOUT_IDS.has(String(layout.id ?? "")) || layoutPlaceholders.length > 0 && layoutPlaceholders.every((placeholder) => headings.has(placeholder.type ?? ""));
|
|
1714
3298
|
const regions = Object.keys(slide).filter((key) => regionParts(key)).sort();
|
|
1715
3299
|
const emptyPayload = (value) => value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
|
|
1716
3300
|
const emptyHost = (host) => emptyPayload(host.blocks) && fields.every((field) => emptyPayload(host[field]));
|
|
1717
|
-
const hasBodyPayload = regions.some((key) => !emptyHost(
|
|
3301
|
+
const hasBodyPayload = regions.some((key) => !emptyHost(record4(slide[key]))) || !emptyPayload(slide.blocks) || fields.some((field) => !emptyPayload(slide[field]));
|
|
1718
3302
|
const isCover = !hasBodyPayload && (headingOnlyLayout || !layout.id && !layoutPlaceholders.some((placeholder) => !headings.has(placeholder.type ?? "")));
|
|
1719
3303
|
coverGroup = isCover;
|
|
1720
3304
|
let logo;
|
|
@@ -1722,8 +3306,9 @@ function composeSlide(input, options = {}) {
|
|
|
1722
3306
|
const resolved = resolveLogo(options.presentation, slide, { slot: "lockup", onDark: options.darkBackground, slideIndex: options.slideIndex });
|
|
1723
3307
|
if (resolved) {
|
|
1724
3308
|
const logoHeight = 56 * scale;
|
|
1725
|
-
const
|
|
1726
|
-
|
|
3309
|
+
const logoWidth = Math.max(scale, Math.min(4 * logoHeight, area.right - area.left - 2 * padding));
|
|
3310
|
+
const box = { x: round2(rtl ? area.right - padding - logoWidth : area.left + padding), y: round2(headingTop), width: round2(logoWidth), height: round2(logoHeight) };
|
|
3311
|
+
logo = { box, slot: "lockup", path: resolved.path, source: resolved.source, variant: resolved.variant, anchor: rtl ? "right" : "left" };
|
|
1727
3312
|
}
|
|
1728
3313
|
}
|
|
1729
3314
|
let y = logo ? logo.box.y + logo.box.height + gap : headingTop;
|
|
@@ -1733,10 +3318,10 @@ function composeSlide(input, options = {}) {
|
|
|
1733
3318
|
const requested = (field === "title" ? 54 : field === "tag" ? 16 : 25) * scale;
|
|
1734
3319
|
const maxHeight = Math.max(height * (field === "title" ? 0.26 : field === "subtitle" ? 0.12 : 0.045), minSize * 1.22 + 2 * rasterPadding);
|
|
1735
3320
|
const box = { x: area.left + padding, y, width: area.right - area.left - padding * 2, height: maxHeight };
|
|
1736
|
-
const
|
|
1737
|
-
box.height = Math.min(maxHeight, Math.max(
|
|
3321
|
+
const text2 = fitPlacedText(field, slide[field], String(slide[field]), box, requested, minSize, `${path}.${field}`);
|
|
3322
|
+
box.height = Math.min(maxHeight, Math.max(text2.lines.length * text2.lineHeight, text2.placement?.height ?? 0));
|
|
1738
3323
|
if (furniture && box.y + box.height > bodyBottom + 0.01) diagnostics.push({ code: "text-overflow", path: `${path}.${field}`, message: "Repeated furniture leaves too little room for this heading. Change the header/footer or slide design." });
|
|
1739
|
-
const item = { path: `${path}.${field}`, field, type: "text", value: slide[field], payload: { text: slide[field] }, box, text, textStyle: styleFor(field, `${path}.${field}`), composition, alignment: alignmentFor(field) };
|
|
3324
|
+
const item = { path: `${path}.${field}`, field, type: "text", value: slide[field], payload: { text: slide[field] }, box, text: text2, textStyle: styleFor(field, `${path}.${field}`), composition, alignment: alignmentFor(field) };
|
|
1740
3325
|
headingItems.push(item);
|
|
1741
3326
|
items.push(item);
|
|
1742
3327
|
y += box.height + gap * 0.5;
|
|
@@ -1756,7 +3341,7 @@ function composeSlide(input, options = {}) {
|
|
|
1756
3341
|
const collect = (host, basePath, depth = 0, ancestors = []) => {
|
|
1757
3342
|
if (Array.isArray(host.blocks)) {
|
|
1758
3343
|
if (depth >= MAX_COMPOSITION_DEPTH || ancestors.includes(host)) throw new RangeError(`Content groups must be acyclic and nest at most ${MAX_COMPOSITION_DEPTH} levels.`);
|
|
1759
|
-
const settings =
|
|
3344
|
+
const settings = record4(host.composition);
|
|
1760
3345
|
assertComposition(settings);
|
|
1761
3346
|
return [{
|
|
1762
3347
|
field: "blocks",
|
|
@@ -1765,12 +3350,21 @@ function composeSlide(input, options = {}) {
|
|
|
1765
3350
|
path: basePath,
|
|
1766
3351
|
payload: host,
|
|
1767
3352
|
composition: settings,
|
|
1768
|
-
children: host.blocks.flatMap((block, index) => collect(
|
|
3353
|
+
children: host.blocks.flatMap((block, index) => collect(record4(block), `${basePath}.blocks.${index}`, depth + 1, [...ancestors, host]))
|
|
1769
3354
|
}];
|
|
1770
3355
|
}
|
|
1771
|
-
|
|
3356
|
+
const captioned = host.caption !== void 0 ? fields.filter((field) => host[field] !== void 0 && CAPTIONABLE_FIELDS.includes(field)) : [];
|
|
3357
|
+
return fields.filter((field) => host[field] !== void 0).map((field) => ({
|
|
3358
|
+
field,
|
|
3359
|
+
type: host.type ?? kind(field),
|
|
3360
|
+
value: host[field],
|
|
3361
|
+
path: `${basePath}.${field}`,
|
|
3362
|
+
payload: { type: host.type ?? kind(field), [field]: host[field], ...host.numbering !== void 0 && (field === "items" || field === "bullets") ? { numbering: host.numbering } : {} },
|
|
3363
|
+
...captioned.length === 1 && captioned[0] === field ? { caption: host.caption, captionPath: `${basePath}.caption` } : {}
|
|
3364
|
+
}));
|
|
1772
3365
|
};
|
|
1773
|
-
const
|
|
3366
|
+
const regionsInReadingOrder = visualReadingOrder(regions.map((key) => ({ key, box: regionBox(regionParts(key), contentBox, gap) }))).map((entry) => entry.key);
|
|
3367
|
+
const pending = regions.length ? regionsInReadingOrder.flatMap((key) => collect(record4(slide[key]), `${path}.${key}`).map((item) => ({ ...item, region: regionParts(key) }))) : Array.isArray(slide.blocks) ? slide.blocks.flatMap((block, index) => collect(record4(block), `${path}.blocks.${index}`)) : collect(slideImage?.replacesContent ? { ...slide, image: void 0 } : slide, path);
|
|
1774
3368
|
const groups = [], flows = [];
|
|
1775
3369
|
const decisions = options.explain ? [] : void 0;
|
|
1776
3370
|
const inheritedSettings = (parent, own = {}) => ({
|
|
@@ -1782,7 +3376,7 @@ function composeSlide(input, options = {}) {
|
|
|
1782
3376
|
const amount = (settings.padding ?? 0) * Math.min(box.width, box.height);
|
|
1783
3377
|
return { x: box.x + amount, y: box.y + amount, width: box.width - amount * 2, height: box.height - amount * 2 };
|
|
1784
3378
|
};
|
|
1785
|
-
const acceptedBox = (box) => ({ x:
|
|
3379
|
+
const acceptedBox = (box) => ({ x: round2(box.x), y: round2(box.y), width: round2(box.width), height: round2(box.height) });
|
|
1786
3380
|
const payloadBox = (box) => {
|
|
1787
3381
|
if (!hasCards) return box;
|
|
1788
3382
|
const frame = acceptedBox(box), padding2 = Math.min(12 * scale, frame.width / 4, frame.height / 4);
|
|
@@ -1793,7 +3387,8 @@ function composeSlide(input, options = {}) {
|
|
|
1793
3387
|
textMeasurement: options.textMeasurement,
|
|
1794
3388
|
scale,
|
|
1795
3389
|
minFontSize: settings.minFontSize,
|
|
1796
|
-
path: node.path
|
|
3390
|
+
path: node.path,
|
|
3391
|
+
direction: textDirection
|
|
1797
3392
|
});
|
|
1798
3393
|
const measureCode = (node, box, settings) => layoutCode(node.value, acceptedBox(box), {
|
|
1799
3394
|
fonts: options.fonts,
|
|
@@ -1809,7 +3404,8 @@ function composeSlide(input, options = {}) {
|
|
|
1809
3404
|
minFontSize: settings.minFontSize,
|
|
1810
3405
|
path: node.path,
|
|
1811
3406
|
textRasterPadding: options.textRasterPadding,
|
|
1812
|
-
align: alignmentFor("metric")
|
|
3407
|
+
align: alignmentFor("metric"),
|
|
3408
|
+
direction: textDirection
|
|
1813
3409
|
});
|
|
1814
3410
|
const measureTimeline = (node, box, settings) => layoutTimeline(node.value, acceptedBox(box), {
|
|
1815
3411
|
fonts: options.fonts,
|
|
@@ -1817,13 +3413,20 @@ function composeSlide(input, options = {}) {
|
|
|
1817
3413
|
scale,
|
|
1818
3414
|
minFontSize: settings.minFontSize,
|
|
1819
3415
|
path: node.path,
|
|
1820
|
-
textRasterPadding: options.textRasterPadding
|
|
3416
|
+
textRasterPadding: options.textRasterPadding,
|
|
3417
|
+
direction: textDirection
|
|
1821
3418
|
});
|
|
3419
|
+
const captionOf = (node, box) => node.caption !== void 0 && node.captionPath ? layoutCaption(node.caption, box, node.captionPath, annotationOptions) : void 0;
|
|
1822
3420
|
const leafScore = (node, box, settings, penalties) => {
|
|
1823
3421
|
box = payloadBox(box);
|
|
1824
|
-
const
|
|
1825
|
-
|
|
1826
|
-
|
|
3422
|
+
const captioned = captionOf(node, box);
|
|
3423
|
+
if (captioned) box = captioned.mediaBox;
|
|
3424
|
+
const text2 = contentText(node.field, node.value);
|
|
3425
|
+
let score = Math.abs(Math.log(box.width / box.height / 1.6)) + (captioned?.overflow ? 1e3 : 0);
|
|
3426
|
+
if (penalties) {
|
|
3427
|
+
penalties.cellProportions += Math.abs(Math.log(box.width / box.height / 1.6));
|
|
3428
|
+
if (captioned?.overflow) penalties.textOverflow += 1e3;
|
|
3429
|
+
}
|
|
1827
3430
|
if (node.field === "quote" || node.field === "code" || node.field === "metric" || node.field === "timeline") {
|
|
1828
3431
|
const internal = node.field === "quote" ? measureQuote(node, box, settings) : node.field === "code" ? measureCode(node, box, settings) : node.field === "metric" ? measureMetric(node, box, settings) : measureTimeline(node, box, settings);
|
|
1829
3432
|
const reduction = internal.parts.reduce((sum, part) => sum + (part.fit ? (part.requestedFontSize - part.fit.fontSize) / scale : 0), 0);
|
|
@@ -1832,9 +3435,9 @@ function composeSlide(input, options = {}) {
|
|
|
1832
3435
|
penalties.fontReduction += reduction;
|
|
1833
3436
|
penalties.textOverflow += internal.overflow ? 1e3 : 0;
|
|
1834
3437
|
}
|
|
1835
|
-
} else if (
|
|
1836
|
-
const fit = fitContent(node.field, node.value,
|
|
1837
|
-
const reduction = Math.max(0, 25 * scale - fit.fontSize) / scale;
|
|
3438
|
+
} else if (text2) {
|
|
3439
|
+
const fit = fitContent(node.field, node.value, text2, box, 25 * scale, snapFontSizeUp((settings.minFontSize ?? 16) * scale), node.path, node.payload.numbering);
|
|
3440
|
+
const reduction = Math.max(0, snapFontSizeDown(25 * scale) - fit.fontSize) / scale;
|
|
1838
3441
|
score += reduction + (fit.overflow ? 1e3 : 0);
|
|
1839
3442
|
if (penalties) {
|
|
1840
3443
|
penalties.fontReduction += reduction;
|
|
@@ -1893,10 +3496,11 @@ function composeSlide(input, options = {}) {
|
|
|
1893
3496
|
candidates: candidates ?? []
|
|
1894
3497
|
});
|
|
1895
3498
|
const grid = gridGeometry(count, area2, cols, actualGap, settings.weights ?? [], mode === "column");
|
|
1896
|
-
const
|
|
1897
|
-
|
|
3499
|
+
const mirrored = (box) => rtl ? { ...box, x: area2.x + area2.width - (box.x - area2.x) - box.width } : box;
|
|
3500
|
+
const boxes = grid.boxes.map(mirrored);
|
|
3501
|
+
if (recorded && !hasRegions) flows.push({ path: containerPath, box: { ...area2 }, composition: { ...settings }, columns: rtl ? grid.columns.map((track) => ({ offset: area2.width - track.offset - track.size, size: track.size })) : grid.columns, rows: grid.rows, gap: grid.gap, itemCount: nodes.length, slotCount: count });
|
|
1898
3502
|
nodes.forEach((node, index) => {
|
|
1899
|
-
let box = node.region ? regionBox(node.region, area2, actualGap) : boxes[index];
|
|
3503
|
+
let box = node.region ? mirrored(regionBox(node.region, area2, actualGap)) : boxes[index];
|
|
1900
3504
|
if (node.children) {
|
|
1901
3505
|
const own = inheritedSettings(settings, node.composition), inner = inset(box, own);
|
|
1902
3506
|
if (!node.synthetic) groups.push({ path: node.path, box, contentBox: inner, composition: own });
|
|
@@ -1904,13 +3508,18 @@ function composeSlide(input, options = {}) {
|
|
|
1904
3508
|
} else {
|
|
1905
3509
|
const frameBox = hasCards ? acceptedBox(box) : void 0;
|
|
1906
3510
|
box = payloadBox(box);
|
|
3511
|
+
const caption = captionOf(node, box);
|
|
3512
|
+
if (caption) {
|
|
3513
|
+
box = caption.mediaBox;
|
|
3514
|
+
diagnostics.push(...caption.diagnostics);
|
|
3515
|
+
}
|
|
1907
3516
|
const textValue = contentText(node.field, node.value);
|
|
1908
3517
|
const quoteLayout = node.field === "quote" ? measureQuote(node, box, settings) : void 0;
|
|
1909
3518
|
const codeLayout = node.field === "code" ? measureCode(node, box, settings) : void 0;
|
|
1910
3519
|
const metricLayout = node.field === "metric" ? measureMetric(node, box, settings) : void 0;
|
|
1911
3520
|
const timelineLayout = node.field === "timeline" ? measureTimeline(node, box, settings) : void 0;
|
|
1912
3521
|
const internal = quoteLayout ?? codeLayout ?? metricLayout ?? timelineLayout, body = internal?.parts.find((part) => part.role === "body" || part.role === "value");
|
|
1913
|
-
const
|
|
3522
|
+
const text2 = internal ? body?.fit : textValue !== void 0 ? fitContent(node.field, node.value, textValue, box, 25 * scale, snapFontSizeUp((settings.minFontSize ?? 16) * scale), node.path, node.payload.numbering) : void 0;
|
|
1914
3523
|
items.push({
|
|
1915
3524
|
path: node.path,
|
|
1916
3525
|
field: node.field,
|
|
@@ -1919,7 +3528,7 @@ function composeSlide(input, options = {}) {
|
|
|
1919
3528
|
payload: node.payload,
|
|
1920
3529
|
box: internal ? acceptedBox(box) : box,
|
|
1921
3530
|
...frameBox ? { frameBox } : {},
|
|
1922
|
-
text,
|
|
3531
|
+
text: text2,
|
|
1923
3532
|
textStyle: body?.style ?? styleFor(node.field, node.path),
|
|
1924
3533
|
composition: settings,
|
|
1925
3534
|
alignment: alignmentFor(node.field),
|
|
@@ -1927,7 +3536,8 @@ function composeSlide(input, options = {}) {
|
|
|
1927
3536
|
...codeLayout ? { codeLayout } : {},
|
|
1928
3537
|
...metricLayout ? { metricLayout } : {},
|
|
1929
3538
|
...timelineLayout ? { timelineLayout } : {},
|
|
1930
|
-
...bulletImage && (node.field === "items" || node.field === "bullets") ? { bulletImage } : {}
|
|
3539
|
+
...bulletImage && node.payload.numbering === void 0 && (node.field === "items" || node.field === "bullets") ? { bulletImage } : {},
|
|
3540
|
+
...caption ? { caption } : {}
|
|
1931
3541
|
});
|
|
1932
3542
|
if (box.width < 100 * scale || box.height < 60 * scale) diagnostics.push({ code: "small-cell", path: node.path, message: "Content cell is too small for comfortable reading; use fewer blocks or a different composition." });
|
|
1933
3543
|
}
|
|
@@ -1938,7 +3548,7 @@ function composeSlide(input, options = {}) {
|
|
|
1938
3548
|
const picture = placeholders.findIndex((p) => p.type === "picture");
|
|
1939
3549
|
if (picture >= 0) placeholders.splice(picture, 1);
|
|
1940
3550
|
}
|
|
1941
|
-
const ownMode =
|
|
3551
|
+
const ownMode = record4(slide.composition).mode;
|
|
1942
3552
|
const direction = designHint("contentDirection")?.value;
|
|
1943
3553
|
const directionMode = direction === "vertical" ? "column" : direction === "horizontal" ? "row" : void 0;
|
|
1944
3554
|
const layoutDirectionMode = layout.slideLayoutDirection === "Vertical" ? "column" : layout.slideLayoutDirection === "Horizontal" ? "row" : "auto";
|
|
@@ -1952,19 +3562,23 @@ function composeSlide(input, options = {}) {
|
|
|
1952
3562
|
const container = { field: "blocks", type: "group", value: rest.map((node) => node.payload), path, payload: {}, children: rest, composition: {}, synthetic: true };
|
|
1953
3563
|
arrange(chartPrimary === "left" || chartPrimary === "top" ? [primary, container] : [container, primary], contentBox, rootSettings, 0);
|
|
1954
3564
|
} else arrange(pending, contentBox, rootSettings, composition.mode ? 0 : placeholders.length);
|
|
1955
|
-
if (listBullet?.value === "image" && !bulletImage && items.some((item) => item.field === "items" || item.field === "bullets")) {
|
|
3565
|
+
if (listBullet?.value === "image" && !bulletImage && items.some((item) => (item.field === "items" || item.field === "bullets") && item.payload.numbering === void 0)) {
|
|
1956
3566
|
diagnostics.push({ code: "unresolved-content", path: listBullet.path, message: "Picture bullets (listBullet: image) need design.logo or a primary organization logo; the marker glyph is drawn instead." });
|
|
1957
3567
|
}
|
|
3568
|
+
const numberingDiagnostics = [];
|
|
1958
3569
|
for (const item of items) {
|
|
1959
|
-
for (const key of ["x", "y", "width", "height"]) item.box[key] =
|
|
3570
|
+
for (const key of ["x", "y", "width", "height"]) item.box[key] = round2(item.box[key]);
|
|
1960
3571
|
if (item.field === "table" && tableOverflows(item.value, item.box, scale, item.composition, options, item.path)) diagnostics.push({ code: "text-overflow", path: item.path, message: "Table cells do not fit; use fewer rows, fewer columns, or split the table across slides." });
|
|
1961
3572
|
if (item.quoteLayout) diagnostics.push(...item.quoteLayout.diagnostics);
|
|
1962
3573
|
else if (item.codeLayout) diagnostics.push(...item.codeLayout.diagnostics);
|
|
1963
3574
|
else if (item.metricLayout) diagnostics.push(...item.metricLayout.diagnostics);
|
|
1964
3575
|
else if (item.timelineLayout) diagnostics.push(...item.timelineLayout.diagnostics);
|
|
1965
3576
|
else if (item.text?.overflow) diagnostics.push({ code: "text-overflow", path: item.path, message: "Text exceeds its cell at the minimum font size; shorten it, increase its space, or split the slide." });
|
|
3577
|
+
for (const entry of item.text?.listEntries ?? []) {
|
|
3578
|
+
if (entry.marker.number?.adapted) numberingDiagnostics.push({ code: "numbering-adapted", path: entry.textPath ?? item.path, message: `Roman numerals stop at 3999; this entry is numbered ${entry.marker.number.value} in arabic, as PowerPoint draws it.` });
|
|
3579
|
+
}
|
|
1966
3580
|
}
|
|
1967
|
-
for (const group of groups) for (const box of [group.box, group.contentBox]) for (const key of ["x", "y", "width", "height"]) box[key] =
|
|
3581
|
+
for (const group of groups) for (const box of [group.box, group.contentBox]) for (const key of ["x", "y", "width", "height"]) box[key] = round2(box[key]);
|
|
1968
3582
|
const strictPaths = new Set(items.filter((item) => item.composition.overflow === "error").map((item) => item.path));
|
|
1969
3583
|
const failures = diagnostics.filter((diagnostic) => {
|
|
1970
3584
|
if (composition.overflow === "error") return true;
|
|
@@ -1986,8 +3600,8 @@ function composeSlide(input, options = {}) {
|
|
|
1986
3600
|
unmeasuredPayloads: items.filter((item) => !headings.has(item.field) && !item.quoteLayout && !item.codeLayout && !item.metricLayout && !item.timelineLayout && !["text", "items", "bullets", "table"].includes(item.field)).map((item) => item.path)
|
|
1987
3601
|
} : void 0;
|
|
1988
3602
|
if (failures.length) throw new OPFCompositionError(failures, explanation);
|
|
1989
|
-
diagnostics.push(...slideImageDiagnostics);
|
|
1990
|
-
return { width, height, contentBox, items, groups, flows, diagnostics, composition, ...furniture ? { furniture } : {}, ...slideImage ? { slideImage } : {}, ...logo ? { logo } : {}, ...explanation ? { explanation } : {} };
|
|
3603
|
+
diagnostics.push(...slideImageDiagnostics, ...numberingDiagnostics);
|
|
3604
|
+
return { width, height, contentBox, items, groups, flows, diagnostics, composition, ...furniture ? { furniture } : {}, ...slideImage ? { slideImage } : {}, ...logo ? { logo } : {}, ...rtl ? { direction: "rtl" } : {}, ...footnotes ? { footnotes } : {}, ...explanation ? { explanation } : {} };
|
|
1991
3605
|
}
|
|
1992
3606
|
function resolveCanvasDimensions(input) {
|
|
1993
3607
|
const presets = {
|
|
@@ -1999,7 +3613,7 @@ function resolveCanvasDimensions(input) {
|
|
|
1999
3613
|
letter: [11, 8.5],
|
|
2000
3614
|
a4: [11.69, 8.27]
|
|
2001
3615
|
};
|
|
2002
|
-
const value =
|
|
3616
|
+
const value = record4(input);
|
|
2003
3617
|
const preset = presets[typeof input === "string" ? input : value.preset] ?? presets.widescreen;
|
|
2004
3618
|
const width = (value.widthInches ?? preset[0]) * 96;
|
|
2005
3619
|
const height = (value.heightInches ?? preset[1]) * 96;
|
|
@@ -2007,4 +3621,4 @@ function resolveCanvasDimensions(input) {
|
|
|
2007
3621
|
return { width, height };
|
|
2008
3622
|
}
|
|
2009
3623
|
|
|
2010
|
-
export { DEFAULT_FONT_SCHEME, DEFAULT_FURNITURE_DATE_FORMAT, DEFAULT_SLIDE_NUMBER_FORMAT, MAX_COMPOSITION_DEPTH, OPFCompositionError, PICTURE_BULLET_SCALE, chartColorForFill, colorContrast, composeSlide, fitList, fitRichText, fitText, formatFurnitureDate, formatSlideNumber, layoutCode, layoutFurniture, layoutMetric, layoutQuote, layoutTable, layoutTimeline, measureText, measureTextOutline, normalizeHexColor, placeTextLines, resolveCanvasDimensions, resolveColorRef, resolveFontFamilies, resolveFontSchemeReference, resolveLogo, resolveSocialProfile, resolveTextStyle, slideImageShape, tableGrid, tableRowBoundaries, textColorForFill, textWidthMeasurer, wrapText };
|
|
3624
|
+
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, isRecord2 as isRecord, layoutCaption, layoutCode, layoutFootnotes, layoutFurniture, layoutMetric, layoutQuote, layoutTable, layoutTimeline, listNumbers, measureText, measureTextOutline, normalizeHexColor, numberingAtLevel, numberingFindings, numberingStyleDraws, paragraphDirection, paragraphDirectionAt, parseIsoDate, pathFor, physicalAlignment, placeTextLines, promotedRegionKeys, readingRows, referencesSlide, resolveCanvasDimensions, resolveChartOptions, resolveColorRef, resolveFontFamilies, resolveFontSchemeReference, resolveLogo, resolveNumbering, resolveScriptFonts, resolveSlideDirection, resolveSocialProfile, resolveTextStyle, scriptFontRole, sliceNumberedItems, slideCitations, slideImageShape, snapFontSizeDown, snapFontSizeUp, tableGrid, tableRowBoundaries, textColorForFill, textWidthMeasurer, visitContentPayloads, visualReadingOrder, walkCitationRuns, wrapText };
|