@openpresentation/opf 0.11.4 → 0.12.1
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-BnI7JKcn.d.ts} +3 -3
- package/dist/catalogs.d.ts +2 -2
- package/dist/catalogs.js +2 -1
- package/dist/{chunk-KJKASEPE.js → chunk-4LAM3N6S.js} +753 -42
- package/dist/chunk-6LZK2IZB.js +1690 -0
- package/dist/{chunk-NSVVQ52O.js → chunk-AUM5MXBR.js} +8 -6
- package/dist/{chunk-AEUSVEUY.js → chunk-E34DJ5LL.js} +50 -14
- package/dist/chunk-FYLJVF7X.js +18448 -0
- package/dist/{chunk-HJL64ETN.js → chunk-GJKJL4YY.js} +14 -0
- package/dist/{chunk-3H2U7FDR.js → chunk-HOBSL7AJ.js} +1867 -247
- package/dist/chunk-JKVKYVWL.js +1332 -0
- package/dist/{chunk-3OJN7QAW.js → chunk-MWPMJE6P.js} +18 -4
- package/dist/chunk-RUC7L5DJ.js +261 -0
- package/dist/chunk-S5W34SIJ.js +1 -0
- package/dist/chunk-V2537ENL.js +198 -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 +72 -18
- package/dist/examples.d.ts +1 -1
- package/dist/examples.js +112 -112
- 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-DcF-MZcz.d.ts} +452 -17
- 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/catalogs/audiences/customers.json +5 -1
- package/dist/spec/catalogs/audiences/executives.json +5 -1
- package/dist/spec/catalogs/audiences/index.json +20 -8
- package/dist/spec/catalogs/audiences/investors.json +5 -1
- package/dist/spec/catalogs/audiences/marketing-team.json +5 -1
- package/dist/spec/catalogs/audiences/regulators.json +5 -1
- package/dist/spec/catalogs/audiences/sales-team.json +5 -1
- package/dist/spec/catalogs/manifest.json +2 -2
- package/dist/spec/reference/engine-defaults.json +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/audience.schema.json +7 -7
- package/dist/spec/schemas/narrative.schema.json +1 -1
- package/dist/spec/schemas/opf.schema.json +597 -28
- 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-DfG9yxba.d.ts +172 -0
- package/dist/validator.d.ts +4 -36
- package/dist/validator.js +5 -4
- package/package.json +32 -3
- package/dist/chunk-FDCWQHWO.js +0 -1366
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { catalogs } from './chunk-E34DJ5LL.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,988 @@ 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
|
+
"audiences/executives": "executive",
|
|
1664
|
+
"audiences/investors": "investor",
|
|
1665
|
+
"audiences/customers": "customer",
|
|
1666
|
+
"audiences/sales-team": "sales",
|
|
1667
|
+
"audiences/marketing-team": "marketing",
|
|
1668
|
+
"audiences/regulators": "regulatory",
|
|
1669
|
+
"chartTypes/100pct-bullet-bar": "100pct-stacked-bar-3x",
|
|
1670
|
+
"chartTypes/100pct-bullet-bar-2x": "100pct-stacked-bar-3x",
|
|
1671
|
+
"chartTypes/100pct-bullet-bar-3x": "100pct-stacked-bar-3x",
|
|
1672
|
+
"chartTypes/100pct-bullet-column": "100pct-stacked-column-3x",
|
|
1673
|
+
"chartTypes/100pct-bullet-column-2x": "100pct-stacked-column-3x",
|
|
1674
|
+
"chartTypes/100pct-bullet-column-3x": "100pct-stacked-column-3x",
|
|
1675
|
+
"chartTypes/100pct-progress-bar": "100pct-stacked-bar-3x",
|
|
1676
|
+
"chartTypes/100pct-stacked-area-2x": "100pct-stacked-area-3x",
|
|
1677
|
+
"chartTypes/100pct-stacked-bar-2x": "100pct-stacked-bar-3x",
|
|
1678
|
+
"chartTypes/100pct-stacked-column-2x": "100pct-stacked-column-3x",
|
|
1679
|
+
"chartTypes/australia": "world",
|
|
1680
|
+
"chartTypes/box-and-whisker-2x": "box-and-whisker",
|
|
1681
|
+
"chartTypes/box-and-whisker-3x": "box-and-whisker",
|
|
1682
|
+
"chartTypes/bullet-bar": "bar",
|
|
1683
|
+
"chartTypes/bullet-bar-2x": "bar",
|
|
1684
|
+
"chartTypes/bullet-bar-3x": "bar",
|
|
1685
|
+
"chartTypes/bullet-column": "column",
|
|
1686
|
+
"chartTypes/bullet-column-2x": "column",
|
|
1687
|
+
"chartTypes/bullet-column-3x": "column",
|
|
1688
|
+
"chartTypes/canada": "world",
|
|
1689
|
+
"chartTypes/clustered-bar-2x": "bar",
|
|
1690
|
+
"chartTypes/clustered-column": "column",
|
|
1691
|
+
"chartTypes/dot-plot": "scatter",
|
|
1692
|
+
"chartTypes/dot-plot-2x": "scatter",
|
|
1693
|
+
"chartTypes/dot-plot-3x": "scatter",
|
|
1694
|
+
"chartTypes/dot-plot-4x": "scatter",
|
|
1695
|
+
"chartTypes/dot-plot-5x": "scatter",
|
|
1696
|
+
"chartTypes/dot-plot-6x": "scatter",
|
|
1697
|
+
"chartTypes/dumbbell": "scatter",
|
|
1698
|
+
"chartTypes/line-2x": "line",
|
|
1699
|
+
"chartTypes/line-3x": "line",
|
|
1700
|
+
"chartTypes/line-with-high-low": "line",
|
|
1701
|
+
"chartTypes/line-with-high-low-and-markers": "line-with-markers",
|
|
1702
|
+
"chartTypes/line-with-markers-2x": "line-with-markers",
|
|
1703
|
+
"chartTypes/line-with-markers-3x": "line-with-markers",
|
|
1704
|
+
"chartTypes/sparkline": "line",
|
|
1705
|
+
"chartTypes/sparkline-2x": "line",
|
|
1706
|
+
"chartTypes/sparkline-3x": "line",
|
|
1707
|
+
"chartTypes/sparkline-4x": "line",
|
|
1708
|
+
"chartTypes/sparkline-5x": "line",
|
|
1709
|
+
"chartTypes/sparkline-6x": "line",
|
|
1710
|
+
"chartTypes/stacked-area-2x": "stacked-area-3x",
|
|
1711
|
+
"chartTypes/stacked-bar-2x": "stacked-bar-3x",
|
|
1712
|
+
"chartTypes/stacked-column-2x": "stacked-column-3x",
|
|
1713
|
+
"chartTypes/stacked-line-2x": "stacked-line-3x",
|
|
1714
|
+
"chartTypes/stacked-line-with-markers-2x": "stacked-line-with-markers-3x",
|
|
1715
|
+
"chartTypes/treemap-2x": "treemap",
|
|
1716
|
+
"chartTypes/treemap-3x": "treemap",
|
|
1717
|
+
"chartTypes/united-kingdom": "world",
|
|
1718
|
+
"chartTypes/united-states": "world"
|
|
1719
|
+
};
|
|
1720
|
+
|
|
1721
|
+
// src/chart-options.ts
|
|
1722
|
+
var END_POSITIONS = ["center", "inside-end", "inside-base", "outside-end"];
|
|
1723
|
+
var STACKED_POSITIONS = ["center", "inside-end", "inside-base"];
|
|
1724
|
+
var POINT_POSITIONS = ["above", "below", "left", "right", "center"];
|
|
1725
|
+
var PIE_POSITIONS = ["center", "inside-end", "outside-end"];
|
|
1726
|
+
var NONE = [];
|
|
1727
|
+
var VALUE_CATEGORY = ["category", "value"];
|
|
1728
|
+
var ALL_CONTENT = ["category", "value", "percent"];
|
|
1729
|
+
var categoryKinds = /* @__PURE__ */ new Set(["bar", "line", "area", "scatter", "histogram", "pareto", "box", "waterfall", "funnel"]);
|
|
1730
|
+
var valueKinds = /* @__PURE__ */ new Set(["bar", "line", "area", "scatter", "histogram", "pareto", "box", "waterfall"]);
|
|
1731
|
+
var legendKinds = /* @__PURE__ */ new Set(["bar", "line", "area", "pie", "doughnut", "scatter", "radar", "box"]);
|
|
1732
|
+
function labelSupport(target) {
|
|
1733
|
+
const none = { supported: true, content: VALUE_CATEGORY, positions: NONE, defaultPosition: null, defaultOn: false };
|
|
1734
|
+
switch (target.kind) {
|
|
1735
|
+
case "bar":
|
|
1736
|
+
return target.stacked ? { ...none, positions: STACKED_POSITIONS, defaultPosition: "center" } : { ...none, positions: END_POSITIONS, defaultPosition: "outside-end" };
|
|
1737
|
+
case "line":
|
|
1738
|
+
case "scatter":
|
|
1739
|
+
return { ...none, positions: POINT_POSITIONS, defaultPosition: "above" };
|
|
1740
|
+
case "pie":
|
|
1741
|
+
return { ...none, content: ALL_CONTENT, positions: PIE_POSITIONS, defaultPosition: "outside-end" };
|
|
1742
|
+
case "doughnut":
|
|
1743
|
+
return { ...none, content: ALL_CONTENT };
|
|
1744
|
+
// The funnel labels its bars with values and the treemap its tiles with category names by default.
|
|
1745
|
+
case "funnel":
|
|
1746
|
+
case "treemap":
|
|
1747
|
+
return { ...none, defaultOn: true };
|
|
1748
|
+
case "area":
|
|
1749
|
+
case "radar":
|
|
1750
|
+
return none;
|
|
1751
|
+
case "histogram":
|
|
1752
|
+
case "pareto":
|
|
1753
|
+
case "waterfall":
|
|
1754
|
+
return { ...none, positions: END_POSITIONS, defaultPosition: "outside-end" };
|
|
1755
|
+
default:
|
|
1756
|
+
return { ...none, supported: false, content: [] };
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
function chartOptionSupport(target) {
|
|
1760
|
+
return {
|
|
1761
|
+
axisTitles: { category: categoryKinds.has(target.kind), value: valueKinds.has(target.kind) },
|
|
1762
|
+
legend: legendKinds.has(target.kind),
|
|
1763
|
+
dataLabels: labelSupport(target)
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1766
|
+
var KEPT = {
|
|
1767
|
+
column: { kind: "bar" },
|
|
1768
|
+
"stacked-column-3x": { kind: "bar", stacked: true },
|
|
1769
|
+
"100pct-stacked-column-3x": { kind: "bar", stacked: true },
|
|
1770
|
+
bar: { kind: "bar" },
|
|
1771
|
+
"stacked-bar-3x": { kind: "bar", stacked: true },
|
|
1772
|
+
"100pct-stacked-bar-3x": { kind: "bar", stacked: true },
|
|
1773
|
+
line: { kind: "line" },
|
|
1774
|
+
"line-with-markers": { kind: "line" },
|
|
1775
|
+
"stacked-line-3x": { kind: "line", stacked: true },
|
|
1776
|
+
"stacked-line-with-markers-3x": { kind: "line", stacked: true },
|
|
1777
|
+
area: { kind: "area" },
|
|
1778
|
+
"stacked-area-3x": { kind: "area", stacked: true },
|
|
1779
|
+
"100pct-stacked-area-3x": { kind: "area", stacked: true },
|
|
1780
|
+
pie: { kind: "pie" },
|
|
1781
|
+
doughnut: { kind: "doughnut" },
|
|
1782
|
+
scatter: { kind: "scatter" },
|
|
1783
|
+
radar: { kind: "radar" },
|
|
1784
|
+
"radar-with-markers": { kind: "radar" },
|
|
1785
|
+
"filled-radar": { kind: "radar" },
|
|
1786
|
+
treemap: { kind: "treemap" },
|
|
1787
|
+
histogram: { kind: "histogram" },
|
|
1788
|
+
pareto: { kind: "pareto" },
|
|
1789
|
+
"box-and-whisker": { kind: "box" },
|
|
1790
|
+
waterfall: { kind: "waterfall" },
|
|
1791
|
+
funnel: { kind: "funnel" },
|
|
1792
|
+
world: { kind: "map" }
|
|
1793
|
+
};
|
|
1794
|
+
function chartOptionTarget(typeId) {
|
|
1795
|
+
if (typeof typeId !== "string") return void 0;
|
|
1796
|
+
const raw = typeId.trim().toLowerCase();
|
|
1797
|
+
const id = raw === "donut" ? "doughnut" : raw;
|
|
1798
|
+
const kept = deprecatedCatalogIds[`chartTypes/${id}`] ?? id;
|
|
1799
|
+
const target = Object.hasOwn(KEPT, kept) ? KEPT[kept] : void 0;
|
|
1800
|
+
return target ? { ...target } : void 0;
|
|
1801
|
+
}
|
|
1802
|
+
var record3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1803
|
+
var LEGEND_POSITIONS = /* @__PURE__ */ new Set(["none", "top", "bottom", "left", "right"]);
|
|
1804
|
+
var CONTENT_ORDER = ["category", "value", "percent"];
|
|
1805
|
+
var POSITIONS = /* @__PURE__ */ new Set(["auto", "center", "inside-end", "inside-base", "outside-end", "above", "below", "left", "right"]);
|
|
1806
|
+
var DEFAULT_CHART_LABEL_SEPARATOR = ", ";
|
|
1807
|
+
function resolveChartOptions(chart, target) {
|
|
1808
|
+
const result = { active: false, axisTitles: {}, diagnostics: [] };
|
|
1809
|
+
if (!record3(chart)) return result;
|
|
1810
|
+
const support = target ? chartOptionSupport(target) : void 0;
|
|
1811
|
+
const adapt = (option, reason, message) => result.diagnostics.push({ code: "chart-option-adapted", option, reason, message });
|
|
1812
|
+
const kind2 = target?.kind ?? "unknown";
|
|
1813
|
+
if (record3(chart.axisTitles)) {
|
|
1814
|
+
for (const axis of ["category", "value"]) {
|
|
1815
|
+
const raw = chart.axisTitles[axis];
|
|
1816
|
+
if (typeof raw !== "string" || !raw.trim()) continue;
|
|
1817
|
+
if (support && !support.axisTitles[axis]) {
|
|
1818
|
+
adapt(`axisTitles.${axis}`, "unsupported-type", `A '${kind2}' chart has no ${axis} axis to title; the ${axis} axis title is not drawn or exported.`);
|
|
1819
|
+
continue;
|
|
1820
|
+
}
|
|
1821
|
+
result.axisTitles[axis] = raw.trim();
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
if (typeof chart.legend === "string" && LEGEND_POSITIONS.has(chart.legend)) {
|
|
1825
|
+
if (support && !support.legend) adapt("legend", "unsupported-type", `A '${kind2}' chart has no legend option; the legend setting is ignored.`);
|
|
1826
|
+
else result.legend = chart.legend;
|
|
1827
|
+
}
|
|
1828
|
+
const labels = chart.dataLabels;
|
|
1829
|
+
if (labels === false && support?.dataLabels.defaultOn) result.dataLabelsOff = true;
|
|
1830
|
+
if (labels === true || record3(labels)) {
|
|
1831
|
+
const options = record3(labels) ? labels : {};
|
|
1832
|
+
if (support && !support.dataLabels.supported) {
|
|
1833
|
+
adapt("dataLabels", "unsupported-type", `A '${kind2}' chart has no data label option; data labels are not drawn or exported.`);
|
|
1834
|
+
} else {
|
|
1835
|
+
const allowed = support?.dataLabels.content ?? ALL_CONTENT;
|
|
1836
|
+
const requested = Array.isArray(options.content) ? options.content.filter((part) => typeof part === "string" && CONTENT_ORDER.includes(part)) : ["value"];
|
|
1837
|
+
const content = CONTENT_ORDER.filter((part) => requested.includes(part) && allowed.includes(part));
|
|
1838
|
+
for (const part of CONTENT_ORDER) {
|
|
1839
|
+
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.`);
|
|
1840
|
+
}
|
|
1841
|
+
if (!content.length) content.push("value");
|
|
1842
|
+
const positions = support?.dataLabels.positions ?? NONE;
|
|
1843
|
+
let position = support?.dataLabels.defaultPosition ?? null;
|
|
1844
|
+
if (typeof options.position === "string" && POSITIONS.has(options.position) && options.position !== "auto") {
|
|
1845
|
+
if (!support || positions.includes(options.position)) position = options.position;
|
|
1846
|
+
else adapt("dataLabels.position", "unsupported-position", `A '${kind2}'${target?.stacked ? " stacked" : ""} chart cannot place data labels '${options.position}'; the type's default position is used.`);
|
|
1847
|
+
}
|
|
1848
|
+
let separator = typeof options.separator === "string" ? options.separator : DEFAULT_CHART_LABEL_SEPARATOR;
|
|
1849
|
+
if (/[\r\n]/.test(separator)) {
|
|
1850
|
+
separator = separator.replace(/[\r\n]+/g, " ");
|
|
1851
|
+
adapt("dataLabels.separator", "unsupported-content", "A data label is one line; the line break in the separator is replaced with a space.");
|
|
1852
|
+
}
|
|
1853
|
+
result.dataLabels = { content, position, separator };
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
result.active = result.legend !== void 0 || result.dataLabels !== void 0 || result.dataLabelsOff === true || result.axisTitles.category !== void 0 || result.axisTitles.value !== void 0;
|
|
1857
|
+
return result;
|
|
1858
|
+
}
|
|
1859
|
+
function formatChartLabelNumber(value) {
|
|
1860
|
+
return String(Number(value.toPrecision(12)));
|
|
1861
|
+
}
|
|
1862
|
+
function formatChartLabelPercent(share) {
|
|
1863
|
+
return `${Math.round(Number((share * 100).toPrecision(12)))}%`;
|
|
1864
|
+
}
|
|
1865
|
+
function chartLabelText(parts, content, separator = DEFAULT_CHART_LABEL_SEPARATOR) {
|
|
1866
|
+
return CONTENT_ORDER.filter((part) => content.includes(part) && parts[part] !== void 0).map((part) => parts[part]).join(separator);
|
|
1867
|
+
}
|
|
361
1868
|
|
|
362
1869
|
// src/composition.ts
|
|
363
1870
|
var MAX_COMPOSITION_DEPTH = 32;
|
|
@@ -368,14 +1875,14 @@ function resolveFontSchemeReference(reference, lookup, path = "design.fontScheme
|
|
|
368
1875
|
const overrides = typeof reference === "object" && reference !== null && !Array.isArray(reference) ? reference : void 0;
|
|
369
1876
|
const id = typeof reference === "string" ? reference : typeof overrides?.id === "string" ? overrides.id : void 0;
|
|
370
1877
|
const found = id === void 0 ? void 0 : lookup(id);
|
|
371
|
-
const base =
|
|
1878
|
+
const base = record4(found ?? lookup(DEFAULT_FONT_SCHEME));
|
|
372
1879
|
const scheme = overrides ? { ...base, ...overrides } : { ...base };
|
|
373
1880
|
if (id === void 0 || found !== void 0) return { scheme };
|
|
374
1881
|
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
1882
|
}
|
|
376
1883
|
function resolveFontFamilies(input) {
|
|
377
|
-
const scheme =
|
|
378
|
-
const family = (value) => typeof value === "string" ? value :
|
|
1884
|
+
const scheme = record4(input);
|
|
1885
|
+
const family = (value) => typeof value === "string" ? value : record4(value).family;
|
|
379
1886
|
const accent = family(scheme.accent);
|
|
380
1887
|
return {
|
|
381
1888
|
heading: family(scheme.heading) ?? scheme.major ?? scheme.minor ?? DEFAULT_FONT_FAMILIES.heading,
|
|
@@ -389,27 +1896,28 @@ function resolveTextStyle(style, measurement) {
|
|
|
389
1896
|
}
|
|
390
1897
|
function textWidthMeasurer(style, measurement) {
|
|
391
1898
|
if (!measurement) return measureText;
|
|
392
|
-
return (
|
|
393
|
-
const width = measurement.measure(
|
|
1899
|
+
return (text2, size) => {
|
|
1900
|
+
const width = measurement.measure(text2, size, style);
|
|
394
1901
|
if (!Number.isFinite(width) || width < 0) throw new RangeError("Text measurement must return a finite, nonnegative width.");
|
|
395
1902
|
return width;
|
|
396
1903
|
};
|
|
397
1904
|
}
|
|
398
|
-
function measureTextOutline(
|
|
1905
|
+
function measureTextOutline(text2, fontSize, style, measurement) {
|
|
399
1906
|
if (measurement?.outlineBounds === void 0) return void 0;
|
|
400
1907
|
if (typeof measurement.outlineBounds !== "function") throw new TypeError("Text outline provider must be a function.");
|
|
401
|
-
const bounds = measurement.outlineBounds(
|
|
1908
|
+
const bounds = measurement.outlineBounds(text2, fontSize, style);
|
|
402
1909
|
if (bounds === null) return null;
|
|
403
1910
|
if (!bounds || ![bounds.x, bounds.y, bounds.width, bounds.height].every(Number.isFinite) || bounds.width < 0 || bounds.height < 0) {
|
|
404
1911
|
throw new RangeError("Text outline measurement must return null or finite bounds with nonnegative dimensions.");
|
|
405
1912
|
}
|
|
406
1913
|
return { x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height };
|
|
407
1914
|
}
|
|
408
|
-
function placeTextLines(lines, box, alignment = "left", rasterPadding = 0) {
|
|
1915
|
+
function placeTextLines(lines, box, alignment = "left", rasterPadding = 0, directions) {
|
|
409
1916
|
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
|
|
1917
|
+
const placed = [];
|
|
411
1918
|
let shift = 0, bottom = box.y, height = 0, overflow = false;
|
|
412
|
-
for (const line of lines) {
|
|
1919
|
+
for (const [lineIndex, line] of lines.entries()) {
|
|
1920
|
+
const lineAlignment = physicalAlignment(alignment, directions?.[lineIndex]), factor = lineAlignment === "right" ? 1 : lineAlignment === "center" ? 0.5 : 0;
|
|
413
1921
|
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
1922
|
const ink = line.outline;
|
|
415
1923
|
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 +1933,7 @@ function placeTextLines(lines, box, alignment = "left", rasterPadding = 0) {
|
|
|
425
1933
|
if (outline) bottom = outline.y + outline.height + rasterPadding;
|
|
426
1934
|
height = Math.max(height, y + line.height - box.y, outline ? bottom - box.y : 0);
|
|
427
1935
|
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 });
|
|
1936
|
+
placed.push({ x, y, baseline, height: line.height, width: line.width, outline, ...directions ? { alignment: lineAlignment } : {} });
|
|
429
1937
|
}
|
|
430
1938
|
return { alignment, rasterPadding, lines: placed, height, overflow };
|
|
431
1939
|
}
|
|
@@ -460,14 +1968,14 @@ var headings = /* @__PURE__ */ new Set(["title", "subtitle", "tag"]);
|
|
|
460
1968
|
var COVER_LAYOUT_IDS = /* @__PURE__ */ new Set(["title", "title-subtitle"]);
|
|
461
1969
|
var rows = ["top", "middle", "bottom"];
|
|
462
1970
|
var columns = ["left", "center", "right"];
|
|
463
|
-
var
|
|
1971
|
+
var record4 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
464
1972
|
var kind = (field) => field === "items" ? "list" : field === "bullets" ? "text" : field;
|
|
465
|
-
var
|
|
1973
|
+
var round2 = (value) => Math.round(value * 1e6) / 1e6 || value;
|
|
466
1974
|
var SLIDE_IMAGE_POSITIONS = ["background", "top", "bottom", "left", "right"];
|
|
467
1975
|
var SLIDE_IMAGE_BAND = 0.5;
|
|
468
|
-
var assetSource = (value) => typeof value === "string" ? value :
|
|
1976
|
+
var assetSource = (value) => typeof value === "string" ? value : record4(value).src;
|
|
469
1977
|
var finite = (value, min, max) => typeof value === "number" && Number.isFinite(value) && value >= min && value <= max ? value : void 0;
|
|
470
|
-
var pathNumber = (value) => String(
|
|
1978
|
+
var pathNumber = (value) => String(round2(value));
|
|
471
1979
|
var SLIDE_IMAGE_SHAPES = { rectangle: "rect", rounded: "roundRect", circle: "ellipse", hexagon: "hexagon" };
|
|
472
1980
|
function slideImageShape(kind2, box, cornerRadius = 1 / 6) {
|
|
473
1981
|
const { x, y, width: w, height: h } = box, r = x + w, b = y + h, ss = Math.min(w, h), n = pathNumber;
|
|
@@ -488,15 +1996,17 @@ function slideImageShape(kind2, box, cornerRadius = 1 / 6) {
|
|
|
488
1996
|
}
|
|
489
1997
|
return { kind: "rectangle", preset: "rect", adjust: {}, path: rect };
|
|
490
1998
|
}
|
|
491
|
-
function resolveSlideImage(slide, layout, presentation, width, height, path, padding, scale, diagnostics) {
|
|
492
|
-
const own =
|
|
1999
|
+
function resolveSlideImage(slide, layout, presentation, width, height, path, padding, scale, diagnostics, rtl = false) {
|
|
2000
|
+
const own = record4(slide.design), deck = record4(record4(presentation).design);
|
|
493
2001
|
const local = own.slideImage !== void 0;
|
|
494
2002
|
const configured = local ? own.slideImage : deck.slideImage;
|
|
495
2003
|
if (!configured || typeof configured !== "object" && typeof configured !== "string") return void 0;
|
|
496
|
-
const treatment = typeof configured === "object" && !Array.isArray(configured) && "position" in configured ?
|
|
2004
|
+
const treatment = typeof configured === "object" && !Array.isArray(configured) && "position" in configured ? record4(configured) : void 0;
|
|
497
2005
|
const alignment = typeof layout.slideImageAlignment === "string" ? layout.slideImageAlignment.toLowerCase() : void 0;
|
|
498
|
-
const
|
|
499
|
-
if (!SLIDE_IMAGE_POSITIONS.includes(
|
|
2006
|
+
const authoredPosition = treatment ? treatment.position : SLIDE_IMAGE_POSITIONS.find((value2) => value2 === alignment) ?? "background";
|
|
2007
|
+
if (!SLIDE_IMAGE_POSITIONS.includes(authoredPosition)) return void 0;
|
|
2008
|
+
const mirrorSide = (side) => !rtl ? side : side === "left" ? "right" : side === "right" ? "left" : side;
|
|
2009
|
+
const position = mirrorSide(authoredPosition);
|
|
500
2010
|
const designSource = treatment ? treatment.src : configured;
|
|
501
2011
|
const root = slide.image, sameSource = root !== void 0 && designSource !== void 0 && assetSource(root) === assetSource(designSource);
|
|
502
2012
|
if (!local && layout.slideImage !== true && !sameSource) return void 0;
|
|
@@ -507,8 +2017,8 @@ function resolveSlideImage(slide, layout, presentation, width, height, path, pad
|
|
|
507
2017
|
const t = treatment ?? {};
|
|
508
2018
|
const fill = t.fill === "crop" || t.fill === "fit" ? t.fill : (own.imageFill ?? deck.imageFill) === "fit" ? "fit" : "crop";
|
|
509
2019
|
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:
|
|
2020
|
+
const band = { width: round2(width * share), height: round2(height * share) };
|
|
2021
|
+
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
2022
|
const designPath = local ? `${path}.design.slideImage` : "design.slideImage";
|
|
513
2023
|
const kind2 = Object.hasOwn(SLIDE_IMAGE_SHAPES, t.shape) ? t.shape : "rectangle";
|
|
514
2024
|
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 +2027,7 @@ function resolveSlideImage(slide, layout, presentation, width, height, path, pad
|
|
|
517
2027
|
const frameWidth = Math.min(box.width, box.height * aspect), frameHeight = frameWidth / aspect;
|
|
518
2028
|
box = { x: box.x + (box.width - frameWidth) / 2, y: box.y + (box.height - frameHeight) / 2, width: frameWidth, height: frameHeight };
|
|
519
2029
|
}
|
|
520
|
-
box = { x:
|
|
2030
|
+
box = { x: round2(box.x), y: round2(box.y), width: round2(box.width), height: round2(box.height) };
|
|
521
2031
|
const result = {
|
|
522
2032
|
path: designPath,
|
|
523
2033
|
sourcePath: replacesContent ? `${path}.image` : designPath,
|
|
@@ -530,19 +2040,19 @@ function resolveSlideImage(slide, layout, presentation, width, height, path, pad
|
|
|
530
2040
|
shape: slideImageShape(kind2, box, finite(t.cornerRadius, 0, 0.5))
|
|
531
2041
|
};
|
|
532
2042
|
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:
|
|
2043
|
+
const border = record4(t.border), borderWidth = finite(border.width, 0, 64);
|
|
2044
|
+
if (border.color !== void 0 && borderWidth) result.border = { color: border.color, width: round2(borderWidth * scale) };
|
|
535
2045
|
const opacity = finite(t.opacity, 0, 1);
|
|
536
2046
|
if (opacity !== void 0 && opacity < 1) result.opacity = opacity;
|
|
537
2047
|
if (t.recolor === "grayscale") result.recolor = { type: "grayscale" };
|
|
538
|
-
else if (
|
|
539
|
-
const overlay =
|
|
2048
|
+
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 };
|
|
2049
|
+
const overlay = record4(t.overlay), overlayOpacity = finite(overlay.opacity, 0, 1);
|
|
540
2050
|
if (overlay.color !== void 0 && overlayOpacity !== void 0) {
|
|
541
|
-
const edge = ["top", "bottom", "left", "right"].includes(overlay.edge) ? overlay.edge : void 0;
|
|
2051
|
+
const edge = ["top", "bottom", "left", "right"].includes(overlay.edge) ? mirrorSide(overlay.edge) : void 0;
|
|
542
2052
|
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
2053
|
else {
|
|
544
2054
|
const part = finite(overlay.size, 0.05, 1) ?? 0.3;
|
|
545
|
-
const bandBox = !edge ? box : edge === "top" ? { ...box, height:
|
|
2055
|
+
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
2056
|
result.overlay = { color: overlay.color, opacity: overlayOpacity, box: bandBox, shape: edge ? slideImageShape("rectangle", bandBox) : result.shape };
|
|
547
2057
|
}
|
|
548
2058
|
}
|
|
@@ -565,10 +2075,10 @@ function resolveLogo(presentation, slide, options = {}) {
|
|
|
565
2075
|
};
|
|
566
2076
|
const pick = (value, path) => {
|
|
567
2077
|
if (value === void 0 || value === null || value === false) return null;
|
|
568
|
-
if (typeof value === "string" || typeof value === "object" && !Array.isArray(value) && "src" in
|
|
2078
|
+
if (typeof value === "string" || typeof value === "object" && !Array.isArray(value) && "src" in record4(value)) {
|
|
569
2079
|
return usable(value) ? { source: value, path, variant: "default", slot } : null;
|
|
570
2080
|
}
|
|
571
|
-
const set =
|
|
2081
|
+
const set = record4(value);
|
|
572
2082
|
if (!Object.keys(set).some((key) => LOGO_SET_KEYS.has(key))) return null;
|
|
573
2083
|
for (const key of chain) {
|
|
574
2084
|
const variant = set[key];
|
|
@@ -576,16 +2086,16 @@ function resolveLogo(presentation, slide, options = {}) {
|
|
|
576
2086
|
}
|
|
577
2087
|
return null;
|
|
578
2088
|
};
|
|
579
|
-
const deck =
|
|
580
|
-
const own = pick(
|
|
2089
|
+
const deck = record4(presentation), slideIndex = Number.isSafeInteger(options.slideIndex) && options.slideIndex >= 0 ? options.slideIndex : 0;
|
|
2090
|
+
const own = pick(record4(record4(slide).design).logo, `slides.${slideIndex}.design.logo`);
|
|
581
2091
|
if (own) return own;
|
|
582
|
-
const shared = pick(
|
|
2092
|
+
const shared = pick(record4(deck.design).logo, "design.logo");
|
|
583
2093
|
if (shared) return shared;
|
|
584
2094
|
const organizations = Array.isArray(deck.organization) ? deck.organization : [deck.organization];
|
|
585
|
-
const primaryIndex = organizations.findIndex((item) =>
|
|
2095
|
+
const primaryIndex = organizations.findIndex((item) => record4(item).role === "primary");
|
|
586
2096
|
const index = primaryIndex >= 0 ? primaryIndex : organizations.findIndex(Boolean);
|
|
587
2097
|
if (index < 0) return null;
|
|
588
|
-
return pick(
|
|
2098
|
+
return pick(record4(organizations[index]).logo, Array.isArray(deck.organization) ? `organization.${index}.logo` : "organization.logo");
|
|
589
2099
|
}
|
|
590
2100
|
var webUrl = /^https?:\/\/\S+$/i;
|
|
591
2101
|
function resolveSocialProfile(platform, value, records = [], owner = "organization") {
|
|
@@ -603,10 +2113,11 @@ function resolveSocialProfile(platform, value, records = [], owner = "organizati
|
|
|
603
2113
|
return { text: display(url), href: encodeURI(url), resolved: true };
|
|
604
2114
|
}
|
|
605
2115
|
function layoutFurniture(input, options = {}) {
|
|
606
|
-
const slide =
|
|
607
|
-
const settings = { ...
|
|
2116
|
+
const slide = record4(input), width = options.width ?? 1280, height = options.height ?? 720, scale = Math.min(width, height) / 720;
|
|
2117
|
+
const settings = { ...record4(record4(options.layout).composition), ...record4(slide.composition) };
|
|
608
2118
|
assertComposition(settings);
|
|
609
|
-
const
|
|
2119
|
+
const furnitureRtl = (options.direction ?? resolveSlideDirection(options.presentation, options.slideIndex)) === "rtl";
|
|
2120
|
+
const minimum = snapFontSizeUp((settings.minFontSize ?? 16) * scale), size = gridFontSize(13 * scale, minimum), padding = (options.textRasterPadding ?? 2) * scale;
|
|
610
2121
|
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
2122
|
const number = options.slideNumber ?? (options.slideIndex ?? 0) + 1;
|
|
612
2123
|
if (!Number.isSafeInteger(number) || number < 1) throw new RangeError("Displayed slide number must be a positive safe integer.");
|
|
@@ -615,32 +2126,33 @@ function layoutFurniture(input, options = {}) {
|
|
|
615
2126
|
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
2127
|
const outlines = options.textMeasurement?.outlineBounds !== void 0, parts = [], diagnostics = [];
|
|
617
2128
|
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 =
|
|
2129
|
+
const primaryIndex = organizations.findIndex((item) => record4(item).role === "primary"), organizationIndex = primaryIndex >= 0 ? primaryIndex : organizations.findIndex(Boolean);
|
|
2130
|
+
const organization = record4(organizations[organizationIndex]), organizationRoot = Array.isArray(options.presentation?.organization) ? `organization.${organizationIndex}` : "organization", organizationPath = `${organizationRoot}.name`;
|
|
2131
|
+
const inlinePlatforms = record4(record4(options.presentation?.catalogs).socialPlatforms).records, platformRecords = [...Array.isArray(inlinePlatforms) ? inlinePlatforms : [], ...options.socialPlatforms ?? []];
|
|
621
2132
|
const fontFamily = options.fonts?.body ?? "sans-serif";
|
|
622
2133
|
let headerBottom = 0, footerTop = height, configured = false;
|
|
623
2134
|
const error = (path, message, code = "text-overflow") => diagnostics.push({ code, path, message });
|
|
624
2135
|
for (const kind2 of ["header", "footer"]) {
|
|
625
|
-
const local =
|
|
2136
|
+
const local = record4(slide.design)[kind2] !== void 0, value = local ? record4(slide.design)[kind2] : options.presentation?.design?.[kind2];
|
|
626
2137
|
if (value === void 0 || value === false) continue;
|
|
627
2138
|
const root = local ? `${sourceRoot}.design.${kind2}` : `design.${kind2}`;
|
|
628
2139
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("Header/footer content must be an object or false.");
|
|
629
2140
|
configured = true;
|
|
630
2141
|
const zones = [];
|
|
631
2142
|
for (const [index, zone] of ["left", "center", "right"].entries()) {
|
|
632
|
-
const
|
|
2143
|
+
const side = furnitureRtl && zone !== "center" ? zone === "left" ? "right" : "left" : zone;
|
|
2144
|
+
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
2145
|
let y = 0;
|
|
634
|
-
const add = (field,
|
|
635
|
-
if (
|
|
636
|
-
if (typeof
|
|
2146
|
+
const add = (field, text2, generated = false, sourcePath, extras = {}) => {
|
|
2147
|
+
if (text2 === void 0) return;
|
|
2148
|
+
if (typeof text2 !== "string") throw new TypeError(`Furniture field ${path}.${field} requires string content.`);
|
|
637
2149
|
const partPath = `${path}.${field}`, style = resolveTextStyle({ fontFamily, fontWeight: 400, italic: false, path: partPath }, options.textMeasurement);
|
|
638
|
-
const fit = fitText(
|
|
2150
|
+
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
2151
|
const ink = fit.sourceLines.map((line, index2) => {
|
|
640
2152
|
let outline = null;
|
|
641
2153
|
if (outlines) {
|
|
642
2154
|
for (const segment of line.segments) if (segment.kind === "text") {
|
|
643
|
-
const bounds = measureTextOutline(
|
|
2155
|
+
const bounds = measureTextOutline(text2.slice(segment.start, segment.end), size, style, options.textMeasurement);
|
|
644
2156
|
if (bounds) {
|
|
645
2157
|
const next = { ...bounds, x: bounds.x + segment.x };
|
|
646
2158
|
if (!outline) outline = next;
|
|
@@ -656,30 +2168,30 @@ function layoutFurniture(input, options = {}) {
|
|
|
656
2168
|
}
|
|
657
2169
|
return { width: line.width, y: index2 * fit.lineHeight, baseline: size + index2 * fit.lineHeight, height: fit.lineHeight, outline };
|
|
658
2170
|
});
|
|
659
|
-
const natural = outlines ? placeTextLines(ink, { x, y, width: zoneWidth, height: Number.MAX_VALUE },
|
|
2171
|
+
const natural = outlines ? placeTextLines(ink, { x, y, width: zoneWidth, height: Number.MAX_VALUE }, side, padding) : void 0;
|
|
660
2172
|
const partHeight = natural?.height ?? fit.sourceLines.length * fit.lineHeight;
|
|
661
2173
|
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,
|
|
2174
|
+
const box = { x, y, width: zoneWidth, height: Math.max(scale, partHeight) }, placement = outlines ? placeTextLines(ink, box, side, padding) : void 0;
|
|
663
2175
|
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:
|
|
2176
|
+
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
2177
|
if (accepted.overflow) error(partPath, "Repeated text exceeds its zone at the selected readability floor; change the furniture or slide design.");
|
|
666
2178
|
y += box.height;
|
|
667
2179
|
};
|
|
668
2180
|
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:
|
|
2181
|
+
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));
|
|
2182
|
+
return { x: side === "left" ? x : side === "center" ? x + (zoneWidth - imageWidth) / 2 : x + zoneWidth - imageWidth, y, width: imageWidth, height: imageHeight };
|
|
671
2183
|
};
|
|
672
2184
|
if (content.logo === true) {
|
|
673
2185
|
const resolved = resolveLogo(options.presentation, slide, { slot: "icon", onDark: options.darkBackground, slideIndex: options.slideIndex });
|
|
674
2186
|
if (resolved) {
|
|
675
2187
|
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:
|
|
2188
|
+
zoneParts.push({ type: "image", kind: kind2, zone, field: "logo", path: `${path}.logo`, sourcePath: resolved.path, generated: true, image: resolved.source, box, alignment: side });
|
|
677
2189
|
y += box.height;
|
|
678
2190
|
} else error(`${path}.logo`, "Generated logo needs design.logo or a primary organization logo.", "unresolved-content");
|
|
679
2191
|
}
|
|
680
2192
|
if (content.image !== void 0) {
|
|
681
2193
|
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:
|
|
2194
|
+
zoneParts.push({ type: "image", kind: kind2, zone, field: "image", path: `${path}.image`, sourcePath: `${path}.image`, generated: false, image: content.image, box, alignment: side });
|
|
683
2195
|
y += box.height;
|
|
684
2196
|
}
|
|
685
2197
|
add("text", content.text);
|
|
@@ -688,7 +2200,7 @@ function layoutFurniture(input, options = {}) {
|
|
|
688
2200
|
else error(`${path}.organization`, "Generated organization name needs a named organization in the presentation.", "unresolved-content");
|
|
689
2201
|
}
|
|
690
2202
|
if (content.socials === true) {
|
|
691
|
-
const links = Object.entries(
|
|
2203
|
+
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
2204
|
if (links.length) add("socials", links.map((link) => link.text).join("\n"), true, `${organizationRoot}.socials`, { links });
|
|
693
2205
|
else error(`${path}.socials`, "Generated social profiles need a primary organization with socials.", "unresolved-content");
|
|
694
2206
|
}
|
|
@@ -740,29 +2252,43 @@ function layoutFurniture(input, options = {}) {
|
|
|
740
2252
|
return { algorithm: "furniture-flow-v2", configured, textMeasurement: options.textMeasurement ? "provided" : "estimated", textOutlines: outlines ? "provided" : "unavailable", parts, headerBottom, footerTop, diagnostics, overflow: diagnostics.length > 0 };
|
|
741
2253
|
}
|
|
742
2254
|
var textSegments = new Intl.Segmenter("und", { granularity: "grapheme" });
|
|
743
|
-
|
|
2255
|
+
var WRAP_TOLERANCE = 0.01;
|
|
2256
|
+
var HANGING_SPACE = /[^\S\r\n\u00a0\u202f\ufeff]/u;
|
|
2257
|
+
function measureText(text2, fontSize) {
|
|
744
2258
|
let units = 0;
|
|
745
|
-
for (const character of
|
|
2259
|
+
for (const character of text2) {
|
|
746
2260
|
if (/\p{Mark}|\u200d|\ufe0f/u.test(character)) continue;
|
|
747
2261
|
units += character === " " ? 0.32 : /[A-Z0-9]/.test(character) ? 0.62 : character.codePointAt(0) > 11904 ? 1 : 0.54;
|
|
748
2262
|
}
|
|
749
2263
|
return units * fontSize;
|
|
750
2264
|
}
|
|
751
|
-
function wrapText(
|
|
752
|
-
return fitSourceText(
|
|
2265
|
+
function wrapText(text2, width, fontSize, measure = measureText) {
|
|
2266
|
+
return fitSourceText(text2, { width, height: Number.MAX_VALUE }, fontSize, fontSize, 1, measure, true, false).lines;
|
|
753
2267
|
}
|
|
754
|
-
function fitText(
|
|
2268
|
+
function fitText(text2, box, requestedSize = 25, minFontSize = 16, measure = measureText, direction) {
|
|
755
2269
|
if (![box.width, box.height, requestedSize, minFontSize].every(Number.isFinite) || box.width <= 0 || box.height <= 0 || requestedSize <= 0 || minFontSize <= 0) {
|
|
756
2270
|
throw new RangeError("Text dimensions and font sizes must be finite and positive.");
|
|
757
2271
|
}
|
|
758
|
-
return fitSourceText(
|
|
2272
|
+
return fitSourceText(text2, box, Math.max(requestedSize, minFontSize), minFontSize, 1, measure, true, true, direction);
|
|
759
2273
|
}
|
|
760
|
-
|
|
2274
|
+
var FONT_SIZE_GRID_PER_PX = 75;
|
|
2275
|
+
var FONT_GRID_EPSILON = 1e-6;
|
|
2276
|
+
function snapFontSizeDown(px) {
|
|
2277
|
+
return Math.max(px > 0 ? 1 : -Infinity, Math.floor(px * FONT_SIZE_GRID_PER_PX + FONT_GRID_EPSILON)) / FONT_SIZE_GRID_PER_PX;
|
|
2278
|
+
}
|
|
2279
|
+
function snapFontSizeUp(px) {
|
|
2280
|
+
return Math.max(px > 0 ? 1 : -Infinity, Math.ceil(px * FONT_SIZE_GRID_PER_PX - FONT_GRID_EPSILON)) / FONT_SIZE_GRID_PER_PX;
|
|
2281
|
+
}
|
|
2282
|
+
function gridFontSize(raw, minimum) {
|
|
2283
|
+
return Math.max(snapFontSizeUp(minimum), snapFontSizeDown(raw));
|
|
2284
|
+
}
|
|
2285
|
+
function fitAtSizes(layout, requested, minimum, step = 1, snap = true) {
|
|
761
2286
|
const start = Math.max(requested, minimum);
|
|
762
2287
|
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
2288
|
for (let trial = 0; trial <= 64; trial++) {
|
|
764
|
-
const
|
|
765
|
-
|
|
2289
|
+
const raw = start - trial * step, last = trial === 64 || raw <= minimum;
|
|
2290
|
+
const size = !snap ? last ? minimum : Math.max(minimum, raw) : last ? snapFontSizeUp(minimum) : gridFontSize(raw, minimum), result = layout(size);
|
|
2291
|
+
if (!result.overflow || last) return result;
|
|
766
2292
|
}
|
|
767
2293
|
throw new Error("Text fitting did not evaluate its bounded floor trial.");
|
|
768
2294
|
}
|
|
@@ -772,7 +2298,7 @@ function layoutQuote(value, box, options = {}) {
|
|
|
772
2298
|
if (!quote || Array.isArray(quote) || typeof quote.text !== "string" || [quote.attribution, quote.source].some((field) => field !== void 0 && typeof field !== "string")) {
|
|
773
2299
|
throw new TypeError("Quote content must be a string or a text object with optional string attribution/source.");
|
|
774
2300
|
}
|
|
775
|
-
const scale = options.scale ?? 1, minimum = (options.minFontSize ?? 16) * scale;
|
|
2301
|
+
const scale = options.scale ?? 1, minimum = snapFontSizeUp((options.minFontSize ?? 16) * scale);
|
|
776
2302
|
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
2303
|
throw new RangeError("Quote dimensions, scale and minimum font size must be finite and positive.");
|
|
778
2304
|
}
|
|
@@ -780,23 +2306,23 @@ function layoutQuote(value, box, options = {}) {
|
|
|
780
2306
|
const path = options.path ?? "quote";
|
|
781
2307
|
const diagnostics = [], parts = [];
|
|
782
2308
|
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,
|
|
2309
|
+
const source = (sourcePath, text2, outputStart) => ({ path: sourcePath, start: 0, end: text2.length, outputStart, outputEnd: outputStart + text2.length });
|
|
2310
|
+
const add = (role, text2, sources, area, fontSize, fontFamily, fontWeight, partPath) => {
|
|
785
2311
|
const requestedStyle = { fontFamily, fontWeight, italic: false, path: partPath };
|
|
786
2312
|
const style = resolveTextStyle({ ...requestedStyle }, options.textMeasurement);
|
|
787
|
-
const requestedFontSize =
|
|
788
|
-
const part = { role, path: partPath, text, sources, box: area, requestedFontSize, minFontSize: minimum, requestedStyle, style };
|
|
2313
|
+
const requestedFontSize = gridFontSize(fontSize * scale, minimum);
|
|
2314
|
+
const part = { role, path: partPath, text: text2, sources, box: area, requestedFontSize, minFontSize: minimum, requestedStyle, style };
|
|
789
2315
|
parts.push(part);
|
|
790
2316
|
return part;
|
|
791
2317
|
};
|
|
792
2318
|
let footer = "";
|
|
793
2319
|
const footerSources = [];
|
|
794
2320
|
for (const field of ["attribution", "source"]) {
|
|
795
|
-
const
|
|
796
|
-
if (!
|
|
2321
|
+
const text2 = quote[field];
|
|
2322
|
+
if (!text2) continue;
|
|
797
2323
|
if (footer) footer += " - ";
|
|
798
|
-
footerSources.push(source(`${path}.${field}`,
|
|
799
|
-
footer +=
|
|
2324
|
+
footerSources.push(source(`${path}.${field}`, text2, footer.length));
|
|
2325
|
+
footer += text2;
|
|
800
2326
|
}
|
|
801
2327
|
const bodyPath = shorthand ? path : `${path}.text`;
|
|
802
2328
|
const body = add(
|
|
@@ -820,7 +2346,7 @@ function layoutQuote(value, box, options = {}) {
|
|
|
820
2346
|
path
|
|
821
2347
|
) : void 0;
|
|
822
2348
|
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;
|
|
2349
|
+
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
2350
|
const inner = { x: box.x + 18, y: box.y + 18, width: box.width - 36, height: box.height - 36 };
|
|
825
2351
|
if (!attribution) body.fit = fit(body, body.box);
|
|
826
2352
|
else if (usable(inner) && inner.height > 18) {
|
|
@@ -877,31 +2403,31 @@ function layoutMetric(value, box, options = {}) {
|
|
|
877
2403
|
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
2404
|
throw new TypeError("Metric content requires a finite numeric or string value and schema-valid display metadata.");
|
|
879
2405
|
}
|
|
880
|
-
const scale = options.scale ?? 1, minimum = (options.minFontSize ?? 16) * scale;
|
|
2406
|
+
const scale = options.scale ?? 1, minimum = snapFontSizeUp((options.minFontSize ?? 16) * scale);
|
|
881
2407
|
const rasterPadding = (options.textRasterPadding ?? 1) * scale, hasOutlines = options.textMeasurement?.outlineBounds !== void 0;
|
|
882
2408
|
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
2409
|
throw new RangeError("Metric dimensions, scale and minimum font size must be finite and positive.");
|
|
884
2410
|
}
|
|
885
2411
|
if (!Number.isFinite(rasterPadding) || rasterPadding < 0) throw new RangeError("Metric raster padding must be finite and nonnegative.");
|
|
886
2412
|
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(
|
|
2413
|
+
const requestedAlignment = options.align ?? "left";
|
|
2414
|
+
if (!["left", "center", "right"].includes(requestedAlignment)) throw new RangeError("Invalid metric alignment.");
|
|
889
2415
|
const sourcePath = options.path ?? "metric", parts = [], diagnostics = [];
|
|
890
2416
|
for (const role of ["value", "unit", "label", "description", "delta", "trend"]) {
|
|
891
2417
|
const sourceValue = metric[role];
|
|
892
2418
|
if (sourceValue === void 0) continue;
|
|
893
|
-
const
|
|
2419
|
+
const text2 = String(sourceValue), path = scalar ? sourcePath : `${sourcePath}.${role}`;
|
|
894
2420
|
const nominal = role === "value" ? Math.min(76 * scale, box.height * 0.28) : (role === "description" ? 20 : role === "trend" ? 18 : 23) * scale;
|
|
895
2421
|
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
2422
|
const part = {
|
|
897
2423
|
role,
|
|
898
2424
|
path,
|
|
899
|
-
text,
|
|
900
|
-
sources: [{ path, value: sourceValue, start: 0, end:
|
|
901
|
-
visible: role === "value" ||
|
|
2425
|
+
text: text2,
|
|
2426
|
+
sources: [{ path, value: sourceValue, start: 0, end: text2.length }],
|
|
2427
|
+
visible: role === "value" || text2.length > 0,
|
|
902
2428
|
linePositions: [],
|
|
903
2429
|
box: { ...box, height: 0 },
|
|
904
|
-
requestedFontSize:
|
|
2430
|
+
requestedFontSize: gridFontSize(nominal, minimum),
|
|
905
2431
|
minFontSize: minimum,
|
|
906
2432
|
requestedStyle,
|
|
907
2433
|
style: resolveTextStyle({ ...requestedStyle }, options.textMeasurement)
|
|
@@ -910,6 +2436,8 @@ function layoutMetric(value, box, options = {}) {
|
|
|
910
2436
|
parts.push(part);
|
|
911
2437
|
}
|
|
912
2438
|
const primary = parts[0], metadata = parts.filter((part) => part !== primary && part.visible), unit = metadata.find((part) => part.role === "unit");
|
|
2439
|
+
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;
|
|
2440
|
+
const alignment = physicalAlignment(requestedAlignment, metricDirection);
|
|
913
2441
|
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
2442
|
const occupied = (fit) => fit.placement?.height ?? fit.lines.length * fit.lineHeight;
|
|
915
2443
|
const gap = 8 * scale, primaryGap = 12 * scale;
|
|
@@ -946,7 +2474,7 @@ function layoutMetric(value, box, options = {}) {
|
|
|
946
2474
|
const key = JSON.stringify([part.role, size, width]);
|
|
947
2475
|
let fit = cache2.get(key);
|
|
948
2476
|
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));
|
|
2477
|
+
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
2478
|
fit = place(part, fit, width);
|
|
951
2479
|
if (!Number.isFinite(occupied(fit))) throw new RangeError("Metric text layout exceeds finite coordinates.");
|
|
952
2480
|
cache2.set(key, fit);
|
|
@@ -956,7 +2484,7 @@ function layoutMetric(value, box, options = {}) {
|
|
|
956
2484
|
const fitValue = (area, singleLine) => {
|
|
957
2485
|
if (!usable(area)) return void 0;
|
|
958
2486
|
for (let step = 0; step <= 76; step++) {
|
|
959
|
-
const size =
|
|
2487
|
+
const size = gridFontSize(primary.requestedFontSize - step * scale, minimum);
|
|
960
2488
|
const natural = measure(primary, size, area.width);
|
|
961
2489
|
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
2490
|
if (!fit.overflow || size === minimum) return fit;
|
|
@@ -965,7 +2493,7 @@ function layoutMetric(value, box, options = {}) {
|
|
|
965
2493
|
let selected, attempts = 0;
|
|
966
2494
|
const reductions = Math.min(23, Math.ceil(Math.max(0, ...metadata.map((part) => (part.requestedFontSize - minimum) / scale))));
|
|
967
2495
|
for (let reduction = 0; reduction <= reductions; reduction++) {
|
|
968
|
-
const measured = new Map(metadata.map((part) => [part, measure(part,
|
|
2496
|
+
const measured = new Map(metadata.map((part) => [part, measure(part, gridFontSize(part.requestedFontSize - reduction * scale, minimum), box.width)]));
|
|
969
2497
|
const measuredUnit = unit ? measured.get(unit) : void 0;
|
|
970
2498
|
const unitWidth = unit && measuredUnit ? naturalWidth(unit, measuredUnit) : 0;
|
|
971
2499
|
const unitFit = unit && measuredUnit && unitWidth > 0 ? place(unit, measuredUnit, unitWidth) : measuredUnit;
|
|
@@ -1043,17 +2571,17 @@ function layoutTimeline(value, box, options = {}) {
|
|
|
1043
2571
|
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
2572
|
throw new TypeError("Timeline content requires ordered events with string labels and optional string metadata.");
|
|
1045
2573
|
}
|
|
1046
|
-
const scale = options.scale ?? 1, minimum = (options.minFontSize ?? 16) * scale, padding = (options.textRasterPadding ?? 1) * scale;
|
|
2574
|
+
const scale = options.scale ?? 1, minimum = snapFontSizeUp((options.minFontSize ?? 16) * scale), padding = (options.textRasterPadding ?? 1) * scale;
|
|
1047
2575
|
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
2576
|
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;
|
|
2577
|
+
const outlines = options.textMeasurement?.outlineBounds !== void 0, rtl = options.direction === "rtl";
|
|
1050
2578
|
if (outlines && typeof options.textMeasurement?.outlineBounds !== "function") throw new TypeError("Text outline provider must be a function.");
|
|
1051
2579
|
const path = options.path ?? "timeline", eventPath = (index) => shorthand ? `${path}.${index}` : `${path}.events.${index}`;
|
|
1052
2580
|
const fonts = resolveFontFamilies(options.fonts), source = [];
|
|
1053
|
-
const add = (role,
|
|
1054
|
-
if (
|
|
2581
|
+
const add = (role, text2, partPath, size, weight, eventIndex) => {
|
|
2582
|
+
if (text2 === void 0) return;
|
|
1055
2583
|
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:
|
|
2584
|
+
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
2585
|
};
|
|
1058
2586
|
add("name", timeline.name, `${path}.name`, 24, 700);
|
|
1059
2587
|
add("description", timeline.description, `${path}.description`, 18, 400);
|
|
@@ -1070,7 +2598,7 @@ function layoutTimeline(value, box, options = {}) {
|
|
|
1070
2598
|
const key = JSON.stringify([part.path, size, width, alignment]);
|
|
1071
2599
|
let measured = cache2.get(key);
|
|
1072
2600
|
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));
|
|
2601
|
+
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
2602
|
const ink = fit.sourceLines.map((line, index) => {
|
|
1075
2603
|
let left = Infinity, top = Infinity, right = -Infinity, bottom = -Infinity, hasInk = false;
|
|
1076
2604
|
if (outlines) {
|
|
@@ -1087,7 +2615,7 @@ function layoutTimeline(value, box, options = {}) {
|
|
|
1087
2615
|
}
|
|
1088
2616
|
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
2617
|
});
|
|
1090
|
-
const placement = outlines ? placeTextLines(ink, { x: 0, y: 0, width, height: Number.MAX_VALUE }, alignment, padding) : void 0;
|
|
2618
|
+
const placement = outlines ? placeTextLines(ink, { x: 0, y: 0, width, height: Number.MAX_VALUE }, alignment, padding, fit.directions) : void 0;
|
|
1091
2619
|
const height = placement?.height ?? fit.sourceLines.length * fit.lineHeight;
|
|
1092
2620
|
if (!Number.isFinite(height)) throw new RangeError("Timeline text exceeds finite layout coordinates.");
|
|
1093
2621
|
measured = { fit, ink, height };
|
|
@@ -1108,8 +2636,8 @@ function layoutTimeline(value, box, options = {}) {
|
|
|
1108
2636
|
score2 += Math.max(1, excess);
|
|
1109
2637
|
};
|
|
1110
2638
|
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;
|
|
2639
|
+
const measured = measure(part, gridFontSize(part.requestedFontSize - reduction * scale, minimum), Math.max(scale, width), alignment);
|
|
2640
|
+
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
2641
|
const fit = { ...measured.fit, ...placement ? { placement } : {}, overflow: measured.fit.overflow || !!placement?.overflow };
|
|
1114
2642
|
const accepted = { ...part, box: area, alignment, fit };
|
|
1115
2643
|
parts.push(accepted);
|
|
@@ -1127,7 +2655,7 @@ function layoutTimeline(value, box, options = {}) {
|
|
|
1127
2655
|
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
2656
|
const lineY = y + Math.max(scale, available) * 0.46;
|
|
1129
2657
|
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;
|
|
2658
|
+
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
2659
|
markers.push({ path: eventPath(index), eventIndex: index, x, y: lineY, radius });
|
|
1132
2660
|
let cursor = top;
|
|
1133
2661
|
for (const part of fields2) {
|
|
@@ -1137,7 +2665,7 @@ function layoutTimeline(value, box, options = {}) {
|
|
|
1137
2665
|
}
|
|
1138
2666
|
});
|
|
1139
2667
|
} else {
|
|
1140
|
-
const x = box.x + radius, textX = box.x + 24 * scale, width = box.width - 24 * scale;
|
|
2668
|
+
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
2669
|
events.forEach((fields2, index) => {
|
|
1142
2670
|
const top = y;
|
|
1143
2671
|
let first2;
|
|
@@ -1154,7 +2682,7 @@ function layoutTimeline(value, box, options = {}) {
|
|
|
1154
2682
|
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
2683
|
score2 += 1;
|
|
1156
2684
|
}
|
|
1157
|
-
const first = markers[0], last = markers.at(-1), connector = { x1: first.x, y1: first.y, x2: last.x, y2: last.y };
|
|
2685
|
+
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
2686
|
const candidate = { arrangement, parts, markers, connector, diagnostics, score: score2 };
|
|
1159
2687
|
if (!selected || score2 < selected.score) selected = candidate;
|
|
1160
2688
|
if (!diagnostics.length) break;
|
|
@@ -1165,26 +2693,27 @@ function layoutTimeline(value, box, options = {}) {
|
|
|
1165
2693
|
if (result.diagnostics.length && options.overflow === "error") throw new OPFCompositionError(result.diagnostics);
|
|
1166
2694
|
return { algorithm: "timeline-flow-v1", attempts, textMeasurement: options.textMeasurement ? "provided" : "estimated", textOutlines: outlines ? "provided" : "unavailable", ...result, overflow: result.diagnostics.length > 0 };
|
|
1167
2695
|
}
|
|
1168
|
-
function fitCodeText(
|
|
1169
|
-
return fitSourceText(
|
|
2696
|
+
function fitCodeText(text2, box, size, minimum, step, measure, direction) {
|
|
2697
|
+
return fitSourceText(text2, box, size, minimum, step, measure, false, true, direction);
|
|
1170
2698
|
}
|
|
1171
|
-
function fitSourceText(
|
|
2699
|
+
function fitSourceText(text2, box, size, minimum, step, measure, prose = false, snap = true, direction) {
|
|
2700
|
+
const directionAt = direction === "rtl" ? paragraphDirectionAt(text2, "rtl") : void 0;
|
|
1172
2701
|
const layout = (fontSize) => {
|
|
1173
2702
|
const tabWidth = measure(" ", fontSize) * 4;
|
|
1174
|
-
if (!Number.isFinite(tabWidth) ||
|
|
1175
|
-
const measureLine = (start2, end2,
|
|
2703
|
+
if (!Number.isFinite(tabWidth) || text2.includes(" ") && tabWidth <= 0) throw new RangeError("Text tabs require a positive finite measured space advance.");
|
|
2704
|
+
const measureLine = (start2, end2, record5 = false) => {
|
|
1176
2705
|
let x = 0, offset = start2;
|
|
1177
2706
|
const segments = [];
|
|
1178
|
-
for (const [index, segment] of
|
|
2707
|
+
for (const [index, segment] of text2.slice(start2, end2).split(" ").entries()) {
|
|
1179
2708
|
if (index) {
|
|
1180
2709
|
const next = (Math.floor(x / tabWidth + 1e-9) + 1) * tabWidth;
|
|
1181
|
-
if (
|
|
2710
|
+
if (record5) segments.push({ kind: "tab", start: offset, end: offset + 1, x, width: next - x });
|
|
1182
2711
|
x = next;
|
|
1183
2712
|
offset++;
|
|
1184
2713
|
}
|
|
1185
2714
|
if (segment) {
|
|
1186
2715
|
const advance = measure(segment, fontSize);
|
|
1187
|
-
if (
|
|
2716
|
+
if (record5) segments.push({ kind: "text", start: offset, end: offset + segment.length, x, width: advance });
|
|
1188
2717
|
x += advance;
|
|
1189
2718
|
offset += segment.length;
|
|
1190
2719
|
}
|
|
@@ -1193,26 +2722,34 @@ function fitSourceText(text, box, size, minimum, step, measure, prose = false) {
|
|
|
1193
2722
|
};
|
|
1194
2723
|
const width = (start2, end2) => measureLine(start2, end2).width;
|
|
1195
2724
|
const sourceLines = [];
|
|
1196
|
-
let start = 0, end = 0;
|
|
2725
|
+
let start = 0, end = 0, hangFrom = -1;
|
|
1197
2726
|
const push = (last, nextStart, boundary) => {
|
|
1198
|
-
|
|
2727
|
+
const measured = measureLine(start, last, true);
|
|
2728
|
+
if (hangFrom >= 0 && hangFrom < last) measured.width = measureLine(start, hangFrom).width;
|
|
2729
|
+
hangFrom = -1;
|
|
2730
|
+
sourceLines.push({ start, end: last, nextStart, boundary, ...measured });
|
|
1199
2731
|
start = nextStart;
|
|
1200
2732
|
end = nextStart;
|
|
1201
2733
|
};
|
|
1202
2734
|
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
|
|
2735
|
+
for (const token of text2.matchAll(tokens)) {
|
|
1204
2736
|
if (token.index === void 0) throw new Error("Missing code token source offset.");
|
|
1205
2737
|
const a = token.index, b = a + token[0].length;
|
|
1206
2738
|
if (/^[\r\n]/.test(token[0])) {
|
|
1207
2739
|
push(a, b, "hard");
|
|
1208
2740
|
continue;
|
|
1209
2741
|
}
|
|
1210
|
-
if (width(start, b) <= box.width +
|
|
2742
|
+
if (width(start, b) <= box.width + WRAP_TOLERANCE) {
|
|
2743
|
+
end = b;
|
|
2744
|
+
continue;
|
|
2745
|
+
}
|
|
2746
|
+
if (prose && end > start && /\S/u.test(text2.slice(start, end)) && HANGING_SPACE.test(token[0][0])) {
|
|
2747
|
+
if (hangFrom < 0) hangFrom = end;
|
|
1211
2748
|
end = b;
|
|
1212
2749
|
continue;
|
|
1213
2750
|
}
|
|
1214
|
-
if (end > start && /\S/u.test(
|
|
1215
|
-
if (width(start, b) <= box.width +
|
|
2751
|
+
if (end > start && /\S/u.test(text2.slice(start, end))) push(a, a, "soft");
|
|
2752
|
+
if (width(start, b) <= box.width + WRAP_TOLERANCE) {
|
|
1216
2753
|
end = b;
|
|
1217
2754
|
continue;
|
|
1218
2755
|
}
|
|
@@ -1229,39 +2766,40 @@ function fitSourceText(text, box, size, minimum, step, measure, prose = false) {
|
|
|
1229
2766
|
push(end, end, "end");
|
|
1230
2767
|
const lineHeight = fontSize * 1.22;
|
|
1231
2768
|
return {
|
|
1232
|
-
lines: sourceLines.map((line) =>
|
|
2769
|
+
lines: sourceLines.map((line) => text2.slice(line.start, line.end)),
|
|
1233
2770
|
sourceLines,
|
|
1234
2771
|
fontSize,
|
|
1235
2772
|
lineHeight,
|
|
1236
2773
|
tabSize: 4,
|
|
1237
2774
|
tabWidth,
|
|
2775
|
+
...directionAt ? { directions: sourceLines.map((line) => directionAt(line.start)) } : {},
|
|
1238
2776
|
overflow: sourceLines.length * lineHeight > box.height + 0.01 || sourceLines.some((line) => line.width > box.width + 0.01)
|
|
1239
2777
|
};
|
|
1240
2778
|
};
|
|
1241
|
-
return fitAtSizes(layout, size, minimum, step);
|
|
2779
|
+
return fitAtSizes(layout, size, minimum, step, snap);
|
|
1242
2780
|
}
|
|
1243
2781
|
function layoutCode(value, box, options = {}) {
|
|
1244
2782
|
const shorthand = typeof value === "string", code = shorthand ? { source: value } : value;
|
|
1245
2783
|
if (!code || Array.isArray(code) || typeof code.source !== "string" || [code.language, code.filename].some((field) => field !== void 0 && typeof field !== "string")) {
|
|
1246
2784
|
throw new TypeError("Code content must be a string or source object with optional string language/filename.");
|
|
1247
2785
|
}
|
|
1248
|
-
const scale = options.scale ?? 1, minimum = (options.minFontSize ?? 16) * scale;
|
|
2786
|
+
const scale = options.scale ?? 1, minimum = snapFontSizeUp((options.minFontSize ?? 16) * scale);
|
|
1249
2787
|
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
2788
|
throw new RangeError("Code dimensions, scale and minimum font size must be finite and positive.");
|
|
1251
2789
|
}
|
|
1252
2790
|
if (options.overflow !== void 0 && !["warn", "error"].includes(options.overflow)) throw new RangeError("Invalid code overflow policy.");
|
|
1253
2791
|
const path = options.path ?? "code", inner = { x: box.x + 18, y: box.y + 18, width: box.width - 36, height: box.height - 36 };
|
|
1254
2792
|
const parts = [], diagnostics = [];
|
|
1255
|
-
const add = (role,
|
|
2793
|
+
const add = (role, text2, partPath, generated = false) => {
|
|
1256
2794
|
const requestedStyle = { fontFamily: options.fonts?.code ?? "monospace", fontWeight: role === "body" ? 400 : 700, italic: false, path: partPath };
|
|
1257
2795
|
const part = {
|
|
1258
2796
|
role,
|
|
1259
2797
|
path: partPath,
|
|
1260
|
-
text,
|
|
1261
|
-
sources: generated ? [] : [{ path: partPath, start: 0, end:
|
|
2798
|
+
text: text2,
|
|
2799
|
+
sources: generated ? [] : [{ path: partPath, start: 0, end: text2.length }],
|
|
1262
2800
|
generated,
|
|
1263
2801
|
box: { ...inner },
|
|
1264
|
-
requestedFontSize:
|
|
2802
|
+
requestedFontSize: gridFontSize((role === "body" ? 18 : 14) * scale, minimum),
|
|
1265
2803
|
minFontSize: minimum,
|
|
1266
2804
|
requestedStyle,
|
|
1267
2805
|
style: resolveTextStyle({ ...requestedStyle }, options.textMeasurement)
|
|
@@ -1318,7 +2856,7 @@ function layoutCode(value, box, options = {}) {
|
|
|
1318
2856
|
}
|
|
1319
2857
|
function fitRichText(input, box, requestedSize = 25, minFontSize = 16, options = { style: { fontFamily: "sans-serif", fontWeight: 400 } }) {
|
|
1320
2858
|
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);
|
|
2859
|
+
const layout = richTextLayouter(input, box, requestedSize, options, minFontSize);
|
|
1322
2860
|
return fitAtSizes(layout, requestedSize, richMinimum(input, requestedSize, minFontSize));
|
|
1323
2861
|
}
|
|
1324
2862
|
function richMinimum(input, requested, minimum) {
|
|
@@ -1332,7 +2870,7 @@ function richMinimum(input, requested, minimum) {
|
|
|
1332
2870
|
}
|
|
1333
2871
|
return visible ? floor : minimum;
|
|
1334
2872
|
}
|
|
1335
|
-
function richTextLayouter(input, box, requestedSize, options) {
|
|
2873
|
+
function richTextLayouter(input, box, requestedSize, options, minimum = 0) {
|
|
1336
2874
|
let offset = 0;
|
|
1337
2875
|
const source = input.map((value, runIndex) => {
|
|
1338
2876
|
const run = typeof value === "string" ? { text: value } : value;
|
|
@@ -1340,23 +2878,26 @@ function richTextLayouter(input, box, requestedSize, options) {
|
|
|
1340
2878
|
const start = offset;
|
|
1341
2879
|
offset += run.text.length;
|
|
1342
2880
|
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
|
-
|
|
2881
|
+
const marker = options.citationMarker && options.style.path && run.text ? options.citationMarker(`${options.style.path}.${runIndex}`) : void 0;
|
|
2882
|
+
return { run, runIndex, start, end: offset, style, ...marker ? { marker } : {} };
|
|
1344
2883
|
});
|
|
1345
2884
|
const whole = source.map((entry) => entry.run.text).join("");
|
|
1346
|
-
const
|
|
1347
|
-
|
|
2885
|
+
const paint = (raw) => raw >= minimum - 1e-6 ? gridFontSize(raw, minimum) : snapFontSizeDown(raw);
|
|
2886
|
+
const directionAt = options.direction === "rtl" ? paragraphDirectionAt(whole, "rtl") : void 0;
|
|
2887
|
+
const layout = (rawFontSize) => {
|
|
2888
|
+
const fontSize = paint(rawFontSize), ratio = rawFontSize / requestedSize;
|
|
1348
2889
|
const fragments = (start, end) => {
|
|
1349
2890
|
let x = 0;
|
|
1350
2891
|
const result = [];
|
|
1351
2892
|
for (const entry of source) {
|
|
1352
2893
|
const a = Math.max(start, entry.start), b = Math.min(end, entry.end);
|
|
1353
2894
|
if (b <= a) continue;
|
|
1354
|
-
const
|
|
2895
|
+
const text2 = whole.slice(a, b), normalSize = entry.run.fontSize !== void 0 ? entry.run.fontSize * 4 / 3 * ratio : fontSize;
|
|
1355
2896
|
const script = entry.run.superscript || entry.run.subscript;
|
|
1356
|
-
const size =
|
|
2897
|
+
const size = script ? paint(normalSize * 0.7) : paint(normalSize), baselineShift = entry.run.superscript ? -normalSize * 0.35 : entry.run.subscript ? normalSize * 0.2 : 0;
|
|
1357
2898
|
const measure = textWidthMeasurer(entry.style, options.textMeasurement);
|
|
1358
2899
|
let cursor = 0;
|
|
1359
|
-
for (const [index, segment] of
|
|
2900
|
+
for (const [index, segment] of text2.split(" ").entries()) {
|
|
1360
2901
|
if (index) {
|
|
1361
2902
|
const tabStart = a + cursor, tabWidth = measure(" ", size) * 4;
|
|
1362
2903
|
if (!Number.isFinite(tabWidth) || tabWidth <= 0) throw new RangeError("Rich text tabs require a positive finite measured space advance.");
|
|
@@ -1372,13 +2913,20 @@ function richTextLayouter(input, box, requestedSize, options) {
|
|
|
1372
2913
|
cursor += segment.length;
|
|
1373
2914
|
}
|
|
1374
2915
|
}
|
|
2916
|
+
if (entry.marker && b === entry.end) {
|
|
2917
|
+
const markerSize = Math.round(normalSize * CITATION_MARKER_SCALE * FONT_SIZE_GRID_PER_PX) / FONT_SIZE_GRID_PER_PX, width2 = measure(entry.marker, markerSize);
|
|
2918
|
+
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 });
|
|
2919
|
+
x += width2;
|
|
2920
|
+
}
|
|
1375
2921
|
}
|
|
1376
2922
|
return result;
|
|
1377
2923
|
};
|
|
1378
2924
|
const width = (start, end) => fragments(start, end).reduce((sum, fragment) => sum + fragment.width, 0);
|
|
1379
|
-
const ranges = [];
|
|
1380
|
-
let lineStart = 0, lineEnd = 0;
|
|
2925
|
+
const ranges = [], hung = /* @__PURE__ */ new Map();
|
|
2926
|
+
let lineStart = 0, lineEnd = 0, hangFrom = -1;
|
|
1381
2927
|
const push = (end) => {
|
|
2928
|
+
if (hangFrom >= 0) hung.set(ranges.length, hangFrom);
|
|
2929
|
+
hangFrom = -1;
|
|
1382
2930
|
ranges.push({ start: lineStart, end });
|
|
1383
2931
|
lineStart = end;
|
|
1384
2932
|
lineEnd = end;
|
|
@@ -1391,26 +2939,32 @@ function richTextLayouter(input, box, requestedSize, options) {
|
|
|
1391
2939
|
lineEnd = end;
|
|
1392
2940
|
continue;
|
|
1393
2941
|
}
|
|
1394
|
-
if (width(lineStart, end) <= box.width) {
|
|
2942
|
+
if (width(lineStart, end) <= box.width + WRAP_TOLERANCE) {
|
|
2943
|
+
lineEnd = end;
|
|
2944
|
+
continue;
|
|
2945
|
+
}
|
|
2946
|
+
if (HANGING_SPACE.test(match[0][0]) && lineEnd > lineStart && /\S/u.test(whole.slice(lineStart, lineEnd))) {
|
|
2947
|
+
if (hangFrom < 0) hangFrom = lineEnd;
|
|
1395
2948
|
lineEnd = end;
|
|
1396
2949
|
continue;
|
|
1397
2950
|
}
|
|
1398
2951
|
if (lineEnd > lineStart) push(start);
|
|
1399
|
-
if (width(start, end) <= box.width) {
|
|
2952
|
+
if (width(start, end) <= box.width + WRAP_TOLERANCE) {
|
|
1400
2953
|
lineEnd = end;
|
|
1401
2954
|
continue;
|
|
1402
2955
|
}
|
|
1403
2956
|
for (const { segment, index } of textSegments.segment(match[0])) {
|
|
1404
2957
|
const next = start + index + segment.length;
|
|
1405
|
-
if (lineEnd > lineStart && width(lineStart, next) > box.width) push(start + index);
|
|
2958
|
+
if (lineEnd > lineStart && width(lineStart, next) > box.width + WRAP_TOLERANCE) push(start + index);
|
|
1406
2959
|
lineEnd = next;
|
|
1407
2960
|
}
|
|
1408
2961
|
}
|
|
2962
|
+
if (hangFrom >= 0) hung.set(ranges.length, hangFrom);
|
|
1409
2963
|
ranges.push({ start: lineStart, end: lineEnd });
|
|
1410
2964
|
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 };
|
|
2965
|
+
const richLines = ranges.map((range, rangeIndex) => {
|
|
2966
|
+
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));
|
|
2967
|
+
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
2968
|
y += height;
|
|
1415
2969
|
return line;
|
|
1416
2970
|
});
|
|
@@ -1424,7 +2978,15 @@ function richTextLayouter(input, box, requestedSize, options) {
|
|
|
1424
2978
|
y += height;
|
|
1425
2979
|
}
|
|
1426
2980
|
}
|
|
1427
|
-
return {
|
|
2981
|
+
return {
|
|
2982
|
+
lines: ranges.map((range) => whole.slice(range.start, range.end)),
|
|
2983
|
+
fontSize,
|
|
2984
|
+
lineHeight: Math.max(fontSize * 1.22, ...richLines.map((line) => line.height)),
|
|
2985
|
+
richLines,
|
|
2986
|
+
height: y,
|
|
2987
|
+
overflow: y > box.height + 0.01 || richLines.some((line) => line.width > box.width + 0.01),
|
|
2988
|
+
...directionAt ? { directions: ranges.map((range) => directionAt(range.start)) } : {}
|
|
2989
|
+
};
|
|
1428
2990
|
};
|
|
1429
2991
|
return layout;
|
|
1430
2992
|
}
|
|
@@ -1433,36 +2995,50 @@ function fitList(input, box, requestedSize = 25, minFontSize = 16, options = { s
|
|
|
1433
2995
|
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
2996
|
const source = input.map((value, index) => {
|
|
1435
2997
|
const object = typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1436
|
-
const
|
|
2998
|
+
const text2 = object ? object.text : value, description = object?.description, level = object?.level ?? 0;
|
|
1437
2999
|
if (!Number.isInteger(level) || level < 0) throw new RangeError("List levels must be nonnegative integers.");
|
|
1438
3000
|
const runs = (value2) => typeof value2 === "string" ? [value2] : value2;
|
|
1439
|
-
if (!Array.isArray(runs(
|
|
3001
|
+
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
3002
|
const base = options.style.path ? `${options.style.path}.${index}` : void 0;
|
|
1441
|
-
|
|
3003
|
+
const plain = (value2) => typeof value2 === "string" ? value2 : value2.map((run) => typeof run === "string" ? run : run.text).join("");
|
|
3004
|
+
const direction = options.direction === "rtl" ? paragraphDirection(plain(text2), "rtl") : void 0;
|
|
3005
|
+
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
3006
|
});
|
|
3007
|
+
const numbers = options.numbering === void 0 ? void 0 : listNumbers(input, options.numbering);
|
|
3008
|
+
const markerStyleFor = (item) => {
|
|
3009
|
+
const first = item.runs.map((run) => typeof run === "string" ? { text: run } : run).find((run) => run.text);
|
|
3010
|
+
return { ...options.style, path: item.textPath, fontWeight: first?.bold === void 0 ? options.style.fontWeight : first.bold ? 700 : 400, italic: first?.italic ?? options.style.italic };
|
|
3011
|
+
};
|
|
1443
3012
|
const layout = (fontSize) => {
|
|
1444
3013
|
let y = box.y, overflow = false;
|
|
1445
|
-
const entries = [], lines = [];
|
|
3014
|
+
const entries = [], lines = [], directions = [];
|
|
3015
|
+
const markerStyles = numbers ? source.map((item) => resolveTextStyle(markerStyleFor(item), options.textMeasurement)) : [];
|
|
3016
|
+
const markerWidths = numbers ? numbers.map((number, index) => textWidthMeasurer(markerStyles[index], options.textMeasurement)(number.text, fontSize)) : [];
|
|
3017
|
+
const hanging = numbers ? Math.max(fontSize * 1.1, Math.max(0, ...markerWidths) + fontSize * 0.3) : fontSize * 1.1;
|
|
1446
3018
|
for (const item of source) {
|
|
1447
|
-
const
|
|
3019
|
+
const rtl = item.direction === "rtl";
|
|
3020
|
+
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
3021
|
if (offset + indent >= box.width) overflow = true;
|
|
1449
3022
|
const textBox = { x, y, width, height: box.height };
|
|
1450
3023
|
const style = { ...options.style, path: item.textPath };
|
|
1451
|
-
const
|
|
1452
|
-
textBox.height =
|
|
1453
|
-
lines.push(...
|
|
1454
|
-
|
|
3024
|
+
const text2 = richTextLayouter(item.runs, textBox, requestedSize, { ...options, style }, minFontSize)(fontSize);
|
|
3025
|
+
textBox.height = text2.height;
|
|
3026
|
+
lines.push(...text2.lines);
|
|
3027
|
+
if (options.direction === "rtl") directions.push(...text2.directions ?? []);
|
|
3028
|
+
y += text2.height;
|
|
1455
3029
|
let description, descriptionBox;
|
|
1456
3030
|
if (item.descriptionRuns !== void 0) {
|
|
1457
3031
|
y += fontSize * 0.12;
|
|
1458
3032
|
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);
|
|
3033
|
+
description = richTextLayouter(item.descriptionRuns, descriptionBox, requestedSize * 0.82, { ...options, style: { ...options.style, path: item.descriptionPath } }, minFontSize)(fontSize * 0.82);
|
|
1460
3034
|
descriptionBox.height = description.height;
|
|
1461
3035
|
lines.push(...description.lines);
|
|
3036
|
+
if (options.direction === "rtl") directions.push(...description.directions ?? []);
|
|
1462
3037
|
y += description.height;
|
|
1463
3038
|
}
|
|
1464
|
-
overflow ||=
|
|
1465
|
-
const
|
|
3039
|
+
overflow ||= text2.overflow || !!description?.overflow;
|
|
3040
|
+
const number = numbers?.[item.index];
|
|
3041
|
+
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
3042
|
entries.push({
|
|
1467
3043
|
index: item.index,
|
|
1468
3044
|
level: item.level,
|
|
@@ -1470,17 +3046,18 @@ function fitList(input, box, requestedSize = 25, minFontSize = 16, options = { s
|
|
|
1470
3046
|
descriptionValue: item.descriptionValue,
|
|
1471
3047
|
textPath: item.textPath,
|
|
1472
3048
|
descriptionPath: item.descriptionPath,
|
|
1473
|
-
text,
|
|
3049
|
+
text: text2,
|
|
1474
3050
|
textBox,
|
|
1475
3051
|
description,
|
|
1476
3052
|
descriptionBox,
|
|
1477
3053
|
marker,
|
|
1478
|
-
...
|
|
3054
|
+
...item.direction ? { direction: item.direction } : {},
|
|
3055
|
+
...options.bulletImage && !number ? { bulletImage: options.bulletImage, bulletBox: { x: rtl ? marker.x - side : marker.x, y: marker.y - side, width: side, height: side } } : {}
|
|
1479
3056
|
});
|
|
1480
3057
|
if (item.index < source.length - 1) y += fontSize * 0.28;
|
|
1481
3058
|
}
|
|
1482
3059
|
const height = y - box.y;
|
|
1483
|
-
return { listEntries: entries, lines, fontSize, lineHeight: fontSize * 1.22, height, overflow: overflow || height > box.height + 0.01 };
|
|
3060
|
+
return { listEntries: entries, lines, fontSize, lineHeight: fontSize * 1.22, height, overflow: overflow || height > box.height + 0.01, ...options.direction === "rtl" ? { directions } : {} };
|
|
1484
3061
|
};
|
|
1485
3062
|
let minimum = minFontSize;
|
|
1486
3063
|
for (const item of source) {
|
|
@@ -1492,16 +3069,16 @@ function fitList(input, box, requestedSize = 25, minFontSize = 16, options = { s
|
|
|
1492
3069
|
function contentText(field, value) {
|
|
1493
3070
|
if (field === "text" || headings.has(field)) return flatten(value);
|
|
1494
3071
|
if (field === "items" || field === "bullets") return (Array.isArray(value) ? value : [value]).map((item) => {
|
|
1495
|
-
const entry =
|
|
3072
|
+
const entry = record4(item);
|
|
1496
3073
|
return `\u2022 ${flatten(item)}${entry.description ? `
|
|
1497
3074
|
${flatten(entry.description)}` : ""}`;
|
|
1498
3075
|
}).join("\n");
|
|
1499
|
-
if (field === "code") return flatten(
|
|
1500
|
-
if (field === "quote") return flatten(
|
|
3076
|
+
if (field === "code") return flatten(record4(value).source ?? value);
|
|
3077
|
+
if (field === "quote") return flatten(record4(value).text ?? value);
|
|
1501
3078
|
return void 0;
|
|
1502
3079
|
}
|
|
1503
3080
|
function layoutTable(value, box, options = {}) {
|
|
1504
|
-
const scale = options.scale ?? 1, minimum = (options.minFontSize ?? 16) * scale, requested =
|
|
3081
|
+
const scale = options.scale ?? 1, minimum = snapFontSizeUp((options.minFontSize ?? 16) * scale), requested = gridFontSize(15 * scale, minimum);
|
|
1505
3082
|
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
3083
|
const grid = tableGrid(value, options.path ?? "table");
|
|
1507
3084
|
if (grid.issues.length) throw new RangeError(`${grid.issues[0].path}: ${grid.issues[0].message}`);
|
|
@@ -1515,13 +3092,13 @@ function layoutTable(value, box, options = {}) {
|
|
|
1515
3092
|
const cellMinimum = (cell) => Array.isArray(cell.value) ? richMinimum(cell.value, requested, minimum) : minimum;
|
|
1516
3093
|
const fitCell = (cell, height, min) => {
|
|
1517
3094
|
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));
|
|
3095
|
+
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
3096
|
};
|
|
1520
3097
|
const textHeight = (fit) => "height" in fit ? fit.height : fit.lines.length * fit.lineHeight;
|
|
1521
3098
|
const required = (cell, natural2) => {
|
|
1522
|
-
const floor = cellMinimum(cell), size = natural2 ? Math.max(requested, floor) : floor;
|
|
3099
|
+
const floor = cellMinimum(cell), size = gridFontSize(natural2 ? Math.max(requested, floor) : floor, floor);
|
|
1523
3100
|
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));
|
|
3101
|
+
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
3102
|
return textHeight(fit) + (cell.padding.top + cell.padding.bottom) * scale;
|
|
1526
3103
|
};
|
|
1527
3104
|
const natural = Array(grid.rowCount).fill(0), needed = Array(grid.rowCount).fill(0);
|
|
@@ -1544,13 +3121,13 @@ function layoutTable(value, box, options = {}) {
|
|
|
1544
3121
|
let overflow = false;
|
|
1545
3122
|
const rows2 = heights.map((height, r) => ({ box: { x: box.x, y: ys[r], width: box.width, height }, cells: [] }));
|
|
1546
3123
|
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] };
|
|
3124
|
+
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
3125
|
const available = cellBox.height - (cell.padding.top + cell.padding.bottom) * scale;
|
|
1549
3126
|
const fit = fitCell(cell, available, minimum), height = textHeight(fit);
|
|
1550
3127
|
const offset = cell.style.verticalAlign === "bottom" ? Math.max(0, available - height) : cell.style.verticalAlign === "middle" ? Math.max(0, (available - height) / 2) : 0;
|
|
1551
3128
|
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
3129
|
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 });
|
|
3130
|
+
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
3131
|
}
|
|
1555
3132
|
return { rows: rows2, columnCount, height: sum(heights), overflow };
|
|
1556
3133
|
}
|
|
@@ -1561,7 +3138,7 @@ function flatten(value) {
|
|
|
1561
3138
|
if (value == null) return "";
|
|
1562
3139
|
if (typeof value !== "object") return String(value);
|
|
1563
3140
|
if (Array.isArray(value)) return value.map(flatten).join("");
|
|
1564
|
-
const item =
|
|
3141
|
+
const item = record4(value);
|
|
1565
3142
|
return flatten(item.text ?? item.value ?? item.source ?? JSON.stringify(value));
|
|
1566
3143
|
}
|
|
1567
3144
|
function regionParts(key) {
|
|
@@ -1617,26 +3194,28 @@ function assertComposition(value) {
|
|
|
1617
3194
|
if (value.overflow !== void 0 && !["warn", "error"].includes(value.overflow)) throw new RangeError("Invalid composition.overflow.");
|
|
1618
3195
|
}
|
|
1619
3196
|
function composeSlide(input, options = {}) {
|
|
1620
|
-
const slide =
|
|
3197
|
+
const slide = record4(input), layout = record4(options.layout);
|
|
1621
3198
|
const width = options.width ?? 1280, height = options.height ?? 720;
|
|
1622
3199
|
if (![width, height].every((value) => Number.isFinite(value) && value > 0)) throw new RangeError("Canvas dimensions must be finite and positive.");
|
|
1623
3200
|
const scale = Math.min(width, height) / 720;
|
|
1624
|
-
const
|
|
1625
|
-
const
|
|
3201
|
+
const deckDirection = options.direction ?? resolveSlideDirection(options.presentation, options.slideIndex);
|
|
3202
|
+
const rtl = deckDirection === "rtl", textDirection = rtl ? "rtl" : void 0;
|
|
3203
|
+
const hasCards = record4(slide.design).contentBox ?? options.contentBox ?? false;
|
|
3204
|
+
const composition = { ...record4(layout.composition), ...record4(slide.composition) };
|
|
1626
3205
|
assertComposition(composition);
|
|
1627
3206
|
const padding = (composition.padding ?? 0.08) * Math.min(width, height);
|
|
1628
3207
|
const gap = (composition.gap ?? 1 / 30) * Math.min(width, height);
|
|
1629
|
-
const minSize = (composition.minFontSize ?? 16) * scale;
|
|
3208
|
+
const minSize = snapFontSizeUp((composition.minFontSize ?? 16) * scale);
|
|
1630
3209
|
const rasterPadding = (options.textRasterPadding ?? 1) * scale;
|
|
1631
3210
|
if (!Number.isFinite(rasterPadding) || rasterPadding < 0) throw new RangeError("Text raster padding must be finite and nonnegative.");
|
|
1632
3211
|
let coverGroup = false;
|
|
1633
|
-
const alignmentFor = (field) => (field === "title" || coverGroup && (field === "tag" || field === "subtitle") &&
|
|
3212
|
+
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
3213
|
const styleFor = (field, path2) => resolveTextStyle({
|
|
1635
3214
|
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
3215
|
fontWeight: field === "title" ? 700 : 400,
|
|
1637
3216
|
path: path2
|
|
1638
3217
|
}, options.textMeasurement);
|
|
1639
|
-
const slideDesign =
|
|
3218
|
+
const slideDesign = record4(slide.design), deckDesign = record4(record4(options.presentation).design);
|
|
1640
3219
|
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
3220
|
const listBullet = designHint("listBullet");
|
|
1642
3221
|
const bulletImage = listBullet?.value === "image" ? (() => {
|
|
@@ -1644,15 +3223,18 @@ function composeSlide(input, options = {}) {
|
|
|
1644
3223
|
return resolved ? { source: resolved.source, path: resolved.path } : void 0;
|
|
1645
3224
|
})() : void 0;
|
|
1646
3225
|
const widthFor = (field, path2) => textWidthMeasurer(styleFor(field, path2), options.textMeasurement);
|
|
1647
|
-
const
|
|
3226
|
+
const citations = slideCitations(slide, options.slideIndex ?? 0, options.presentation);
|
|
3227
|
+
const citationMarker = citations ? (runPath) => citations.markers.get(runPath) : void 0;
|
|
3228
|
+
const richOptions = (style) => ({ style, textMeasurement: options.textMeasurement, ...citationMarker ? { citationMarker } : {}, ...rtl ? { direction: "rtl" } : {} });
|
|
3229
|
+
const fitPlacedText = (field, value, text2, box, size, minimum, path2, explicitAlignment) => {
|
|
1648
3230
|
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);
|
|
3231
|
+
if (!options.textMeasurement?.outlineBounds) return rich ? fitRichText(value, box, size, minimum, richOptions(style)) : fitText(text2, box, size, minimum, textWidthMeasurer(style, options.textMeasurement), textDirection);
|
|
3232
|
+
const alignment = explicitAlignment ?? alignmentFor(field);
|
|
3233
|
+
const richLayout = rich ? richTextLayouter(value, box, size, richOptions(style), minimum) : void 0;
|
|
3234
|
+
const measure = textWidthMeasurer(style, options.textMeasurement), floor = snapFontSizeUp(rich ? richMinimum(value, size, minimum) : minimum), start = Math.max(size, floor);
|
|
1653
3235
|
for (let trial = 0; trial <= 64; trial++) {
|
|
1654
|
-
const
|
|
1655
|
-
const fit = richLayout ? richLayout(fontSize) : fitText(
|
|
3236
|
+
const raw = start - trial * scale, last = trial === 64 || raw <= floor, fontSize = last ? floor : gridFontSize(raw, floor);
|
|
3237
|
+
const fit = richLayout ? richLayout(fontSize) : fitText(text2, box, fontSize, fontSize, measure, textDirection);
|
|
1656
3238
|
const richLines = "richLines" in fit ? fit.richLines : void 0;
|
|
1657
3239
|
const lines = richLines ? richLines.map((line) => {
|
|
1658
3240
|
let outline = null;
|
|
@@ -1675,7 +3257,7 @@ function composeSlide(input, options = {}) {
|
|
|
1675
3257
|
let outline = null;
|
|
1676
3258
|
for (const segment of line.segments) {
|
|
1677
3259
|
if (segment.kind === "tab") continue;
|
|
1678
|
-
const bounds = measureTextOutline(
|
|
3260
|
+
const bounds = measureTextOutline(text2.slice(segment.start, segment.end), fontSize, style, options.textMeasurement);
|
|
1679
3261
|
if (!bounds) continue;
|
|
1680
3262
|
const next = { ...bounds, x: segment.x + bounds.x };
|
|
1681
3263
|
if (!outline) outline = next;
|
|
@@ -1689,15 +3271,17 @@ function composeSlide(input, options = {}) {
|
|
|
1689
3271
|
}
|
|
1690
3272
|
return { width: line.width, y: index * fit.lineHeight, baseline: fontSize + index * fit.lineHeight, height: fit.lineHeight, outline };
|
|
1691
3273
|
});
|
|
1692
|
-
const placement = placeTextLines(lines, box, alignment, rasterPadding), result = { ...fit, placement, overflow: fit.overflow || placement.overflow };
|
|
1693
|
-
if (!result.overflow ||
|
|
3274
|
+
const placement = placeTextLines(lines, box, alignment, rasterPadding, fit.directions), result = { ...fit, placement, overflow: fit.overflow || placement.overflow };
|
|
3275
|
+
if (!result.overflow || last) return result;
|
|
1694
3276
|
}
|
|
1695
3277
|
throw new Error("Text placement did not evaluate its bounded floor trial.");
|
|
1696
3278
|
};
|
|
1697
|
-
const fitContent = (field, value,
|
|
3279
|
+
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
3280
|
const path = `slides.${options.slideIndex ?? 0}`;
|
|
3281
|
+
const annotationFit = (value, box, requestedSize, minFontSize, fitPath, alignment) => fitPlacedText("text", value, annotationText(value), box, requestedSize, minFontSize, fitPath, alignment);
|
|
3282
|
+
const annotationOptions = { scale, minFontSize: minSize, fit: annotationFit, textStyle: (fitPath) => styleFor("text", fitPath) };
|
|
1699
3283
|
const slideImageDiagnostics = [];
|
|
1700
|
-
const slideImage = resolveSlideImage(slide, layout, options.presentation, width, height, path, padding, scale, slideImageDiagnostics);
|
|
3284
|
+
const slideImage = resolveSlideImage(slide, layout, options.presentation, width, height, path, padding, scale, slideImageDiagnostics, rtl);
|
|
1701
3285
|
const area = { left: 0, top: 0, right: width, bottom: height };
|
|
1702
3286
|
if (slideImage?.position === "left") area.left = slideImage.region.width;
|
|
1703
3287
|
else if (slideImage?.position === "right") area.right = slideImage.region.x;
|
|
@@ -1707,14 +3291,20 @@ function composeSlide(input, options = {}) {
|
|
|
1707
3291
|
const measuredFurniture = layoutFurniture(slide, options);
|
|
1708
3292
|
const furniture = measuredFurniture.configured || measuredFurniture.diagnostics.length ? measuredFurniture : void 0;
|
|
1709
3293
|
if (furniture) diagnostics.push(...furniture.diagnostics);
|
|
1710
|
-
|
|
3294
|
+
let bodyBottom = Math.min(area.bottom - padding, furniture && furniture.footerTop < height ? furniture.footerTop - gap * 0.5 : height - padding);
|
|
1711
3295
|
const headingTop = Math.max(area.top + padding, furniture?.headerBottom ? furniture.headerBottom + gap * 0.5 : padding);
|
|
3296
|
+
let footnotes;
|
|
3297
|
+
if (citations) {
|
|
3298
|
+
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 });
|
|
3299
|
+
diagnostics.push(...footnotes.diagnostics);
|
|
3300
|
+
bodyBottom = footnotes.box.y - gap * 0.5;
|
|
3301
|
+
}
|
|
1712
3302
|
const layoutPlaceholders = Array.isArray(layout.placeholders) ? layout.placeholders : [];
|
|
1713
3303
|
const headingOnlyLayout = COVER_LAYOUT_IDS.has(String(layout.id ?? "")) || layoutPlaceholders.length > 0 && layoutPlaceholders.every((placeholder) => headings.has(placeholder.type ?? ""));
|
|
1714
3304
|
const regions = Object.keys(slide).filter((key) => regionParts(key)).sort();
|
|
1715
3305
|
const emptyPayload = (value) => value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
|
|
1716
3306
|
const emptyHost = (host) => emptyPayload(host.blocks) && fields.every((field) => emptyPayload(host[field]));
|
|
1717
|
-
const hasBodyPayload = regions.some((key) => !emptyHost(
|
|
3307
|
+
const hasBodyPayload = regions.some((key) => !emptyHost(record4(slide[key]))) || !emptyPayload(slide.blocks) || fields.some((field) => !emptyPayload(slide[field]));
|
|
1718
3308
|
const isCover = !hasBodyPayload && (headingOnlyLayout || !layout.id && !layoutPlaceholders.some((placeholder) => !headings.has(placeholder.type ?? "")));
|
|
1719
3309
|
coverGroup = isCover;
|
|
1720
3310
|
let logo;
|
|
@@ -1722,8 +3312,9 @@ function composeSlide(input, options = {}) {
|
|
|
1722
3312
|
const resolved = resolveLogo(options.presentation, slide, { slot: "lockup", onDark: options.darkBackground, slideIndex: options.slideIndex });
|
|
1723
3313
|
if (resolved) {
|
|
1724
3314
|
const logoHeight = 56 * scale;
|
|
1725
|
-
const
|
|
1726
|
-
|
|
3315
|
+
const logoWidth = Math.max(scale, Math.min(4 * logoHeight, area.right - area.left - 2 * padding));
|
|
3316
|
+
const box = { x: round2(rtl ? area.right - padding - logoWidth : area.left + padding), y: round2(headingTop), width: round2(logoWidth), height: round2(logoHeight) };
|
|
3317
|
+
logo = { box, slot: "lockup", path: resolved.path, source: resolved.source, variant: resolved.variant, anchor: rtl ? "right" : "left" };
|
|
1727
3318
|
}
|
|
1728
3319
|
}
|
|
1729
3320
|
let y = logo ? logo.box.y + logo.box.height + gap : headingTop;
|
|
@@ -1733,10 +3324,10 @@ function composeSlide(input, options = {}) {
|
|
|
1733
3324
|
const requested = (field === "title" ? 54 : field === "tag" ? 16 : 25) * scale;
|
|
1734
3325
|
const maxHeight = Math.max(height * (field === "title" ? 0.26 : field === "subtitle" ? 0.12 : 0.045), minSize * 1.22 + 2 * rasterPadding);
|
|
1735
3326
|
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(
|
|
3327
|
+
const text2 = fitPlacedText(field, slide[field], String(slide[field]), box, requested, minSize, `${path}.${field}`);
|
|
3328
|
+
box.height = Math.min(maxHeight, Math.max(text2.lines.length * text2.lineHeight, text2.placement?.height ?? 0));
|
|
1738
3329
|
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) };
|
|
3330
|
+
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
3331
|
headingItems.push(item);
|
|
1741
3332
|
items.push(item);
|
|
1742
3333
|
y += box.height + gap * 0.5;
|
|
@@ -1756,7 +3347,7 @@ function composeSlide(input, options = {}) {
|
|
|
1756
3347
|
const collect = (host, basePath, depth = 0, ancestors = []) => {
|
|
1757
3348
|
if (Array.isArray(host.blocks)) {
|
|
1758
3349
|
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 =
|
|
3350
|
+
const settings = record4(host.composition);
|
|
1760
3351
|
assertComposition(settings);
|
|
1761
3352
|
return [{
|
|
1762
3353
|
field: "blocks",
|
|
@@ -1765,12 +3356,21 @@ function composeSlide(input, options = {}) {
|
|
|
1765
3356
|
path: basePath,
|
|
1766
3357
|
payload: host,
|
|
1767
3358
|
composition: settings,
|
|
1768
|
-
children: host.blocks.flatMap((block, index) => collect(
|
|
3359
|
+
children: host.blocks.flatMap((block, index) => collect(record4(block), `${basePath}.blocks.${index}`, depth + 1, [...ancestors, host]))
|
|
1769
3360
|
}];
|
|
1770
3361
|
}
|
|
1771
|
-
|
|
3362
|
+
const captioned = host.caption !== void 0 ? fields.filter((field) => host[field] !== void 0 && CAPTIONABLE_FIELDS.includes(field)) : [];
|
|
3363
|
+
return fields.filter((field) => host[field] !== void 0).map((field) => ({
|
|
3364
|
+
field,
|
|
3365
|
+
type: host.type ?? kind(field),
|
|
3366
|
+
value: host[field],
|
|
3367
|
+
path: `${basePath}.${field}`,
|
|
3368
|
+
payload: { type: host.type ?? kind(field), [field]: host[field], ...host.numbering !== void 0 && (field === "items" || field === "bullets") ? { numbering: host.numbering } : {} },
|
|
3369
|
+
...captioned.length === 1 && captioned[0] === field ? { caption: host.caption, captionPath: `${basePath}.caption` } : {}
|
|
3370
|
+
}));
|
|
1772
3371
|
};
|
|
1773
|
-
const
|
|
3372
|
+
const regionsInReadingOrder = visualReadingOrder(regions.map((key) => ({ key, box: regionBox(regionParts(key), contentBox, gap) }))).map((entry) => entry.key);
|
|
3373
|
+
const pending = regions.length ? regionsInReadingOrder.flatMap((key) => collect(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
3374
|
const groups = [], flows = [];
|
|
1775
3375
|
const decisions = options.explain ? [] : void 0;
|
|
1776
3376
|
const inheritedSettings = (parent, own = {}) => ({
|
|
@@ -1782,7 +3382,7 @@ function composeSlide(input, options = {}) {
|
|
|
1782
3382
|
const amount = (settings.padding ?? 0) * Math.min(box.width, box.height);
|
|
1783
3383
|
return { x: box.x + amount, y: box.y + amount, width: box.width - amount * 2, height: box.height - amount * 2 };
|
|
1784
3384
|
};
|
|
1785
|
-
const acceptedBox = (box) => ({ x:
|
|
3385
|
+
const acceptedBox = (box) => ({ x: round2(box.x), y: round2(box.y), width: round2(box.width), height: round2(box.height) });
|
|
1786
3386
|
const payloadBox = (box) => {
|
|
1787
3387
|
if (!hasCards) return box;
|
|
1788
3388
|
const frame = acceptedBox(box), padding2 = Math.min(12 * scale, frame.width / 4, frame.height / 4);
|
|
@@ -1793,7 +3393,8 @@ function composeSlide(input, options = {}) {
|
|
|
1793
3393
|
textMeasurement: options.textMeasurement,
|
|
1794
3394
|
scale,
|
|
1795
3395
|
minFontSize: settings.minFontSize,
|
|
1796
|
-
path: node.path
|
|
3396
|
+
path: node.path,
|
|
3397
|
+
direction: textDirection
|
|
1797
3398
|
});
|
|
1798
3399
|
const measureCode = (node, box, settings) => layoutCode(node.value, acceptedBox(box), {
|
|
1799
3400
|
fonts: options.fonts,
|
|
@@ -1809,7 +3410,8 @@ function composeSlide(input, options = {}) {
|
|
|
1809
3410
|
minFontSize: settings.minFontSize,
|
|
1810
3411
|
path: node.path,
|
|
1811
3412
|
textRasterPadding: options.textRasterPadding,
|
|
1812
|
-
align: alignmentFor("metric")
|
|
3413
|
+
align: alignmentFor("metric"),
|
|
3414
|
+
direction: textDirection
|
|
1813
3415
|
});
|
|
1814
3416
|
const measureTimeline = (node, box, settings) => layoutTimeline(node.value, acceptedBox(box), {
|
|
1815
3417
|
fonts: options.fonts,
|
|
@@ -1817,13 +3419,20 @@ function composeSlide(input, options = {}) {
|
|
|
1817
3419
|
scale,
|
|
1818
3420
|
minFontSize: settings.minFontSize,
|
|
1819
3421
|
path: node.path,
|
|
1820
|
-
textRasterPadding: options.textRasterPadding
|
|
3422
|
+
textRasterPadding: options.textRasterPadding,
|
|
3423
|
+
direction: textDirection
|
|
1821
3424
|
});
|
|
3425
|
+
const captionOf = (node, box) => node.caption !== void 0 && node.captionPath ? layoutCaption(node.caption, box, node.captionPath, annotationOptions) : void 0;
|
|
1822
3426
|
const leafScore = (node, box, settings, penalties) => {
|
|
1823
3427
|
box = payloadBox(box);
|
|
1824
|
-
const
|
|
1825
|
-
|
|
1826
|
-
|
|
3428
|
+
const captioned = captionOf(node, box);
|
|
3429
|
+
if (captioned) box = captioned.mediaBox;
|
|
3430
|
+
const text2 = contentText(node.field, node.value);
|
|
3431
|
+
let score = Math.abs(Math.log(box.width / box.height / 1.6)) + (captioned?.overflow ? 1e3 : 0);
|
|
3432
|
+
if (penalties) {
|
|
3433
|
+
penalties.cellProportions += Math.abs(Math.log(box.width / box.height / 1.6));
|
|
3434
|
+
if (captioned?.overflow) penalties.textOverflow += 1e3;
|
|
3435
|
+
}
|
|
1827
3436
|
if (node.field === "quote" || node.field === "code" || node.field === "metric" || node.field === "timeline") {
|
|
1828
3437
|
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
3438
|
const reduction = internal.parts.reduce((sum, part) => sum + (part.fit ? (part.requestedFontSize - part.fit.fontSize) / scale : 0), 0);
|
|
@@ -1832,9 +3441,9 @@ function composeSlide(input, options = {}) {
|
|
|
1832
3441
|
penalties.fontReduction += reduction;
|
|
1833
3442
|
penalties.textOverflow += internal.overflow ? 1e3 : 0;
|
|
1834
3443
|
}
|
|
1835
|
-
} else if (
|
|
1836
|
-
const fit = fitContent(node.field, node.value,
|
|
1837
|
-
const reduction = Math.max(0, 25 * scale - fit.fontSize) / scale;
|
|
3444
|
+
} else if (text2) {
|
|
3445
|
+
const fit = fitContent(node.field, node.value, text2, box, 25 * scale, snapFontSizeUp((settings.minFontSize ?? 16) * scale), node.path, node.payload.numbering);
|
|
3446
|
+
const reduction = Math.max(0, snapFontSizeDown(25 * scale) - fit.fontSize) / scale;
|
|
1838
3447
|
score += reduction + (fit.overflow ? 1e3 : 0);
|
|
1839
3448
|
if (penalties) {
|
|
1840
3449
|
penalties.fontReduction += reduction;
|
|
@@ -1893,10 +3502,11 @@ function composeSlide(input, options = {}) {
|
|
|
1893
3502
|
candidates: candidates ?? []
|
|
1894
3503
|
});
|
|
1895
3504
|
const grid = gridGeometry(count, area2, cols, actualGap, settings.weights ?? [], mode === "column");
|
|
1896
|
-
const
|
|
1897
|
-
|
|
3505
|
+
const mirrored = (box) => rtl ? { ...box, x: area2.x + area2.width - (box.x - area2.x) - box.width } : box;
|
|
3506
|
+
const boxes = grid.boxes.map(mirrored);
|
|
3507
|
+
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
3508
|
nodes.forEach((node, index) => {
|
|
1899
|
-
let box = node.region ? regionBox(node.region, area2, actualGap) : boxes[index];
|
|
3509
|
+
let box = node.region ? mirrored(regionBox(node.region, area2, actualGap)) : boxes[index];
|
|
1900
3510
|
if (node.children) {
|
|
1901
3511
|
const own = inheritedSettings(settings, node.composition), inner = inset(box, own);
|
|
1902
3512
|
if (!node.synthetic) groups.push({ path: node.path, box, contentBox: inner, composition: own });
|
|
@@ -1904,13 +3514,18 @@ function composeSlide(input, options = {}) {
|
|
|
1904
3514
|
} else {
|
|
1905
3515
|
const frameBox = hasCards ? acceptedBox(box) : void 0;
|
|
1906
3516
|
box = payloadBox(box);
|
|
3517
|
+
const caption = captionOf(node, box);
|
|
3518
|
+
if (caption) {
|
|
3519
|
+
box = caption.mediaBox;
|
|
3520
|
+
diagnostics.push(...caption.diagnostics);
|
|
3521
|
+
}
|
|
1907
3522
|
const textValue = contentText(node.field, node.value);
|
|
1908
3523
|
const quoteLayout = node.field === "quote" ? measureQuote(node, box, settings) : void 0;
|
|
1909
3524
|
const codeLayout = node.field === "code" ? measureCode(node, box, settings) : void 0;
|
|
1910
3525
|
const metricLayout = node.field === "metric" ? measureMetric(node, box, settings) : void 0;
|
|
1911
3526
|
const timelineLayout = node.field === "timeline" ? measureTimeline(node, box, settings) : void 0;
|
|
1912
3527
|
const internal = quoteLayout ?? codeLayout ?? metricLayout ?? timelineLayout, body = internal?.parts.find((part) => part.role === "body" || part.role === "value");
|
|
1913
|
-
const
|
|
3528
|
+
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
3529
|
items.push({
|
|
1915
3530
|
path: node.path,
|
|
1916
3531
|
field: node.field,
|
|
@@ -1919,7 +3534,7 @@ function composeSlide(input, options = {}) {
|
|
|
1919
3534
|
payload: node.payload,
|
|
1920
3535
|
box: internal ? acceptedBox(box) : box,
|
|
1921
3536
|
...frameBox ? { frameBox } : {},
|
|
1922
|
-
text,
|
|
3537
|
+
text: text2,
|
|
1923
3538
|
textStyle: body?.style ?? styleFor(node.field, node.path),
|
|
1924
3539
|
composition: settings,
|
|
1925
3540
|
alignment: alignmentFor(node.field),
|
|
@@ -1927,7 +3542,8 @@ function composeSlide(input, options = {}) {
|
|
|
1927
3542
|
...codeLayout ? { codeLayout } : {},
|
|
1928
3543
|
...metricLayout ? { metricLayout } : {},
|
|
1929
3544
|
...timelineLayout ? { timelineLayout } : {},
|
|
1930
|
-
...bulletImage && (node.field === "items" || node.field === "bullets") ? { bulletImage } : {}
|
|
3545
|
+
...bulletImage && node.payload.numbering === void 0 && (node.field === "items" || node.field === "bullets") ? { bulletImage } : {},
|
|
3546
|
+
...caption ? { caption } : {}
|
|
1931
3547
|
});
|
|
1932
3548
|
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
3549
|
}
|
|
@@ -1938,7 +3554,7 @@ function composeSlide(input, options = {}) {
|
|
|
1938
3554
|
const picture = placeholders.findIndex((p) => p.type === "picture");
|
|
1939
3555
|
if (picture >= 0) placeholders.splice(picture, 1);
|
|
1940
3556
|
}
|
|
1941
|
-
const ownMode =
|
|
3557
|
+
const ownMode = record4(slide.composition).mode;
|
|
1942
3558
|
const direction = designHint("contentDirection")?.value;
|
|
1943
3559
|
const directionMode = direction === "vertical" ? "column" : direction === "horizontal" ? "row" : void 0;
|
|
1944
3560
|
const layoutDirectionMode = layout.slideLayoutDirection === "Vertical" ? "column" : layout.slideLayoutDirection === "Horizontal" ? "row" : "auto";
|
|
@@ -1952,19 +3568,23 @@ function composeSlide(input, options = {}) {
|
|
|
1952
3568
|
const container = { field: "blocks", type: "group", value: rest.map((node) => node.payload), path, payload: {}, children: rest, composition: {}, synthetic: true };
|
|
1953
3569
|
arrange(chartPrimary === "left" || chartPrimary === "top" ? [primary, container] : [container, primary], contentBox, rootSettings, 0);
|
|
1954
3570
|
} 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")) {
|
|
3571
|
+
if (listBullet?.value === "image" && !bulletImage && items.some((item) => (item.field === "items" || item.field === "bullets") && item.payload.numbering === void 0)) {
|
|
1956
3572
|
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
3573
|
}
|
|
3574
|
+
const numberingDiagnostics = [];
|
|
1958
3575
|
for (const item of items) {
|
|
1959
|
-
for (const key of ["x", "y", "width", "height"]) item.box[key] =
|
|
3576
|
+
for (const key of ["x", "y", "width", "height"]) item.box[key] = round2(item.box[key]);
|
|
1960
3577
|
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
3578
|
if (item.quoteLayout) diagnostics.push(...item.quoteLayout.diagnostics);
|
|
1962
3579
|
else if (item.codeLayout) diagnostics.push(...item.codeLayout.diagnostics);
|
|
1963
3580
|
else if (item.metricLayout) diagnostics.push(...item.metricLayout.diagnostics);
|
|
1964
3581
|
else if (item.timelineLayout) diagnostics.push(...item.timelineLayout.diagnostics);
|
|
1965
3582
|
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." });
|
|
3583
|
+
for (const entry of item.text?.listEntries ?? []) {
|
|
3584
|
+
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.` });
|
|
3585
|
+
}
|
|
1966
3586
|
}
|
|
1967
|
-
for (const group of groups) for (const box of [group.box, group.contentBox]) for (const key of ["x", "y", "width", "height"]) box[key] =
|
|
3587
|
+
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
3588
|
const strictPaths = new Set(items.filter((item) => item.composition.overflow === "error").map((item) => item.path));
|
|
1969
3589
|
const failures = diagnostics.filter((diagnostic) => {
|
|
1970
3590
|
if (composition.overflow === "error") return true;
|
|
@@ -1986,8 +3606,8 @@ function composeSlide(input, options = {}) {
|
|
|
1986
3606
|
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
3607
|
} : void 0;
|
|
1988
3608
|
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 } : {} };
|
|
3609
|
+
diagnostics.push(...slideImageDiagnostics, ...numberingDiagnostics);
|
|
3610
|
+
return { width, height, contentBox, items, groups, flows, diagnostics, composition, ...furniture ? { furniture } : {}, ...slideImage ? { slideImage } : {}, ...logo ? { logo } : {}, ...rtl ? { direction: "rtl" } : {}, ...footnotes ? { footnotes } : {}, ...explanation ? { explanation } : {} };
|
|
1991
3611
|
}
|
|
1992
3612
|
function resolveCanvasDimensions(input) {
|
|
1993
3613
|
const presets = {
|
|
@@ -1999,7 +3619,7 @@ function resolveCanvasDimensions(input) {
|
|
|
1999
3619
|
letter: [11, 8.5],
|
|
2000
3620
|
a4: [11.69, 8.27]
|
|
2001
3621
|
};
|
|
2002
|
-
const value =
|
|
3622
|
+
const value = record4(input);
|
|
2003
3623
|
const preset = presets[typeof input === "string" ? input : value.preset] ?? presets.widescreen;
|
|
2004
3624
|
const width = (value.widthInches ?? preset[0]) * 96;
|
|
2005
3625
|
const height = (value.heightInches ?? preset[1]) * 96;
|
|
@@ -2007,4 +3627,4 @@ function resolveCanvasDimensions(input) {
|
|
|
2007
3627
|
return { width, height };
|
|
2008
3628
|
}
|
|
2009
3629
|
|
|
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 };
|
|
3630
|
+
export { CAPTIONABLE_FIELDS, CAPTION_FONT_RATIO, CAPTION_MAX_RATIO, CHART_SERIES_MIN_DIFFERENCE, CHART_SERIES_MIN_LIGHTNESS_STEP, CITATION_MARKER_RAISE, CITATION_MARKER_SCALE, DEFAULT_CHART_LABEL_SEPARATOR, DEFAULT_FONT_SCHEME, DEFAULT_FURNITURE_DATE_FORMAT, DEFAULT_SLIDE_NUMBER_FORMAT, FONT_SIZE_GRID_PER_PX, FOOTNOTE_MAX_RATIO, MAX_COMPOSITION_DEPTH, MAX_NUMBERING_LEVELS, MAX_NUMBERING_VALUE, MAX_ROMAN_VALUE, NUMBERING_STYLES, NUMBERING_SUFFIXES, OPFCompositionError, PICTURE_BULLET_SCALE, annotationText, bareIdPattern, captionSettings, catalogIds, chartColorForFill, chartLabelText, chartOptionSupport, chartOptionTarget, chartPaletteForFill, citationMarkerText, collectCitations, colorContrast, composeSlide, deprecatedCatalogIds, fitList, fitRichText, fitText, formatChartLabelNumber, formatChartLabelPercent, formatFurnitureDate, formatListNumber, formatSlideNumber, intrinsicImageSize, 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 };
|