@openpresentation/opf 0.11.3 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/audit.d.ts +156 -0
- package/dist/audit.js +7 -0
- package/dist/{catalogs-CUClB3nl.d.ts → catalogs-u6TEeMcz.d.ts} +1 -1
- package/dist/catalogs.d.ts +2 -2
- package/dist/catalogs.js +2 -1
- package/dist/{chunk-UDPSGTZI.js → chunk-32DIB5BN.js} +12 -9
- package/dist/chunk-4FWCY4ZV.js +198 -0
- package/dist/chunk-6TSDIPSH.js +3624 -0
- package/dist/chunk-FYLJVF7X.js +18448 -0
- package/dist/{chunk-HJL64ETN.js → chunk-GJKJL4YY.js} +14 -0
- package/dist/chunk-JENJZR3F.js +1690 -0
- package/dist/{chunk-57NWIYX3.js → chunk-JVTN3HPP.js} +18 -4
- package/dist/{chunk-JTXCMVVP.js → chunk-RVN5C2FX.js} +754 -39
- package/dist/chunk-S5W34SIJ.js +1 -0
- package/dist/chunk-S667QI3M.js +261 -0
- package/dist/chunk-UQKJWHSS.js +1332 -0
- package/dist/{chunk-RXNFDGPC.js → chunk-WXSPETQ6.js} +311 -40
- package/dist/composition-DWbCiMwF.d.ts +1523 -0
- 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 +69 -15
- package/dist/examples.d.ts +1 -1
- package/dist/font-policy.d.ts +15 -0
- package/dist/font-policy.js +1 -1
- package/dist/format.d.ts +20 -0
- package/dist/format.js +83 -0
- package/dist/index.d.ts +173 -24
- package/dist/index.js +926 -262
- package/dist/lint.d.ts +4 -4
- package/dist/lint.js +6 -5
- package/dist/markdown.d.ts +74 -0
- package/dist/markdown.js +1941 -0
- package/dist/pagination.d.ts +1 -1
- package/dist/pagination.js +6 -5
- package/dist/patch.d.ts +99 -0
- package/dist/patch.js +6 -0
- package/dist/{presentation-Cn6vTOCP.d.ts → presentation-DRx4hpNZ.d.ts} +479 -32
- package/dist/repo-readme.js +1 -1
- package/dist/{schemas-X_NniU4A.d.ts → schemas-BSjlG8-R.d.ts} +28 -0
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.js +1 -1
- package/dist/spec/reference/font-policy.json +311 -40
- package/dist/spec/reference/font-policy.schema.json +40 -0
- package/dist/spec/reference/symbol-font-encodings.json +18354 -0
- package/dist/spec/reference/symbol-font-encodings.schema.json +156 -0
- package/dist/spec/schemas/opf.schema.json +608 -35
- package/dist/spec-files.d.ts +1 -1
- package/dist/spec-files.js +1 -1
- package/dist/symbol-font-encodings.d.ts +128 -0
- package/dist/symbol-font-encodings.js +1 -0
- package/dist/types.d.ts +4 -4
- package/dist/validator-bu396ffT.d.ts +172 -0
- package/dist/validator.d.ts +4 -36
- package/dist/validator.js +5 -4
- package/package.json +31 -2
- package/dist/chunk-BVLWFMDX.js +0 -1812
- package/dist/chunk-JCXHSTSM.js +0 -1366
- package/dist/composition-DzLwY-Mi.d.ts +0 -887
- /package/dist/{chunk-AEUSVEUY.js → chunk-3ZGSWEHK.js} +0 -0
|
@@ -0,0 +1,3624 @@
|
|
|
1
|
+
import { catalogs } from './chunk-3ZGSWEHK.js';
|
|
2
|
+
|
|
3
|
+
// src/table.ts
|
|
4
|
+
var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
5
|
+
function tableGrid(value, path = "table") {
|
|
6
|
+
const table = record(value), hasHeaders = Array.isArray(table.columns) && table.columns.length > 0;
|
|
7
|
+
const values = [...hasHeaders ? [table.columns] : [], ...Array.isArray(table.rows) ? table.rows : []];
|
|
8
|
+
const columnCount = values.reduce((count, row) => Math.max(count, Array.isArray(row) ? row.length : 0), 1), rowCount = values.length;
|
|
9
|
+
const owners = Array.from({ length: rowCount }, () => Array(columnCount));
|
|
10
|
+
const rows2 = Array.from({ length: rowCount }, () => []), issues = [];
|
|
11
|
+
const location = (r, c) => `${path}.${hasHeaders && r === 0 ? "columns" : `rows.${r - Number(hasHeaders)}`}.${c}`;
|
|
12
|
+
for (let r = 0; r < rowCount; r++) for (let c = 0; c < columnCount; c++) {
|
|
13
|
+
const input = values[r]?.[c], cellPath = location(r, c), existing = owners[r][c];
|
|
14
|
+
if (existing) {
|
|
15
|
+
if (input !== null) issues.push({ path: cellPath, message: "A position covered by a spanning cell must explicitly contain null; content cannot be hidden." });
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const object = record(input), styled = Object.hasOwn(object, "value");
|
|
19
|
+
const rowSpan = styled ? object.rowSpan ?? 1 : 1, colSpan = styled ? object.colSpan ?? 1 : 1;
|
|
20
|
+
if (!Number.isSafeInteger(rowSpan) || !Number.isSafeInteger(colSpan) || rowSpan < 1 || colSpan < 1 || r + rowSpan > rowCount || c + colSpan > columnCount || hasHeaders && r === 0 && rowSpan > 1) {
|
|
21
|
+
issues.push({ path: cellPath, message: "Cell spans must be positive integers within the table grid and cannot cross from headers into body rows." });
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const cell = { input, value: styled ? object.value : input, style: styled ? record(object.style) : {}, row: r, column: c, rowSpan, colSpan, header: hasHeaders && r === 0, path: cellPath, valuePath: styled ? cellPath + ".value" : cellPath };
|
|
25
|
+
rows2[r].push(cell);
|
|
26
|
+
for (let y = r; y < r + rowSpan; y++) for (let x = c; x < c + colSpan; x++) {
|
|
27
|
+
if (owners[y][x]) issues.push({ path: cellPath, message: "Cell spans cannot overlap another spanning cell." });
|
|
28
|
+
else owners[y][x] = cell;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { rows: rows2, columnCount, rowCount, hasHeaders, owners, issues };
|
|
32
|
+
}
|
|
33
|
+
function tableRowBoundaries(value) {
|
|
34
|
+
const grid = tableGrid(value), offset = Number(grid.hasHeaders), count = grid.rowCount - offset;
|
|
35
|
+
const allowed = new Set(Array.from({ length: count + 1 }, (_, i) => i));
|
|
36
|
+
for (const row of grid.rows) for (const cell of row) if (!cell.header) for (let r = 1; r < cell.rowSpan; r++) allowed.delete(cell.row - offset + r);
|
|
37
|
+
return [...allowed];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/image-aspect.ts
|
|
41
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
42
|
+
function sourceOf(value) {
|
|
43
|
+
if (typeof value === "string") return value;
|
|
44
|
+
return isRecord(value) && typeof value.src === "string" ? value.src : void 0;
|
|
45
|
+
}
|
|
46
|
+
var PREFIX_BYTES = 64 * 1024;
|
|
47
|
+
var JPEG_PREFIX_BYTES = 1024 * 1024;
|
|
48
|
+
function dataBytes(uri, limit) {
|
|
49
|
+
const match = /^data:([^;,]*)((?:;[^;,]*)*),/i.exec(uri);
|
|
50
|
+
if (!match) return void 0;
|
|
51
|
+
const type = (match[1] ?? "").toLowerCase(), start = match[0].length;
|
|
52
|
+
try {
|
|
53
|
+
if (/;base64/i.test(match[2] ?? "")) {
|
|
54
|
+
const chars = Math.ceil(limit / 3) * 4, text2 = uri.slice(start, start + chars + chars / 8).replace(/\s+/g, "").slice(0, chars);
|
|
55
|
+
return { type, bytes: Uint8Array.from(atob(text2.slice(0, text2.length - text2.length % 4)), (char) => char.charCodeAt(0)) };
|
|
56
|
+
}
|
|
57
|
+
const prefix = uri.slice(start, start + limit).replace(/%[0-9a-f]?$/i, "");
|
|
58
|
+
return { type, bytes: new TextEncoder().encode(decodeURIComponent(prefix)) };
|
|
59
|
+
} catch {
|
|
60
|
+
return void 0;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
var at = (b, i) => b[i] ?? 0;
|
|
64
|
+
var u16be = (b, i) => at(b, i) << 8 | at(b, i + 1);
|
|
65
|
+
var u32be = (b, i) => (at(b, i) << 24 | at(b, i + 1) << 16 | at(b, i + 2) << 8 | at(b, i + 3)) >>> 0;
|
|
66
|
+
var u16le = (b, i) => at(b, i) | at(b, i + 1) << 8;
|
|
67
|
+
var u24le = (b, i) => at(b, i) | at(b, i + 1) << 8 | at(b, i + 2) << 16;
|
|
68
|
+
var ascii = (b, i, n) => String.fromCharCode(...b.subarray(i, i + n));
|
|
69
|
+
function rasterDimensions(b) {
|
|
70
|
+
if (b.length >= 24 && u32be(b, 0) === 2303741511 && ascii(b, 12, 4) === "IHDR") return { width: u32be(b, 16), height: u32be(b, 20) };
|
|
71
|
+
if (b.length >= 10 && (ascii(b, 0, 6) === "GIF87a" || ascii(b, 0, 6) === "GIF89a")) return { width: u16le(b, 6), height: u16le(b, 8) };
|
|
72
|
+
if (b.length >= 4 && b[0] === 255 && b[1] === 216) {
|
|
73
|
+
let i = 2;
|
|
74
|
+
while (i + 9 < b.length) {
|
|
75
|
+
if (at(b, i) !== 255) {
|
|
76
|
+
i++;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const marker = at(b, i + 1);
|
|
80
|
+
if (marker === 255) {
|
|
81
|
+
i++;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (marker === 216 || marker === 1 || marker >= 208 && marker <= 215) {
|
|
85
|
+
i += 2;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) return { height: u16be(b, i + 5), width: u16be(b, i + 7) };
|
|
89
|
+
i += 2 + u16be(b, i + 2);
|
|
90
|
+
}
|
|
91
|
+
return void 0;
|
|
92
|
+
}
|
|
93
|
+
if (b.length >= 30 && ascii(b, 0, 4) === "RIFF" && ascii(b, 8, 4) === "WEBP") {
|
|
94
|
+
const chunk = ascii(b, 12, 4);
|
|
95
|
+
if (chunk === "VP8X") return { width: u24le(b, 24) + 1, height: u24le(b, 27) + 1 };
|
|
96
|
+
if (chunk === "VP8L" && at(b, 20) === 47) {
|
|
97
|
+
const bits = u16le(b, 21) | u16le(b, 23) << 16;
|
|
98
|
+
return { width: (bits & 16383) + 1, height: (bits >>> 14 & 16383) + 1 };
|
|
99
|
+
}
|
|
100
|
+
if (chunk === "VP8 ") return { width: u16le(b, 26) & 16383, height: u16le(b, 28) & 16383 };
|
|
101
|
+
}
|
|
102
|
+
return void 0;
|
|
103
|
+
}
|
|
104
|
+
function svgDimensions(text2) {
|
|
105
|
+
const root = /<svg\b([^>]*)>/i.exec(text2)?.[1];
|
|
106
|
+
if (root === void 0) return void 0;
|
|
107
|
+
const attribute = (name) => new RegExp(`\\s${name}\\s*=\\s*["']([^"']*)["']`, "i").exec(root)?.[1];
|
|
108
|
+
const length = (value) => {
|
|
109
|
+
const match = value && /^\s*([0-9]*\.?[0-9]+)\s*(px|pt|in|cm|mm|em)?\s*$/i.exec(value);
|
|
110
|
+
return match ? Number(match[1]) : void 0;
|
|
111
|
+
};
|
|
112
|
+
const width = length(attribute("width")), height = length(attribute("height"));
|
|
113
|
+
if (width && height) return { width, height };
|
|
114
|
+
const box = attribute("viewBox")?.trim().split(/[\s,]+/).map(Number);
|
|
115
|
+
if (box && box.length === 4 && box.every(Number.isFinite)) {
|
|
116
|
+
const [, , width2 = 0, height2 = 0] = box;
|
|
117
|
+
if (width2 > 0 && height2 > 0) return { width: width2, height: height2 };
|
|
118
|
+
}
|
|
119
|
+
return void 0;
|
|
120
|
+
}
|
|
121
|
+
var cache = /* @__PURE__ */ new Map();
|
|
122
|
+
var CACHE_ENTRIES = 64;
|
|
123
|
+
function readDimensions(source) {
|
|
124
|
+
const first = dataBytes(source, PREFIX_BYTES);
|
|
125
|
+
if (!first) return void 0;
|
|
126
|
+
let dimensions;
|
|
127
|
+
if (first.type.startsWith("image/svg")) dimensions = svgDimensions(new TextDecoder().decode(first.bytes));
|
|
128
|
+
else {
|
|
129
|
+
dimensions = rasterDimensions(first.bytes);
|
|
130
|
+
if (!dimensions && first.bytes.length >= 2 && first.bytes[0] === 255 && first.bytes[1] === 216) {
|
|
131
|
+
const more = dataBytes(source, JPEG_PREFIX_BYTES);
|
|
132
|
+
if (more) dimensions = rasterDimensions(more.bytes);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return dimensions && dimensions.width > 0 && dimensions.height > 0 ? dimensions : void 0;
|
|
136
|
+
}
|
|
137
|
+
function intrinsicImageSize(value, assets) {
|
|
138
|
+
let source = sourceOf(value);
|
|
139
|
+
for (let hop = 0; source?.startsWith("asset:") && hop < 8; hop++) source = sourceOf(isRecord(assets) ? assets[source.slice(6)] : void 0);
|
|
140
|
+
if (!source?.startsWith("data:")) return void 0;
|
|
141
|
+
if (!cache.has(source)) {
|
|
142
|
+
if (cache.size >= CACHE_ENTRIES) cache.delete(cache.keys().next().value);
|
|
143
|
+
cache.set(source, readDimensions(source));
|
|
144
|
+
}
|
|
145
|
+
const size = cache.get(source);
|
|
146
|
+
return size ? { ...size, svg: /^data:image\/svg/i.test(source) } : void 0;
|
|
147
|
+
}
|
|
148
|
+
function intrinsicImageAspect(value, assets) {
|
|
149
|
+
const size = intrinsicImageSize(value, assets);
|
|
150
|
+
return size ? size.width / size.height : void 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/reading-order.ts
|
|
154
|
+
function visualReadingOrder(items, direction = "ltr") {
|
|
155
|
+
const index = new Map(items.map((item, i) => [item, i]));
|
|
156
|
+
const along = direction === "rtl" ? (item) => -(item.box.x + item.box.width) : (item) => item.box.x;
|
|
157
|
+
return readingRows(items).flatMap((row) => [...row].sort((a, b) => along(a) - along(b) || a.box.y - b.box.y || index.get(a) - index.get(b)));
|
|
158
|
+
}
|
|
159
|
+
function readingRows(items) {
|
|
160
|
+
const index = new Map(items.map((item, i) => [item, i]));
|
|
161
|
+
const sorted = [...items].sort((a, b) => a.box.y - b.box.y || a.box.x - b.box.x || index.get(a) - index.get(b));
|
|
162
|
+
const rows2 = [];
|
|
163
|
+
for (const item of sorted) {
|
|
164
|
+
const centre = item.box.y + item.box.height / 2;
|
|
165
|
+
const row = rows2.find((candidate) => centre >= candidate.top && centre <= candidate.bottom);
|
|
166
|
+
if (row) {
|
|
167
|
+
row.items.push(item);
|
|
168
|
+
row.top = Math.min(row.top, item.box.y);
|
|
169
|
+
row.bottom = Math.max(row.bottom, item.box.y + item.box.height);
|
|
170
|
+
} else rows2.push({ top: item.box.y, bottom: item.box.y + item.box.height, items: [item] });
|
|
171
|
+
}
|
|
172
|
+
rows2.sort((a, b) => a.top - b.top);
|
|
173
|
+
return rows2.map((row) => row.items.sort((a, b) => index.get(a) - index.get(b)));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/direction.ts
|
|
177
|
+
var strongRtl = /[\u200F\u061C]|(?=\p{L})[\p{Script=Arabic}\p{Script=Hebrew}\p{Script=Syriac}\p{Script=Thaana}\p{Script=Nko}\p{Script=Adlam}\p{Script=Hanifi_Rohingya}\p{Script=Mandaic}\p{Script=Samaritan}\p{Script=Mende_Kikakui}\p{Script=Imperial_Aramaic}\p{Script=Phoenician}\p{Script=Kharoshthi}\p{Script=Old_South_Arabian}\p{Script=Old_North_Arabian}\p{Script=Avestan}\p{Script=Inscriptional_Parthian}\p{Script=Inscriptional_Pahlavi}\p{Script=Psalter_Pahlavi}\p{Script=Old_Turkic}\p{Script=Old_Hungarian}\p{Script=Nabataean}\p{Script=Palmyrene}\p{Script=Hatran}\p{Script=Manichaean}\p{Script=Sogdian}\p{Script=Old_Sogdian}\p{Script=Elymaic}\p{Script=Chorasmian}\p{Script=Yezidi}\p{Script=Cypriot}\p{Script=Lydian}\p{Script=Meroitic_Cursive}\p{Script=Meroitic_Hieroglyphs}\u{10F70}-\u{10FAF}\u{10D40}-\u{10D8F}]/u;
|
|
178
|
+
var strongLtr = /[\u200E\p{L}\p{Nl}]/u;
|
|
179
|
+
function paragraphDirection(text2, deckDirection) {
|
|
180
|
+
if (deckDirection !== "rtl") return "ltr";
|
|
181
|
+
let isolates = 0;
|
|
182
|
+
for (const char of String(text2 ?? "")) {
|
|
183
|
+
if (char === "\u2066" || char === "\u2067" || char === "\u2068") isolates += 1;
|
|
184
|
+
else if (char === "\u2069") isolates = Math.max(0, isolates - 1);
|
|
185
|
+
else if (isolates === 0) {
|
|
186
|
+
if (strongRtl.test(char)) return "rtl";
|
|
187
|
+
if (strongLtr.test(char)) return "ltr";
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return "rtl";
|
|
191
|
+
}
|
|
192
|
+
function physicalAlignment(alignment, direction) {
|
|
193
|
+
const value = alignment ?? "left";
|
|
194
|
+
if (direction !== "rtl" || value === "center") return value;
|
|
195
|
+
return value === "left" ? "right" : "left";
|
|
196
|
+
}
|
|
197
|
+
function paragraphDirectionAt(text2, deckDirection) {
|
|
198
|
+
if (deckDirection !== "rtl") return () => "ltr";
|
|
199
|
+
const source = String(text2 ?? ""), starts = [], directions = [];
|
|
200
|
+
let start = 0;
|
|
201
|
+
const close = (end) => {
|
|
202
|
+
starts.push(start);
|
|
203
|
+
directions.push(paragraphDirection(source.slice(start, end), "rtl"));
|
|
204
|
+
};
|
|
205
|
+
for (const match of source.matchAll(/\r\n|\r|\n/g)) {
|
|
206
|
+
close(match.index);
|
|
207
|
+
start = match.index + match[0].length;
|
|
208
|
+
}
|
|
209
|
+
close(source.length);
|
|
210
|
+
return (offset) => {
|
|
211
|
+
for (let index = starts.length - 1; index >= 0; index--) if (offset >= starts[index]) return directions[index];
|
|
212
|
+
return directions[0];
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/content-walk.ts
|
|
217
|
+
function isRecord2(value) {
|
|
218
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
219
|
+
}
|
|
220
|
+
function pathFor(parentPath, key) {
|
|
221
|
+
return parentPath === "/" ? `/${key}` : `${parentPath}/${key.replaceAll("~", "~0").replaceAll("/", "~1")}`;
|
|
222
|
+
}
|
|
223
|
+
var bareIdPattern = /^[a-z0-9][a-z0-9-]*$/;
|
|
224
|
+
var promotedRegionKeys = [
|
|
225
|
+
"left",
|
|
226
|
+
"center",
|
|
227
|
+
"right",
|
|
228
|
+
"left+center",
|
|
229
|
+
"center+right",
|
|
230
|
+
"left+center+right",
|
|
231
|
+
"top",
|
|
232
|
+
"middle",
|
|
233
|
+
"bottom",
|
|
234
|
+
"top+middle",
|
|
235
|
+
"middle+bottom",
|
|
236
|
+
"top+middle+bottom",
|
|
237
|
+
"top:left",
|
|
238
|
+
"top:center",
|
|
239
|
+
"top:right",
|
|
240
|
+
"top:left+center",
|
|
241
|
+
"top:center+right",
|
|
242
|
+
"top:left+center+right",
|
|
243
|
+
"middle:left",
|
|
244
|
+
"middle:center",
|
|
245
|
+
"middle:right",
|
|
246
|
+
"middle:left+center",
|
|
247
|
+
"middle:center+right",
|
|
248
|
+
"middle:left+center+right",
|
|
249
|
+
"bottom:left",
|
|
250
|
+
"bottom:center",
|
|
251
|
+
"bottom:right",
|
|
252
|
+
"bottom:left+center",
|
|
253
|
+
"bottom:center+right",
|
|
254
|
+
"bottom:left+center+right",
|
|
255
|
+
"top+middle:left",
|
|
256
|
+
"top+middle:center",
|
|
257
|
+
"top+middle:right",
|
|
258
|
+
"top+middle:left+center",
|
|
259
|
+
"top+middle:center+right",
|
|
260
|
+
"top+middle:left+center+right",
|
|
261
|
+
"middle+bottom:left",
|
|
262
|
+
"middle+bottom:center",
|
|
263
|
+
"middle+bottom:right",
|
|
264
|
+
"middle+bottom:left+center",
|
|
265
|
+
"middle+bottom:center+right",
|
|
266
|
+
"middle+bottom:left+center+right",
|
|
267
|
+
"top+middle+bottom:left",
|
|
268
|
+
"top+middle+bottom:center",
|
|
269
|
+
"top+middle+bottom:right",
|
|
270
|
+
"top+middle+bottom:left+center",
|
|
271
|
+
"top+middle+bottom:center+right",
|
|
272
|
+
"top+middle+bottom:left+center+right"
|
|
273
|
+
];
|
|
274
|
+
function visitContentPayloads(slide, slidePath, visit) {
|
|
275
|
+
const walk = (payload, path, depth, ancestors) => {
|
|
276
|
+
visit(payload, path);
|
|
277
|
+
if (!Array.isArray(payload.blocks) || depth >= MAX_COMPOSITION_DEPTH || ancestors.includes(payload)) return;
|
|
278
|
+
const nested = [...ancestors, payload];
|
|
279
|
+
const blocksPath = pathFor(path, "blocks");
|
|
280
|
+
payload.blocks.forEach((block, index) => {
|
|
281
|
+
if (isRecord2(block)) walk(block, `${blocksPath}/${index}`, depth + 1, nested);
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
if (Array.isArray(slide.blocks)) {
|
|
285
|
+
const blocksPath = pathFor(slidePath, "blocks");
|
|
286
|
+
slide.blocks.forEach((block, index) => {
|
|
287
|
+
if (isRecord2(block)) walk(block, `${blocksPath}/${index}`, 0, []);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
for (const key of promotedRegionKeys) {
|
|
291
|
+
const region = slide[key];
|
|
292
|
+
if (isRecord2(region)) walk(region, pathFor(slidePath, key), 0, []);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/script-fonts.ts
|
|
297
|
+
var eastAsianScripts = /* @__PURE__ */ new Set(["Hani", "Hans", "Hant", "Hanb", "Jpan", "Kore", "Hang", "Jamo", "Hira", "Kana", "Hrkt", "Bopo", "Yiii"]);
|
|
298
|
+
var complexScripts = /* @__PURE__ */ new Set([
|
|
299
|
+
"Arab",
|
|
300
|
+
"Hebr",
|
|
301
|
+
"Syrc",
|
|
302
|
+
"Thaa",
|
|
303
|
+
"Nkoo",
|
|
304
|
+
"Adlm",
|
|
305
|
+
"Rohg",
|
|
306
|
+
"Mand",
|
|
307
|
+
"Samr",
|
|
308
|
+
"Deva",
|
|
309
|
+
"Beng",
|
|
310
|
+
"Guru",
|
|
311
|
+
"Gujr",
|
|
312
|
+
"Orya",
|
|
313
|
+
"Taml",
|
|
314
|
+
"Telu",
|
|
315
|
+
"Knda",
|
|
316
|
+
"Mlym",
|
|
317
|
+
"Sinh",
|
|
318
|
+
"Thai",
|
|
319
|
+
"Laoo",
|
|
320
|
+
"Tibt",
|
|
321
|
+
"Mymr",
|
|
322
|
+
"Khmr",
|
|
323
|
+
"Mong",
|
|
324
|
+
"Bali",
|
|
325
|
+
"Java",
|
|
326
|
+
"Lana",
|
|
327
|
+
"Tale",
|
|
328
|
+
"Talu",
|
|
329
|
+
"Cakm",
|
|
330
|
+
"Olck"
|
|
331
|
+
]);
|
|
332
|
+
var rtlScripts = /* @__PURE__ */ new Set(["Arab", "Hebr", "Syrc", "Thaa", "Nkoo", "Adlm", "Rohg", "Mand", "Samr"]);
|
|
333
|
+
var latinSlotScripts = /* @__PURE__ */ new Set(["Latn", "Cyrl", "Grek", "Zyyy", "Zzzz"]);
|
|
334
|
+
var deprecatedLanguages = { iw: "he", in: "id", ji: "yi" };
|
|
335
|
+
var matchingLanguages = { ...deprecatedLanguages, no: "nb", zsm: "ms", ku: "kmr" };
|
|
336
|
+
var likelyScripts = {
|
|
337
|
+
zh: { script: "Hans", regions: { TW: "Hant", HK: "Hant", MO: "Hant" } },
|
|
338
|
+
sr: { script: "Cyrl", regions: { ME: "Latn" } },
|
|
339
|
+
pa: { script: "Guru", regions: { PK: "Arab" } },
|
|
340
|
+
az: { script: "Latn", regions: { IR: "Arab" } },
|
|
341
|
+
uz: { script: "Latn", regions: { AF: "Arab" } },
|
|
342
|
+
bs: { script: "Latn" },
|
|
343
|
+
mn: { script: "Cyrl", regions: { CN: "Mong" } },
|
|
344
|
+
ms: { script: "Latn" }
|
|
345
|
+
};
|
|
346
|
+
var bareLanguageId = /^[a-z][a-z0-9-]*$/;
|
|
347
|
+
function scriptFontRole(script) {
|
|
348
|
+
const code = normalizeScript(script);
|
|
349
|
+
if (code && eastAsianScripts.has(code)) return "eastAsian";
|
|
350
|
+
if (code && complexScripts.has(code)) return "complexScript";
|
|
351
|
+
return "latin";
|
|
352
|
+
}
|
|
353
|
+
function normalizeScript(value) {
|
|
354
|
+
if (typeof value !== "string" || !/^[A-Za-z]{4}$/.test(value)) return void 0;
|
|
355
|
+
return value.slice(0, 1).toUpperCase() + value.slice(1).toLowerCase();
|
|
356
|
+
}
|
|
357
|
+
function text(value) {
|
|
358
|
+
return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
|
|
359
|
+
}
|
|
360
|
+
var tagSyntax = /^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/;
|
|
361
|
+
function splitTag(tag) {
|
|
362
|
+
if (!tagSyntax.test(tag)) return void 0;
|
|
363
|
+
const [first = "", ...rest] = tag.split("-");
|
|
364
|
+
const language = first.toLowerCase();
|
|
365
|
+
if (language === "und") return void 0;
|
|
366
|
+
let script;
|
|
367
|
+
let region;
|
|
368
|
+
for (const subtag of rest) {
|
|
369
|
+
if (subtag.length === 1) break;
|
|
370
|
+
if (!script && !region && /^[A-Za-z]{4}$/.test(subtag)) script = normalizeScript(subtag);
|
|
371
|
+
else if (!region && /^(?:[A-Za-z]{2}|\d{3})$/.test(subtag)) region = subtag.toUpperCase();
|
|
372
|
+
}
|
|
373
|
+
return { language, script, region };
|
|
374
|
+
}
|
|
375
|
+
function canonicalTag(tag) {
|
|
376
|
+
let extension = false;
|
|
377
|
+
return tag.split("-").map((subtag, index) => {
|
|
378
|
+
const lower = subtag.toLowerCase();
|
|
379
|
+
if (index === 0) return deprecatedLanguages[lower] ?? lower;
|
|
380
|
+
if (extension) return lower;
|
|
381
|
+
if (subtag.length === 1) {
|
|
382
|
+
extension = true;
|
|
383
|
+
return lower;
|
|
384
|
+
}
|
|
385
|
+
if (/^[A-Za-z]{4}$/.test(subtag)) return normalizeScript(subtag);
|
|
386
|
+
if (/^[A-Za-z]{2}$/.test(subtag)) return subtag.toUpperCase();
|
|
387
|
+
return lower;
|
|
388
|
+
}).join("-");
|
|
389
|
+
}
|
|
390
|
+
function vendoredScript(language, region) {
|
|
391
|
+
const entry = likelyScripts[language];
|
|
392
|
+
return entry ? (region && entry.regions?.[region]) ?? entry.script : void 0;
|
|
393
|
+
}
|
|
394
|
+
function inferScript(tag) {
|
|
395
|
+
const parts = splitTag(tag);
|
|
396
|
+
if (!parts) return void 0;
|
|
397
|
+
const known = parts.script ?? vendoredScript(matchingLanguages[parts.language] ?? parts.language, parts.region);
|
|
398
|
+
if (known) return known;
|
|
399
|
+
try {
|
|
400
|
+
return normalizeScript(new Intl.Locale(tag).maximize().script);
|
|
401
|
+
} catch {
|
|
402
|
+
return void 0;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function inlineRecords(document, kind2) {
|
|
406
|
+
const kinds = isRecord2(document.catalogs) ? document.catalogs : {};
|
|
407
|
+
const entry = kinds[kind2];
|
|
408
|
+
return isRecord2(entry) && Array.isArray(entry.records) ? entry.records.filter(isRecord2) : [];
|
|
409
|
+
}
|
|
410
|
+
function allRecords(document, kind2) {
|
|
411
|
+
return [...inlineRecords(document, kind2), ...catalogs[kind2]];
|
|
412
|
+
}
|
|
413
|
+
function resolveReference(lookup, kind2, reference) {
|
|
414
|
+
if (typeof reference === "string") return lookup(kind2, reference);
|
|
415
|
+
if (!isRecord2(reference)) return void 0;
|
|
416
|
+
const base = typeof reference.id === "string" ? lookup(kind2, reference.id) : void 0;
|
|
417
|
+
return { ...base, ...reference };
|
|
418
|
+
}
|
|
419
|
+
function matchLanguageTag(document, tag) {
|
|
420
|
+
const wanted = splitTag(tag);
|
|
421
|
+
if (!wanted) return void 0;
|
|
422
|
+
const records = allRecords(document, "languages").filter((record5) => typeof record5.bcp47 === "string");
|
|
423
|
+
const key = canonicalTag(tag).toLowerCase();
|
|
424
|
+
const exact = records.find((record5) => canonicalTag(record5.bcp47).toLowerCase() === key);
|
|
425
|
+
if (exact) return exact;
|
|
426
|
+
const language = matchingLanguages[wanted.language] ?? wanted.language;
|
|
427
|
+
const script = wanted.script ?? vendoredScript(language, wanted.region);
|
|
428
|
+
const candidates = records.flatMap((record5) => {
|
|
429
|
+
const parts = splitTag(record5.bcp47);
|
|
430
|
+
if (!parts || (matchingLanguages[parts.language] ?? parts.language) !== language) return [];
|
|
431
|
+
const recordScript = normalizeScript(record5.script) ?? parts.script ?? vendoredScript(language, parts.region);
|
|
432
|
+
if (script && recordScript && script !== recordScript) return [];
|
|
433
|
+
return [{ record: record5, region: parts.region }];
|
|
434
|
+
});
|
|
435
|
+
return candidates.find((candidate) => wanted.region && candidate.region === wanted.region)?.record ?? candidates.find((candidate) => !candidate.region)?.record ?? candidates[0]?.record;
|
|
436
|
+
}
|
|
437
|
+
function resolveLanguage(document, lookup, reference) {
|
|
438
|
+
if (typeof reference === "string") {
|
|
439
|
+
const value = reference.trim();
|
|
440
|
+
const byId = bareLanguageId.test(value) ? lookup("languages", value) : void 0;
|
|
441
|
+
if (byId) return { record: byId, languageId: value };
|
|
442
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(value) || !splitTag(value)) return void 0;
|
|
443
|
+
const matched = matchLanguageTag(document, value);
|
|
444
|
+
if (matched) return { record: matched, authoredTag: value, languageId: text(matched.id) };
|
|
445
|
+
return inferScript(value) ? { record: {}, authoredTag: value } : void 0;
|
|
446
|
+
}
|
|
447
|
+
if (!isRecord2(reference)) return void 0;
|
|
448
|
+
const authoredTag = text(reference.bcp47);
|
|
449
|
+
if (authoredTag !== void 0 && !splitTag(authoredTag)) return void 0;
|
|
450
|
+
const base = typeof reference.id === "string" ? lookup("languages", reference.id) : authoredTag ? matchLanguageTag(document, authoredTag) : void 0;
|
|
451
|
+
const record5 = { ...base, ...reference };
|
|
452
|
+
const tag = text(record5.bcp47);
|
|
453
|
+
if (!tag || !splitTag(tag)) return void 0;
|
|
454
|
+
return { record: record5, authoredTag, authoredOoxmlLang: text(reference.ooxmlLang), languageId: text(base?.id) };
|
|
455
|
+
}
|
|
456
|
+
function pairFamilies(value) {
|
|
457
|
+
if (!isRecord2(value)) return void 0;
|
|
458
|
+
const major = text(value.major);
|
|
459
|
+
const minor = text(value.minor);
|
|
460
|
+
const heading = major ?? minor;
|
|
461
|
+
const body = minor ?? major;
|
|
462
|
+
return heading && body ? { heading, body } : void 0;
|
|
463
|
+
}
|
|
464
|
+
function schemeServesLanguage(scheme, language) {
|
|
465
|
+
const entries = Array.isArray(scheme.languages) ? scheme.languages.filter((entry) => typeof entry === "string") : [];
|
|
466
|
+
if (entries.length === 0) return true;
|
|
467
|
+
const name = text(language.name)?.toLowerCase();
|
|
468
|
+
if (!name) return false;
|
|
469
|
+
const base = name.replace(/\s*\([^)]*\)\s*$/, "");
|
|
470
|
+
return entries.some((entry) => {
|
|
471
|
+
const value = entry.trim().toLowerCase();
|
|
472
|
+
return value === name || value === base;
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
function resolveScriptFonts(input, options = {}) {
|
|
476
|
+
const document = isRecord2(input) ? input : {};
|
|
477
|
+
const lookup = (kind2, id) => inlineRecords(document, kind2).find((record6) => record6.id === id) ?? catalogs[kind2].find((record6) => record6.id === id);
|
|
478
|
+
let design = isRecord2(document.design) ? document.design : {};
|
|
479
|
+
if (options.slideIndex !== void 0) {
|
|
480
|
+
const slides = Array.isArray(document.slides) ? document.slides : [];
|
|
481
|
+
if (!Number.isInteger(options.slideIndex) || options.slideIndex < 0 || options.slideIndex >= slides.length) {
|
|
482
|
+
throw new RangeError(`slideIndex must be an integer between 0 and ${slides.length - 1}.`);
|
|
483
|
+
}
|
|
484
|
+
const slide = slides[options.slideIndex];
|
|
485
|
+
if (isRecord2(slide) && isRecord2(slide.design)) design = { ...design, ...slide.design };
|
|
486
|
+
}
|
|
487
|
+
const theme = resolveReference(lookup, "themes", design.theme ?? "minimal") ?? {};
|
|
488
|
+
const scheme = resolveFontSchemeReference(design.fontScheme ?? theme.fontScheme ?? DEFAULT_FONT_SCHEME, (id) => lookup("fontSchemes", id)).scheme;
|
|
489
|
+
const latin = resolveFontFamilies(scheme);
|
|
490
|
+
const fromOption = options.language !== void 0 ? resolveLanguage(document, lookup, options.language) : void 0;
|
|
491
|
+
const fromDocument = fromOption ? void 0 : resolveLanguage(document, lookup, document.language);
|
|
492
|
+
const requestedDefault = text(options.defaultLanguage);
|
|
493
|
+
const fallbackTag = requestedDefault && splitTag(requestedDefault) ? requestedDefault : "en-US";
|
|
494
|
+
const language = fromOption ?? fromDocument ?? resolveLanguage(document, lookup, fallbackTag) ?? { record: {}, authoredTag: fallbackTag };
|
|
495
|
+
const languageSource = fromOption ? "option" : fromDocument ? "document" : "default";
|
|
496
|
+
const record5 = language.record;
|
|
497
|
+
const bcp47 = canonicalTag(language.authoredTag ?? text(record5.bcp47) ?? fallbackTag);
|
|
498
|
+
const curated = text(record5.ooxmlLang);
|
|
499
|
+
const lang = language.authoredOoxmlLang ? canonicalTag(language.authoredOoxmlLang) : language.authoredTag && splitTag(language.authoredTag)?.region ? canonicalTag(language.authoredTag) : curated ? canonicalTag(curated) : bcp47;
|
|
500
|
+
const parts = splitTag(bcp47);
|
|
501
|
+
const script = normalizeScript(record5.script) ?? (language.languageId ? parts?.script ?? (parts ? vendoredScript(matchingLanguages[parts.language] ?? parts.language, parts.region) : void 0) ?? normalizeScript(matchLanguageTag({}, bcp47)?.script) : inferScript(bcp47)) ?? "Zzzz";
|
|
502
|
+
const scriptRole = scriptFontRole(script);
|
|
503
|
+
const declaredDirection = record5.direction;
|
|
504
|
+
const direction = declaredDirection === "rtl" || declaredDirection === "ltr" ? declaredDirection : rtlScripts.has(script) ? "rtl" : "ltr";
|
|
505
|
+
const app = options.app ?? "PowerPoint";
|
|
506
|
+
const preferred = app === "Google Slides" ? record5.googleFontScheme : record5.fontScheme;
|
|
507
|
+
const alternate = app === "Google Slides" ? record5.fontScheme : record5.googleFontScheme;
|
|
508
|
+
const languageScheme = resolveReference(lookup, "fontSchemes", preferred) ?? resolveReference(lookup, "fontSchemes", alternate);
|
|
509
|
+
const languageFamilies = pairFamilies(languageScheme);
|
|
510
|
+
const schemeAdmitsLanguage = schemeServesLanguage(scheme, record5);
|
|
511
|
+
const slot = (role, family) => {
|
|
512
|
+
const explicit = pairFamilies(scheme[role]);
|
|
513
|
+
if (explicit) return { ...explicit, source: "fontScheme" };
|
|
514
|
+
if (scheme.languageFamily === family && schemeAdmitsLanguage) return { ...latin, source: "schemeFamily" };
|
|
515
|
+
if (scriptRole === role && languageFamilies) return { ...languageFamilies, source: "language" };
|
|
516
|
+
return { heading: latin.heading, body: latin.body, source: "latin" };
|
|
517
|
+
};
|
|
518
|
+
const eastAsian = slot("eastAsian", "ea");
|
|
519
|
+
const complexScript = slot("complexScript", "cs");
|
|
520
|
+
const heading = { latin: latin.heading, eastAsian: eastAsian.heading, complexScript: complexScript.heading };
|
|
521
|
+
const body = { latin: latin.body, eastAsian: eastAsian.body, complexScript: complexScript.body };
|
|
522
|
+
let supplement;
|
|
523
|
+
if (!latinSlotScripts.has(script)) {
|
|
524
|
+
const families = scriptRole === "latin" ? languageFamilies : (scriptRole === "eastAsian" ? eastAsian : complexScript).source === "latin" ? void 0 : { heading: heading[scriptRole], body: body[scriptRole] };
|
|
525
|
+
if (families) supplement = { script: script === "Kore" ? "Hang" : script, heading: families.heading, body: families.body };
|
|
526
|
+
}
|
|
527
|
+
return {
|
|
528
|
+
...body,
|
|
529
|
+
lang,
|
|
530
|
+
bcp47,
|
|
531
|
+
...language.languageId ? { languageId: language.languageId } : {},
|
|
532
|
+
languageSource,
|
|
533
|
+
script,
|
|
534
|
+
scriptRole,
|
|
535
|
+
direction,
|
|
536
|
+
rtl: direction === "rtl",
|
|
537
|
+
heading,
|
|
538
|
+
body,
|
|
539
|
+
...supplement ? { supplement } : {},
|
|
540
|
+
sources: { eastAsian: eastAsian.source, complexScript: complexScript.source }
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
function resolveSlideDirection(presentation, slideIndex) {
|
|
544
|
+
if (!isRecord2(presentation) || presentation.language === void 0) return "ltr";
|
|
545
|
+
try {
|
|
546
|
+
return resolveScriptFonts(presentation, Number.isInteger(slideIndex) ? { slideIndex } : {}).direction;
|
|
547
|
+
} catch {
|
|
548
|
+
try {
|
|
549
|
+
return resolveScriptFonts(presentation).direction;
|
|
550
|
+
} catch {
|
|
551
|
+
return "ltr";
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/numbering.ts
|
|
557
|
+
var NUMBERING_STYLES = ["arabic", "roman-upper", "roman-lower", "alpha-upper", "alpha-lower"];
|
|
558
|
+
var NUMBERING_SUFFIXES = ["period", "paren", "paren-both"];
|
|
559
|
+
var MAX_NUMBERING_VALUE = 32767;
|
|
560
|
+
var MAX_ROMAN_VALUE = 3999;
|
|
561
|
+
var MAX_NUMBERING_LEVELS = 9;
|
|
562
|
+
var styleSet = new Set(NUMBERING_STYLES);
|
|
563
|
+
var suffixSet = new Set(NUMBERING_SUFFIXES);
|
|
564
|
+
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
565
|
+
function resolveEntry(entry, label) {
|
|
566
|
+
if (typeof entry === "string") {
|
|
567
|
+
if (!styleSet.has(entry)) throw new RangeError(`${label} must be one of ${NUMBERING_STYLES.join(", ")}.`);
|
|
568
|
+
return { style: entry, start: 1, suffix: "period" };
|
|
569
|
+
}
|
|
570
|
+
if (!isObject(entry)) throw new TypeError(`${label} must be a style name or a numbering object.`);
|
|
571
|
+
const { style = "arabic", start = 1, suffix = "period" } = entry;
|
|
572
|
+
if (typeof style !== "string" || !styleSet.has(style)) throw new RangeError(`${label}.style must be one of ${NUMBERING_STYLES.join(", ")}.`);
|
|
573
|
+
if (typeof suffix !== "string" || !suffixSet.has(suffix)) throw new RangeError(`${label}.suffix must be one of ${NUMBERING_SUFFIXES.join(", ")}.`);
|
|
574
|
+
if (typeof start !== "number" || !Number.isInteger(start) || start < 1 || start > MAX_NUMBERING_VALUE) throw new RangeError(`${label}.start must be an integer from 1 to ${MAX_NUMBERING_VALUE}.`);
|
|
575
|
+
return { style, start, suffix };
|
|
576
|
+
}
|
|
577
|
+
function resolveNumbering(input) {
|
|
578
|
+
if (Array.isArray(input)) {
|
|
579
|
+
if (!input.length || input.length > MAX_NUMBERING_LEVELS) throw new RangeError(`numbering must list from 1 to ${MAX_NUMBERING_LEVELS} levels.`);
|
|
580
|
+
return input.map((entry, index) => resolveEntry(entry, `numbering[${index}]`));
|
|
581
|
+
}
|
|
582
|
+
return [resolveEntry(input, "numbering")];
|
|
583
|
+
}
|
|
584
|
+
function numberingAtLevel(levels, level) {
|
|
585
|
+
return levels[Math.min(Math.max(0, level), levels.length - 1)];
|
|
586
|
+
}
|
|
587
|
+
var ROMAN = [[1e3, "m"], [900, "cm"], [500, "d"], [400, "cd"], [100, "c"], [90, "xc"], [50, "l"], [40, "xl"], [10, "x"], [9, "ix"], [5, "v"], [4, "iv"], [1, "i"]];
|
|
588
|
+
function bareNumber(value, style) {
|
|
589
|
+
if (style === "arabic") return String(value);
|
|
590
|
+
if (style === "roman-upper" || style === "roman-lower") {
|
|
591
|
+
if (value > MAX_ROMAN_VALUE) return null;
|
|
592
|
+
let rest = value, out = "";
|
|
593
|
+
for (const [amount, glyphs] of ROMAN) while (rest >= amount) {
|
|
594
|
+
out += glyphs;
|
|
595
|
+
rest -= amount;
|
|
596
|
+
}
|
|
597
|
+
return style === "roman-upper" ? out.toUpperCase() : out;
|
|
598
|
+
}
|
|
599
|
+
const letter = String.fromCharCode(97 + (value - 1) % 26).repeat(Math.floor((value - 1) / 26) + 1);
|
|
600
|
+
return style === "alpha-upper" ? letter.toUpperCase() : letter;
|
|
601
|
+
}
|
|
602
|
+
function numberingStyleDraws(style, value) {
|
|
603
|
+
return bareNumber(value, style) !== null;
|
|
604
|
+
}
|
|
605
|
+
function formatListNumber(value, style = "arabic", suffix = "period") {
|
|
606
|
+
if (!Number.isInteger(value) || value < 1) throw new RangeError("A list number must be an integer of at least 1.");
|
|
607
|
+
if (!styleSet.has(style)) throw new RangeError(`Unknown numbering style '${String(style)}'.`);
|
|
608
|
+
if (!suffixSet.has(suffix)) throw new RangeError(`Unknown numbering suffix '${String(suffix)}'.`);
|
|
609
|
+
const bare = bareNumber(value, style) ?? String(value);
|
|
610
|
+
return suffix === "period" ? `${bare}.` : suffix === "paren" ? `${bare})` : `(${bare})`;
|
|
611
|
+
}
|
|
612
|
+
function listNumbers(items, numbering) {
|
|
613
|
+
const levels = resolveNumbering(numbering);
|
|
614
|
+
const counters = [];
|
|
615
|
+
return items.map((item, index) => {
|
|
616
|
+
const object = isObject(item) ? item : void 0;
|
|
617
|
+
const level = object?.level === void 0 ? 0 : object.level;
|
|
618
|
+
if (typeof level !== "number" || !Number.isInteger(level) || level < 0) throw new RangeError("List levels must be nonnegative integers.");
|
|
619
|
+
const own = object?.start;
|
|
620
|
+
if (own !== void 0 && (typeof own !== "number" || !Number.isInteger(own) || own < 1 || own > MAX_NUMBERING_VALUE)) throw new RangeError(`A list item start must be an integer from 1 to ${MAX_NUMBERING_VALUE}.`);
|
|
621
|
+
const { style: authored, start, suffix } = numberingAtLevel(levels, level);
|
|
622
|
+
const previous = counters[level];
|
|
623
|
+
const value = own ?? (previous === void 0 ? start : previous + 1);
|
|
624
|
+
counters[level] = value;
|
|
625
|
+
counters.length = level + 1;
|
|
626
|
+
const adapted = !numberingStyleDraws(authored, value);
|
|
627
|
+
const style = adapted ? "arabic" : authored;
|
|
628
|
+
return { index, level, value, style, suffix, text: formatListNumber(value, style, suffix), ...adapted ? { adapted: "roman-range" } : {} };
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
function sliceNumberedItems(items, numbering, from, to = items.length) {
|
|
632
|
+
const whole = listNumbers(items, numbering);
|
|
633
|
+
const result = items.slice(from, to);
|
|
634
|
+
for (let index = 0; index < result.length; index++) {
|
|
635
|
+
const wanted = whole[from + index].value;
|
|
636
|
+
if (listNumbers(result.slice(0, index + 1), numbering)[index].value === wanted) continue;
|
|
637
|
+
const item = result[index];
|
|
638
|
+
const object = isObject(item) ? item : { text: item };
|
|
639
|
+
result[index] = { ...object, start: wanted };
|
|
640
|
+
}
|
|
641
|
+
return result;
|
|
642
|
+
}
|
|
643
|
+
function numberingFindings(payload) {
|
|
644
|
+
const errors = [], warnings = [];
|
|
645
|
+
const lists = ["items", "bullets"].filter((key) => Array.isArray(payload[key]));
|
|
646
|
+
if (Object.hasOwn(payload, "numbering")) {
|
|
647
|
+
if (!Object.hasOwn(payload, "items") && !Object.hasOwn(payload, "bullets")) {
|
|
648
|
+
errors.push({ key: "numbering", message: "numbering applies to 'items' or 'bullets'; this payload has neither", params: {} });
|
|
649
|
+
} else {
|
|
650
|
+
for (const key of lists) {
|
|
651
|
+
let numbers;
|
|
652
|
+
try {
|
|
653
|
+
numbers = listNumbers(payload[key], payload.numbering);
|
|
654
|
+
} catch {
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
const largest = numbers.reduce((max, number) => Math.max(max, number.value), 0);
|
|
658
|
+
if (largest > MAX_NUMBERING_VALUE) errors.push({ key, message: `numbering counts up to ${largest}, past the native limit of ${MAX_NUMBERING_VALUE}`, params: { largest, limit: MAX_NUMBERING_VALUE } });
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
} else {
|
|
662
|
+
for (const key of lists) {
|
|
663
|
+
if (payload[key].some((item) => isObject(item) && item.start !== void 0)) {
|
|
664
|
+
warnings.push({ key, message: `'start' on ${key} entries has no effect without a 'numbering' field on the payload`, params: {} });
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return { errors, warnings };
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// src/furniture-fields.ts
|
|
672
|
+
var DEFAULT_FURNITURE_DATE_FORMAT = "M/d/yyyy";
|
|
673
|
+
var DEFAULT_SLIDE_NUMBER_FORMAT = "{current}";
|
|
674
|
+
var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
675
|
+
var weekdays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
676
|
+
var tokenWidths = { y: [1, 2, 4], M: [1, 2, 3, 4], d: [1, 2], E: [1, 2, 3, 4] };
|
|
677
|
+
function parseFurnitureDateFormat(pattern) {
|
|
678
|
+
if (!pattern) return "dateFormat must not be empty.";
|
|
679
|
+
const tokens = [];
|
|
680
|
+
const literal = (text2) => {
|
|
681
|
+
const last = tokens[tokens.length - 1];
|
|
682
|
+
if (last?.kind === "literal") last.text += text2;
|
|
683
|
+
else tokens.push({ kind: "literal", text: text2 });
|
|
684
|
+
};
|
|
685
|
+
for (let index = 0; index < pattern.length; ) {
|
|
686
|
+
const character = pattern.charAt(index);
|
|
687
|
+
if (character === "'") {
|
|
688
|
+
if (pattern[index + 1] === "'") {
|
|
689
|
+
literal("'");
|
|
690
|
+
index += 2;
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
let end = index + 1, text2 = "";
|
|
694
|
+
for (; ; ) {
|
|
695
|
+
if (end >= pattern.length) return "dateFormat has an unterminated quoted literal.";
|
|
696
|
+
if (pattern[end] === "'") {
|
|
697
|
+
if (pattern[end + 1] === "'") {
|
|
698
|
+
text2 += "'";
|
|
699
|
+
end += 2;
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
break;
|
|
703
|
+
}
|
|
704
|
+
text2 += pattern[end];
|
|
705
|
+
end++;
|
|
706
|
+
}
|
|
707
|
+
literal(text2);
|
|
708
|
+
index = end + 1;
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
if (/[A-Za-z]/.test(character)) {
|
|
712
|
+
let end = index;
|
|
713
|
+
while (pattern[end] === character) end++;
|
|
714
|
+
const width = end - index;
|
|
715
|
+
if (!tokenWidths[character]?.includes(width)) return `dateFormat token '${pattern.slice(index, end)}' is not supported; use yyyy, yy, MMMM, MMM, MM, M, dd, d, EEEE or EEE, and quote literal letters.`;
|
|
716
|
+
tokens.push({ kind: "field", letter: character, width });
|
|
717
|
+
index = end;
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
literal(character);
|
|
721
|
+
index++;
|
|
722
|
+
}
|
|
723
|
+
return tokens;
|
|
724
|
+
}
|
|
725
|
+
function parseIsoDate(value) {
|
|
726
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
727
|
+
if (!match) return null;
|
|
728
|
+
const year = Number(match[1]), month = Number(match[2]), day = Number(match[3]);
|
|
729
|
+
const date = new Date(Date.UTC(2e3, month - 1, day));
|
|
730
|
+
date.setUTCFullYear(year);
|
|
731
|
+
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) return null;
|
|
732
|
+
return { year, month, day };
|
|
733
|
+
}
|
|
734
|
+
function formatFurnitureDate(iso, pattern) {
|
|
735
|
+
const date = parseIsoDate(iso);
|
|
736
|
+
if (!date) return { error: `'${iso}' is not an ISO YYYY-MM-DD calendar date.` };
|
|
737
|
+
const tokens = parseFurnitureDateFormat(pattern);
|
|
738
|
+
if (typeof tokens === "string") return { error: tokens };
|
|
739
|
+
const utc = new Date(Date.UTC(2e3, date.month - 1, date.day));
|
|
740
|
+
utc.setUTCFullYear(date.year);
|
|
741
|
+
const pad = (value, width) => String(value).padStart(width, "0");
|
|
742
|
+
let text2 = "";
|
|
743
|
+
for (const token of tokens) {
|
|
744
|
+
if (token.kind === "literal") {
|
|
745
|
+
text2 += token.text;
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
const { letter, width } = token;
|
|
749
|
+
if (letter === "y") text2 += width === 2 ? pad(date.year % 100, 2) : width === 4 ? pad(date.year, 4) : String(date.year);
|
|
750
|
+
else if (letter === "M") {
|
|
751
|
+
const name = months[date.month - 1] ?? "";
|
|
752
|
+
text2 += width >= 3 ? width === 4 ? name : name.slice(0, 3) : pad(date.month, width);
|
|
753
|
+
} else if (letter === "d") text2 += pad(date.day, width);
|
|
754
|
+
else {
|
|
755
|
+
const name = weekdays[utc.getUTCDay()] ?? "";
|
|
756
|
+
text2 += width === 4 ? name : name.slice(0, 3);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return { text: text2 };
|
|
760
|
+
}
|
|
761
|
+
function formatSlideNumber(format, current, total) {
|
|
762
|
+
if (!format.includes("{current}")) return { error: "slideNumberFormat must contain {current}." };
|
|
763
|
+
if (format.includes("{total}") && total === void 0) return { error: "slideNumberFormat uses {total}, which needs the rendered slide count." };
|
|
764
|
+
let text2 = "";
|
|
765
|
+
const fields2 = [];
|
|
766
|
+
for (const piece of format.split(/(\{current\}|\{total\})/)) {
|
|
767
|
+
if (piece === "{current}") {
|
|
768
|
+
const value = String(current);
|
|
769
|
+
fields2.push({ type: "slideNumber", start: text2.length, end: text2.length + value.length });
|
|
770
|
+
text2 += value;
|
|
771
|
+
} else if (piece === "{total}") text2 += String(total);
|
|
772
|
+
else text2 += piece;
|
|
773
|
+
}
|
|
774
|
+
return { text: text2, fields: fields2 };
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// src/color.ts
|
|
778
|
+
var schemeColorSlots = /* @__PURE__ */ new Set([
|
|
779
|
+
"accent1",
|
|
780
|
+
"accent2",
|
|
781
|
+
"accent3",
|
|
782
|
+
"accent4",
|
|
783
|
+
"accent5",
|
|
784
|
+
"accent6",
|
|
785
|
+
"dark1",
|
|
786
|
+
"dark2",
|
|
787
|
+
"light1",
|
|
788
|
+
"light2",
|
|
789
|
+
"hyperlink",
|
|
790
|
+
"followedHyperlink"
|
|
791
|
+
]);
|
|
792
|
+
var schemeColorRoles = /* @__PURE__ */ new Set([
|
|
793
|
+
"primary",
|
|
794
|
+
"secondary",
|
|
795
|
+
"accent",
|
|
796
|
+
"background",
|
|
797
|
+
"surface",
|
|
798
|
+
"text",
|
|
799
|
+
"textSecondary"
|
|
800
|
+
]);
|
|
801
|
+
var defaultRoleSlots = {
|
|
802
|
+
primary: "accent1",
|
|
803
|
+
secondary: "accent2",
|
|
804
|
+
accent: "accent3",
|
|
805
|
+
background: "light1",
|
|
806
|
+
surface: "light2",
|
|
807
|
+
text: "dark1",
|
|
808
|
+
textSecondary: "dark2"
|
|
809
|
+
};
|
|
810
|
+
function normalizeHexColor(value) {
|
|
811
|
+
if (typeof value !== "string") return void 0;
|
|
812
|
+
let hex = value.trim();
|
|
813
|
+
if (/^#[0-9a-f]{3}$/i.test(hex)) hex = `#${[...hex.slice(1)].map((char) => char + char).join("")}`;
|
|
814
|
+
if (/^#[0-9a-f]{6}([0-9a-f]{2})?$/i.test(hex)) return hex.length === 9 ? `#${hex.slice(1, 7).toUpperCase()}` : hex.toUpperCase();
|
|
815
|
+
return void 0;
|
|
816
|
+
}
|
|
817
|
+
function schemeSlot(colorScheme, slot, fallback) {
|
|
818
|
+
return normalizeHexColor(colorScheme[slot]) ?? fallback;
|
|
819
|
+
}
|
|
820
|
+
function resolveVariable(variables, id, fallback) {
|
|
821
|
+
const entry = variables[id];
|
|
822
|
+
if (typeof entry === "string") return normalizeHexColor(entry) ?? fallback;
|
|
823
|
+
if (entry && typeof entry === "object" && entry.type === "color") {
|
|
824
|
+
return normalizeHexColor(entry.value) ?? fallback;
|
|
825
|
+
}
|
|
826
|
+
return fallback;
|
|
827
|
+
}
|
|
828
|
+
function resolveColorRef(reference, options) {
|
|
829
|
+
const { colorScheme, roles, variables = {}, fallback } = options;
|
|
830
|
+
const literal = normalizeHexColor(reference);
|
|
831
|
+
if (literal) return literal;
|
|
832
|
+
if (reference.startsWith("var:")) {
|
|
833
|
+
const id = reference.slice("var:".length);
|
|
834
|
+
if (/^[a-z][a-z0-9-]*$/.test(id)) return resolveVariable(variables, id, fallback);
|
|
835
|
+
return fallback;
|
|
836
|
+
}
|
|
837
|
+
if (schemeColorRoles.has(reference)) {
|
|
838
|
+
const roleKey = reference;
|
|
839
|
+
const fromRoles = roles?.[roleKey];
|
|
840
|
+
if (fromRoles) return normalizeHexColor(fromRoles) ?? fallback;
|
|
841
|
+
const fromScheme = normalizeHexColor(colorScheme[reference]);
|
|
842
|
+
if (fromScheme) return fromScheme;
|
|
843
|
+
const slot = defaultRoleSlots[reference];
|
|
844
|
+
return slot ? schemeSlot(colorScheme, slot, fallback) : fallback;
|
|
845
|
+
}
|
|
846
|
+
if (schemeColorSlots.has(reference)) return schemeSlot(colorScheme, reference, fallback);
|
|
847
|
+
return fallback;
|
|
848
|
+
}
|
|
849
|
+
function luminance(value) {
|
|
850
|
+
if (typeof value !== "string") return void 0;
|
|
851
|
+
let hex = value.trim();
|
|
852
|
+
if (/^#[0-9a-f]{3}$/i.test(hex)) hex = `#${[...hex.slice(1)].map((c) => c + c).join("")}`;
|
|
853
|
+
if (/^#[0-9a-f]{6}ff$/i.test(hex)) hex = hex.slice(0, 7);
|
|
854
|
+
if (!/^#[0-9a-f]{6}$/i.test(hex)) return void 0;
|
|
855
|
+
return [0.2126, 0.7152, 0.0722].reduce((sum, weight, index) => {
|
|
856
|
+
const offset = index * 2 + 1;
|
|
857
|
+
const n = Number.parseInt(hex.slice(offset, offset + 2), 16) / 255;
|
|
858
|
+
return sum + weight * (n <= 0.04045 ? n / 12.92 : ((n + 0.055) / 1.055) ** 2.4);
|
|
859
|
+
}, 0);
|
|
860
|
+
}
|
|
861
|
+
function colorContrast(foreground, background) {
|
|
862
|
+
const a = luminance(foreground), b = luminance(background);
|
|
863
|
+
return a === void 0 || b === void 0 ? void 0 : (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);
|
|
864
|
+
}
|
|
865
|
+
function textColorForFill(fill, preferred) {
|
|
866
|
+
const background = luminance(fill), contrast = colorContrast(preferred, fill);
|
|
867
|
+
if (background === void 0 || contrast === void 0 || contrast >= 4.5) return preferred;
|
|
868
|
+
return (background + 0.05) / 0.05 >= 1.05 / (background + 0.05) ? "#000000" : "#FFFFFF";
|
|
869
|
+
}
|
|
870
|
+
function chartColorForFill(fill, preferred) {
|
|
871
|
+
const contrast = colorContrast(preferred, fill);
|
|
872
|
+
if (contrast === void 0 || contrast >= 3) return preferred;
|
|
873
|
+
let hex = preferred.trim();
|
|
874
|
+
if (hex.length === 4) hex = `#${[...hex.slice(1)].map((c) => c + c).join("")}`;
|
|
875
|
+
const channels = [1, 3, 5].map((offset) => Number.parseInt(hex.slice(offset, offset + 2), 16));
|
|
876
|
+
for (let step = 1; step <= 255; step++) {
|
|
877
|
+
const candidates = [0, 255].map((target) => {
|
|
878
|
+
const color = `#${channels.map((channel) => Math.round(channel + (target - channel) * step / 255).toString(16).padStart(2, "0")).join("")}`.toUpperCase();
|
|
879
|
+
return { color, contrast: colorContrast(color, fill) ?? 0 };
|
|
880
|
+
}).sort((a, b) => b.contrast - a.contrast);
|
|
881
|
+
const best = candidates[0];
|
|
882
|
+
if (best && best.contrast >= 3) return best.color;
|
|
883
|
+
}
|
|
884
|
+
return textColorForFill(fill, preferred);
|
|
885
|
+
}
|
|
886
|
+
var CHART_SERIES_MIN_LIGHTNESS_STEP = 11;
|
|
887
|
+
var CHART_SERIES_MIN_DIFFERENCE = 12;
|
|
888
|
+
var D65 = [0.95047, 1, 1.08883];
|
|
889
|
+
function opaqueChannels(value) {
|
|
890
|
+
if (luminance(value) === void 0) return void 0;
|
|
891
|
+
let hex = value.trim();
|
|
892
|
+
if (hex.length === 4) hex = `#${[...hex.slice(1)].map((c) => c + c).join("")}`;
|
|
893
|
+
return [1, 3, 5].map((offset) => Number.parseInt(hex.slice(offset, offset + 2), 16));
|
|
894
|
+
}
|
|
895
|
+
var toLinear = (channel) => {
|
|
896
|
+
const n = channel / 255;
|
|
897
|
+
return n <= 0.04045 ? n / 12.92 : ((n + 0.055) / 1.055) ** 2.4;
|
|
898
|
+
};
|
|
899
|
+
var fromLinear = (value) => 255 * (value <= 31308e-7 ? value * 12.92 : 1.055 * value ** (1 / 2.4) - 0.055);
|
|
900
|
+
function labOf(channels) {
|
|
901
|
+
const [r, g, b] = channels.map(toLinear);
|
|
902
|
+
const f = (t) => t > 216 / 24389 ? Math.cbrt(t) : (24389 / 27 * t + 16) / 116;
|
|
903
|
+
const fx = f((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / D65[0]), fy = f(0.2126729 * r + 0.7151522 * g + 0.072175 * b), fz = f((0.0193339 * r + 0.119192 * g + 0.9503041 * b) / D65[2]);
|
|
904
|
+
return [116 * fy - 16, 500 * (fx - fy), 200 * (fy - fz)];
|
|
905
|
+
}
|
|
906
|
+
function labToLinear([l, a, b]) {
|
|
907
|
+
const fy = (l + 16) / 116, fx = fy + a / 500, fz = fy - b / 200;
|
|
908
|
+
const inverse = (t) => t ** 3 > 216 / 24389 ? t ** 3 : (116 * t - 16) / (24389 / 27);
|
|
909
|
+
const x = inverse(fx) * D65[0], y = l > 8 ? fy ** 3 : l / (24389 / 27), z = inverse(fz) * D65[2];
|
|
910
|
+
const rgb = [
|
|
911
|
+
3.2404542 * x - 1.5371385 * y - 0.4985314 * z,
|
|
912
|
+
-0.969266 * x + 1.8760108 * y + 0.041556 * z,
|
|
913
|
+
0.0556434 * x - 0.2040259 * y + 1.0572252 * z
|
|
914
|
+
];
|
|
915
|
+
return rgb.every((channel) => channel >= -5e-4 && channel <= 1.0005) ? rgb : void 0;
|
|
916
|
+
}
|
|
917
|
+
var hexOfChannels = (channels) => `#${channels.map((channel) => Math.max(0, Math.min(255, Math.round(channel))).toString(16).padStart(2, "0")).join("").toUpperCase()}`;
|
|
918
|
+
function labWithLightness(l, a, b) {
|
|
919
|
+
const chroma = Math.hypot(a, b), hue = Math.atan2(b, a);
|
|
920
|
+
const at2 = (scale) => labToLinear([l, Math.cos(hue) * chroma * scale, Math.sin(hue) * chroma * scale]);
|
|
921
|
+
let linear = at2(1);
|
|
922
|
+
if (!linear) {
|
|
923
|
+
let low = 0, high = 1;
|
|
924
|
+
for (let step = 0; step < 24; step++) {
|
|
925
|
+
const mid = (low + high) / 2;
|
|
926
|
+
if (at2(mid)) low = mid;
|
|
927
|
+
else high = mid;
|
|
928
|
+
}
|
|
929
|
+
linear = at2(low) ?? [0, 0, 0];
|
|
930
|
+
}
|
|
931
|
+
return hexOfChannels(linear.map((channel) => fromLinear(Math.max(0, Math.min(1, channel)))));
|
|
932
|
+
}
|
|
933
|
+
function labDifference(a, b) {
|
|
934
|
+
return Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
|
|
935
|
+
}
|
|
936
|
+
var CHART_SERIES_SHORTFALL_WEIGHT = 8;
|
|
937
|
+
function chartPaletteForFill(fill, palette) {
|
|
938
|
+
const opaqueFill = colorContrast(fill, fill) !== void 0;
|
|
939
|
+
const preferredLab = palette.map((color) => {
|
|
940
|
+
const channels = opaqueChannels(color);
|
|
941
|
+
return channels ? labOf(channels) : void 0;
|
|
942
|
+
});
|
|
943
|
+
const adjusted = palette.map((color) => chartColorForFill(fill, color));
|
|
944
|
+
const kept = palette.map((color, index) => opaqueFill && preferredLab[index] !== void 0 && adjusted[index] === color);
|
|
945
|
+
const placed = palette.map((_, index) => kept[index] || !opaqueFill || !preferredLab[index] ? adjusted[index] : void 0);
|
|
946
|
+
const placedLab = placed.map((color) => {
|
|
947
|
+
const channels = color === void 0 ? void 0 : opaqueChannels(color);
|
|
948
|
+
return channels ? labOf(channels) : void 0;
|
|
949
|
+
});
|
|
950
|
+
palette.forEach((_, index) => {
|
|
951
|
+
const wanted = preferredLab[index], base = adjusted[index];
|
|
952
|
+
if (placed[index] !== void 0 || !wanted) return;
|
|
953
|
+
const baseLab = labOf(opaqueChannels(base));
|
|
954
|
+
const needs = palette.flatMap((_2, other) => {
|
|
955
|
+
const before = preferredLab[other], lab = placedLab[other];
|
|
956
|
+
return other !== index && before && lab && placed[other] !== void 0 ? [{ lab, adjacent: Math.abs(other - index) === 1, wide: Math.min(labDifference(before, wanted), CHART_SERIES_MIN_DIFFERENCE), step: Math.min(Math.abs(before[0] - wanted[0]), CHART_SERIES_MIN_LIGHTNESS_STEP) }] : [];
|
|
957
|
+
});
|
|
958
|
+
const shortfall = (candidate) => needs.reduce((worst, need) => {
|
|
959
|
+
const lightness = Math.abs(need.lab[0] - candidate[0]) - need.step, difference = labDifference(need.lab, candidate) - need.wide;
|
|
960
|
+
return need.adjacent ? { near: Math.min(worst.near, difference, lightness), far: worst.far } : { near: Math.min(worst.near, difference), far: Math.min(worst.far, lightness) };
|
|
961
|
+
}, { near: Infinity, far: Infinity });
|
|
962
|
+
const deficit = (value) => Math.max(0, -value - 1e-9);
|
|
963
|
+
const cost = (candidate) => candidate.moved + CHART_SERIES_SHORTFALL_WEIGHT * deficit(candidate.near) + deficit(candidate.far);
|
|
964
|
+
const start = shortfall(baseLab);
|
|
965
|
+
if (deficit(start.near) === 0 && deficit(start.far) === 0) {
|
|
966
|
+
placed[index] = base;
|
|
967
|
+
placedLab[index] = baseLab;
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
let best = { color: base, lab: baseLab, cost: cost({ moved: 0, ...start }), moved: 0 };
|
|
971
|
+
for (let step = 0; step <= 400; step++) {
|
|
972
|
+
const color = labWithLightness(step / 4, wanted[1], wanted[2]), contrast = colorContrast(color, fill);
|
|
973
|
+
if (contrast === void 0 || contrast < 3) continue;
|
|
974
|
+
const lab = labOf(opaqueChannels(color)), moved = Math.abs(lab[0] - baseLab[0]), total = cost({ moved, ...shortfall(lab) });
|
|
975
|
+
if (total < best.cost - 1e-9 || Math.abs(total - best.cost) <= 1e-9 && moved < best.moved) best = { color, lab, cost: total, moved };
|
|
976
|
+
}
|
|
977
|
+
placed[index] = best.color;
|
|
978
|
+
placedLab[index] = best.lab;
|
|
979
|
+
});
|
|
980
|
+
return placed;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// src/annotations.ts
|
|
984
|
+
var CITATION_MARKER_SCALE = 2 / 3;
|
|
985
|
+
var CITATION_MARKER_RAISE = 0.3;
|
|
986
|
+
var CAPTION_FONT_RATIO = 0.6;
|
|
987
|
+
var CAPTION_MAX_RATIO = 0.35;
|
|
988
|
+
var FOOTNOTE_MAX_RATIO = 0.35;
|
|
989
|
+
var CAPTIONABLE_FIELDS = Object.freeze(["image", "chart", "table", "video"]);
|
|
990
|
+
var record2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
991
|
+
var isRecord3 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
992
|
+
var round = (value) => Math.round(value * 1e6) / 1e6 || value;
|
|
993
|
+
var regionKey = /^(top|middle|bottom|left|center|right)([+:]|$)/;
|
|
994
|
+
var TEXT_FIELDS = ["text", "items", "bullets"];
|
|
995
|
+
function annotationText(value) {
|
|
996
|
+
if (typeof value === "string") return value;
|
|
997
|
+
if (Array.isArray(value)) return value.map((run) => typeof run === "string" ? run : record2(run).text ?? "").join("");
|
|
998
|
+
if (isRecord3(value) && value.text !== void 0) return annotationText(value.text);
|
|
999
|
+
return value === void 0 || value === null ? "" : String(value);
|
|
1000
|
+
}
|
|
1001
|
+
function captionSettings(value) {
|
|
1002
|
+
if (value === void 0 || value === null) return void 0;
|
|
1003
|
+
if (typeof value === "string" || Array.isArray(value)) return { text: value, position: "below", align: "left" };
|
|
1004
|
+
if (!isRecord3(value) || value.text === void 0) return void 0;
|
|
1005
|
+
return { text: value.text, position: value.position === "above" ? "above" : "below", align: value.align === "center" || value.align === "right" ? value.align : "left" };
|
|
1006
|
+
}
|
|
1007
|
+
function walkCitationRuns(slide, basePath, visit) {
|
|
1008
|
+
const runs = (value2, path) => {
|
|
1009
|
+
if (!Array.isArray(value2)) return;
|
|
1010
|
+
value2.forEach((run, index) => {
|
|
1011
|
+
if (!isRecord3(run) || typeof run.text !== "string") return;
|
|
1012
|
+
const cite = run.cite === void 0 ? [] : Array.isArray(run.cite) ? run.cite.filter((id) => typeof id === "string") : typeof run.cite === "string" ? [run.cite] : [];
|
|
1013
|
+
const footnote = typeof run.footnote === "string" || Array.isArray(run.footnote) ? run.footnote : void 0;
|
|
1014
|
+
if (!cite.length && footnote === void 0) return;
|
|
1015
|
+
visit({ path: `${path}.${index}`, run, cite, footnote });
|
|
1016
|
+
});
|
|
1017
|
+
};
|
|
1018
|
+
const list = (items, path) => {
|
|
1019
|
+
if (!Array.isArray(items)) return;
|
|
1020
|
+
items.forEach((item, index) => {
|
|
1021
|
+
if (Array.isArray(item)) runs(item, `${path}.${index}`);
|
|
1022
|
+
else if (isRecord3(item)) {
|
|
1023
|
+
runs(item.text, `${path}.${index}.text`);
|
|
1024
|
+
runs(item.description, `${path}.${index}.description`);
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1027
|
+
};
|
|
1028
|
+
const host = (node, path, depth) => {
|
|
1029
|
+
const payload = record2(node);
|
|
1030
|
+
if (Array.isArray(payload.blocks)) {
|
|
1031
|
+
if (depth > 32) return;
|
|
1032
|
+
payload.blocks.forEach((block, index) => {
|
|
1033
|
+
host(block, `${path}.blocks.${index}`, depth + 1);
|
|
1034
|
+
});
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
for (const field of TEXT_FIELDS) {
|
|
1038
|
+
if (payload[field] === void 0) continue;
|
|
1039
|
+
if (field === "text") runs(payload.text, `${path}.text`);
|
|
1040
|
+
else list(payload[field], `${path}.${field}`);
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1043
|
+
const value = record2(slide);
|
|
1044
|
+
const regions = Object.keys(value).filter((key) => regionKey.test(key)).sort();
|
|
1045
|
+
if (regions.length) {
|
|
1046
|
+
for (const key of regions) host(value[key], `${basePath}.${key}`, 0);
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
if (Array.isArray(value.blocks)) {
|
|
1050
|
+
value.blocks.forEach((block, index) => {
|
|
1051
|
+
host(block, `${basePath}.blocks.${index}`, 0);
|
|
1052
|
+
});
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
host(value, basePath, 0);
|
|
1056
|
+
}
|
|
1057
|
+
var citationMarkerText = (numbers) => numbers.join(",");
|
|
1058
|
+
function referenceList(presentation) {
|
|
1059
|
+
const list = record2(presentation).references;
|
|
1060
|
+
return Array.isArray(list) ? list.filter((item) => isRecord3(item) && typeof item.id === "string") : [];
|
|
1061
|
+
}
|
|
1062
|
+
function numberSlide(slide, slideIndex, references, numbering) {
|
|
1063
|
+
const markers = /* @__PURE__ */ new Map(), marked = [], used = /* @__PURE__ */ new Map();
|
|
1064
|
+
walkCitationRuns(slide, `slides.${slideIndex}`, (entry) => {
|
|
1065
|
+
const numbers = [];
|
|
1066
|
+
for (const id of entry.cite) {
|
|
1067
|
+
let note = numbering.byId.get(id);
|
|
1068
|
+
if (!note) {
|
|
1069
|
+
const index = references.findIndex((item) => item.id === id), reference = references[index];
|
|
1070
|
+
note = { number: numbering.notes.length + 1, kind: "reference", id, text: reference?.text ?? id, sourcePath: index >= 0 ? `references.${index}` : entry.path, resolved: !!reference, ...typeof reference?.url === "string" ? { url: reference.url } : {} };
|
|
1071
|
+
numbering.notes.push(note);
|
|
1072
|
+
numbering.byId.set(id, note);
|
|
1073
|
+
}
|
|
1074
|
+
if (!numbers.includes(note.number)) numbers.push(note.number);
|
|
1075
|
+
used.set(note.number, note);
|
|
1076
|
+
}
|
|
1077
|
+
if (entry.footnote !== void 0) {
|
|
1078
|
+
const note = { number: numbering.notes.length + 1, kind: "footnote", text: entry.footnote, sourcePath: entry.path, resolved: true };
|
|
1079
|
+
numbering.notes.push(note);
|
|
1080
|
+
numbers.push(note.number);
|
|
1081
|
+
used.set(note.number, note);
|
|
1082
|
+
}
|
|
1083
|
+
const text2 = citationMarkerText(numbers);
|
|
1084
|
+
markers.set(entry.path, text2);
|
|
1085
|
+
marked.push({ path: entry.path, numbers, text: text2 });
|
|
1086
|
+
});
|
|
1087
|
+
if (!marked.length) return void 0;
|
|
1088
|
+
return { markers, marked, notes: [...used.values()].sort((a, b) => a.number - b.number) };
|
|
1089
|
+
}
|
|
1090
|
+
function collectCitations(presentation) {
|
|
1091
|
+
const references = referenceList(presentation), numbering = { notes: [], byId: /* @__PURE__ */ new Map() }, slides = /* @__PURE__ */ new Map();
|
|
1092
|
+
const list = record2(presentation).slides;
|
|
1093
|
+
if (Array.isArray(list)) list.forEach((slide, index) => {
|
|
1094
|
+
const result = numberSlide(slide, index, references, numbering);
|
|
1095
|
+
if (result) slides.set(index, result);
|
|
1096
|
+
});
|
|
1097
|
+
const cited = new Set(numbering.notes.filter((note) => note.kind === "reference").map((note) => note.id));
|
|
1098
|
+
return { notes: numbering.notes, references: numbering.notes.filter((note) => note.kind === "reference"), slides, unused: references.map((item) => item.id).filter((id) => !cited.has(id)) };
|
|
1099
|
+
}
|
|
1100
|
+
function slideCitations(slide, slideIndex, presentation) {
|
|
1101
|
+
const references = referenceList(presentation), numbering = { notes: [], byId: /* @__PURE__ */ new Map() };
|
|
1102
|
+
const list = record2(presentation).slides;
|
|
1103
|
+
if (Array.isArray(list)) for (let index = 0; index < Math.min(slideIndex, list.length); index += 1) numberSlide(list[index], index, references, numbering);
|
|
1104
|
+
return numberSlide(slide, slideIndex, references, numbering);
|
|
1105
|
+
}
|
|
1106
|
+
var fitHeight = (fit) => Math.max(fit.placement?.height ?? 0, "richLines" in fit ? fit.height : fit.lines.length * fit.lineHeight);
|
|
1107
|
+
function layoutCaption(value, box, path, options) {
|
|
1108
|
+
const settings = captionSettings(value);
|
|
1109
|
+
if (!settings) return void 0;
|
|
1110
|
+
const fontSize = Math.max(options.minFontSize, CAPTION_FONT_RATIO * 25 * options.scale), gap = fontSize * 0.4;
|
|
1111
|
+
const maxHeight = Math.max(fontSize * 1.22, box.height * CAPTION_MAX_RATIO);
|
|
1112
|
+
const diagnostics = [];
|
|
1113
|
+
const probe = options.fit(settings.text, { x: box.x, y: box.y, width: box.width, height: maxHeight }, fontSize, fontSize, path, settings.align);
|
|
1114
|
+
const height = Math.min(maxHeight, Math.max(fontSize * 1.22, fitHeight(probe)));
|
|
1115
|
+
const captionBox = { x: box.x, y: settings.position === "above" ? box.y : box.y + box.height - height, width: box.width, height };
|
|
1116
|
+
const fit = options.fit(settings.text, captionBox, fontSize, fontSize, path, settings.align);
|
|
1117
|
+
const overflow = fit.overflow || fitHeight(fit) > height + 0.01;
|
|
1118
|
+
if (overflow) diagnostics.push({ code: "text-overflow", path, message: "The caption does not fit its band at the minimum font size; shorten it or give the block more space." });
|
|
1119
|
+
const mediaHeight = Math.max(options.scale, box.height - height - gap);
|
|
1120
|
+
const mediaBox = { x: box.x, y: settings.position === "above" ? box.y + height + gap : box.y, width: box.width, height: mediaHeight };
|
|
1121
|
+
for (const target of [captionBox, mediaBox]) for (const key of ["x", "y", "width", "height"]) target[key] = round(target[key]);
|
|
1122
|
+
return { path, value, text: settings.text, position: settings.position, alignment: settings.align, box: captionBox, mediaBox, fit, textStyle: options.textStyle(path), fontSize, overflow, diagnostics };
|
|
1123
|
+
}
|
|
1124
|
+
function layoutFootnotes(citations, options) {
|
|
1125
|
+
const fontSize = Math.max(options.minFontSize, 13 * options.scale), thickness = Math.max(1, round(options.scale));
|
|
1126
|
+
const gap = fontSize * 0.5, spacing = fontSize * 0.25, diagnostics = [];
|
|
1127
|
+
const width = Math.max(options.scale, options.width);
|
|
1128
|
+
const measured = citations.notes.map((note) => {
|
|
1129
|
+
const value = typeof note.text === "string" ? `${note.number} ${note.text}` : [`${note.number} `, ...note.text];
|
|
1130
|
+
const probe = options.fit(value, { x: options.x, y: 0, width, height: Number.MAX_SAFE_INTEGER }, fontSize, fontSize, note.sourcePath, "left");
|
|
1131
|
+
return { note, value, height: Math.max(fontSize * 1.22, fitHeight(probe)) };
|
|
1132
|
+
});
|
|
1133
|
+
const total = thickness + gap + measured.reduce((sum, item) => sum + item.height, 0) + spacing * Math.max(0, measured.length - 1);
|
|
1134
|
+
const height = round(Math.min(total, Math.max(fontSize * 1.22 + thickness + gap, options.maxHeight)));
|
|
1135
|
+
const top = round(options.bottom - height), box = { x: round(options.x), y: top, width: round(width), height };
|
|
1136
|
+
let cursor = top + thickness + gap, overflow = false;
|
|
1137
|
+
const entries = measured.map((item, index) => {
|
|
1138
|
+
const entryBox = { x: box.x, y: round(cursor), width: box.width, height: round(item.height) };
|
|
1139
|
+
const fit = options.fit(item.value, entryBox, fontSize, fontSize, item.note.sourcePath, "left");
|
|
1140
|
+
const exceeds = fit.overflow || entryBox.y + entryBox.height > options.bottom + 0.01;
|
|
1141
|
+
if (exceeds) {
|
|
1142
|
+
overflow = true;
|
|
1143
|
+
diagnostics.push({ code: "text-overflow", path: item.note.sourcePath, message: `Footnote ${item.note.number} does not fit the slide's footnote area; shorten the note or split the slide.` });
|
|
1144
|
+
}
|
|
1145
|
+
cursor += item.height + (index < measured.length - 1 ? spacing : 0);
|
|
1146
|
+
return { number: item.note.number, kind: item.note.kind, ...item.note.id !== void 0 ? { id: item.note.id } : {}, sourcePath: item.note.sourcePath, value: item.value, text: item.note.text, box: entryBox, fit, textStyle: options.textStyle(item.note.sourcePath), ...item.note.url !== void 0 ? { url: item.note.url } : {}, overflow: exceeds };
|
|
1147
|
+
});
|
|
1148
|
+
for (const note of citations.notes) if (!note.resolved) diagnostics.push({ code: "unresolved-content", path: note.sourcePath, message: `Reference '${note.id}' is not in the presentation's references list; its id is listed instead.` });
|
|
1149
|
+
return { algorithm: "footnote-area-v1", path: options.path, box, rule: { x: box.x, y: top, width: box.width, thickness }, entries, markers: citations.marked, fontSize, overflow, diagnostics };
|
|
1150
|
+
}
|
|
1151
|
+
function referencesSlide(presentation, options = {}) {
|
|
1152
|
+
const title = options.title ?? "References";
|
|
1153
|
+
const { references } = collectCitations(presentation);
|
|
1154
|
+
if (!references.length) return { title };
|
|
1155
|
+
const items = references.map((note) => {
|
|
1156
|
+
const label = `${note.number}. `;
|
|
1157
|
+
if (typeof note.text === "string" && note.url === void 0) return `${label}${note.text}`;
|
|
1158
|
+
const runs = [label, ...typeof note.text === "string" ? [note.text] : note.text];
|
|
1159
|
+
if (note.url !== void 0) runs.push(" ", { text: note.url, link: note.url });
|
|
1160
|
+
return runs;
|
|
1161
|
+
});
|
|
1162
|
+
return { title, items };
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
// src/generated/catalog-ids.ts
|
|
1166
|
+
var catalogIds = {
|
|
1167
|
+
audiences: [
|
|
1168
|
+
"executives",
|
|
1169
|
+
"board",
|
|
1170
|
+
"engineering-team",
|
|
1171
|
+
"investors",
|
|
1172
|
+
"customers",
|
|
1173
|
+
"sales-team",
|
|
1174
|
+
"marketing-team",
|
|
1175
|
+
"all-hands",
|
|
1176
|
+
"candidates",
|
|
1177
|
+
"regulators",
|
|
1178
|
+
"executive",
|
|
1179
|
+
"investor",
|
|
1180
|
+
"technical",
|
|
1181
|
+
"sales",
|
|
1182
|
+
"marketing",
|
|
1183
|
+
"academic",
|
|
1184
|
+
"internal-team",
|
|
1185
|
+
"customer",
|
|
1186
|
+
"general-public",
|
|
1187
|
+
"media",
|
|
1188
|
+
"partner",
|
|
1189
|
+
"regulatory"
|
|
1190
|
+
],
|
|
1191
|
+
purposes: [
|
|
1192
|
+
"inform",
|
|
1193
|
+
"decide",
|
|
1194
|
+
"align",
|
|
1195
|
+
"persuade",
|
|
1196
|
+
"educate",
|
|
1197
|
+
"report",
|
|
1198
|
+
"pitch",
|
|
1199
|
+
"sell",
|
|
1200
|
+
"plan"
|
|
1201
|
+
],
|
|
1202
|
+
tones: [
|
|
1203
|
+
"formal",
|
|
1204
|
+
"casual",
|
|
1205
|
+
"inspirational",
|
|
1206
|
+
"technical",
|
|
1207
|
+
"persuasive",
|
|
1208
|
+
"authoritative",
|
|
1209
|
+
"conversational"
|
|
1210
|
+
],
|
|
1211
|
+
themes: [
|
|
1212
|
+
"minimal",
|
|
1213
|
+
"classic",
|
|
1214
|
+
"dark",
|
|
1215
|
+
"bold"
|
|
1216
|
+
],
|
|
1217
|
+
layouts: [
|
|
1218
|
+
"blank",
|
|
1219
|
+
"chart-1x",
|
|
1220
|
+
"chart-2x",
|
|
1221
|
+
"chart-3x",
|
|
1222
|
+
"code-1x",
|
|
1223
|
+
"image-1x",
|
|
1224
|
+
"image-2x",
|
|
1225
|
+
"image-3x",
|
|
1226
|
+
"image-bleed",
|
|
1227
|
+
"list-1x",
|
|
1228
|
+
"list-2x",
|
|
1229
|
+
"list-3x",
|
|
1230
|
+
"list-4x",
|
|
1231
|
+
"list-5x",
|
|
1232
|
+
"list-6x",
|
|
1233
|
+
"media-1x",
|
|
1234
|
+
"number-1x",
|
|
1235
|
+
"number-2x",
|
|
1236
|
+
"number-3x",
|
|
1237
|
+
"number-4x",
|
|
1238
|
+
"number-5x",
|
|
1239
|
+
"number-6x",
|
|
1240
|
+
"quote-1x",
|
|
1241
|
+
"table-1x",
|
|
1242
|
+
"text-1x",
|
|
1243
|
+
"text-2x",
|
|
1244
|
+
"text-3x",
|
|
1245
|
+
"timeline-1x",
|
|
1246
|
+
"title",
|
|
1247
|
+
"title-subtitle",
|
|
1248
|
+
"title-slide",
|
|
1249
|
+
"two-column",
|
|
1250
|
+
"image-focus",
|
|
1251
|
+
"data-visualization",
|
|
1252
|
+
"quote-highlight",
|
|
1253
|
+
"section-divider",
|
|
1254
|
+
"agenda",
|
|
1255
|
+
"thank-you-cta",
|
|
1256
|
+
"bullet-list",
|
|
1257
|
+
"three-column",
|
|
1258
|
+
"icon-grid",
|
|
1259
|
+
"faq",
|
|
1260
|
+
"stats-metrics",
|
|
1261
|
+
"comparison",
|
|
1262
|
+
"swot-analysis",
|
|
1263
|
+
"matrix-quadrant",
|
|
1264
|
+
"funnel-pyramid",
|
|
1265
|
+
"timeline",
|
|
1266
|
+
"process-flow",
|
|
1267
|
+
"photo-grid",
|
|
1268
|
+
"before-after",
|
|
1269
|
+
"testimonial-grid",
|
|
1270
|
+
"team-bio",
|
|
1271
|
+
"pricing-table",
|
|
1272
|
+
"roadmap",
|
|
1273
|
+
"product-feature",
|
|
1274
|
+
"executive-summary",
|
|
1275
|
+
"key-takeaway",
|
|
1276
|
+
"chapter-recap",
|
|
1277
|
+
"qa-discussion",
|
|
1278
|
+
"single-message-statement",
|
|
1279
|
+
"one-column-explainer",
|
|
1280
|
+
"case-study",
|
|
1281
|
+
"pros-cons",
|
|
1282
|
+
"feature-comparison-matrix",
|
|
1283
|
+
"venn-overlap",
|
|
1284
|
+
"decision-tree",
|
|
1285
|
+
"kpi-scorecard",
|
|
1286
|
+
"dashboard",
|
|
1287
|
+
"waterfall-bridge",
|
|
1288
|
+
"benchmark-target-table",
|
|
1289
|
+
"heatmap-cohort",
|
|
1290
|
+
"risk-heatmap",
|
|
1291
|
+
"forecast-scenario",
|
|
1292
|
+
"swimlane-process",
|
|
1293
|
+
"gantt-project-plan",
|
|
1294
|
+
"circular-lifecycle",
|
|
1295
|
+
"customer-journey",
|
|
1296
|
+
"org-chart",
|
|
1297
|
+
"capability-map",
|
|
1298
|
+
"logic-tree",
|
|
1299
|
+
"hub-and-spoke",
|
|
1300
|
+
"architecture-stack",
|
|
1301
|
+
"ecosystem-map",
|
|
1302
|
+
"network-dependency-map",
|
|
1303
|
+
"wheel-radial",
|
|
1304
|
+
"geographic-map",
|
|
1305
|
+
"screenshot-walkthrough",
|
|
1306
|
+
"market-size",
|
|
1307
|
+
"persona-grid",
|
|
1308
|
+
"competitive-landscape",
|
|
1309
|
+
"business-model-canvas",
|
|
1310
|
+
"customer-logos",
|
|
1311
|
+
"executive-decision",
|
|
1312
|
+
"status-rag",
|
|
1313
|
+
"raci-matrix",
|
|
1314
|
+
"action-plan",
|
|
1315
|
+
"assumptions-dependencies",
|
|
1316
|
+
"appendix-index",
|
|
1317
|
+
"detailed-data-table"
|
|
1318
|
+
],
|
|
1319
|
+
chartTypes: [
|
|
1320
|
+
"100pct-stacked-area-3x",
|
|
1321
|
+
"100pct-stacked-bar-3x",
|
|
1322
|
+
"100pct-stacked-column-3x",
|
|
1323
|
+
"area",
|
|
1324
|
+
"bar",
|
|
1325
|
+
"box-and-whisker",
|
|
1326
|
+
"column",
|
|
1327
|
+
"doughnut",
|
|
1328
|
+
"filled-radar",
|
|
1329
|
+
"funnel",
|
|
1330
|
+
"histogram",
|
|
1331
|
+
"line",
|
|
1332
|
+
"line-with-markers",
|
|
1333
|
+
"pareto",
|
|
1334
|
+
"pie",
|
|
1335
|
+
"radar",
|
|
1336
|
+
"radar-with-markers",
|
|
1337
|
+
"scatter",
|
|
1338
|
+
"stacked-area-3x",
|
|
1339
|
+
"stacked-bar-3x",
|
|
1340
|
+
"stacked-column-3x",
|
|
1341
|
+
"stacked-line-3x",
|
|
1342
|
+
"stacked-line-with-markers-3x",
|
|
1343
|
+
"treemap",
|
|
1344
|
+
"waterfall",
|
|
1345
|
+
"world",
|
|
1346
|
+
"100pct-bullet-bar",
|
|
1347
|
+
"100pct-bullet-bar-2x",
|
|
1348
|
+
"100pct-bullet-bar-3x",
|
|
1349
|
+
"100pct-bullet-column",
|
|
1350
|
+
"100pct-bullet-column-2x",
|
|
1351
|
+
"100pct-bullet-column-3x",
|
|
1352
|
+
"100pct-progress-bar",
|
|
1353
|
+
"100pct-stacked-area-2x",
|
|
1354
|
+
"100pct-stacked-bar-2x",
|
|
1355
|
+
"100pct-stacked-column-2x",
|
|
1356
|
+
"australia",
|
|
1357
|
+
"box-and-whisker-2x",
|
|
1358
|
+
"box-and-whisker-3x",
|
|
1359
|
+
"bullet-bar",
|
|
1360
|
+
"bullet-bar-2x",
|
|
1361
|
+
"bullet-bar-3x",
|
|
1362
|
+
"bullet-column",
|
|
1363
|
+
"bullet-column-2x",
|
|
1364
|
+
"bullet-column-3x",
|
|
1365
|
+
"canada",
|
|
1366
|
+
"clustered-bar-2x",
|
|
1367
|
+
"clustered-column",
|
|
1368
|
+
"dot-plot",
|
|
1369
|
+
"dot-plot-2x",
|
|
1370
|
+
"dot-plot-3x",
|
|
1371
|
+
"dot-plot-4x",
|
|
1372
|
+
"dot-plot-5x",
|
|
1373
|
+
"dot-plot-6x",
|
|
1374
|
+
"dumbbell",
|
|
1375
|
+
"line-2x",
|
|
1376
|
+
"line-3x",
|
|
1377
|
+
"line-with-high-low",
|
|
1378
|
+
"line-with-high-low-and-markers",
|
|
1379
|
+
"line-with-markers-2x",
|
|
1380
|
+
"line-with-markers-3x",
|
|
1381
|
+
"sparkline",
|
|
1382
|
+
"sparkline-2x",
|
|
1383
|
+
"sparkline-3x",
|
|
1384
|
+
"sparkline-4x",
|
|
1385
|
+
"sparkline-5x",
|
|
1386
|
+
"sparkline-6x",
|
|
1387
|
+
"stacked-area-2x",
|
|
1388
|
+
"stacked-bar-2x",
|
|
1389
|
+
"stacked-column-2x",
|
|
1390
|
+
"stacked-line-2x",
|
|
1391
|
+
"stacked-line-with-markers-2x",
|
|
1392
|
+
"treemap-2x",
|
|
1393
|
+
"treemap-3x",
|
|
1394
|
+
"united-kingdom",
|
|
1395
|
+
"united-states"
|
|
1396
|
+
],
|
|
1397
|
+
narratives: [
|
|
1398
|
+
"problem-solution",
|
|
1399
|
+
"scqa",
|
|
1400
|
+
"pyramid-principle",
|
|
1401
|
+
"situation-complication-resolution",
|
|
1402
|
+
"what-so-what-now-what",
|
|
1403
|
+
"strategic-narrative",
|
|
1404
|
+
"golden-circle",
|
|
1405
|
+
"conference-talk",
|
|
1406
|
+
"transformation-arc",
|
|
1407
|
+
"pitch-deck",
|
|
1408
|
+
"early-startup-pitch",
|
|
1409
|
+
"venture-pitch",
|
|
1410
|
+
"persuasive-sales",
|
|
1411
|
+
"sparkline",
|
|
1412
|
+
"company-intro",
|
|
1413
|
+
"marketing-strategy",
|
|
1414
|
+
"product-launch",
|
|
1415
|
+
"strategic-advisory",
|
|
1416
|
+
"challenge-resolution",
|
|
1417
|
+
"project-proposal",
|
|
1418
|
+
"vision-roadmap",
|
|
1419
|
+
"failure-analysis",
|
|
1420
|
+
"star-method",
|
|
1421
|
+
"classic-story",
|
|
1422
|
+
"heros-journey",
|
|
1423
|
+
"business-narrative",
|
|
1424
|
+
"change-story",
|
|
1425
|
+
"rags-to-riches",
|
|
1426
|
+
"underdog-victory",
|
|
1427
|
+
"survival-story",
|
|
1428
|
+
"educate",
|
|
1429
|
+
"persuade",
|
|
1430
|
+
"reveal",
|
|
1431
|
+
"justice",
|
|
1432
|
+
"innovation",
|
|
1433
|
+
"focus",
|
|
1434
|
+
"qbr",
|
|
1435
|
+
"business-review",
|
|
1436
|
+
"board-meeting",
|
|
1437
|
+
"weekly-progress",
|
|
1438
|
+
"status-update",
|
|
1439
|
+
"performance-review",
|
|
1440
|
+
"survey-analysis",
|
|
1441
|
+
"trend-analysis",
|
|
1442
|
+
"data-story",
|
|
1443
|
+
"employee-review",
|
|
1444
|
+
"performance-improvement-plan",
|
|
1445
|
+
"capacity-planning"
|
|
1446
|
+
],
|
|
1447
|
+
socialPlatforms: [
|
|
1448
|
+
"linkedin",
|
|
1449
|
+
"x",
|
|
1450
|
+
"github",
|
|
1451
|
+
"youtube",
|
|
1452
|
+
"instagram",
|
|
1453
|
+
"facebook",
|
|
1454
|
+
"tiktok",
|
|
1455
|
+
"threads",
|
|
1456
|
+
"mastodon",
|
|
1457
|
+
"bluesky"
|
|
1458
|
+
],
|
|
1459
|
+
languages: [
|
|
1460
|
+
"afrikaans",
|
|
1461
|
+
"albanian",
|
|
1462
|
+
"amharic",
|
|
1463
|
+
"arabic",
|
|
1464
|
+
"armenian",
|
|
1465
|
+
"aymara",
|
|
1466
|
+
"azerbaijani",
|
|
1467
|
+
"bengali",
|
|
1468
|
+
"berber-latin",
|
|
1469
|
+
"bosnian-latin",
|
|
1470
|
+
"bulgarian",
|
|
1471
|
+
"catalan",
|
|
1472
|
+
"cebuano",
|
|
1473
|
+
"chinese-simplified",
|
|
1474
|
+
"chinese-traditional",
|
|
1475
|
+
"chittagonian",
|
|
1476
|
+
"croatian",
|
|
1477
|
+
"czech",
|
|
1478
|
+
"danish",
|
|
1479
|
+
"dutch",
|
|
1480
|
+
"english",
|
|
1481
|
+
"english-au",
|
|
1482
|
+
"english-ca",
|
|
1483
|
+
"english-gb",
|
|
1484
|
+
"english-in",
|
|
1485
|
+
"english-us",
|
|
1486
|
+
"estonian",
|
|
1487
|
+
"filipino",
|
|
1488
|
+
"finnish",
|
|
1489
|
+
"french",
|
|
1490
|
+
"fulfulde",
|
|
1491
|
+
"galician",
|
|
1492
|
+
"georgian",
|
|
1493
|
+
"german",
|
|
1494
|
+
"greek",
|
|
1495
|
+
"gujarati",
|
|
1496
|
+
"hausa",
|
|
1497
|
+
"hebrew",
|
|
1498
|
+
"hindi",
|
|
1499
|
+
"hungarian",
|
|
1500
|
+
"igbo",
|
|
1501
|
+
"indonesian",
|
|
1502
|
+
"italian",
|
|
1503
|
+
"japanese",
|
|
1504
|
+
"kannada",
|
|
1505
|
+
"kazakh",
|
|
1506
|
+
"khmer",
|
|
1507
|
+
"kinyarwanda",
|
|
1508
|
+
"korean",
|
|
1509
|
+
"kurmanji",
|
|
1510
|
+
"latvian",
|
|
1511
|
+
"lithuanian",
|
|
1512
|
+
"macedonian",
|
|
1513
|
+
"malagasy",
|
|
1514
|
+
"malay",
|
|
1515
|
+
"malayalam",
|
|
1516
|
+
"maori",
|
|
1517
|
+
"marathi",
|
|
1518
|
+
"mongolian",
|
|
1519
|
+
"nepali",
|
|
1520
|
+
"norwegian",
|
|
1521
|
+
"odia",
|
|
1522
|
+
"oromo",
|
|
1523
|
+
"pashto",
|
|
1524
|
+
"persian",
|
|
1525
|
+
"polish",
|
|
1526
|
+
"portuguese",
|
|
1527
|
+
"punjabi-gurmukhi",
|
|
1528
|
+
"punjabi-shahmukhi",
|
|
1529
|
+
"romanian",
|
|
1530
|
+
"russian",
|
|
1531
|
+
"serbian-cyrillic",
|
|
1532
|
+
"serbian-latin",
|
|
1533
|
+
"shona",
|
|
1534
|
+
"slovak",
|
|
1535
|
+
"slovenian",
|
|
1536
|
+
"somali",
|
|
1537
|
+
"spanish",
|
|
1538
|
+
"swahili",
|
|
1539
|
+
"swedish",
|
|
1540
|
+
"tagalog",
|
|
1541
|
+
"tajik",
|
|
1542
|
+
"tamil",
|
|
1543
|
+
"telugu",
|
|
1544
|
+
"thai",
|
|
1545
|
+
"turkish",
|
|
1546
|
+
"ukrainian",
|
|
1547
|
+
"urdu",
|
|
1548
|
+
"uzbek-latin",
|
|
1549
|
+
"vietnamese-quoc-ngu",
|
|
1550
|
+
"xhosa",
|
|
1551
|
+
"yoruba",
|
|
1552
|
+
"zulu"
|
|
1553
|
+
],
|
|
1554
|
+
colorSchemes: [
|
|
1555
|
+
"black-and-white",
|
|
1556
|
+
"bold-red",
|
|
1557
|
+
"boost",
|
|
1558
|
+
"burnt-orange",
|
|
1559
|
+
"cool-horizon",
|
|
1560
|
+
"corporate-blue",
|
|
1561
|
+
"deep-purple",
|
|
1562
|
+
"forest-green",
|
|
1563
|
+
"golden-yellow",
|
|
1564
|
+
"luxury",
|
|
1565
|
+
"pastel-red",
|
|
1566
|
+
"slate-gray",
|
|
1567
|
+
"steel-blue",
|
|
1568
|
+
"vibes"
|
|
1569
|
+
],
|
|
1570
|
+
fontSchemes: [
|
|
1571
|
+
"consolas",
|
|
1572
|
+
"courier-new",
|
|
1573
|
+
"arial",
|
|
1574
|
+
"calibri",
|
|
1575
|
+
"impact",
|
|
1576
|
+
"lucida-sans",
|
|
1577
|
+
"segoe-ui",
|
|
1578
|
+
"segoe-ui-light",
|
|
1579
|
+
"tahoma",
|
|
1580
|
+
"trebuchet-ms",
|
|
1581
|
+
"verdana",
|
|
1582
|
+
"bookman",
|
|
1583
|
+
"century-schoolbook",
|
|
1584
|
+
"constantia",
|
|
1585
|
+
"garamond",
|
|
1586
|
+
"rockwell",
|
|
1587
|
+
"times-new-roman",
|
|
1588
|
+
"aptos",
|
|
1589
|
+
"grandview",
|
|
1590
|
+
"seaford",
|
|
1591
|
+
"skeena",
|
|
1592
|
+
"tenorite",
|
|
1593
|
+
"georgia",
|
|
1594
|
+
"microsoft-yahei",
|
|
1595
|
+
"simsun",
|
|
1596
|
+
"fangsong",
|
|
1597
|
+
"yu-gothic",
|
|
1598
|
+
"pmingliu",
|
|
1599
|
+
"mingliu",
|
|
1600
|
+
"ms-mincho",
|
|
1601
|
+
"malgun-gothic",
|
|
1602
|
+
"batang",
|
|
1603
|
+
"gungsuh",
|
|
1604
|
+
"mangal",
|
|
1605
|
+
"nirmala-ui",
|
|
1606
|
+
"latha",
|
|
1607
|
+
"david",
|
|
1608
|
+
"miriam",
|
|
1609
|
+
"gisha",
|
|
1610
|
+
"vrinda",
|
|
1611
|
+
"shruti",
|
|
1612
|
+
"tunga",
|
|
1613
|
+
"kartika",
|
|
1614
|
+
"kalinga",
|
|
1615
|
+
"raavi",
|
|
1616
|
+
"gautami",
|
|
1617
|
+
"arabic-typesetting",
|
|
1618
|
+
"microsoft-jhenghei",
|
|
1619
|
+
"meiryo",
|
|
1620
|
+
"angsana-new",
|
|
1621
|
+
"aparajita",
|
|
1622
|
+
"dilleniaupc",
|
|
1623
|
+
"sakkal-majalla",
|
|
1624
|
+
"shonar-bangla",
|
|
1625
|
+
"sylfaen",
|
|
1626
|
+
"traditional-arabic",
|
|
1627
|
+
"montserrat",
|
|
1628
|
+
"open-sans",
|
|
1629
|
+
"poppins",
|
|
1630
|
+
"pt-serif",
|
|
1631
|
+
"raleway",
|
|
1632
|
+
"roboto",
|
|
1633
|
+
"noto-sans-arabic",
|
|
1634
|
+
"noto-sans-armenian",
|
|
1635
|
+
"noto-sans-bengali",
|
|
1636
|
+
"noto-sans-devangari",
|
|
1637
|
+
"noto-sans-georgian",
|
|
1638
|
+
"noto-sans-gujarati",
|
|
1639
|
+
"noto-sans-gurmukhi",
|
|
1640
|
+
"noto-sans-hebrew",
|
|
1641
|
+
"noto-sans-jp",
|
|
1642
|
+
"noto-sans-kannada",
|
|
1643
|
+
"noto-sans-kr",
|
|
1644
|
+
"noto-sans-malayalam",
|
|
1645
|
+
"noto-sans-mongolian",
|
|
1646
|
+
"noto-sans-oriya",
|
|
1647
|
+
"noto-sans-sc",
|
|
1648
|
+
"noto-sans-tamil",
|
|
1649
|
+
"noto-sans-tc",
|
|
1650
|
+
"noto-sans-telugu",
|
|
1651
|
+
"noto-sans",
|
|
1652
|
+
"noto-sans-thai",
|
|
1653
|
+
"noto-nastaliq-urdu",
|
|
1654
|
+
"noto-naksh-arabic",
|
|
1655
|
+
"noto-sans-ethiopic",
|
|
1656
|
+
"noto-sans-khmer",
|
|
1657
|
+
"nyala",
|
|
1658
|
+
"daunpenh",
|
|
1659
|
+
"khmer-ui"
|
|
1660
|
+
]
|
|
1661
|
+
};
|
|
1662
|
+
var deprecatedCatalogIds = {
|
|
1663
|
+
"chartTypes/100pct-bullet-bar": "100pct-stacked-bar-3x",
|
|
1664
|
+
"chartTypes/100pct-bullet-bar-2x": "100pct-stacked-bar-3x",
|
|
1665
|
+
"chartTypes/100pct-bullet-bar-3x": "100pct-stacked-bar-3x",
|
|
1666
|
+
"chartTypes/100pct-bullet-column": "100pct-stacked-column-3x",
|
|
1667
|
+
"chartTypes/100pct-bullet-column-2x": "100pct-stacked-column-3x",
|
|
1668
|
+
"chartTypes/100pct-bullet-column-3x": "100pct-stacked-column-3x",
|
|
1669
|
+
"chartTypes/100pct-progress-bar": "100pct-stacked-bar-3x",
|
|
1670
|
+
"chartTypes/100pct-stacked-area-2x": "100pct-stacked-area-3x",
|
|
1671
|
+
"chartTypes/100pct-stacked-bar-2x": "100pct-stacked-bar-3x",
|
|
1672
|
+
"chartTypes/100pct-stacked-column-2x": "100pct-stacked-column-3x",
|
|
1673
|
+
"chartTypes/australia": "world",
|
|
1674
|
+
"chartTypes/box-and-whisker-2x": "box-and-whisker",
|
|
1675
|
+
"chartTypes/box-and-whisker-3x": "box-and-whisker",
|
|
1676
|
+
"chartTypes/bullet-bar": "bar",
|
|
1677
|
+
"chartTypes/bullet-bar-2x": "bar",
|
|
1678
|
+
"chartTypes/bullet-bar-3x": "bar",
|
|
1679
|
+
"chartTypes/bullet-column": "column",
|
|
1680
|
+
"chartTypes/bullet-column-2x": "column",
|
|
1681
|
+
"chartTypes/bullet-column-3x": "column",
|
|
1682
|
+
"chartTypes/canada": "world",
|
|
1683
|
+
"chartTypes/clustered-bar-2x": "bar",
|
|
1684
|
+
"chartTypes/clustered-column": "column",
|
|
1685
|
+
"chartTypes/dot-plot": "scatter",
|
|
1686
|
+
"chartTypes/dot-plot-2x": "scatter",
|
|
1687
|
+
"chartTypes/dot-plot-3x": "scatter",
|
|
1688
|
+
"chartTypes/dot-plot-4x": "scatter",
|
|
1689
|
+
"chartTypes/dot-plot-5x": "scatter",
|
|
1690
|
+
"chartTypes/dot-plot-6x": "scatter",
|
|
1691
|
+
"chartTypes/dumbbell": "scatter",
|
|
1692
|
+
"chartTypes/line-2x": "line",
|
|
1693
|
+
"chartTypes/line-3x": "line",
|
|
1694
|
+
"chartTypes/line-with-high-low": "line",
|
|
1695
|
+
"chartTypes/line-with-high-low-and-markers": "line-with-markers",
|
|
1696
|
+
"chartTypes/line-with-markers-2x": "line-with-markers",
|
|
1697
|
+
"chartTypes/line-with-markers-3x": "line-with-markers",
|
|
1698
|
+
"chartTypes/sparkline": "line",
|
|
1699
|
+
"chartTypes/sparkline-2x": "line",
|
|
1700
|
+
"chartTypes/sparkline-3x": "line",
|
|
1701
|
+
"chartTypes/sparkline-4x": "line",
|
|
1702
|
+
"chartTypes/sparkline-5x": "line",
|
|
1703
|
+
"chartTypes/sparkline-6x": "line",
|
|
1704
|
+
"chartTypes/stacked-area-2x": "stacked-area-3x",
|
|
1705
|
+
"chartTypes/stacked-bar-2x": "stacked-bar-3x",
|
|
1706
|
+
"chartTypes/stacked-column-2x": "stacked-column-3x",
|
|
1707
|
+
"chartTypes/stacked-line-2x": "stacked-line-3x",
|
|
1708
|
+
"chartTypes/stacked-line-with-markers-2x": "stacked-line-with-markers-3x",
|
|
1709
|
+
"chartTypes/treemap-2x": "treemap",
|
|
1710
|
+
"chartTypes/treemap-3x": "treemap",
|
|
1711
|
+
"chartTypes/united-kingdom": "world",
|
|
1712
|
+
"chartTypes/united-states": "world"
|
|
1713
|
+
};
|
|
1714
|
+
|
|
1715
|
+
// src/chart-options.ts
|
|
1716
|
+
var END_POSITIONS = ["center", "inside-end", "inside-base", "outside-end"];
|
|
1717
|
+
var STACKED_POSITIONS = ["center", "inside-end", "inside-base"];
|
|
1718
|
+
var POINT_POSITIONS = ["above", "below", "left", "right", "center"];
|
|
1719
|
+
var PIE_POSITIONS = ["center", "inside-end", "outside-end"];
|
|
1720
|
+
var NONE = [];
|
|
1721
|
+
var VALUE_CATEGORY = ["category", "value"];
|
|
1722
|
+
var ALL_CONTENT = ["category", "value", "percent"];
|
|
1723
|
+
var categoryKinds = /* @__PURE__ */ new Set(["bar", "line", "area", "scatter", "histogram", "pareto", "box", "waterfall", "funnel"]);
|
|
1724
|
+
var valueKinds = /* @__PURE__ */ new Set(["bar", "line", "area", "scatter", "histogram", "pareto", "box", "waterfall"]);
|
|
1725
|
+
var legendKinds = /* @__PURE__ */ new Set(["bar", "line", "area", "pie", "doughnut", "scatter", "radar", "box"]);
|
|
1726
|
+
function labelSupport(target) {
|
|
1727
|
+
const none = { supported: true, content: VALUE_CATEGORY, positions: NONE, defaultPosition: null, defaultOn: false };
|
|
1728
|
+
switch (target.kind) {
|
|
1729
|
+
case "bar":
|
|
1730
|
+
return target.stacked ? { ...none, positions: STACKED_POSITIONS, defaultPosition: "center" } : { ...none, positions: END_POSITIONS, defaultPosition: "outside-end" };
|
|
1731
|
+
case "line":
|
|
1732
|
+
case "scatter":
|
|
1733
|
+
return { ...none, positions: POINT_POSITIONS, defaultPosition: "above" };
|
|
1734
|
+
case "pie":
|
|
1735
|
+
return { ...none, content: ALL_CONTENT, positions: PIE_POSITIONS, defaultPosition: "outside-end" };
|
|
1736
|
+
case "doughnut":
|
|
1737
|
+
return { ...none, content: ALL_CONTENT };
|
|
1738
|
+
// The funnel labels its bars with values and the treemap its tiles with category names by default.
|
|
1739
|
+
case "funnel":
|
|
1740
|
+
case "treemap":
|
|
1741
|
+
return { ...none, defaultOn: true };
|
|
1742
|
+
case "area":
|
|
1743
|
+
case "radar":
|
|
1744
|
+
return none;
|
|
1745
|
+
case "histogram":
|
|
1746
|
+
case "pareto":
|
|
1747
|
+
case "waterfall":
|
|
1748
|
+
return { ...none, positions: END_POSITIONS, defaultPosition: "outside-end" };
|
|
1749
|
+
default:
|
|
1750
|
+
return { ...none, supported: false, content: [] };
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
function chartOptionSupport(target) {
|
|
1754
|
+
return {
|
|
1755
|
+
axisTitles: { category: categoryKinds.has(target.kind), value: valueKinds.has(target.kind) },
|
|
1756
|
+
legend: legendKinds.has(target.kind),
|
|
1757
|
+
dataLabels: labelSupport(target)
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
var KEPT = {
|
|
1761
|
+
column: { kind: "bar" },
|
|
1762
|
+
"stacked-column-3x": { kind: "bar", stacked: true },
|
|
1763
|
+
"100pct-stacked-column-3x": { kind: "bar", stacked: true },
|
|
1764
|
+
bar: { kind: "bar" },
|
|
1765
|
+
"stacked-bar-3x": { kind: "bar", stacked: true },
|
|
1766
|
+
"100pct-stacked-bar-3x": { kind: "bar", stacked: true },
|
|
1767
|
+
line: { kind: "line" },
|
|
1768
|
+
"line-with-markers": { kind: "line" },
|
|
1769
|
+
"stacked-line-3x": { kind: "line", stacked: true },
|
|
1770
|
+
"stacked-line-with-markers-3x": { kind: "line", stacked: true },
|
|
1771
|
+
area: { kind: "area" },
|
|
1772
|
+
"stacked-area-3x": { kind: "area", stacked: true },
|
|
1773
|
+
"100pct-stacked-area-3x": { kind: "area", stacked: true },
|
|
1774
|
+
pie: { kind: "pie" },
|
|
1775
|
+
doughnut: { kind: "doughnut" },
|
|
1776
|
+
scatter: { kind: "scatter" },
|
|
1777
|
+
radar: { kind: "radar" },
|
|
1778
|
+
"radar-with-markers": { kind: "radar" },
|
|
1779
|
+
"filled-radar": { kind: "radar" },
|
|
1780
|
+
treemap: { kind: "treemap" },
|
|
1781
|
+
histogram: { kind: "histogram" },
|
|
1782
|
+
pareto: { kind: "pareto" },
|
|
1783
|
+
"box-and-whisker": { kind: "box" },
|
|
1784
|
+
waterfall: { kind: "waterfall" },
|
|
1785
|
+
funnel: { kind: "funnel" },
|
|
1786
|
+
world: { kind: "map" }
|
|
1787
|
+
};
|
|
1788
|
+
function chartOptionTarget(typeId) {
|
|
1789
|
+
if (typeof typeId !== "string") return void 0;
|
|
1790
|
+
const raw = typeId.trim().toLowerCase();
|
|
1791
|
+
const id = raw === "donut" ? "doughnut" : raw;
|
|
1792
|
+
const kept = deprecatedCatalogIds[`chartTypes/${id}`] ?? id;
|
|
1793
|
+
const target = Object.hasOwn(KEPT, kept) ? KEPT[kept] : void 0;
|
|
1794
|
+
return target ? { ...target } : void 0;
|
|
1795
|
+
}
|
|
1796
|
+
var record3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1797
|
+
var LEGEND_POSITIONS = /* @__PURE__ */ new Set(["none", "top", "bottom", "left", "right"]);
|
|
1798
|
+
var CONTENT_ORDER = ["category", "value", "percent"];
|
|
1799
|
+
var POSITIONS = /* @__PURE__ */ new Set(["auto", "center", "inside-end", "inside-base", "outside-end", "above", "below", "left", "right"]);
|
|
1800
|
+
var DEFAULT_CHART_LABEL_SEPARATOR = ", ";
|
|
1801
|
+
function resolveChartOptions(chart, target) {
|
|
1802
|
+
const result = { active: false, axisTitles: {}, diagnostics: [] };
|
|
1803
|
+
if (!record3(chart)) return result;
|
|
1804
|
+
const support = target ? chartOptionSupport(target) : void 0;
|
|
1805
|
+
const adapt = (option, reason, message) => result.diagnostics.push({ code: "chart-option-adapted", option, reason, message });
|
|
1806
|
+
const kind2 = target?.kind ?? "unknown";
|
|
1807
|
+
if (record3(chart.axisTitles)) {
|
|
1808
|
+
for (const axis of ["category", "value"]) {
|
|
1809
|
+
const raw = chart.axisTitles[axis];
|
|
1810
|
+
if (typeof raw !== "string" || !raw.trim()) continue;
|
|
1811
|
+
if (support && !support.axisTitles[axis]) {
|
|
1812
|
+
adapt(`axisTitles.${axis}`, "unsupported-type", `A '${kind2}' chart has no ${axis} axis to title; the ${axis} axis title is not drawn or exported.`);
|
|
1813
|
+
continue;
|
|
1814
|
+
}
|
|
1815
|
+
result.axisTitles[axis] = raw.trim();
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
if (typeof chart.legend === "string" && LEGEND_POSITIONS.has(chart.legend)) {
|
|
1819
|
+
if (support && !support.legend) adapt("legend", "unsupported-type", `A '${kind2}' chart has no legend option; the legend setting is ignored.`);
|
|
1820
|
+
else result.legend = chart.legend;
|
|
1821
|
+
}
|
|
1822
|
+
const labels = chart.dataLabels;
|
|
1823
|
+
if (labels === false && support?.dataLabels.defaultOn) result.dataLabelsOff = true;
|
|
1824
|
+
if (labels === true || record3(labels)) {
|
|
1825
|
+
const options = record3(labels) ? labels : {};
|
|
1826
|
+
if (support && !support.dataLabels.supported) {
|
|
1827
|
+
adapt("dataLabels", "unsupported-type", `A '${kind2}' chart has no data label option; data labels are not drawn or exported.`);
|
|
1828
|
+
} else {
|
|
1829
|
+
const allowed = support?.dataLabels.content ?? ALL_CONTENT;
|
|
1830
|
+
const requested = Array.isArray(options.content) ? options.content.filter((part) => typeof part === "string" && CONTENT_ORDER.includes(part)) : ["value"];
|
|
1831
|
+
const content = CONTENT_ORDER.filter((part) => requested.includes(part) && allowed.includes(part));
|
|
1832
|
+
for (const part of CONTENT_ORDER) {
|
|
1833
|
+
if (requested.includes(part) && !allowed.includes(part)) adapt("dataLabels.content", "unsupported-content", `A '${kind2}' chart cannot label '${part}'${part === "percent" ? " (percent exists only on pie and doughnut charts)" : ""}; it is dropped from the data labels.`);
|
|
1834
|
+
}
|
|
1835
|
+
if (!content.length) content.push("value");
|
|
1836
|
+
const positions = support?.dataLabels.positions ?? NONE;
|
|
1837
|
+
let position = support?.dataLabels.defaultPosition ?? null;
|
|
1838
|
+
if (typeof options.position === "string" && POSITIONS.has(options.position) && options.position !== "auto") {
|
|
1839
|
+
if (!support || positions.includes(options.position)) position = options.position;
|
|
1840
|
+
else adapt("dataLabels.position", "unsupported-position", `A '${kind2}'${target?.stacked ? " stacked" : ""} chart cannot place data labels '${options.position}'; the type's default position is used.`);
|
|
1841
|
+
}
|
|
1842
|
+
let separator = typeof options.separator === "string" ? options.separator : DEFAULT_CHART_LABEL_SEPARATOR;
|
|
1843
|
+
if (/[\r\n]/.test(separator)) {
|
|
1844
|
+
separator = separator.replace(/[\r\n]+/g, " ");
|
|
1845
|
+
adapt("dataLabels.separator", "unsupported-content", "A data label is one line; the line break in the separator is replaced with a space.");
|
|
1846
|
+
}
|
|
1847
|
+
result.dataLabels = { content, position, separator };
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
result.active = result.legend !== void 0 || result.dataLabels !== void 0 || result.dataLabelsOff === true || result.axisTitles.category !== void 0 || result.axisTitles.value !== void 0;
|
|
1851
|
+
return result;
|
|
1852
|
+
}
|
|
1853
|
+
function formatChartLabelNumber(value) {
|
|
1854
|
+
return String(Number(value.toPrecision(12)));
|
|
1855
|
+
}
|
|
1856
|
+
function formatChartLabelPercent(share) {
|
|
1857
|
+
return `${Math.round(Number((share * 100).toPrecision(12)))}%`;
|
|
1858
|
+
}
|
|
1859
|
+
function chartLabelText(parts, content, separator = DEFAULT_CHART_LABEL_SEPARATOR) {
|
|
1860
|
+
return CONTENT_ORDER.filter((part) => content.includes(part) && parts[part] !== void 0).map((part) => parts[part]).join(separator);
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
// src/composition.ts
|
|
1864
|
+
var MAX_COMPOSITION_DEPTH = 32;
|
|
1865
|
+
var FALLBACK_CODE_FAMILY = "Roboto Mono";
|
|
1866
|
+
var DEFAULT_FONT_SCHEME = "aptos";
|
|
1867
|
+
var DEFAULT_FONT_FAMILIES = { heading: "Aptos Display", body: "Aptos" };
|
|
1868
|
+
function resolveFontSchemeReference(reference, lookup, path = "design.fontScheme") {
|
|
1869
|
+
const overrides = typeof reference === "object" && reference !== null && !Array.isArray(reference) ? reference : void 0;
|
|
1870
|
+
const id = typeof reference === "string" ? reference : typeof overrides?.id === "string" ? overrides.id : void 0;
|
|
1871
|
+
const found = id === void 0 ? void 0 : lookup(id);
|
|
1872
|
+
const base = record4(found ?? lookup(DEFAULT_FONT_SCHEME));
|
|
1873
|
+
const scheme = overrides ? { ...base, ...overrides } : { ...base };
|
|
1874
|
+
if (id === void 0 || found !== void 0) return { scheme };
|
|
1875
|
+
return { scheme, diagnostic: { code: "unresolved-font-scheme", path, id, fallback: DEFAULT_FONT_SCHEME, message: `Font scheme '${id}' is not in the inline or bundled catalogs; using the default font scheme '${DEFAULT_FONT_SCHEME}'.` } };
|
|
1876
|
+
}
|
|
1877
|
+
function resolveFontFamilies(input) {
|
|
1878
|
+
const scheme = record4(input);
|
|
1879
|
+
const family = (value) => typeof value === "string" ? value : record4(value).family;
|
|
1880
|
+
const accent = family(scheme.accent);
|
|
1881
|
+
return {
|
|
1882
|
+
heading: family(scheme.heading) ?? scheme.major ?? scheme.minor ?? DEFAULT_FONT_FAMILIES.heading,
|
|
1883
|
+
body: family(scheme.body) ?? scheme.minor ?? scheme.major ?? DEFAULT_FONT_FAMILIES.body,
|
|
1884
|
+
code: family(scheme.code) ?? FALLBACK_CODE_FAMILY,
|
|
1885
|
+
...typeof accent === "string" && accent ? { accent } : {}
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
function resolveTextStyle(style, measurement) {
|
|
1889
|
+
return measurement?.resolveStyle ? measurement.resolveStyle(style) : style;
|
|
1890
|
+
}
|
|
1891
|
+
function textWidthMeasurer(style, measurement) {
|
|
1892
|
+
if (!measurement) return measureText;
|
|
1893
|
+
return (text2, size) => {
|
|
1894
|
+
const width = measurement.measure(text2, size, style);
|
|
1895
|
+
if (!Number.isFinite(width) || width < 0) throw new RangeError("Text measurement must return a finite, nonnegative width.");
|
|
1896
|
+
return width;
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1899
|
+
function measureTextOutline(text2, fontSize, style, measurement) {
|
|
1900
|
+
if (measurement?.outlineBounds === void 0) return void 0;
|
|
1901
|
+
if (typeof measurement.outlineBounds !== "function") throw new TypeError("Text outline provider must be a function.");
|
|
1902
|
+
const bounds = measurement.outlineBounds(text2, fontSize, style);
|
|
1903
|
+
if (bounds === null) return null;
|
|
1904
|
+
if (!bounds || ![bounds.x, bounds.y, bounds.width, bounds.height].every(Number.isFinite) || bounds.width < 0 || bounds.height < 0) {
|
|
1905
|
+
throw new RangeError("Text outline measurement must return null or finite bounds with nonnegative dimensions.");
|
|
1906
|
+
}
|
|
1907
|
+
return { x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height };
|
|
1908
|
+
}
|
|
1909
|
+
function placeTextLines(lines, box, alignment = "left", rasterPadding = 0, directions) {
|
|
1910
|
+
if (!Array.isArray(lines) || !box || ![box.x, box.y, box.width, box.height, rasterPadding].every(Number.isFinite) || box.width <= 0 || box.height <= 0 || rasterPadding < 0 || !["left", "center", "right"].includes(alignment)) throw new RangeError("Text placement requires lines, finite positive dimensions, nonnegative padding and a valid alignment.");
|
|
1911
|
+
const placed = [];
|
|
1912
|
+
let shift = 0, bottom = box.y, height = 0, overflow = false;
|
|
1913
|
+
for (const [lineIndex, line] of lines.entries()) {
|
|
1914
|
+
const lineAlignment = physicalAlignment(alignment, directions?.[lineIndex]), factor = lineAlignment === "right" ? 1 : lineAlignment === "center" ? 0.5 : 0;
|
|
1915
|
+
if (!line || ![line.width, line.y, line.baseline, line.height].every(Number.isFinite) || line.width < 0 || line.height <= 0 || line.y < 0 || line.baseline < line.y) throw new RangeError("Text lines require finite coordinates, nonnegative advances and a baseline at or below their top.");
|
|
1916
|
+
const ink = line.outline;
|
|
1917
|
+
if (ink !== null && (!ink || ![ink.x, ink.y, ink.width, ink.height].every(Number.isFinite) || ink.width < 0 || ink.height < 0)) throw new RangeError("Text outlines must be null or finite coordinates with nonnegative dimensions.");
|
|
1918
|
+
let x = box.x + (box.width - line.width) * factor;
|
|
1919
|
+
if (ink) {
|
|
1920
|
+
const low = box.x + rasterPadding - ink.x, high = box.x + box.width - rasterPadding - ink.x - ink.width;
|
|
1921
|
+
if (low > high + 0.01) overflow = true;
|
|
1922
|
+
else x = Math.max(low, Math.min(x, high));
|
|
1923
|
+
shift += Math.max(0, bottom + rasterPadding - (box.y + line.baseline + shift + ink.y));
|
|
1924
|
+
}
|
|
1925
|
+
const y = box.y + line.y + shift, baseline = box.y + line.baseline + shift;
|
|
1926
|
+
const outline = ink ? { ...ink, x: x + ink.x, y: baseline + ink.y } : null;
|
|
1927
|
+
if (outline) bottom = outline.y + outline.height + rasterPadding;
|
|
1928
|
+
height = Math.max(height, y + line.height - box.y, outline ? bottom - box.y : 0);
|
|
1929
|
+
if (line.width > box.width + 0.01 || height > box.height + 0.01) overflow = true;
|
|
1930
|
+
placed.push({ x, y, baseline, height: line.height, width: line.width, outline, ...directions ? { alignment: lineAlignment } : {} });
|
|
1931
|
+
}
|
|
1932
|
+
return { alignment, rasterPadding, lines: placed, height, overflow };
|
|
1933
|
+
}
|
|
1934
|
+
function translateTextFit(fit, dy) {
|
|
1935
|
+
if (!dy || !fit.placement) return fit;
|
|
1936
|
+
return {
|
|
1937
|
+
...fit,
|
|
1938
|
+
placement: {
|
|
1939
|
+
...fit.placement,
|
|
1940
|
+
lines: fit.placement.lines.map((line) => ({
|
|
1941
|
+
...line,
|
|
1942
|
+
y: line.y + dy,
|
|
1943
|
+
baseline: line.baseline + dy,
|
|
1944
|
+
outline: line.outline ? { ...line.outline, y: line.outline.y + dy } : line.outline
|
|
1945
|
+
}))
|
|
1946
|
+
}
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1949
|
+
var OPFCompositionError = class extends Error {
|
|
1950
|
+
constructor(diagnostics, explanation) {
|
|
1951
|
+
super("Slide content does not fit its composition.");
|
|
1952
|
+
this.diagnostics = diagnostics;
|
|
1953
|
+
this.name = "OPFCompositionError";
|
|
1954
|
+
if (explanation) this.explanation = explanation;
|
|
1955
|
+
}
|
|
1956
|
+
diagnostics;
|
|
1957
|
+
code = "layout-overflow";
|
|
1958
|
+
explanation;
|
|
1959
|
+
};
|
|
1960
|
+
var fields = ["text", "items", "bullets", "image", "video", "chart", "table", "code", "metric", "quote", "timeline"];
|
|
1961
|
+
var headings = /* @__PURE__ */ new Set(["title", "subtitle", "tag"]);
|
|
1962
|
+
var COVER_LAYOUT_IDS = /* @__PURE__ */ new Set(["title", "title-subtitle"]);
|
|
1963
|
+
var rows = ["top", "middle", "bottom"];
|
|
1964
|
+
var columns = ["left", "center", "right"];
|
|
1965
|
+
var record4 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1966
|
+
var kind = (field) => field === "items" ? "list" : field === "bullets" ? "text" : field;
|
|
1967
|
+
var round2 = (value) => Math.round(value * 1e6) / 1e6 || value;
|
|
1968
|
+
var SLIDE_IMAGE_POSITIONS = ["background", "top", "bottom", "left", "right"];
|
|
1969
|
+
var SLIDE_IMAGE_BAND = 0.5;
|
|
1970
|
+
var assetSource = (value) => typeof value === "string" ? value : record4(value).src;
|
|
1971
|
+
var finite = (value, min, max) => typeof value === "number" && Number.isFinite(value) && value >= min && value <= max ? value : void 0;
|
|
1972
|
+
var pathNumber = (value) => String(round2(value));
|
|
1973
|
+
var SLIDE_IMAGE_SHAPES = { rectangle: "rect", rounded: "roundRect", circle: "ellipse", hexagon: "hexagon" };
|
|
1974
|
+
function slideImageShape(kind2, box, cornerRadius = 1 / 6) {
|
|
1975
|
+
const { x, y, width: w, height: h } = box, r = x + w, b = y + h, ss = Math.min(w, h), n = pathNumber;
|
|
1976
|
+
const rect = `M${n(x)} ${n(y)}H${n(r)}V${n(b)}H${n(x)}Z`;
|
|
1977
|
+
if (kind2 === "rounded") {
|
|
1978
|
+
const adj = Math.round(Math.min(0.5, Math.max(0, cornerRadius)) * 1e5), radius = ss * adj / 1e5;
|
|
1979
|
+
const path = radius <= 0 ? rect : `M${n(x)} ${n(y + radius)}A${n(radius)} ${n(radius)} 0 0 1 ${n(x + radius)} ${n(y)}H${n(r - radius)}A${n(radius)} ${n(radius)} 0 0 1 ${n(r)} ${n(y + radius)}V${n(b - radius)}A${n(radius)} ${n(radius)} 0 0 1 ${n(r - radius)} ${n(b)}H${n(x + radius)}A${n(radius)} ${n(radius)} 0 0 1 ${n(x)} ${n(b - radius)}Z`;
|
|
1980
|
+
return { kind: kind2, preset: "roundRect", adjust: { adj }, path };
|
|
1981
|
+
}
|
|
1982
|
+
if (kind2 === "circle") {
|
|
1983
|
+
const cy = y + h / 2;
|
|
1984
|
+
return { kind: kind2, preset: "ellipse", adjust: {}, path: `M${n(x)} ${n(cy)}A${n(w / 2)} ${n(h / 2)} 0 1 1 ${n(r)} ${n(cy)}A${n(w / 2)} ${n(h / 2)} 0 1 1 ${n(x)} ${n(cy)}Z` };
|
|
1985
|
+
}
|
|
1986
|
+
if (kind2 === "hexagon") {
|
|
1987
|
+
const adj = 25e3, vf = 115470, a = Math.min(Math.max(adj, 0), 5e4 * w / ss);
|
|
1988
|
+
const x1 = ss * a / 1e5, x2 = r - x1, vc = y + h / 2, dy1 = h / 2 * vf / 1e5 * Math.sin(Math.PI / 3);
|
|
1989
|
+
return { kind: kind2, preset: "hexagon", adjust: { adj, vf }, path: `M${n(x)} ${n(vc)}L${n(x + x1)} ${n(vc - dy1)}L${n(x2)} ${n(vc - dy1)}L${n(r)} ${n(vc)}L${n(x2)} ${n(vc + dy1)}L${n(x + x1)} ${n(vc + dy1)}Z` };
|
|
1990
|
+
}
|
|
1991
|
+
return { kind: "rectangle", preset: "rect", adjust: {}, path: rect };
|
|
1992
|
+
}
|
|
1993
|
+
function resolveSlideImage(slide, layout, presentation, width, height, path, padding, scale, diagnostics, rtl = false) {
|
|
1994
|
+
const own = record4(slide.design), deck = record4(record4(presentation).design);
|
|
1995
|
+
const local = own.slideImage !== void 0;
|
|
1996
|
+
const configured = local ? own.slideImage : deck.slideImage;
|
|
1997
|
+
if (!configured || typeof configured !== "object" && typeof configured !== "string") return void 0;
|
|
1998
|
+
const treatment = typeof configured === "object" && !Array.isArray(configured) && "position" in configured ? record4(configured) : void 0;
|
|
1999
|
+
const alignment = typeof layout.slideImageAlignment === "string" ? layout.slideImageAlignment.toLowerCase() : void 0;
|
|
2000
|
+
const authoredPosition = treatment ? treatment.position : SLIDE_IMAGE_POSITIONS.find((value2) => value2 === alignment) ?? "background";
|
|
2001
|
+
if (!SLIDE_IMAGE_POSITIONS.includes(authoredPosition)) return void 0;
|
|
2002
|
+
const mirrorSide = (side) => !rtl ? side : side === "left" ? "right" : side === "right" ? "left" : side;
|
|
2003
|
+
const position = mirrorSide(authoredPosition);
|
|
2004
|
+
const designSource = treatment ? treatment.src : configured;
|
|
2005
|
+
const root = slide.image, sameSource = root !== void 0 && designSource !== void 0 && assetSource(root) === assetSource(designSource);
|
|
2006
|
+
if (!local && layout.slideImage !== true && !sameSource) return void 0;
|
|
2007
|
+
const replacesContent = root !== void 0 && (designSource === void 0 || sameSource);
|
|
2008
|
+
const value = replacesContent ? root : designSource;
|
|
2009
|
+
const source = assetSource(value);
|
|
2010
|
+
if (typeof source !== "string" || !source) return void 0;
|
|
2011
|
+
const t = treatment ?? {};
|
|
2012
|
+
const fill = t.fill === "crop" || t.fill === "fit" ? t.fill : (own.imageFill ?? deck.imageFill) === "fit" ? "fit" : "crop";
|
|
2013
|
+
const share = finite(t.size, 0.1, 0.9) ?? SLIDE_IMAGE_BAND;
|
|
2014
|
+
const band = { width: round2(width * share), height: round2(height * share) };
|
|
2015
|
+
const region = position === "left" ? { x: 0, y: 0, width: band.width, height } : position === "right" ? { x: round2(width - band.width), y: 0, width: band.width, height } : position === "top" ? { x: 0, y: 0, width, height: band.height } : position === "bottom" ? { x: 0, y: round2(height - band.height), width, height: band.height } : { x: 0, y: 0, width, height };
|
|
2016
|
+
const designPath = local ? `${path}.design.slideImage` : "design.slideImage";
|
|
2017
|
+
const kind2 = Object.hasOwn(SLIDE_IMAGE_SHAPES, t.shape) ? t.shape : "rectangle";
|
|
2018
|
+
let box = t.inset === true ? { x: region.x + padding, y: region.y + padding, width: Math.max(scale, region.width - 2 * padding), height: Math.max(scale, region.height - 2 * padding) } : { ...region };
|
|
2019
|
+
const aspect = kind2 === "circle" ? 1 : finite(t.aspectRatio, Number.MIN_VALUE, 10);
|
|
2020
|
+
if (aspect) {
|
|
2021
|
+
const frameWidth = Math.min(box.width, box.height * aspect), frameHeight = frameWidth / aspect;
|
|
2022
|
+
box = { x: box.x + (box.width - frameWidth) / 2, y: box.y + (box.height - frameHeight) / 2, width: frameWidth, height: frameHeight };
|
|
2023
|
+
}
|
|
2024
|
+
box = { x: round2(box.x), y: round2(box.y), width: round2(box.width), height: round2(box.height) };
|
|
2025
|
+
const result = {
|
|
2026
|
+
path: designPath,
|
|
2027
|
+
sourcePath: replacesContent ? `${path}.image` : designPath,
|
|
2028
|
+
value,
|
|
2029
|
+
position,
|
|
2030
|
+
fill,
|
|
2031
|
+
region,
|
|
2032
|
+
box,
|
|
2033
|
+
replacesContent,
|
|
2034
|
+
shape: slideImageShape(kind2, box, finite(t.cornerRadius, 0, 0.5))
|
|
2035
|
+
};
|
|
2036
|
+
if (typeof t.alt === "string") result.alt = t.alt;
|
|
2037
|
+
const border = record4(t.border), borderWidth = finite(border.width, 0, 64);
|
|
2038
|
+
if (border.color !== void 0 && borderWidth) result.border = { color: border.color, width: round2(borderWidth * scale) };
|
|
2039
|
+
const opacity = finite(t.opacity, 0, 1);
|
|
2040
|
+
if (opacity !== void 0 && opacity < 1) result.opacity = opacity;
|
|
2041
|
+
if (t.recolor === "grayscale") result.recolor = { type: "grayscale" };
|
|
2042
|
+
else if (record4(t.recolor).dark !== void 0 && record4(t.recolor).light !== void 0) result.recolor = { type: "duotone", dark: t.recolor.dark, light: t.recolor.light };
|
|
2043
|
+
const overlay = record4(t.overlay), overlayOpacity = finite(overlay.opacity, 0, 1);
|
|
2044
|
+
if (overlay.color !== void 0 && overlayOpacity !== void 0) {
|
|
2045
|
+
const edge = ["top", "bottom", "left", "right"].includes(overlay.edge) ? mirrorSide(overlay.edge) : void 0;
|
|
2046
|
+
if (edge && kind2 !== "rectangle") diagnostics.push({ code: "unsupported-image-treatment", path: `${designPath}.overlay.edge`, message: "An edge overlay needs a rectangle frame; a band cannot follow a rounded, circular or hexagonal mask as one native shape. Remove edge or use shape rectangle." });
|
|
2047
|
+
else {
|
|
2048
|
+
const part = finite(overlay.size, 0.05, 1) ?? 0.3;
|
|
2049
|
+
const bandBox = !edge ? box : edge === "top" ? { ...box, height: round2(box.height * part) } : edge === "bottom" ? { ...box, y: round2(box.y + box.height * (1 - part)), height: round2(box.height * part) } : edge === "left" ? { ...box, width: round2(box.width * part) } : { ...box, x: round2(box.x + box.width * (1 - part)), width: round2(box.width * part) };
|
|
2050
|
+
result.overlay = { color: overlay.color, opacity: overlayOpacity, box: bandBox, shape: edge ? slideImageShape("rectangle", bandBox) : result.shape };
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
return result;
|
|
2054
|
+
}
|
|
2055
|
+
var LOGO_LOCKUP_CHAINS = {
|
|
2056
|
+
dark: ["light", "default", "stackedLight", "stacked", "wordmarkLight", "wordmark", "iconLight", "icon", "dark", "stackedDark", "wordmarkDark", "iconDark"],
|
|
2057
|
+
light: ["dark", "default", "stackedDark", "stacked", "wordmarkDark", "wordmark", "iconDark", "icon", "light", "stackedLight", "wordmarkLight", "iconLight"]
|
|
2058
|
+
};
|
|
2059
|
+
var LOGO_SET_KEYS = new Set(LOGO_LOCKUP_CHAINS.dark);
|
|
2060
|
+
function resolveLogo(presentation, slide, options = {}) {
|
|
2061
|
+
const slot = options.slot === "icon" || options.slot === "stacked" ? options.slot : "lockup";
|
|
2062
|
+
if (options.slot !== void 0 && slot !== options.slot) throw new RangeError("Logo slot must be lockup, icon or stacked.");
|
|
2063
|
+
const tone = options.onDark === true ? "dark" : "light";
|
|
2064
|
+
const lockup = LOGO_LOCKUP_CHAINS[tone];
|
|
2065
|
+
const chain = slot === "icon" ? [tone === "dark" ? "iconLight" : "iconDark", "icon", ...lockup] : slot === "stacked" ? [tone === "dark" ? "stackedLight" : "stackedDark", "stacked", ...lockup] : lockup;
|
|
2066
|
+
const usable = (value) => {
|
|
2067
|
+
const source = assetSource(value);
|
|
2068
|
+
return typeof source === "string" && source.length > 0;
|
|
2069
|
+
};
|
|
2070
|
+
const pick = (value, path) => {
|
|
2071
|
+
if (value === void 0 || value === null || value === false) return null;
|
|
2072
|
+
if (typeof value === "string" || typeof value === "object" && !Array.isArray(value) && "src" in record4(value)) {
|
|
2073
|
+
return usable(value) ? { source: value, path, variant: "default", slot } : null;
|
|
2074
|
+
}
|
|
2075
|
+
const set = record4(value);
|
|
2076
|
+
if (!Object.keys(set).some((key) => LOGO_SET_KEYS.has(key))) return null;
|
|
2077
|
+
for (const key of chain) {
|
|
2078
|
+
const variant = set[key];
|
|
2079
|
+
if (variant !== void 0 && usable(variant)) return { source: variant, path: `${path}.${key}`, variant: key, slot };
|
|
2080
|
+
}
|
|
2081
|
+
return null;
|
|
2082
|
+
};
|
|
2083
|
+
const deck = record4(presentation), slideIndex = Number.isSafeInteger(options.slideIndex) && options.slideIndex >= 0 ? options.slideIndex : 0;
|
|
2084
|
+
const own = pick(record4(record4(slide).design).logo, `slides.${slideIndex}.design.logo`);
|
|
2085
|
+
if (own) return own;
|
|
2086
|
+
const shared = pick(record4(deck.design).logo, "design.logo");
|
|
2087
|
+
if (shared) return shared;
|
|
2088
|
+
const organizations = Array.isArray(deck.organization) ? deck.organization : [deck.organization];
|
|
2089
|
+
const primaryIndex = organizations.findIndex((item) => record4(item).role === "primary");
|
|
2090
|
+
const index = primaryIndex >= 0 ? primaryIndex : organizations.findIndex(Boolean);
|
|
2091
|
+
if (index < 0) return null;
|
|
2092
|
+
return pick(record4(organizations[index]).logo, Array.isArray(deck.organization) ? `organization.${index}.logo` : "organization.logo");
|
|
2093
|
+
}
|
|
2094
|
+
var webUrl = /^https?:\/\/\S+$/i;
|
|
2095
|
+
function resolveSocialProfile(platform, value, records = [], owner = "organization") {
|
|
2096
|
+
const raw = String(value).trim().replace(/\s+/gu, " ");
|
|
2097
|
+
const display = (url2) => url2.replace(/^https:\/\//i, "");
|
|
2098
|
+
if (webUrl.test(raw)) return { text: display(raw), href: raw, resolved: false };
|
|
2099
|
+
const platformRecord = records.find((item) => item?.id === platform);
|
|
2100
|
+
const base = typeof platformRecord?.baseUrl === "string" && webUrl.test(platformRecord.baseUrl) ? `${platformRecord.baseUrl.replace(/\/+$/u, "")}/{handle}` : void 0;
|
|
2101
|
+
const pattern = (owner === "organization" ? platformRecord?.companyUrlPattern : void 0) ?? platformRecord?.profileUrlPattern ?? base;
|
|
2102
|
+
const prefix = platformRecord?.handlePrefix ?? "";
|
|
2103
|
+
const handle = prefix && raw.startsWith(prefix) ? raw.slice(prefix.length) : raw;
|
|
2104
|
+
if (!handle || typeof pattern !== "string" || !pattern.includes("{handle}")) return { text: raw, resolved: false };
|
|
2105
|
+
const url = pattern.split("{handle}").join(handle);
|
|
2106
|
+
if (!webUrl.test(url)) return { text: raw, resolved: false };
|
|
2107
|
+
return { text: display(url), href: encodeURI(url), resolved: true };
|
|
2108
|
+
}
|
|
2109
|
+
function layoutFurniture(input, options = {}) {
|
|
2110
|
+
const slide = record4(input), width = options.width ?? 1280, height = options.height ?? 720, scale = Math.min(width, height) / 720;
|
|
2111
|
+
const settings = { ...record4(record4(options.layout).composition), ...record4(slide.composition) };
|
|
2112
|
+
assertComposition(settings);
|
|
2113
|
+
const furnitureRtl = (options.direction ?? resolveSlideDirection(options.presentation, options.slideIndex)) === "rtl";
|
|
2114
|
+
const minimum = snapFontSizeUp((settings.minFontSize ?? 16) * scale), size = gridFontSize(13 * scale, minimum), padding = (options.textRasterPadding ?? 2) * scale;
|
|
2115
|
+
if (![width, height, scale, minimum, padding].every(Number.isFinite) || width <= 0 || height <= 0 || minimum <= 0 || padding < 0) throw new RangeError("Furniture requires finite positive dimensions and nonnegative raster padding.");
|
|
2116
|
+
const number = options.slideNumber ?? (options.slideIndex ?? 0) + 1;
|
|
2117
|
+
if (!Number.isSafeInteger(number) || number < 1) throw new RangeError("Displayed slide number must be a positive safe integer.");
|
|
2118
|
+
const presentationSlides = options.presentation?.slides, slideCount = options.slideCount ?? (Array.isArray(presentationSlides) && presentationSlides.length ? presentationSlides.length : void 0);
|
|
2119
|
+
if (slideCount !== void 0 && (!Number.isSafeInteger(slideCount) || slideCount < 1)) throw new RangeError("Displayed slide count must be a positive safe integer.");
|
|
2120
|
+
if (options.date !== void 0 && (typeof options.date !== "string" || !parseIsoDate(options.date))) throw new RangeError("The furniture date option must be an ISO YYYY-MM-DD calendar date.");
|
|
2121
|
+
const outlines = options.textMeasurement?.outlineBounds !== void 0, parts = [], diagnostics = [];
|
|
2122
|
+
const sourceRoot = `slides.${options.slideIndex ?? 0}`, organizations = Array.isArray(options.presentation?.organization) ? options.presentation.organization : [options.presentation?.organization];
|
|
2123
|
+
const primaryIndex = organizations.findIndex((item) => record4(item).role === "primary"), organizationIndex = primaryIndex >= 0 ? primaryIndex : organizations.findIndex(Boolean);
|
|
2124
|
+
const organization = record4(organizations[organizationIndex]), organizationRoot = Array.isArray(options.presentation?.organization) ? `organization.${organizationIndex}` : "organization", organizationPath = `${organizationRoot}.name`;
|
|
2125
|
+
const inlinePlatforms = record4(record4(options.presentation?.catalogs).socialPlatforms).records, platformRecords = [...Array.isArray(inlinePlatforms) ? inlinePlatforms : [], ...options.socialPlatforms ?? []];
|
|
2126
|
+
const fontFamily = options.fonts?.body ?? "sans-serif";
|
|
2127
|
+
let headerBottom = 0, footerTop = height, configured = false;
|
|
2128
|
+
const error = (path, message, code = "text-overflow") => diagnostics.push({ code, path, message });
|
|
2129
|
+
for (const kind2 of ["header", "footer"]) {
|
|
2130
|
+
const local = record4(slide.design)[kind2] !== void 0, value = local ? record4(slide.design)[kind2] : options.presentation?.design?.[kind2];
|
|
2131
|
+
if (value === void 0 || value === false) continue;
|
|
2132
|
+
const root = local ? `${sourceRoot}.design.${kind2}` : `design.${kind2}`;
|
|
2133
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("Header/footer content must be an object or false.");
|
|
2134
|
+
configured = true;
|
|
2135
|
+
const zones = [];
|
|
2136
|
+
for (const [index, zone] of ["left", "center", "right"].entries()) {
|
|
2137
|
+
const side = furnitureRtl && zone !== "center" ? zone === "left" ? "right" : "left" : zone;
|
|
2138
|
+
const content = record4(record4(value)[zone]), path = `${root}.${zone}`, x = width * (0.07 + (furnitureRtl ? 2 - index : index) * 0.3), zoneWidth = width * 0.26, zoneParts = [];
|
|
2139
|
+
let y = 0;
|
|
2140
|
+
const add = (field, text2, generated = false, sourcePath, extras = {}) => {
|
|
2141
|
+
if (text2 === void 0) return;
|
|
2142
|
+
if (typeof text2 !== "string") throw new TypeError(`Furniture field ${path}.${field} requires string content.`);
|
|
2143
|
+
const partPath = `${path}.${field}`, style = resolveTextStyle({ fontFamily, fontWeight: 400, italic: false, path: partPath }, options.textMeasurement);
|
|
2144
|
+
const fit = fitText(text2, { width: Math.max(Number.MIN_VALUE, zoneWidth - (outlines ? 2 * padding : 0)), height: Number.MAX_VALUE }, size, size, textWidthMeasurer(style, options.textMeasurement));
|
|
2145
|
+
const ink = fit.sourceLines.map((line, index2) => {
|
|
2146
|
+
let outline = null;
|
|
2147
|
+
if (outlines) {
|
|
2148
|
+
for (const segment of line.segments) if (segment.kind === "text") {
|
|
2149
|
+
const bounds = measureTextOutline(text2.slice(segment.start, segment.end), size, style, options.textMeasurement);
|
|
2150
|
+
if (bounds) {
|
|
2151
|
+
const next = { ...bounds, x: bounds.x + segment.x };
|
|
2152
|
+
if (!outline) outline = next;
|
|
2153
|
+
else {
|
|
2154
|
+
const right = Math.max(outline.x + outline.width, next.x + next.width), bottom = Math.max(outline.y + outline.height, next.y + next.height);
|
|
2155
|
+
outline.x = Math.min(outline.x, next.x);
|
|
2156
|
+
outline.y = Math.min(outline.y, next.y);
|
|
2157
|
+
outline.width = right - outline.x;
|
|
2158
|
+
outline.height = bottom - outline.y;
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
return { width: line.width, y: index2 * fit.lineHeight, baseline: size + index2 * fit.lineHeight, height: fit.lineHeight, outline };
|
|
2164
|
+
});
|
|
2165
|
+
const natural = outlines ? placeTextLines(ink, { x, y, width: zoneWidth, height: Number.MAX_VALUE }, side, padding) : void 0;
|
|
2166
|
+
const partHeight = natural?.height ?? fit.sourceLines.length * fit.lineHeight;
|
|
2167
|
+
if (!Number.isFinite(partHeight)) throw new RangeError("Furniture exceeds finite layout coordinates.");
|
|
2168
|
+
const box = { x, y, width: zoneWidth, height: Math.max(scale, partHeight) }, placement = outlines ? placeTextLines(ink, box, side, padding) : void 0;
|
|
2169
|
+
const accepted = { ...fit, ...placement ? { placement } : {}, overflow: fit.overflow || !!placement?.overflow };
|
|
2170
|
+
zoneParts.push({ type: "text", kind: kind2, zone, field, path: partPath, sourcePath: sourcePath ?? (!generated ? partPath : void 0), generated, text: text2, style, requestedFontSize: size, minFontSize: minimum, box, alignment: side, fit: accepted, ...extras.fields?.length ? { fields: extras.fields } : {}, ...extras.links?.length ? { links: extras.links } : {} });
|
|
2171
|
+
if (accepted.overflow) error(partPath, "Repeated text exceeds its zone at the selected readability floor; change the furniture or slide design.");
|
|
2172
|
+
y += box.height;
|
|
2173
|
+
};
|
|
2174
|
+
const imageBox = (source) => {
|
|
2175
|
+
const imageHeight = Math.max(32 * scale, Math.min(height * 0.05, 72 * scale)), imageWidth = Math.min(zoneWidth, imageHeight * (intrinsicImageAspect(source, record4(options.presentation).assets) ?? 1));
|
|
2176
|
+
return { x: side === "left" ? x : side === "center" ? x + (zoneWidth - imageWidth) / 2 : x + zoneWidth - imageWidth, y, width: imageWidth, height: imageHeight };
|
|
2177
|
+
};
|
|
2178
|
+
if (content.logo === true) {
|
|
2179
|
+
const resolved = resolveLogo(options.presentation, slide, { slot: "icon", onDark: options.darkBackground, slideIndex: options.slideIndex });
|
|
2180
|
+
if (resolved) {
|
|
2181
|
+
const box = imageBox(resolved.source);
|
|
2182
|
+
zoneParts.push({ type: "image", kind: kind2, zone, field: "logo", path: `${path}.logo`, sourcePath: resolved.path, generated: true, image: resolved.source, box, alignment: side });
|
|
2183
|
+
y += box.height;
|
|
2184
|
+
} else error(`${path}.logo`, "Generated logo needs design.logo or a primary organization logo.", "unresolved-content");
|
|
2185
|
+
}
|
|
2186
|
+
if (content.image !== void 0) {
|
|
2187
|
+
const box = imageBox(content.image);
|
|
2188
|
+
zoneParts.push({ type: "image", kind: kind2, zone, field: "image", path: `${path}.image`, sourcePath: `${path}.image`, generated: false, image: content.image, box, alignment: side });
|
|
2189
|
+
y += box.height;
|
|
2190
|
+
}
|
|
2191
|
+
add("text", content.text);
|
|
2192
|
+
if (content.organization === true) {
|
|
2193
|
+
if (typeof organization.name === "string") add("organization", organization.name, true, organizationPath);
|
|
2194
|
+
else error(`${path}.organization`, "Generated organization name needs a named organization in the presentation.", "unresolved-content");
|
|
2195
|
+
}
|
|
2196
|
+
if (content.socials === true) {
|
|
2197
|
+
const links = Object.entries(record4(organization.socials)).filter(([, value2]) => typeof value2 === "string" && value2.trim()).map(([platform, value2]) => ({ platform, sourcePath: `${organizationRoot}.socials.${platform}`, ...resolveSocialProfile(platform, value2, platformRecords, "organization") }));
|
|
2198
|
+
if (links.length) add("socials", links.map((link) => link.text).join("\n"), true, `${organizationRoot}.socials`, { links });
|
|
2199
|
+
else error(`${path}.socials`, "Generated social profiles need a primary organization with socials.", "unresolved-content");
|
|
2200
|
+
}
|
|
2201
|
+
if (content.section === true) {
|
|
2202
|
+
if (typeof slide.section === "string") add("section", slide.section, true, `${sourceRoot}.section`);
|
|
2203
|
+
else error(`${path}.section`, "Generated section needs a literal slide section.", "unresolved-content");
|
|
2204
|
+
}
|
|
2205
|
+
for (const setting of ["slideNumberFormat", "dateFormat"]) if (content[setting] !== void 0 && typeof content[setting] !== "string") throw new TypeError(`Furniture setting ${path}.${setting} requires a string.`);
|
|
2206
|
+
if (content.slideNumber === true) {
|
|
2207
|
+
const resolved = formatSlideNumber(content.slideNumberFormat ?? DEFAULT_SLIDE_NUMBER_FORMAT, number, slideCount);
|
|
2208
|
+
if ("error" in resolved) error(`${path}.slideNumberFormat`, resolved.error, "unresolved-content");
|
|
2209
|
+
else add("slideNumber", resolved.text, true, void 0, { fields: resolved.fields });
|
|
2210
|
+
}
|
|
2211
|
+
if (content.date === true) {
|
|
2212
|
+
const format = content.dateFormat ?? DEFAULT_FURNITURE_DATE_FORMAT;
|
|
2213
|
+
if (options.date === void 0) error(`${path}.date`, "A current date needs a host-supplied ISO date option. For fixed content use a literal date, or an ISO date with dateFormat.", "unresolved-content");
|
|
2214
|
+
else {
|
|
2215
|
+
const resolved = formatFurnitureDate(options.date, format);
|
|
2216
|
+
if ("error" in resolved) error(`${path}.dateFormat`, resolved.error, "unresolved-content");
|
|
2217
|
+
else add("date", resolved.text, true, void 0, { fields: [{ type: "date", start: 0, end: resolved.text.length, format }] });
|
|
2218
|
+
}
|
|
2219
|
+
} else if (typeof content.date === "string") {
|
|
2220
|
+
if (content.dateFormat === void 0) add("date", content.date);
|
|
2221
|
+
else {
|
|
2222
|
+
const resolved = formatFurnitureDate(content.date, content.dateFormat);
|
|
2223
|
+
if ("error" in resolved) error(`${path}.${parseIsoDate(content.date) ? "dateFormat" : "date"}`, resolved.error, "unresolved-content");
|
|
2224
|
+
else add("date", resolved.text, true, `${path}.date`);
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
zones.push(zoneParts);
|
|
2228
|
+
}
|
|
2229
|
+
const tallest = Math.max(0, ...zones.map((zone) => zone.reduce((sum, part) => sum + part.box.height, 0)));
|
|
2230
|
+
if (!tallest) continue;
|
|
2231
|
+
const top = kind2 === "header" ? height * 0.025 : Math.max(height * 0.025, height - height * 0.04 - tallest);
|
|
2232
|
+
if (kind2 === "header") headerBottom = top + tallest;
|
|
2233
|
+
else footerTop = top;
|
|
2234
|
+
for (const zone of zones) for (const part of zone) {
|
|
2235
|
+
part.box.y += top;
|
|
2236
|
+
if (part.type === "text" && part.fit.placement) for (const line of part.fit.placement.lines) {
|
|
2237
|
+
line.y += top;
|
|
2238
|
+
line.baseline += top;
|
|
2239
|
+
if (line.outline) line.outline.y += top;
|
|
2240
|
+
}
|
|
2241
|
+
if (part.box.y + part.box.height > height - height * 0.04 + 0.01) error(part.path, "Repeated header/footer content extends beyond the usable slide height. Change its content or design before pagination.");
|
|
2242
|
+
parts.push(part);
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
if (headerBottom > footerTop + 0.01) error(parts.find((part) => part.kind === "footer")?.path ?? sourceRoot, "Header and footer content overlap; repeated content cannot be repaired by splitting body content.");
|
|
2246
|
+
return { algorithm: "furniture-flow-v2", configured, textMeasurement: options.textMeasurement ? "provided" : "estimated", textOutlines: outlines ? "provided" : "unavailable", parts, headerBottom, footerTop, diagnostics, overflow: diagnostics.length > 0 };
|
|
2247
|
+
}
|
|
2248
|
+
var textSegments = new Intl.Segmenter("und", { granularity: "grapheme" });
|
|
2249
|
+
var WRAP_TOLERANCE = 0.01;
|
|
2250
|
+
var HANGING_SPACE = /[^\S\r\n\u00a0\u202f\ufeff]/u;
|
|
2251
|
+
function measureText(text2, fontSize) {
|
|
2252
|
+
let units = 0;
|
|
2253
|
+
for (const character of text2) {
|
|
2254
|
+
if (/\p{Mark}|\u200d|\ufe0f/u.test(character)) continue;
|
|
2255
|
+
units += character === " " ? 0.32 : /[A-Z0-9]/.test(character) ? 0.62 : character.codePointAt(0) > 11904 ? 1 : 0.54;
|
|
2256
|
+
}
|
|
2257
|
+
return units * fontSize;
|
|
2258
|
+
}
|
|
2259
|
+
function wrapText(text2, width, fontSize, measure = measureText) {
|
|
2260
|
+
return fitSourceText(text2, { width, height: Number.MAX_VALUE }, fontSize, fontSize, 1, measure, true, false).lines;
|
|
2261
|
+
}
|
|
2262
|
+
function fitText(text2, box, requestedSize = 25, minFontSize = 16, measure = measureText, direction) {
|
|
2263
|
+
if (![box.width, box.height, requestedSize, minFontSize].every(Number.isFinite) || box.width <= 0 || box.height <= 0 || requestedSize <= 0 || minFontSize <= 0) {
|
|
2264
|
+
throw new RangeError("Text dimensions and font sizes must be finite and positive.");
|
|
2265
|
+
}
|
|
2266
|
+
return fitSourceText(text2, box, Math.max(requestedSize, minFontSize), minFontSize, 1, measure, true, true, direction);
|
|
2267
|
+
}
|
|
2268
|
+
var FONT_SIZE_GRID_PER_PX = 75;
|
|
2269
|
+
var FONT_GRID_EPSILON = 1e-6;
|
|
2270
|
+
function snapFontSizeDown(px) {
|
|
2271
|
+
return Math.max(px > 0 ? 1 : -Infinity, Math.floor(px * FONT_SIZE_GRID_PER_PX + FONT_GRID_EPSILON)) / FONT_SIZE_GRID_PER_PX;
|
|
2272
|
+
}
|
|
2273
|
+
function snapFontSizeUp(px) {
|
|
2274
|
+
return Math.max(px > 0 ? 1 : -Infinity, Math.ceil(px * FONT_SIZE_GRID_PER_PX - FONT_GRID_EPSILON)) / FONT_SIZE_GRID_PER_PX;
|
|
2275
|
+
}
|
|
2276
|
+
function gridFontSize(raw, minimum) {
|
|
2277
|
+
return Math.max(snapFontSizeUp(minimum), snapFontSizeDown(raw));
|
|
2278
|
+
}
|
|
2279
|
+
function fitAtSizes(layout, requested, minimum, step = 1, snap = true) {
|
|
2280
|
+
const start = Math.max(requested, minimum);
|
|
2281
|
+
if (![start, minimum, step].every((value) => Number.isFinite(value) && value > 0)) throw new RangeError("Readable font sizes and fitting steps must be finite and positive.");
|
|
2282
|
+
for (let trial = 0; trial <= 64; trial++) {
|
|
2283
|
+
const raw = start - trial * step, last = trial === 64 || raw <= minimum;
|
|
2284
|
+
const size = !snap ? last ? minimum : Math.max(minimum, raw) : last ? snapFontSizeUp(minimum) : gridFontSize(raw, minimum), result = layout(size);
|
|
2285
|
+
if (!result.overflow || last) return result;
|
|
2286
|
+
}
|
|
2287
|
+
throw new Error("Text fitting did not evaluate its bounded floor trial.");
|
|
2288
|
+
}
|
|
2289
|
+
function layoutQuote(value, box, options = {}) {
|
|
2290
|
+
const shorthand = typeof value === "string";
|
|
2291
|
+
const quote = shorthand ? { text: value } : value;
|
|
2292
|
+
if (!quote || Array.isArray(quote) || typeof quote.text !== "string" || [quote.attribution, quote.source].some((field) => field !== void 0 && typeof field !== "string")) {
|
|
2293
|
+
throw new TypeError("Quote content must be a string or a text object with optional string attribution/source.");
|
|
2294
|
+
}
|
|
2295
|
+
const scale = options.scale ?? 1, minimum = snapFontSizeUp((options.minFontSize ?? 16) * scale);
|
|
2296
|
+
if (![box.x, box.y, box.width, box.height, scale, minimum].every(Number.isFinite) || box.width <= 0 || box.height <= 0 || scale <= 0 || minimum <= 0) {
|
|
2297
|
+
throw new RangeError("Quote dimensions, scale and minimum font size must be finite and positive.");
|
|
2298
|
+
}
|
|
2299
|
+
if (options.overflow !== void 0 && !["warn", "error"].includes(options.overflow)) throw new RangeError("Invalid quote overflow policy.");
|
|
2300
|
+
const path = options.path ?? "quote";
|
|
2301
|
+
const diagnostics = [], parts = [];
|
|
2302
|
+
const report = (reason, diagnosticPath, roles, message) => diagnostics.push({ code: "text-overflow", reason, path: diagnosticPath, parts: roles, message });
|
|
2303
|
+
const source = (sourcePath, text2, outputStart) => ({ path: sourcePath, start: 0, end: text2.length, outputStart, outputEnd: outputStart + text2.length });
|
|
2304
|
+
const add = (role, text2, sources, area, fontSize, fontFamily, fontWeight, partPath) => {
|
|
2305
|
+
const requestedStyle = { fontFamily, fontWeight, italic: false, path: partPath };
|
|
2306
|
+
const style = resolveTextStyle({ ...requestedStyle }, options.textMeasurement);
|
|
2307
|
+
const requestedFontSize = gridFontSize(fontSize * scale, minimum);
|
|
2308
|
+
const part = { role, path: partPath, text: text2, sources, box: area, requestedFontSize, minFontSize: minimum, requestedStyle, style };
|
|
2309
|
+
parts.push(part);
|
|
2310
|
+
return part;
|
|
2311
|
+
};
|
|
2312
|
+
let footer = "";
|
|
2313
|
+
const footerSources = [];
|
|
2314
|
+
for (const field of ["attribution", "source"]) {
|
|
2315
|
+
const text2 = quote[field];
|
|
2316
|
+
if (!text2) continue;
|
|
2317
|
+
if (footer) footer += " - ";
|
|
2318
|
+
footerSources.push(source(`${path}.${field}`, text2, footer.length));
|
|
2319
|
+
footer += text2;
|
|
2320
|
+
}
|
|
2321
|
+
const bodyPath = shorthand ? path : `${path}.text`;
|
|
2322
|
+
const body = add(
|
|
2323
|
+
"body",
|
|
2324
|
+
`"${quote.text}"`,
|
|
2325
|
+
[source(bodyPath, quote.text, 1)],
|
|
2326
|
+
{ x: box.x + 18, y: box.y + 18, width: box.width - 36, height: box.height - (footer ? 94 : 36) },
|
|
2327
|
+
28,
|
|
2328
|
+
options.fonts?.accent ?? options.fonts?.heading ?? "sans-serif",
|
|
2329
|
+
600,
|
|
2330
|
+
bodyPath
|
|
2331
|
+
);
|
|
2332
|
+
const attribution = footer ? add(
|
|
2333
|
+
"footer",
|
|
2334
|
+
footer,
|
|
2335
|
+
footerSources,
|
|
2336
|
+
{ x: box.x + 18, y: box.y + box.height - 58, width: box.width - 36, height: 40 },
|
|
2337
|
+
17,
|
|
2338
|
+
options.fonts?.body ?? "sans-serif",
|
|
2339
|
+
500,
|
|
2340
|
+
path
|
|
2341
|
+
) : void 0;
|
|
2342
|
+
const usable = (area) => [area.x, area.y, area.width, area.height].every(Number.isFinite) && area.width > 0 && area.height > 0;
|
|
2343
|
+
const fit = (part, area, size = part.requestedFontSize, floor = minimum) => usable(area) ? fitText(part.text, area, size, floor, textWidthMeasurer(part.style, options.textMeasurement), options.direction) : void 0;
|
|
2344
|
+
const inner = { x: box.x + 18, y: box.y + 18, width: box.width - 36, height: box.height - 36 };
|
|
2345
|
+
if (!attribution) body.fit = fit(body, body.box);
|
|
2346
|
+
else if (usable(inner) && inner.height > 18) {
|
|
2347
|
+
const available = inner.height - 18;
|
|
2348
|
+
const preferredBody = fit(body, inner, body.requestedFontSize, body.requestedFontSize);
|
|
2349
|
+
const minimumBody = body.requestedFontSize === minimum ? preferredBody : fit(body, inner, minimum, minimum);
|
|
2350
|
+
const preferredBodyHeight = preferredBody.lines.length * preferredBody.lineHeight;
|
|
2351
|
+
const minimumBodyHeight = minimumBody.lines.length * minimumBody.lineHeight;
|
|
2352
|
+
let selected;
|
|
2353
|
+
for (const size of /* @__PURE__ */ new Set([attribution.requestedFontSize, minimum])) {
|
|
2354
|
+
const natural = fit(attribution, inner, size, size);
|
|
2355
|
+
const naturalHeight = natural.lines.length * natural.lineHeight;
|
|
2356
|
+
const preferredHeight = Math.max(40, naturalHeight);
|
|
2357
|
+
const bodyReservation = Math.min(minimumBodyHeight, Math.max(minimumBody.lineHeight, available - natural.lineHeight));
|
|
2358
|
+
const footerHeight = preferredBodyHeight + preferredHeight <= available + 0.01 ? preferredHeight : Math.min(naturalHeight, Math.max(0, available - bodyReservation));
|
|
2359
|
+
const bodyBox = { ...inner, height: available - footerHeight };
|
|
2360
|
+
const footerBox = { ...inner, y: inner.y + inner.height - footerHeight, height: footerHeight };
|
|
2361
|
+
const bodyFit = fit(body, bodyBox);
|
|
2362
|
+
const footerFit = usable(footerBox) ? { ...natural, overflow: natural.overflow || naturalHeight > footerHeight + 0.01 } : void 0;
|
|
2363
|
+
const overflow = !bodyFit || !footerFit || bodyFit.overflow || footerFit.overflow;
|
|
2364
|
+
const score = (body.requestedFontSize - (bodyFit?.fontSize ?? minimum) + attribution.requestedFontSize - size) / scale;
|
|
2365
|
+
if (!selected || !overflow && (selected.overflow || score < selected.score) || overflow && selected.overflow) {
|
|
2366
|
+
selected = { bodyBox, footerBox, bodyFit, footerFit, score, overflow };
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
if (selected) {
|
|
2370
|
+
body.box = selected.bodyBox;
|
|
2371
|
+
body.fit = selected.bodyFit;
|
|
2372
|
+
attribution.box = selected.footerBox;
|
|
2373
|
+
attribution.fit = selected.footerFit;
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
for (const part of parts) {
|
|
2377
|
+
const area = part.box;
|
|
2378
|
+
if (!part.fit) {
|
|
2379
|
+
report("invalid-part-box", part.path, [part.role], `Quote ${part.role} has no usable space after its insets; increase the cell size or change the arrangement.`);
|
|
2380
|
+
} else if (part.fit.overflow) {
|
|
2381
|
+
report("text-fit", part.path, [part.role], `Quote ${part.role} exceeds its available space at the readability floor; increase its space or change the arrangement.`);
|
|
2382
|
+
}
|
|
2383
|
+
if (usable(area) && (area.x < box.x || area.y < box.y || area.x + area.width > box.x + box.width + 0.01 || area.y + area.height > box.y + box.height + 0.01)) {
|
|
2384
|
+
report("part-outside-cell", part.path, [part.role], `Quote ${part.role} extends outside its cell; increase the cell size or change the arrangement.`);
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
if (body?.fit && attribution?.fit && body.box.y + body.fit.lines.length * body.fit.lineHeight > attribution.box.y + 0.01 && attribution.box.y + attribution.fit.lines.length * attribution.fit.lineHeight > body.box.y + 0.01) {
|
|
2388
|
+
report("part-overlap", path, ["body", "footer"], "Quote body and footer line boxes overlap; do not accept this layout without more space.");
|
|
2389
|
+
}
|
|
2390
|
+
if (diagnostics.length && options.overflow === "error") throw new OPFCompositionError(diagnostics);
|
|
2391
|
+
return { algorithm: "quote-flow-v1", textMeasurement: options.textMeasurement ? "provided" : "estimated", parts, diagnostics, overflow: diagnostics.length > 0 };
|
|
2392
|
+
}
|
|
2393
|
+
function layoutMetric(value, box, options = {}) {
|
|
2394
|
+
const scalar = typeof value === "string" || typeof value === "number";
|
|
2395
|
+
const metric = scalar ? { value } : value;
|
|
2396
|
+
const isValue = (input) => typeof input === "string" || typeof input === "number" && Number.isFinite(input);
|
|
2397
|
+
if (!metric || Array.isArray(metric) || !isValue(metric.value) || [metric.label, metric.description, metric.unit].some((field) => field !== void 0 && typeof field !== "string") || metric.delta !== void 0 && !isValue(metric.delta) || metric.trend !== void 0 && !["up", "down", "flat"].includes(metric.trend)) {
|
|
2398
|
+
throw new TypeError("Metric content requires a finite numeric or string value and schema-valid display metadata.");
|
|
2399
|
+
}
|
|
2400
|
+
const scale = options.scale ?? 1, minimum = snapFontSizeUp((options.minFontSize ?? 16) * scale);
|
|
2401
|
+
const rasterPadding = (options.textRasterPadding ?? 1) * scale, hasOutlines = options.textMeasurement?.outlineBounds !== void 0;
|
|
2402
|
+
if (![box.x, box.y, box.width, box.height, box.x + box.width, box.y + box.height, scale, minimum, Math.max(76 * scale, minimum) * 1.22].every(Number.isFinite) || box.width <= 0 || box.height <= 0 || scale <= 0 || minimum <= 0) {
|
|
2403
|
+
throw new RangeError("Metric dimensions, scale and minimum font size must be finite and positive.");
|
|
2404
|
+
}
|
|
2405
|
+
if (!Number.isFinite(rasterPadding) || rasterPadding < 0) throw new RangeError("Metric raster padding must be finite and nonnegative.");
|
|
2406
|
+
if (options.overflow !== void 0 && !["warn", "error"].includes(options.overflow)) throw new RangeError("Invalid metric overflow policy.");
|
|
2407
|
+
const requestedAlignment = options.align ?? "left";
|
|
2408
|
+
if (!["left", "center", "right"].includes(requestedAlignment)) throw new RangeError("Invalid metric alignment.");
|
|
2409
|
+
const sourcePath = options.path ?? "metric", parts = [], diagnostics = [];
|
|
2410
|
+
for (const role of ["value", "unit", "label", "description", "delta", "trend"]) {
|
|
2411
|
+
const sourceValue = metric[role];
|
|
2412
|
+
if (sourceValue === void 0) continue;
|
|
2413
|
+
const text2 = String(sourceValue), path = scalar ? sourcePath : `${sourcePath}.${role}`;
|
|
2414
|
+
const nominal = role === "value" ? Math.min(76 * scale, box.height * 0.28) : (role === "description" ? 20 : role === "trend" ? 18 : 23) * scale;
|
|
2415
|
+
const requestedStyle = { fontFamily: (role === "value" ? options.fonts?.heading : options.fonts?.body) ?? "sans-serif", fontWeight: role === "value" ? 800 : role === "description" ? 400 : 500, italic: false, path };
|
|
2416
|
+
const part = {
|
|
2417
|
+
role,
|
|
2418
|
+
path,
|
|
2419
|
+
text: text2,
|
|
2420
|
+
sources: [{ path, value: sourceValue, start: 0, end: text2.length }],
|
|
2421
|
+
visible: role === "value" || text2.length > 0,
|
|
2422
|
+
linePositions: [],
|
|
2423
|
+
box: { ...box, height: 0 },
|
|
2424
|
+
requestedFontSize: gridFontSize(nominal, minimum),
|
|
2425
|
+
minFontSize: minimum,
|
|
2426
|
+
requestedStyle,
|
|
2427
|
+
style: resolveTextStyle({ ...requestedStyle }, options.textMeasurement)
|
|
2428
|
+
};
|
|
2429
|
+
if (!part.visible) part.fit = { lines: [], sourceLines: [], fontSize: part.requestedFontSize, lineHeight: part.requestedFontSize * 1.22, tabSize: 4, tabWidth: 0, overflow: false };
|
|
2430
|
+
parts.push(part);
|
|
2431
|
+
}
|
|
2432
|
+
const primary = parts[0], metadata = parts.filter((part) => part !== primary && part.visible), unit = metadata.find((part) => part.role === "unit");
|
|
2433
|
+
const metricDirection = options.direction === "rtl" ? paragraphDirection(["label", "description", "unit"].map((role) => parts.find((part) => part.role === role)?.text).find(Boolean) ?? primary.text, "rtl") : void 0;
|
|
2434
|
+
const alignment = physicalAlignment(requestedAlignment, metricDirection);
|
|
2435
|
+
const usable = (area) => [area.x, area.y, area.width, area.height, area.x + area.width, area.y + area.height].every(Number.isFinite) && area.width > 0 && area.height > 0;
|
|
2436
|
+
const occupied = (fit) => fit.placement?.height ?? fit.lines.length * fit.lineHeight;
|
|
2437
|
+
const gap = 8 * scale, primaryGap = 12 * scale;
|
|
2438
|
+
const lineInk = (part, fit) => fit.sourceLines.map((line, index) => {
|
|
2439
|
+
let outline = null;
|
|
2440
|
+
for (const segment of line.segments) {
|
|
2441
|
+
if (segment.kind !== "text") continue;
|
|
2442
|
+
const bounds = measureTextOutline(part.text.slice(segment.start, segment.end), fit.fontSize, part.style, options.textMeasurement);
|
|
2443
|
+
if (!bounds) continue;
|
|
2444
|
+
const next = { ...bounds, x: segment.x + bounds.x };
|
|
2445
|
+
if (!outline) outline = next;
|
|
2446
|
+
else {
|
|
2447
|
+
const right = Math.max(outline.x + outline.width, next.x + next.width), bottom = Math.max(outline.y + outline.height, next.y + next.height);
|
|
2448
|
+
outline.x = Math.min(outline.x, next.x);
|
|
2449
|
+
outline.y = Math.min(outline.y, next.y);
|
|
2450
|
+
outline.width = right - outline.x;
|
|
2451
|
+
outline.height = bottom - outline.y;
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
return { width: line.width, y: index * fit.lineHeight, baseline: fit.fontSize + index * fit.lineHeight, height: fit.lineHeight, outline };
|
|
2455
|
+
});
|
|
2456
|
+
const place = (part, fit, width) => {
|
|
2457
|
+
if (!hasOutlines) return fit;
|
|
2458
|
+
const placement = placeTextLines(lineInk(part, fit), { x: 0, y: 0, width, height: Number.MAX_VALUE }, alignment, rasterPadding);
|
|
2459
|
+
return { ...fit, placement, overflow: placement.overflow };
|
|
2460
|
+
};
|
|
2461
|
+
const naturalWidth = (part, fit) => {
|
|
2462
|
+
if (!hasOutlines) return Math.max(...fit.sourceLines.map((line) => line.width));
|
|
2463
|
+
const width = Math.max(...lineInk(part, fit).map((line) => Math.max(line.width, line.outline ? line.outline.x + line.outline.width : 0) - Math.min(0, line.outline?.x ?? 0)));
|
|
2464
|
+
return width > 0 ? width + 2 * rasterPadding : 0;
|
|
2465
|
+
};
|
|
2466
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
2467
|
+
const measure = (part, size, width) => {
|
|
2468
|
+
const key = JSON.stringify([part.role, size, width]);
|
|
2469
|
+
let fit = cache2.get(key);
|
|
2470
|
+
if (!fit) {
|
|
2471
|
+
fit = fitCodeText(part.text, { ...box, width: hasOutlines ? Math.max(Number.MIN_VALUE, width - 2 * rasterPadding) : width }, size, size, scale, textWidthMeasurer(part.style, options.textMeasurement), options.direction);
|
|
2472
|
+
fit = place(part, fit, width);
|
|
2473
|
+
if (!Number.isFinite(occupied(fit))) throw new RangeError("Metric text layout exceeds finite coordinates.");
|
|
2474
|
+
cache2.set(key, fit);
|
|
2475
|
+
}
|
|
2476
|
+
return fit;
|
|
2477
|
+
};
|
|
2478
|
+
const fitValue = (area, singleLine) => {
|
|
2479
|
+
if (!usable(area)) return void 0;
|
|
2480
|
+
for (let step = 0; step <= 76; step++) {
|
|
2481
|
+
const size = gridFontSize(primary.requestedFontSize - step * scale, minimum);
|
|
2482
|
+
const natural = measure(primary, size, area.width);
|
|
2483
|
+
const fit = { ...natural, overflow: !!natural.placement?.overflow || singleLine && natural.lines.length !== 1 || occupied(natural) > area.height + 0.01 || natural.sourceLines.some((line) => line.width > area.width + 0.01) };
|
|
2484
|
+
if (!fit.overflow || size === minimum) return fit;
|
|
2485
|
+
}
|
|
2486
|
+
};
|
|
2487
|
+
let selected, attempts = 0;
|
|
2488
|
+
const reductions = Math.min(23, Math.ceil(Math.max(0, ...metadata.map((part) => (part.requestedFontSize - minimum) / scale))));
|
|
2489
|
+
for (let reduction = 0; reduction <= reductions; reduction++) {
|
|
2490
|
+
const measured = new Map(metadata.map((part) => [part, measure(part, gridFontSize(part.requestedFontSize - reduction * scale, minimum), box.width)]));
|
|
2491
|
+
const measuredUnit = unit ? measured.get(unit) : void 0;
|
|
2492
|
+
const unitWidth = unit && measuredUnit ? naturalWidth(unit, measuredUnit) : 0;
|
|
2493
|
+
const unitFit = unit && measuredUnit && unitWidth > 0 ? place(unit, measuredUnit, unitWidth) : measuredUnit;
|
|
2494
|
+
const inline = unitFit?.lines.length === 1 && unitWidth > 0 && unitWidth <= box.width * 0.35 && unitWidth + gap < box.width;
|
|
2495
|
+
for (const arrangement of inline ? ["inline-unit", "stacked"] : ["stacked"]) {
|
|
2496
|
+
attempts++;
|
|
2497
|
+
const tail = metadata.filter((part) => arrangement !== "inline-unit" || part !== unit);
|
|
2498
|
+
const tailHeight = tail.reduce((sum, part) => sum + occupied(measured.get(part)), 0) + Math.max(0, tail.length - 1) * gap;
|
|
2499
|
+
if (!Number.isFinite(tailHeight)) throw new RangeError("Metric metadata exceeds finite coordinates.");
|
|
2500
|
+
const area = { ...box, width: arrangement === "inline-unit" ? box.width - unitWidth - gap : box.width, height: box.height - tailHeight - (tail.length ? primaryGap : 0) };
|
|
2501
|
+
const valueFit = fitValue(area, arrangement === "inline-unit"), valueHeight = valueFit ? occupied(valueFit) : 0;
|
|
2502
|
+
const allocations = [];
|
|
2503
|
+
let primaryHeight = valueHeight;
|
|
2504
|
+
if (arrangement === "inline-unit" && unit && unitFit && valueFit) {
|
|
2505
|
+
const valueBaseline = valueFit.placement?.lines.at(-1)?.baseline ?? valueHeight - valueFit.lineHeight + valueFit.fontSize;
|
|
2506
|
+
const unitBaseline = unitFit.placement?.lines.at(-1)?.baseline ?? occupied(unitFit) - unitFit.lineHeight + unitFit.fontSize;
|
|
2507
|
+
const valueY = box.y + Math.max(0, unitBaseline - valueBaseline), unitY = box.y + Math.max(0, valueBaseline - unitBaseline);
|
|
2508
|
+
const measuredWidth = naturalWidth(primary, valueFit);
|
|
2509
|
+
const valueWidth = Math.min(area.width, measuredWidth || valueFit.fontSize);
|
|
2510
|
+
allocations.push({ part: primary, box: { ...area, y: valueY, width: valueWidth, height: valueHeight }, fit: place(primary, valueFit, valueWidth) });
|
|
2511
|
+
allocations.push({ part: unit, box: { x: box.x + valueWidth + gap, y: unitY, width: unitWidth, height: occupied(unitFit) }, fit: { ...unitFit, overflow: !!unitFit.placement?.overflow } });
|
|
2512
|
+
primaryHeight = Math.max(valueY - box.y + valueHeight, unitY - box.y + occupied(unitFit));
|
|
2513
|
+
} else allocations.push({ part: primary, box: valueFit ? { ...area, height: valueHeight } : area, fit: valueFit });
|
|
2514
|
+
let y = box.y + primaryHeight + (tail.length ? primaryGap : 0);
|
|
2515
|
+
for (const part of tail) {
|
|
2516
|
+
const natural = measured.get(part), height = occupied(natural);
|
|
2517
|
+
allocations.push({ part, box: { ...box, y, height }, fit: { ...natural, overflow: !!natural.placement?.overflow || natural.sourceLines.some((line) => line.width > box.width + 0.01) } });
|
|
2518
|
+
y += height + gap;
|
|
2519
|
+
}
|
|
2520
|
+
const overflow = !valueFit || valueFit.overflow || arrangement === "inline-unit" && valueFit.lines.length !== 1 || primaryHeight > area.height + 0.01 || allocations.some(({ box: area2, fit }) => !fit || fit.overflow || !usable(area2) || area2.y + area2.height > box.y + box.height + 0.01);
|
|
2521
|
+
const score = allocations.reduce((sum, { part, fit }) => sum + (part.requestedFontSize - (fit?.fontSize ?? minimum)) / scale, 0);
|
|
2522
|
+
const candidate = { arrangement, allocations, score, overflow };
|
|
2523
|
+
if (!selected || !overflow && (selected.overflow || score < selected.score) || overflow && selected.overflow) selected = candidate;
|
|
2524
|
+
}
|
|
2525
|
+
if (selected && !selected.overflow && selected.score === 0) break;
|
|
2526
|
+
}
|
|
2527
|
+
for (const allocation of selected.allocations) {
|
|
2528
|
+
allocation.part.box = allocation.box;
|
|
2529
|
+
allocation.part.fit = allocation.fit;
|
|
2530
|
+
}
|
|
2531
|
+
const alignmentFactor = alignment === "center" ? 0.5 : alignment === "right" ? 1 : 0;
|
|
2532
|
+
if (selected.arrangement === "inline-unit" && unit) {
|
|
2533
|
+
const offset = (box.width - (unit.box.x + unit.box.width - box.x)) * alignmentFactor;
|
|
2534
|
+
primary.box.x += offset;
|
|
2535
|
+
unit.box.x += offset;
|
|
2536
|
+
}
|
|
2537
|
+
for (const part of parts) if (part.fit) {
|
|
2538
|
+
if (part.fit.placement) {
|
|
2539
|
+
part.fit = { ...part.fit, placement: { ...part.fit.placement, lines: part.fit.placement.lines.map((line) => ({ ...line, x: line.x + part.box.x, y: line.y + part.box.y, baseline: line.baseline + part.box.y, outline: line.outline ? { ...line.outline, x: line.outline.x + part.box.x, y: line.outline.y + part.box.y } : null })) } };
|
|
2540
|
+
part.linePositions = part.fit.placement.lines.map(({ x, baseline }) => ({ x, baseline }));
|
|
2541
|
+
} else part.linePositions = part.fit.sourceLines.map((line, index) => ({
|
|
2542
|
+
x: part.box.x + (part.box.width - line.width) * alignmentFactor,
|
|
2543
|
+
baseline: part.box.y + part.fit.fontSize + index * part.fit.lineHeight
|
|
2544
|
+
}));
|
|
2545
|
+
}
|
|
2546
|
+
const report = (reason, part, roles, message) => diagnostics.push({ code: "text-overflow", reason, path: part.path, parts: roles, message });
|
|
2547
|
+
for (const part of parts.filter((part2) => part2.visible)) {
|
|
2548
|
+
if (!part.fit || !usable(part.box)) report("invalid-part-box", part, [part.role], `Metric ${part.role} has no usable space after metadata; increase the cell or change the arrangement.`);
|
|
2549
|
+
else if (part.fit.overflow) report("text-fit", part, [part.role], `Metric ${part.role} exceeds its space at the readability floor; increase the cell or change the arrangement.`);
|
|
2550
|
+
if (usable(part.box) && (part.box.x < box.x || part.box.y < box.y || part.box.x + part.box.width > box.x + box.width + 0.01 || part.box.y + part.box.height > box.y + box.height + 0.01)) {
|
|
2551
|
+
report("part-outside-cell", part, [part.role], `Metric ${part.role} extends outside its cell; increase the cell or change the arrangement.`);
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
const visible = parts.filter((part) => part.visible && part.fit);
|
|
2555
|
+
for (const [index, first] of visible.entries()) for (const second of visible.slice(index + 1)) {
|
|
2556
|
+
if (first.box.x < second.box.x + second.box.width - 0.01 && second.box.x < first.box.x + first.box.width - 0.01 && first.box.y < second.box.y + occupied(second.fit) - 0.01 && second.box.y < first.box.y + occupied(first.fit) - 0.01) {
|
|
2557
|
+
report("part-overlap", first, [first.role, second.role], "Metric part line boxes overlap; do not accept this layout without more space.");
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
if (diagnostics.length && options.overflow === "error") throw new OPFCompositionError(diagnostics);
|
|
2561
|
+
return { algorithm: "metric-flow-v1", alignment, textMeasurement: options.textMeasurement ? "provided" : "estimated", arrangement: selected.arrangement, attempts, parts, diagnostics, overflow: diagnostics.length > 0 };
|
|
2562
|
+
}
|
|
2563
|
+
function layoutTimeline(value, box, options = {}) {
|
|
2564
|
+
const shorthand = Array.isArray(value), timeline = shorthand ? { events: value } : value;
|
|
2565
|
+
if (!timeline || !Array.isArray(timeline.events) || !timeline.events.length || [timeline.name, timeline.description].some((field) => field !== void 0 && typeof field !== "string") || timeline.events.some((event) => !event || typeof event.what !== "string" || [event.when, event.description].some((field) => field !== void 0 && typeof field !== "string"))) {
|
|
2566
|
+
throw new TypeError("Timeline content requires ordered events with string labels and optional string metadata.");
|
|
2567
|
+
}
|
|
2568
|
+
const scale = options.scale ?? 1, minimum = snapFontSizeUp((options.minFontSize ?? 16) * scale), padding = (options.textRasterPadding ?? 1) * scale;
|
|
2569
|
+
if (![box.x, box.y, box.width, box.height, scale, minimum, padding].every(Number.isFinite) || box.width <= 0 || box.height <= 0 || scale <= 0 || minimum <= 0 || padding < 0) throw new RangeError("Timeline dimensions, scale and minimum must be positive, with finite nonnegative raster padding.");
|
|
2570
|
+
if (options.overflow !== void 0 && !["warn", "error"].includes(options.overflow)) throw new RangeError("Invalid timeline overflow policy.");
|
|
2571
|
+
const outlines = options.textMeasurement?.outlineBounds !== void 0, rtl = options.direction === "rtl";
|
|
2572
|
+
if (outlines && typeof options.textMeasurement?.outlineBounds !== "function") throw new TypeError("Text outline provider must be a function.");
|
|
2573
|
+
const path = options.path ?? "timeline", eventPath = (index) => shorthand ? `${path}.${index}` : `${path}.events.${index}`;
|
|
2574
|
+
const fonts = resolveFontFamilies(options.fonts), source = [];
|
|
2575
|
+
const add = (role, text2, partPath, size, weight, eventIndex) => {
|
|
2576
|
+
if (text2 === void 0) return;
|
|
2577
|
+
const requestedStyle = { fontFamily: fonts.body, fontWeight: weight, italic: false, path: partPath };
|
|
2578
|
+
source.push({ role, eventIndex, path: partPath, text: text2, sources: [{ path: partPath, start: 0, end: text2.length }], box: { ...box }, alignment: "center", requestedFontSize: gridFontSize(size * scale, minimum), minFontSize: minimum, requestedStyle, style: resolveTextStyle({ ...requestedStyle }, options.textMeasurement) });
|
|
2579
|
+
};
|
|
2580
|
+
add("name", timeline.name, `${path}.name`, 24, 700);
|
|
2581
|
+
add("description", timeline.description, `${path}.description`, 18, 400);
|
|
2582
|
+
timeline.events.forEach((event, index) => {
|
|
2583
|
+
add("when", event.when, `${eventPath(index)}.when`, 16, 500, index);
|
|
2584
|
+
add("what", event.what, `${eventPath(index)}.what`, 16, 500, index);
|
|
2585
|
+
add("event-description", event.description, `${eventPath(index)}.description`, 16, 500, index);
|
|
2586
|
+
});
|
|
2587
|
+
const metadata = [], events = timeline.events.map(() => []);
|
|
2588
|
+
for (const part of source) if (part.eventIndex === void 0) metadata.push(part);
|
|
2589
|
+
else events[part.eventIndex].push(part);
|
|
2590
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
2591
|
+
const measure = (part, size, width, alignment) => {
|
|
2592
|
+
const key = JSON.stringify([part.path, size, width, alignment]);
|
|
2593
|
+
let measured = cache2.get(key);
|
|
2594
|
+
if (measured) return measured;
|
|
2595
|
+
const fit = fitText(part.text, { width: Math.max(Number.MIN_VALUE, width - (outlines ? 2 * padding : 0)), height: Number.MAX_VALUE }, size, size, textWidthMeasurer(part.style, options.textMeasurement), options.direction);
|
|
2596
|
+
const ink = fit.sourceLines.map((line, index) => {
|
|
2597
|
+
let left = Infinity, top = Infinity, right = -Infinity, bottom = -Infinity, hasInk = false;
|
|
2598
|
+
if (outlines) {
|
|
2599
|
+
for (const segment of line.segments) if (segment.kind === "text") {
|
|
2600
|
+
const outline = measureTextOutline(part.text.slice(segment.start, segment.end), size, part.style, options.textMeasurement);
|
|
2601
|
+
if (outline) {
|
|
2602
|
+
hasInk = true;
|
|
2603
|
+
left = Math.min(left, outline.x + segment.x);
|
|
2604
|
+
top = Math.min(top, outline.y);
|
|
2605
|
+
right = Math.max(right, outline.x + segment.x + outline.width);
|
|
2606
|
+
bottom = Math.max(bottom, outline.y + outline.height);
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
return { width: line.width, y: index * fit.lineHeight, baseline: size + index * fit.lineHeight, height: fit.lineHeight, outline: hasInk ? { x: left, y: top, width: right - left, height: bottom - top } : null };
|
|
2611
|
+
});
|
|
2612
|
+
const placement = outlines ? placeTextLines(ink, { x: 0, y: 0, width, height: Number.MAX_VALUE }, alignment, padding, fit.directions) : void 0;
|
|
2613
|
+
const height = placement?.height ?? fit.sourceLines.length * fit.lineHeight;
|
|
2614
|
+
if (!Number.isFinite(height)) throw new RangeError("Timeline text exceeds finite layout coordinates.");
|
|
2615
|
+
measured = { fit, ink, height };
|
|
2616
|
+
cache2.set(key, measured);
|
|
2617
|
+
return measured;
|
|
2618
|
+
};
|
|
2619
|
+
let selected, attempts = 0;
|
|
2620
|
+
let reductionLimit = 0;
|
|
2621
|
+
for (const part of source) reductionLimit = Math.max(reductionLimit, (part.requestedFontSize - minimum) / scale);
|
|
2622
|
+
const reductions = Math.min(24, Math.ceil(reductionLimit));
|
|
2623
|
+
for (let reduction = 0; reduction <= reductions; reduction++) {
|
|
2624
|
+
for (const arrangement of ["alternating", "vertical"]) {
|
|
2625
|
+
attempts++;
|
|
2626
|
+
const parts = [], markers = [], diagnostics = [];
|
|
2627
|
+
let score2 = 0;
|
|
2628
|
+
const report = (part, reason, message, excess = 1) => {
|
|
2629
|
+
diagnostics.push({ code: "text-overflow", reason, path: part.path, message });
|
|
2630
|
+
score2 += Math.max(1, excess);
|
|
2631
|
+
};
|
|
2632
|
+
const place = (part, x, y2, width, alignment) => {
|
|
2633
|
+
const measured = measure(part, gridFontSize(part.requestedFontSize - reduction * scale, minimum), Math.max(scale, width), alignment);
|
|
2634
|
+
const area = { x, y: y2, width: Math.max(scale, width), height: Math.max(scale, measured.height) }, placement = outlines ? placeTextLines(measured.ink, area, alignment, padding, measured.fit.directions) : void 0;
|
|
2635
|
+
const fit = { ...measured.fit, ...placement ? { placement } : {}, overflow: measured.fit.overflow || !!placement?.overflow };
|
|
2636
|
+
const accepted = { ...part, box: area, alignment, fit };
|
|
2637
|
+
parts.push(accepted);
|
|
2638
|
+
if (fit.overflow) report(accepted, "text-fit", "Timeline field exceeds its readable width; increase its space or split the timeline.");
|
|
2639
|
+
if (area.x < box.x - 0.01 || area.y < box.y - 0.01 || area.x + area.width > box.x + box.width + 0.01 || area.y + area.height > box.y + box.height + 0.01) report(accepted, "part-outside-cell", "Timeline field extends outside its cell; increase the cell or paginate events.", Math.max(area.x + area.width - box.x - box.width, area.y + area.height - box.y - box.height));
|
|
2640
|
+
return accepted;
|
|
2641
|
+
};
|
|
2642
|
+
let y = box.y;
|
|
2643
|
+
for (const part of metadata) {
|
|
2644
|
+
const placed = place(part, box.x, y, box.width, "center");
|
|
2645
|
+
y += placed.box.height + 8 * scale;
|
|
2646
|
+
}
|
|
2647
|
+
const available = box.y + box.height - y, count = events.length, radius = Math.min(9 * scale, box.width / Math.max(2, count) / 5, Math.max(scale, available) * 0.04);
|
|
2648
|
+
if (arrangement === "alternating") {
|
|
2649
|
+
const width = box.width / Math.max(2, count), step = (box.width - width) / Math.max(1, count - 1), start = count === 1 ? box.x + box.width / 2 : box.x + width / 2;
|
|
2650
|
+
const lineY = y + Math.max(scale, available) * 0.46;
|
|
2651
|
+
events.forEach((fields2, index) => {
|
|
2652
|
+
const x = start + (rtl ? count - 1 - index : index) * step, top = index % 2 === 0 ? y : lineY + 24 * scale, bottom = index % 2 === 0 ? lineY - 24 * scale : box.y + box.height;
|
|
2653
|
+
markers.push({ path: eventPath(index), eventIndex: index, x, y: lineY, radius });
|
|
2654
|
+
let cursor = top;
|
|
2655
|
+
for (const part of fields2) {
|
|
2656
|
+
const placed = place(part, x - width / 2, cursor, width, "center");
|
|
2657
|
+
cursor += placed.box.height;
|
|
2658
|
+
if (cursor > bottom + 0.01) report(placed, "event-space", "Timeline event labels exceed their side of the connector; change the arrangement or paginate events.", cursor - bottom);
|
|
2659
|
+
}
|
|
2660
|
+
});
|
|
2661
|
+
} else {
|
|
2662
|
+
const x = rtl ? box.x + box.width - radius : box.x + radius, textX = rtl ? box.x : box.x + 24 * scale, width = box.width - 24 * scale;
|
|
2663
|
+
events.forEach((fields2, index) => {
|
|
2664
|
+
const top = y;
|
|
2665
|
+
let first2;
|
|
2666
|
+
for (const part of fields2) {
|
|
2667
|
+
const placed = place(part, textX, y, width, "left");
|
|
2668
|
+
first2 ??= placed;
|
|
2669
|
+
y += placed.box.height;
|
|
2670
|
+
}
|
|
2671
|
+
markers.push({ path: eventPath(index), eventIndex: index, x, y: top + Math.min(first2?.box.height ?? 2 * radius, 2 * radius) / 2, radius });
|
|
2672
|
+
y += 16 * scale;
|
|
2673
|
+
});
|
|
2674
|
+
}
|
|
2675
|
+
for (const marker of markers) if (marker.x - marker.radius < box.x - 0.01 || marker.y - marker.radius < box.y - 0.01 || marker.x + marker.radius > box.x + box.width + 0.01 || marker.y + marker.radius > box.y + box.height + 0.01) {
|
|
2676
|
+
diagnostics.push({ code: "text-overflow", reason: "event-space", path: marker.path, message: "Timeline marker has no usable space; increase the cell or paginate events." });
|
|
2677
|
+
score2 += 1;
|
|
2678
|
+
}
|
|
2679
|
+
const first = markers[0], last = markers.at(-1), connector = rtl ? { x1: Math.min(first.x, last.x), y1: first.y, x2: Math.max(first.x, last.x), y2: last.y } : { x1: first.x, y1: first.y, x2: last.x, y2: last.y };
|
|
2680
|
+
const candidate = { arrangement, parts, markers, connector, diagnostics, score: score2 };
|
|
2681
|
+
if (!selected || score2 < selected.score) selected = candidate;
|
|
2682
|
+
if (!diagnostics.length) break;
|
|
2683
|
+
}
|
|
2684
|
+
if (!selected.diagnostics.length) break;
|
|
2685
|
+
}
|
|
2686
|
+
const { score, ...result } = selected;
|
|
2687
|
+
if (result.diagnostics.length && options.overflow === "error") throw new OPFCompositionError(result.diagnostics);
|
|
2688
|
+
return { algorithm: "timeline-flow-v1", attempts, textMeasurement: options.textMeasurement ? "provided" : "estimated", textOutlines: outlines ? "provided" : "unavailable", ...result, overflow: result.diagnostics.length > 0 };
|
|
2689
|
+
}
|
|
2690
|
+
function fitCodeText(text2, box, size, minimum, step, measure, direction) {
|
|
2691
|
+
return fitSourceText(text2, box, size, minimum, step, measure, false, true, direction);
|
|
2692
|
+
}
|
|
2693
|
+
function fitSourceText(text2, box, size, minimum, step, measure, prose = false, snap = true, direction) {
|
|
2694
|
+
const directionAt = direction === "rtl" ? paragraphDirectionAt(text2, "rtl") : void 0;
|
|
2695
|
+
const layout = (fontSize) => {
|
|
2696
|
+
const tabWidth = measure(" ", fontSize) * 4;
|
|
2697
|
+
if (!Number.isFinite(tabWidth) || text2.includes(" ") && tabWidth <= 0) throw new RangeError("Text tabs require a positive finite measured space advance.");
|
|
2698
|
+
const measureLine = (start2, end2, record5 = false) => {
|
|
2699
|
+
let x = 0, offset = start2;
|
|
2700
|
+
const segments = [];
|
|
2701
|
+
for (const [index, segment] of text2.slice(start2, end2).split(" ").entries()) {
|
|
2702
|
+
if (index) {
|
|
2703
|
+
const next = (Math.floor(x / tabWidth + 1e-9) + 1) * tabWidth;
|
|
2704
|
+
if (record5) segments.push({ kind: "tab", start: offset, end: offset + 1, x, width: next - x });
|
|
2705
|
+
x = next;
|
|
2706
|
+
offset++;
|
|
2707
|
+
}
|
|
2708
|
+
if (segment) {
|
|
2709
|
+
const advance = measure(segment, fontSize);
|
|
2710
|
+
if (record5) segments.push({ kind: "text", start: offset, end: offset + segment.length, x, width: advance });
|
|
2711
|
+
x += advance;
|
|
2712
|
+
offset += segment.length;
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
return { width: x, segments };
|
|
2716
|
+
};
|
|
2717
|
+
const width = (start2, end2) => measureLine(start2, end2).width;
|
|
2718
|
+
const sourceLines = [];
|
|
2719
|
+
let start = 0, end = 0, hangFrom = -1;
|
|
2720
|
+
const push = (last, nextStart, boundary) => {
|
|
2721
|
+
const measured = measureLine(start, last, true);
|
|
2722
|
+
if (hangFrom >= 0 && hangFrom < last) measured.width = measureLine(start, hangFrom).width;
|
|
2723
|
+
hangFrom = -1;
|
|
2724
|
+
sourceLines.push({ start, end: last, nextStart, boundary, ...measured });
|
|
2725
|
+
start = nextStart;
|
|
2726
|
+
end = nextStart;
|
|
2727
|
+
};
|
|
2728
|
+
const tokens = prose ? /\r\n|\r|\n|[^\S\r\n\u00a0\u202f\ufeff]+|(?:[^\s]|\u00a0|\u202f|\ufeff)+/gu : /\r\n|\r|\n|[^\S\r\n]+|[^\s]+/gu;
|
|
2729
|
+
for (const token of text2.matchAll(tokens)) {
|
|
2730
|
+
if (token.index === void 0) throw new Error("Missing code token source offset.");
|
|
2731
|
+
const a = token.index, b = a + token[0].length;
|
|
2732
|
+
if (/^[\r\n]/.test(token[0])) {
|
|
2733
|
+
push(a, b, "hard");
|
|
2734
|
+
continue;
|
|
2735
|
+
}
|
|
2736
|
+
if (width(start, b) <= box.width + WRAP_TOLERANCE) {
|
|
2737
|
+
end = b;
|
|
2738
|
+
continue;
|
|
2739
|
+
}
|
|
2740
|
+
if (prose && end > start && /\S/u.test(text2.slice(start, end)) && HANGING_SPACE.test(token[0][0])) {
|
|
2741
|
+
if (hangFrom < 0) hangFrom = end;
|
|
2742
|
+
end = b;
|
|
2743
|
+
continue;
|
|
2744
|
+
}
|
|
2745
|
+
if (end > start && /\S/u.test(text2.slice(start, end))) push(a, a, "soft");
|
|
2746
|
+
if (width(start, b) <= box.width + WRAP_TOLERANCE) {
|
|
2747
|
+
end = b;
|
|
2748
|
+
continue;
|
|
2749
|
+
}
|
|
2750
|
+
if (prose && /[\u00a0\u202f\u2060\ufeff]/u.test(token[0])) {
|
|
2751
|
+
end = b;
|
|
2752
|
+
continue;
|
|
2753
|
+
}
|
|
2754
|
+
for (const segment of textSegments.segment(token[0])) {
|
|
2755
|
+
const next = a + segment.index + segment.segment.length;
|
|
2756
|
+
if (end > start && width(start, next) > box.width + 0.01) push(a + segment.index, a + segment.index, "soft");
|
|
2757
|
+
end = next;
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
push(end, end, "end");
|
|
2761
|
+
const lineHeight = fontSize * 1.22;
|
|
2762
|
+
return {
|
|
2763
|
+
lines: sourceLines.map((line) => text2.slice(line.start, line.end)),
|
|
2764
|
+
sourceLines,
|
|
2765
|
+
fontSize,
|
|
2766
|
+
lineHeight,
|
|
2767
|
+
tabSize: 4,
|
|
2768
|
+
tabWidth,
|
|
2769
|
+
...directionAt ? { directions: sourceLines.map((line) => directionAt(line.start)) } : {},
|
|
2770
|
+
overflow: sourceLines.length * lineHeight > box.height + 0.01 || sourceLines.some((line) => line.width > box.width + 0.01)
|
|
2771
|
+
};
|
|
2772
|
+
};
|
|
2773
|
+
return fitAtSizes(layout, size, minimum, step, snap);
|
|
2774
|
+
}
|
|
2775
|
+
function layoutCode(value, box, options = {}) {
|
|
2776
|
+
const shorthand = typeof value === "string", code = shorthand ? { source: value } : value;
|
|
2777
|
+
if (!code || Array.isArray(code) || typeof code.source !== "string" || [code.language, code.filename].some((field) => field !== void 0 && typeof field !== "string")) {
|
|
2778
|
+
throw new TypeError("Code content must be a string or source object with optional string language/filename.");
|
|
2779
|
+
}
|
|
2780
|
+
const scale = options.scale ?? 1, minimum = snapFontSizeUp((options.minFontSize ?? 16) * scale);
|
|
2781
|
+
if (![box.x, box.y, box.width, box.height, box.x + box.width, box.y + box.height, scale, minimum, Math.max(18 * scale, minimum) * 1.22].every(Number.isFinite) || box.width <= 0 || box.height <= 0 || scale <= 0 || minimum <= 0) {
|
|
2782
|
+
throw new RangeError("Code dimensions, scale and minimum font size must be finite and positive.");
|
|
2783
|
+
}
|
|
2784
|
+
if (options.overflow !== void 0 && !["warn", "error"].includes(options.overflow)) throw new RangeError("Invalid code overflow policy.");
|
|
2785
|
+
const path = options.path ?? "code", inner = { x: box.x + 18, y: box.y + 18, width: box.width - 36, height: box.height - 36 };
|
|
2786
|
+
const parts = [], diagnostics = [];
|
|
2787
|
+
const add = (role, text2, partPath, generated = false) => {
|
|
2788
|
+
const requestedStyle = { fontFamily: options.fonts?.code ?? "monospace", fontWeight: role === "body" ? 400 : 700, italic: false, path: partPath };
|
|
2789
|
+
const part = {
|
|
2790
|
+
role,
|
|
2791
|
+
path: partPath,
|
|
2792
|
+
text: text2,
|
|
2793
|
+
sources: generated ? [] : [{ path: partPath, start: 0, end: text2.length }],
|
|
2794
|
+
generated,
|
|
2795
|
+
box: { ...inner },
|
|
2796
|
+
requestedFontSize: gridFontSize((role === "body" ? 18 : 14) * scale, minimum),
|
|
2797
|
+
minFontSize: minimum,
|
|
2798
|
+
requestedStyle,
|
|
2799
|
+
style: resolveTextStyle({ ...requestedStyle }, options.textMeasurement)
|
|
2800
|
+
};
|
|
2801
|
+
parts.push(part);
|
|
2802
|
+
return part;
|
|
2803
|
+
};
|
|
2804
|
+
if (code.filename) add("filename", code.filename, `${path}.filename`);
|
|
2805
|
+
if (code.language) add("language", code.language, `${path}.language`);
|
|
2806
|
+
if (!parts.length) add("language", "code", path, true);
|
|
2807
|
+
const body = add("body", code.source, shorthand ? path : `${path}.source`), headers = parts.filter((part) => part !== body);
|
|
2808
|
+
const usable = (area) => [area.x, area.y, area.width, area.height].every(Number.isFinite) && area.width > 0 && area.height > 0;
|
|
2809
|
+
const fit = (part, area, size = part.requestedFontSize, floor = minimum) => usable(area) ? fitCodeText(part.text, area, size, floor, scale, textWidthMeasurer(part.style, options.textMeasurement)) : void 0;
|
|
2810
|
+
if (usable(inner)) {
|
|
2811
|
+
const combinations = headers.reduce((choices, part) => choices.flatMap((choice) => [.../* @__PURE__ */ new Set([part.requestedFontSize, minimum])].map((size) => [...choice, { part, size }])), [[]]);
|
|
2812
|
+
let selected;
|
|
2813
|
+
for (const combination of combinations) {
|
|
2814
|
+
let y = inner.y;
|
|
2815
|
+
const allocations = [];
|
|
2816
|
+
for (const [index, { part, size }] of combination.entries()) {
|
|
2817
|
+
const measured = fitCodeText(part.text, inner, size, size, scale, textWidthMeasurer(part.style, options.textMeasurement));
|
|
2818
|
+
const area2 = { ...inner, y, height: measured.lines.length * measured.lineHeight };
|
|
2819
|
+
allocations.push({ part, box: area2, fit: usable(area2) ? { ...measured, overflow: measured.sourceLines.some((line) => line.width > area2.width + 0.01) } : void 0 });
|
|
2820
|
+
y += area2.height + (index < headers.length - 1 ? 8 : 12);
|
|
2821
|
+
}
|
|
2822
|
+
const area = { ...inner, y, height: inner.y + inner.height - y };
|
|
2823
|
+
allocations.push({ part: body, box: area, fit: fit(body, area) });
|
|
2824
|
+
const overflow = allocations.some(({ box: box2, fit: fit2 }) => !fit2 || fit2.overflow || !usable(box2) || box2.y + box2.height > inner.y + inner.height + 0.01);
|
|
2825
|
+
const score = allocations.reduce((sum, { part, fit: fit2 }) => sum + (part.requestedFontSize - (fit2?.fontSize ?? minimum)) / scale, 0);
|
|
2826
|
+
if (!selected || !overflow && (selected.overflow || score < selected.score) || overflow && selected.overflow) selected = { allocations, score, overflow };
|
|
2827
|
+
}
|
|
2828
|
+
if (selected) for (const { part, box: box2, fit: fit2 } of selected.allocations) {
|
|
2829
|
+
part.box = box2;
|
|
2830
|
+
part.fit = fit2;
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
const report = (reason, part, roles, message) => diagnostics.push({ code: "text-overflow", reason, path: part.path, parts: roles, message });
|
|
2834
|
+
for (const part of parts) {
|
|
2835
|
+
if (!part.fit) report("invalid-part-box", part, [part.role], `Code ${part.role} has no usable space after metadata and insets; increase the cell or change the arrangement.`);
|
|
2836
|
+
else if (part.fit.overflow) report("text-fit", part, [part.role], `Code ${part.role} exceeds its space at the readability floor; increase its space or change the arrangement.`);
|
|
2837
|
+
const area = part.box;
|
|
2838
|
+
if (usable(area) && (area.x < box.x || area.y < box.y || area.x + area.width > box.x + box.width + 0.01 || area.y + area.height > box.y + box.height + 0.01)) {
|
|
2839
|
+
report("part-outside-cell", part, [part.role], `Code ${part.role} extends outside its cell; increase the cell or change the arrangement.`);
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
for (const [index, first] of parts.entries()) {
|
|
2843
|
+
const second = parts[index + 1];
|
|
2844
|
+
if (first.fit && second?.fit && first.box.y + first.fit.lines.length * first.fit.lineHeight > second.box.y + 0.01) {
|
|
2845
|
+
report("part-overlap", first, [first.role, second.role], "Code part line boxes overlap; do not accept this layout without more space.");
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
if (diagnostics.length && options.overflow === "error") throw new OPFCompositionError(diagnostics);
|
|
2849
|
+
return { algorithm: "code-flow-v1", textMeasurement: options.textMeasurement ? "provided" : "estimated", parts, diagnostics, overflow: diagnostics.length > 0 };
|
|
2850
|
+
}
|
|
2851
|
+
function fitRichText(input, box, requestedSize = 25, minFontSize = 16, options = { style: { fontFamily: "sans-serif", fontWeight: 400 } }) {
|
|
2852
|
+
if (![box.width, box.height, requestedSize, minFontSize].every((value) => Number.isFinite(value) && value > 0)) throw new RangeError("Rich text dimensions and font sizes must be finite and positive.");
|
|
2853
|
+
const layout = richTextLayouter(input, box, requestedSize, options, minFontSize);
|
|
2854
|
+
return fitAtSizes(layout, requestedSize, richMinimum(input, requestedSize, minFontSize));
|
|
2855
|
+
}
|
|
2856
|
+
function richMinimum(input, requested, minimum) {
|
|
2857
|
+
let floor = Math.min(requested, minimum), visible = false;
|
|
2858
|
+
for (const value of input) {
|
|
2859
|
+
const run = typeof value === "string" ? { text: value } : value;
|
|
2860
|
+
if (!run.text) continue;
|
|
2861
|
+
const ratio = (run.fontSize === void 0 ? 1 : run.fontSize * 4 / 3 / requested) * (run.superscript || run.subscript ? 0.7 : 1);
|
|
2862
|
+
floor = Math.max(floor, minimum / ratio);
|
|
2863
|
+
visible = true;
|
|
2864
|
+
}
|
|
2865
|
+
return visible ? floor : minimum;
|
|
2866
|
+
}
|
|
2867
|
+
function richTextLayouter(input, box, requestedSize, options, minimum = 0) {
|
|
2868
|
+
let offset = 0;
|
|
2869
|
+
const source = input.map((value, runIndex) => {
|
|
2870
|
+
const run = typeof value === "string" ? { text: value } : value;
|
|
2871
|
+
if (typeof run?.text !== "string" || run.fontSize !== void 0 && (!Number.isFinite(run.fontSize) || run.fontSize <= 0)) throw new RangeError("Rich text runs need text and a positive finite font size.");
|
|
2872
|
+
const start = offset;
|
|
2873
|
+
offset += run.text.length;
|
|
2874
|
+
const style = resolveTextStyle({ ...options.style, fontFamily: run.fontFamily ?? options.style.fontFamily, fontWeight: run.bold === void 0 ? options.style.fontWeight : run.bold ? 700 : 400, italic: run.italic ?? options.style.italic, path: options.style.path ? `${options.style.path}.${runIndex}` : void 0 }, options.textMeasurement);
|
|
2875
|
+
const marker = options.citationMarker && options.style.path && run.text ? options.citationMarker(`${options.style.path}.${runIndex}`) : void 0;
|
|
2876
|
+
return { run, runIndex, start, end: offset, style, ...marker ? { marker } : {} };
|
|
2877
|
+
});
|
|
2878
|
+
const whole = source.map((entry) => entry.run.text).join("");
|
|
2879
|
+
const paint = (raw) => raw >= minimum - 1e-6 ? gridFontSize(raw, minimum) : snapFontSizeDown(raw);
|
|
2880
|
+
const directionAt = options.direction === "rtl" ? paragraphDirectionAt(whole, "rtl") : void 0;
|
|
2881
|
+
const layout = (rawFontSize) => {
|
|
2882
|
+
const fontSize = paint(rawFontSize), ratio = rawFontSize / requestedSize;
|
|
2883
|
+
const fragments = (start, end) => {
|
|
2884
|
+
let x = 0;
|
|
2885
|
+
const result = [];
|
|
2886
|
+
for (const entry of source) {
|
|
2887
|
+
const a = Math.max(start, entry.start), b = Math.min(end, entry.end);
|
|
2888
|
+
if (b <= a) continue;
|
|
2889
|
+
const text2 = whole.slice(a, b), normalSize = entry.run.fontSize !== void 0 ? entry.run.fontSize * 4 / 3 * ratio : fontSize;
|
|
2890
|
+
const script = entry.run.superscript || entry.run.subscript;
|
|
2891
|
+
const size = script ? paint(normalSize * 0.7) : paint(normalSize), baselineShift = entry.run.superscript ? -normalSize * 0.35 : entry.run.subscript ? normalSize * 0.2 : 0;
|
|
2892
|
+
const measure = textWidthMeasurer(entry.style, options.textMeasurement);
|
|
2893
|
+
let cursor = 0;
|
|
2894
|
+
for (const [index, segment] of text2.split(" ").entries()) {
|
|
2895
|
+
if (index) {
|
|
2896
|
+
const tabStart = a + cursor, tabWidth = measure(" ", size) * 4;
|
|
2897
|
+
if (!Number.isFinite(tabWidth) || tabWidth <= 0) throw new RangeError("Rich text tabs require a positive finite measured space advance.");
|
|
2898
|
+
const next = (Math.floor(x / tabWidth + 1e-9) + 1) * tabWidth;
|
|
2899
|
+
result.push({ kind: "tab", text: " ", runIndex: entry.runIndex, start: tabStart - entry.start, end: tabStart - entry.start + 1, x, width: next - x, fontSize: size, baselineShift, style: entry.style, run: entry.run });
|
|
2900
|
+
x = next;
|
|
2901
|
+
cursor++;
|
|
2902
|
+
}
|
|
2903
|
+
if (segment) {
|
|
2904
|
+
const width2 = measure(segment, size), segmentStart = a + cursor;
|
|
2905
|
+
result.push({ text: segment, runIndex: entry.runIndex, start: segmentStart - entry.start, end: segmentStart - entry.start + segment.length, x, width: width2, fontSize: size, baselineShift, style: entry.style, run: entry.run });
|
|
2906
|
+
x += width2;
|
|
2907
|
+
cursor += segment.length;
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
if (entry.marker && b === entry.end) {
|
|
2911
|
+
const markerSize = Math.round(normalSize * CITATION_MARKER_SCALE * FONT_SIZE_GRID_PER_PX) / FONT_SIZE_GRID_PER_PX, width2 = measure(entry.marker, markerSize);
|
|
2912
|
+
result.push({ kind: "marker", text: entry.marker, runIndex: entry.runIndex, start: entry.run.text.length, end: entry.run.text.length, x, width: width2, fontSize: markerSize, nominalSize: normalSize, baselineShift: -normalSize * CITATION_MARKER_RAISE, style: entry.style, run: entry.run });
|
|
2913
|
+
x += width2;
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
return result;
|
|
2917
|
+
};
|
|
2918
|
+
const width = (start, end) => fragments(start, end).reduce((sum, fragment) => sum + fragment.width, 0);
|
|
2919
|
+
const ranges = [], hung = /* @__PURE__ */ new Map();
|
|
2920
|
+
let lineStart = 0, lineEnd = 0, hangFrom = -1;
|
|
2921
|
+
const push = (end) => {
|
|
2922
|
+
if (hangFrom >= 0) hung.set(ranges.length, hangFrom);
|
|
2923
|
+
hangFrom = -1;
|
|
2924
|
+
ranges.push({ start: lineStart, end });
|
|
2925
|
+
lineStart = end;
|
|
2926
|
+
lineEnd = end;
|
|
2927
|
+
};
|
|
2928
|
+
for (const match of whole.matchAll(/\r\n|\r|\n|[^\S\r\n]+|[^\s]+/gu)) {
|
|
2929
|
+
const start = match.index, end = start + match[0].length;
|
|
2930
|
+
if (/^[\r\n]/.test(match[0])) {
|
|
2931
|
+
push(start);
|
|
2932
|
+
lineStart = end;
|
|
2933
|
+
lineEnd = end;
|
|
2934
|
+
continue;
|
|
2935
|
+
}
|
|
2936
|
+
if (width(lineStart, end) <= box.width + WRAP_TOLERANCE) {
|
|
2937
|
+
lineEnd = end;
|
|
2938
|
+
continue;
|
|
2939
|
+
}
|
|
2940
|
+
if (HANGING_SPACE.test(match[0][0]) && lineEnd > lineStart && /\S/u.test(whole.slice(lineStart, lineEnd))) {
|
|
2941
|
+
if (hangFrom < 0) hangFrom = lineEnd;
|
|
2942
|
+
lineEnd = end;
|
|
2943
|
+
continue;
|
|
2944
|
+
}
|
|
2945
|
+
if (lineEnd > lineStart) push(start);
|
|
2946
|
+
if (width(start, end) <= box.width + WRAP_TOLERANCE) {
|
|
2947
|
+
lineEnd = end;
|
|
2948
|
+
continue;
|
|
2949
|
+
}
|
|
2950
|
+
for (const { segment, index } of textSegments.segment(match[0])) {
|
|
2951
|
+
const next = start + index + segment.length;
|
|
2952
|
+
if (lineEnd > lineStart && width(lineStart, next) > box.width + WRAP_TOLERANCE) push(start + index);
|
|
2953
|
+
lineEnd = next;
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
if (hangFrom >= 0) hung.set(ranges.length, hangFrom);
|
|
2957
|
+
ranges.push({ start: lineStart, end: lineEnd });
|
|
2958
|
+
let y = 0;
|
|
2959
|
+
const richLines = ranges.map((range, rangeIndex) => {
|
|
2960
|
+
const parts = fragments(range.start, range.end), shown = hung.get(rangeIndex) ?? range.end, ascent = Math.max(fontSize, ...parts.map((part) => part.fontSize - part.baselineShift)), descent = Math.max(fontSize * 0.22, ...parts.map((part) => part.fontSize * 0.22 + part.baselineShift));
|
|
2961
|
+
const height = ascent + descent, line = { fragments: parts, width: shown === range.end ? parts.reduce((sum, p) => sum + p.width, 0) : width(range.start, shown), y, baseline: y + ascent, height };
|
|
2962
|
+
y += height;
|
|
2963
|
+
return line;
|
|
2964
|
+
});
|
|
2965
|
+
if (options.uniformLineHeight) {
|
|
2966
|
+
const height = Math.max(...richLines.map((line) => line.height));
|
|
2967
|
+
y = 0;
|
|
2968
|
+
for (const line of richLines) {
|
|
2969
|
+
line.baseline += y - line.y;
|
|
2970
|
+
line.y = y;
|
|
2971
|
+
line.height = height;
|
|
2972
|
+
y += height;
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
return {
|
|
2976
|
+
lines: ranges.map((range) => whole.slice(range.start, range.end)),
|
|
2977
|
+
fontSize,
|
|
2978
|
+
lineHeight: Math.max(fontSize * 1.22, ...richLines.map((line) => line.height)),
|
|
2979
|
+
richLines,
|
|
2980
|
+
height: y,
|
|
2981
|
+
overflow: y > box.height + 0.01 || richLines.some((line) => line.width > box.width + 0.01),
|
|
2982
|
+
...directionAt ? { directions: ranges.map((range) => directionAt(range.start)) } : {}
|
|
2983
|
+
};
|
|
2984
|
+
};
|
|
2985
|
+
return layout;
|
|
2986
|
+
}
|
|
2987
|
+
var PICTURE_BULLET_SCALE = 0.65;
|
|
2988
|
+
function fitList(input, box, requestedSize = 25, minFontSize = 16, options = { style: { fontFamily: "sans-serif", fontWeight: 400 } }) {
|
|
2989
|
+
if (!Array.isArray(input) || ![box.width, box.height, requestedSize, minFontSize].every((value) => Number.isFinite(value) && value > 0)) throw new RangeError("List content, dimensions and font sizes must be valid.");
|
|
2990
|
+
const source = input.map((value, index) => {
|
|
2991
|
+
const object = typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2992
|
+
const text2 = object ? object.text : value, description = object?.description, level = object?.level ?? 0;
|
|
2993
|
+
if (!Number.isInteger(level) || level < 0) throw new RangeError("List levels must be nonnegative integers.");
|
|
2994
|
+
const runs = (value2) => typeof value2 === "string" ? [value2] : value2;
|
|
2995
|
+
if (!Array.isArray(runs(text2)) || description !== void 0 && !Array.isArray(runs(description))) throw new TypeError("List text and descriptions must be strings or text runs.");
|
|
2996
|
+
const base = options.style.path ? `${options.style.path}.${index}` : void 0;
|
|
2997
|
+
const plain = (value2) => typeof value2 === "string" ? value2 : value2.map((run) => typeof run === "string" ? run : run.text).join("");
|
|
2998
|
+
const direction = options.direction === "rtl" ? paragraphDirection(plain(text2), "rtl") : void 0;
|
|
2999
|
+
return { index, level, direction, value: text2, descriptionValue: description, textPath: base ? base + (object ? ".text" : "") : void 0, descriptionPath: base ? base + ".description" : void 0, runs: runs(text2), descriptionRuns: description === void 0 ? void 0 : runs(description) };
|
|
3000
|
+
});
|
|
3001
|
+
const numbers = options.numbering === void 0 ? void 0 : listNumbers(input, options.numbering);
|
|
3002
|
+
const markerStyleFor = (item) => {
|
|
3003
|
+
const first = item.runs.map((run) => typeof run === "string" ? { text: run } : run).find((run) => run.text);
|
|
3004
|
+
return { ...options.style, path: item.textPath, fontWeight: first?.bold === void 0 ? options.style.fontWeight : first.bold ? 700 : 400, italic: first?.italic ?? options.style.italic };
|
|
3005
|
+
};
|
|
3006
|
+
const layout = (fontSize) => {
|
|
3007
|
+
let y = box.y, overflow = false;
|
|
3008
|
+
const entries = [], lines = [], directions = [];
|
|
3009
|
+
const markerStyles = numbers ? source.map((item) => resolveTextStyle(markerStyleFor(item), options.textMeasurement)) : [];
|
|
3010
|
+
const markerWidths = numbers ? numbers.map((number, index) => textWidthMeasurer(markerStyles[index], options.textMeasurement)(number.text, fontSize)) : [];
|
|
3011
|
+
const hanging = numbers ? Math.max(fontSize * 1.1, Math.max(0, ...markerWidths) + fontSize * 0.3) : fontSize * 1.1;
|
|
3012
|
+
for (const item of source) {
|
|
3013
|
+
const rtl = item.direction === "rtl";
|
|
3014
|
+
const indent = hanging, offset = Math.min(box.width, item.level * indent), x = rtl ? box.x : box.x + offset + indent, width = Math.max(1, rtl ? box.width - offset - indent : box.x + box.width - x);
|
|
3015
|
+
if (offset + indent >= box.width) overflow = true;
|
|
3016
|
+
const textBox = { x, y, width, height: box.height };
|
|
3017
|
+
const style = { ...options.style, path: item.textPath };
|
|
3018
|
+
const text2 = richTextLayouter(item.runs, textBox, requestedSize, { ...options, style }, minFontSize)(fontSize);
|
|
3019
|
+
textBox.height = text2.height;
|
|
3020
|
+
lines.push(...text2.lines);
|
|
3021
|
+
if (options.direction === "rtl") directions.push(...text2.directions ?? []);
|
|
3022
|
+
y += text2.height;
|
|
3023
|
+
let description, descriptionBox;
|
|
3024
|
+
if (item.descriptionRuns !== void 0) {
|
|
3025
|
+
y += fontSize * 0.12;
|
|
3026
|
+
descriptionBox = { x, y, width, height: box.height };
|
|
3027
|
+
description = richTextLayouter(item.descriptionRuns, descriptionBox, requestedSize * 0.82, { ...options, style: { ...options.style, path: item.descriptionPath } }, minFontSize)(fontSize * 0.82);
|
|
3028
|
+
descriptionBox.height = description.height;
|
|
3029
|
+
lines.push(...description.lines);
|
|
3030
|
+
if (options.direction === "rtl") directions.push(...description.directions ?? []);
|
|
3031
|
+
y += description.height;
|
|
3032
|
+
}
|
|
3033
|
+
overflow ||= text2.overflow || !!description?.overflow;
|
|
3034
|
+
const number = numbers?.[item.index];
|
|
3035
|
+
const marker = { text: number ? number.text : ["\u2022", "\u25E6", "\u25AA"][item.level % 3], x: rtl ? box.x + box.width - offset : box.x + offset, y: textBox.y + (text2.richLines[0]?.baseline ?? fontSize), fontSize, style: number ? markerStyles[item.index] : resolveTextStyle(style, options.textMeasurement), indent, ...number ? { number, width: markerWidths[item.index] } : {}, ...rtl ? { anchor: "end" } : {} }, side = fontSize * PICTURE_BULLET_SCALE;
|
|
3036
|
+
entries.push({
|
|
3037
|
+
index: item.index,
|
|
3038
|
+
level: item.level,
|
|
3039
|
+
value: item.value,
|
|
3040
|
+
descriptionValue: item.descriptionValue,
|
|
3041
|
+
textPath: item.textPath,
|
|
3042
|
+
descriptionPath: item.descriptionPath,
|
|
3043
|
+
text: text2,
|
|
3044
|
+
textBox,
|
|
3045
|
+
description,
|
|
3046
|
+
descriptionBox,
|
|
3047
|
+
marker,
|
|
3048
|
+
...item.direction ? { direction: item.direction } : {},
|
|
3049
|
+
...options.bulletImage && !number ? { bulletImage: options.bulletImage, bulletBox: { x: rtl ? marker.x - side : marker.x, y: marker.y - side, width: side, height: side } } : {}
|
|
3050
|
+
});
|
|
3051
|
+
if (item.index < source.length - 1) y += fontSize * 0.28;
|
|
3052
|
+
}
|
|
3053
|
+
const height = y - box.y;
|
|
3054
|
+
return { listEntries: entries, lines, fontSize, lineHeight: fontSize * 1.22, height, overflow: overflow || height > box.height + 0.01, ...options.direction === "rtl" ? { directions } : {} };
|
|
3055
|
+
};
|
|
3056
|
+
let minimum = minFontSize;
|
|
3057
|
+
for (const item of source) {
|
|
3058
|
+
minimum = Math.max(minimum, richMinimum(item.runs, requestedSize, minFontSize));
|
|
3059
|
+
if (item.descriptionRuns !== void 0) minimum = Math.max(minimum, richMinimum(item.descriptionRuns, requestedSize * 0.82, minFontSize) / 0.82);
|
|
3060
|
+
}
|
|
3061
|
+
return fitAtSizes(layout, requestedSize, minimum);
|
|
3062
|
+
}
|
|
3063
|
+
function contentText(field, value) {
|
|
3064
|
+
if (field === "text" || headings.has(field)) return flatten(value);
|
|
3065
|
+
if (field === "items" || field === "bullets") return (Array.isArray(value) ? value : [value]).map((item) => {
|
|
3066
|
+
const entry = record4(item);
|
|
3067
|
+
return `\u2022 ${flatten(item)}${entry.description ? `
|
|
3068
|
+
${flatten(entry.description)}` : ""}`;
|
|
3069
|
+
}).join("\n");
|
|
3070
|
+
if (field === "code") return flatten(record4(value).source ?? value);
|
|
3071
|
+
if (field === "quote") return flatten(record4(value).text ?? value);
|
|
3072
|
+
return void 0;
|
|
3073
|
+
}
|
|
3074
|
+
function layoutTable(value, box, options = {}) {
|
|
3075
|
+
const scale = options.scale ?? 1, minimum = snapFontSizeUp((options.minFontSize ?? 16) * scale), requested = gridFontSize(15 * scale, minimum);
|
|
3076
|
+
if (![box.x, box.y, box.width, box.height, scale, minimum].every(Number.isFinite) || box.width <= 0 || box.height <= 0 || scale <= 0 || minimum <= 0) throw new RangeError("Table dimensions, scale and font sizes must be finite and positive.");
|
|
3077
|
+
const grid = tableGrid(value, options.path ?? "table");
|
|
3078
|
+
if (grid.issues.length) throw new RangeError(`${grid.issues[0].path}: ${grid.issues[0].message}`);
|
|
3079
|
+
const columnCount = grid.columnCount, cellWidth = box.width / columnCount;
|
|
3080
|
+
const cells = grid.rows.flat().map((cell) => {
|
|
3081
|
+
const padding = { top: 8, right: 10, bottom: 4, left: 10, ...cell.style.padding };
|
|
3082
|
+
const width = cellWidth * cell.colSpan - (padding.left + padding.right) * scale;
|
|
3083
|
+
const style = { fontFamily: options.fontFamily ?? "sans-serif", fontWeight: cell.header ? 700 : 400, italic: false, path: cell.valuePath };
|
|
3084
|
+
return { ...cell, padding, width, textStyle: style };
|
|
3085
|
+
});
|
|
3086
|
+
const cellMinimum = (cell) => Array.isArray(cell.value) ? richMinimum(cell.value, requested, minimum) : minimum;
|
|
3087
|
+
const fitCell = (cell, height, min) => {
|
|
3088
|
+
const textBox = { width: Math.max(scale, cell.width), height: Math.max(scale, height) };
|
|
3089
|
+
return Array.isArray(cell.value) ? fitRichText(cell.value, textBox, requested, min, { style: cell.textStyle, textMeasurement: options.textMeasurement, uniformLineHeight: true, direction: options.direction }) : fitText(flatten(cell.value), textBox, requested, min, textWidthMeasurer(resolveTextStyle(cell.textStyle, options.textMeasurement), options.textMeasurement), options.direction);
|
|
3090
|
+
};
|
|
3091
|
+
const textHeight = (fit) => "height" in fit ? fit.height : fit.lines.length * fit.lineHeight;
|
|
3092
|
+
const required = (cell, natural2) => {
|
|
3093
|
+
const floor = cellMinimum(cell), size = gridFontSize(natural2 ? Math.max(requested, floor) : floor, floor);
|
|
3094
|
+
const textBox = { width: Math.max(scale, cell.width), height: scale };
|
|
3095
|
+
const fit = Array.isArray(cell.value) ? richTextLayouter(cell.value, textBox, requested, { style: cell.textStyle, textMeasurement: options.textMeasurement, uniformLineHeight: true }, minimum)(size) : fitText(flatten(cell.value), textBox, size, size, textWidthMeasurer(resolveTextStyle(cell.textStyle, options.textMeasurement), options.textMeasurement));
|
|
3096
|
+
return textHeight(fit) + (cell.padding.top + cell.padding.bottom) * scale;
|
|
3097
|
+
};
|
|
3098
|
+
const natural = Array(grid.rowCount).fill(0), needed = Array(grid.rowCount).fill(0);
|
|
3099
|
+
for (const cell of cells) if (cell.rowSpan === 1) {
|
|
3100
|
+
natural[cell.row] = Math.max(natural[cell.row], required(cell, true));
|
|
3101
|
+
needed[cell.row] = Math.max(needed[cell.row], required(cell, false));
|
|
3102
|
+
}
|
|
3103
|
+
for (const cell of cells) if (cell.rowSpan > 1) for (const [heights2, preferredSize] of [[natural, true], [needed, false]]) {
|
|
3104
|
+
const current = heights2.slice(cell.row, cell.row + cell.rowSpan).reduce((a, b) => a + b, 0);
|
|
3105
|
+
const extra = Math.max(0, required(cell, preferredSize) - current) / cell.rowSpan;
|
|
3106
|
+
for (let r = cell.row; r < cell.row + cell.rowSpan; r++) heights2[r] += extra;
|
|
3107
|
+
}
|
|
3108
|
+
const preferred = natural.map((height) => Math.max(54 * scale, height));
|
|
3109
|
+
for (let r = 0; r < needed.length; r++) needed[r] = Math.min(preferred[r], needed[r]);
|
|
3110
|
+
const sum = (values) => values.reduce((a, b) => a + b, 0);
|
|
3111
|
+
const preferredTotal = sum(preferred), naturalTotal = sum(natural), neededTotal = sum(needed);
|
|
3112
|
+
const heights = preferred.map((height, r) => preferredTotal <= box.height ? height : naturalTotal <= box.height ? natural[r] + (height - natural[r]) * (box.height - naturalTotal) / Math.max(Number.EPSILON, preferredTotal - naturalTotal) : neededTotal <= box.height ? needed[r] + (natural[r] - needed[r]) * (box.height - neededTotal) / Math.max(Number.EPSILON, naturalTotal - neededTotal) : needed[r] * box.height / neededTotal);
|
|
3113
|
+
const ys = [box.y];
|
|
3114
|
+
for (const height of heights) ys.push(ys.at(-1) + height);
|
|
3115
|
+
let overflow = false;
|
|
3116
|
+
const rows2 = heights.map((height, r) => ({ box: { x: box.x, y: ys[r], width: box.width, height }, cells: [] }));
|
|
3117
|
+
for (const cell of cells) {
|
|
3118
|
+
const cellBox = { x: box.x + (options.direction === "rtl" ? columnCount - cell.column - cell.colSpan : cell.column) * cellWidth, y: ys[cell.row], width: cell.colSpan * cellWidth, height: ys[cell.row + cell.rowSpan] - ys[cell.row] };
|
|
3119
|
+
const available = cellBox.height - (cell.padding.top + cell.padding.bottom) * scale;
|
|
3120
|
+
const fit = fitCell(cell, available, minimum), height = textHeight(fit);
|
|
3121
|
+
const offset = cell.style.verticalAlign === "bottom" ? Math.max(0, available - height) : cell.style.verticalAlign === "middle" ? Math.max(0, (available - height) / 2) : 0;
|
|
3122
|
+
const textBox = { x: cellBox.x + cell.padding.left * scale, y: cellBox.y + cell.padding.top * scale + offset, width: Math.max(scale, cell.width), height: Math.max(scale, available - offset) };
|
|
3123
|
+
overflow ||= cell.width <= 0 || available <= 0 || fit.overflow;
|
|
3124
|
+
rows2[cell.row].cells.push({ value: cell.value, input: cell.input, path: cell.valuePath, sourcePath: cell.path, style: cell.style, row: cell.row, column: cell.column, rowSpan: cell.rowSpan, colSpan: cell.colSpan, header: cell.header, rich: Array.isArray(cell.value), box: cellBox, textBox, textStyle: resolveTextStyle(cell.textStyle, options.textMeasurement), fit, ...fit.directions ? { direction: fit.directions[0] } : {} });
|
|
3125
|
+
}
|
|
3126
|
+
return { rows: rows2, columnCount, height: sum(heights), overflow };
|
|
3127
|
+
}
|
|
3128
|
+
function tableOverflows(value, box, scale, settings, options, path) {
|
|
3129
|
+
return box.width <= 0 || box.height <= 0 || layoutTable(value, box, { scale, minFontSize: settings.minFontSize, fontFamily: options.fonts?.body, textMeasurement: options.textMeasurement, path }).overflow;
|
|
3130
|
+
}
|
|
3131
|
+
function flatten(value) {
|
|
3132
|
+
if (value == null) return "";
|
|
3133
|
+
if (typeof value !== "object") return String(value);
|
|
3134
|
+
if (Array.isArray(value)) return value.map(flatten).join("");
|
|
3135
|
+
const item = record4(value);
|
|
3136
|
+
return flatten(item.text ?? item.value ?? item.source ?? JSON.stringify(value));
|
|
3137
|
+
}
|
|
3138
|
+
function regionParts(key) {
|
|
3139
|
+
const span = (part, order) => {
|
|
3140
|
+
const indices = part.split("+").map((value) => order.indexOf(value));
|
|
3141
|
+
return indices.every((index) => index >= 0) ? indices : void 0;
|
|
3142
|
+
};
|
|
3143
|
+
const parts = key.split(":");
|
|
3144
|
+
if (parts.length === 2) {
|
|
3145
|
+
const r = span(parts[0], rows), c = span(parts[1], columns);
|
|
3146
|
+
if (r && c) return [r, c];
|
|
3147
|
+
} else if (parts.length === 1) {
|
|
3148
|
+
const r = span(key, rows), c = span(key, columns);
|
|
3149
|
+
if (r) return [r, [0, 1, 2]];
|
|
3150
|
+
if (c) return [[0, 1, 2], c];
|
|
3151
|
+
}
|
|
3152
|
+
return void 0;
|
|
3153
|
+
}
|
|
3154
|
+
function regionBox(parts, area, requestedGap) {
|
|
3155
|
+
const gap = Math.min(requestedGap, area.width / 6, area.height / 6);
|
|
3156
|
+
const cw = (area.width - gap * 2) / 3, ch = (area.height - gap * 2) / 3;
|
|
3157
|
+
const [r, c] = parts;
|
|
3158
|
+
return { x: area.x + Math.min(...c) * (cw + gap), y: area.y + Math.min(...r) * (ch + gap), width: (Math.max(...c) - Math.min(...c) + 1) * (cw + gap) - gap, height: (Math.max(...r) - Math.min(...r) + 1) * (ch + gap) - gap };
|
|
3159
|
+
}
|
|
3160
|
+
function gridGeometry(count, area, cols, gap, weights, vertical) {
|
|
3161
|
+
const rowCount = Math.ceil(count / cols);
|
|
3162
|
+
gap = Math.min(gap, area.width / (cols * 2), area.height / (rowCount * 2));
|
|
3163
|
+
const tracks = (length, total, weighted) => {
|
|
3164
|
+
const values = Array.from({ length }, (_, i) => weighted ? weights[i] ?? 1 : 1);
|
|
3165
|
+
const sum = values.reduce((a, b) => a + b, 0);
|
|
3166
|
+
let cursor = 0;
|
|
3167
|
+
return values.map((weight) => {
|
|
3168
|
+
const size = (total - gap * (length - 1)) * weight / sum;
|
|
3169
|
+
const track = { offset: cursor, size };
|
|
3170
|
+
cursor += size + gap;
|
|
3171
|
+
return track;
|
|
3172
|
+
});
|
|
3173
|
+
};
|
|
3174
|
+
const xs = tracks(cols, area.width, !vertical), ys = tracks(rowCount, area.height, vertical);
|
|
3175
|
+
const boxes = Array.from({ length: count }, (_, i) => ({ x: area.x + xs[i % cols].offset, y: area.y + ys[Math.floor(i / cols)].offset, width: xs[i % cols].size, height: ys[Math.floor(i / cols)].size }));
|
|
3176
|
+
return { boxes, columns: xs, rows: ys, gap };
|
|
3177
|
+
}
|
|
3178
|
+
function gridBoxes(count, area, cols, gap, weights, vertical) {
|
|
3179
|
+
return gridGeometry(count, area, cols, gap, weights, vertical).boxes;
|
|
3180
|
+
}
|
|
3181
|
+
function assertComposition(value) {
|
|
3182
|
+
if (value.mode !== void 0 && !["auto", "grid", "row", "column"].includes(value.mode)) throw new RangeError("Unknown composition mode.");
|
|
3183
|
+
for (const [key, min, max] of [["gap", 0, 0.1], ["padding", 0, 0.2], ["minFontSize", 8, 32], ["columns", 1, 12]]) {
|
|
3184
|
+
const number = value[key];
|
|
3185
|
+
if (number !== void 0 && (!Number.isFinite(number) || number < min || number > max || key === "columns" && !Number.isInteger(number))) throw new RangeError(`Invalid composition.${key}.`);
|
|
3186
|
+
}
|
|
3187
|
+
if (value.weights !== void 0 && (!Array.isArray(value.weights) || !value.weights.length || value.weights.length > 12 || value.weights.some((weight) => !Number.isFinite(weight) || weight <= 0 || weight > 100))) throw new RangeError("Invalid composition.weights.");
|
|
3188
|
+
if (value.overflow !== void 0 && !["warn", "error"].includes(value.overflow)) throw new RangeError("Invalid composition.overflow.");
|
|
3189
|
+
}
|
|
3190
|
+
function composeSlide(input, options = {}) {
|
|
3191
|
+
const slide = record4(input), layout = record4(options.layout);
|
|
3192
|
+
const width = options.width ?? 1280, height = options.height ?? 720;
|
|
3193
|
+
if (![width, height].every((value) => Number.isFinite(value) && value > 0)) throw new RangeError("Canvas dimensions must be finite and positive.");
|
|
3194
|
+
const scale = Math.min(width, height) / 720;
|
|
3195
|
+
const deckDirection = options.direction ?? resolveSlideDirection(options.presentation, options.slideIndex);
|
|
3196
|
+
const rtl = deckDirection === "rtl", textDirection = rtl ? "rtl" : void 0;
|
|
3197
|
+
const hasCards = record4(slide.design).contentBox ?? options.contentBox ?? false;
|
|
3198
|
+
const composition = { ...record4(layout.composition), ...record4(slide.composition) };
|
|
3199
|
+
assertComposition(composition);
|
|
3200
|
+
const padding = (composition.padding ?? 0.08) * Math.min(width, height);
|
|
3201
|
+
const gap = (composition.gap ?? 1 / 30) * Math.min(width, height);
|
|
3202
|
+
const minSize = snapFontSizeUp((composition.minFontSize ?? 16) * scale);
|
|
3203
|
+
const rasterPadding = (options.textRasterPadding ?? 1) * scale;
|
|
3204
|
+
if (!Number.isFinite(rasterPadding) || rasterPadding < 0) throw new RangeError("Text raster padding must be finite and nonnegative.");
|
|
3205
|
+
let coverGroup = false;
|
|
3206
|
+
const alignmentFor = (field) => (field === "title" || coverGroup && (field === "tag" || field === "subtitle") && record4(slide.design).contentAlignment === void 0 ? record4(slide.design).titleAlignment ?? options.titleAlignment : record4(slide.design).contentAlignment ?? options.contentAlignment) ?? "left";
|
|
3207
|
+
const styleFor = (field, path2) => resolveTextStyle({
|
|
3208
|
+
fontFamily: (field === "title" ? options.fonts?.heading : field === "code" ? options.fonts?.code : field === "tag" ? options.fonts?.accent ?? options.fonts?.body : options.fonts?.body) ?? (field === "code" ? "monospace" : "sans-serif"),
|
|
3209
|
+
fontWeight: field === "title" ? 700 : 400,
|
|
3210
|
+
path: path2
|
|
3211
|
+
}, options.textMeasurement);
|
|
3212
|
+
const slideDesign = record4(slide.design), deckDesign = record4(record4(options.presentation).design);
|
|
3213
|
+
const designHint = (field) => slideDesign[field] !== void 0 ? { value: slideDesign[field], path: `slides.${options.slideIndex ?? 0}.design.${field}` } : deckDesign[field] !== void 0 ? { value: deckDesign[field], path: `design.${field}` } : void 0;
|
|
3214
|
+
const listBullet = designHint("listBullet");
|
|
3215
|
+
const bulletImage = listBullet?.value === "image" ? (() => {
|
|
3216
|
+
const resolved = resolveLogo(options.presentation, slide, { slot: "icon", onDark: options.darkBackground, slideIndex: options.slideIndex });
|
|
3217
|
+
return resolved ? { source: resolved.source, path: resolved.path } : void 0;
|
|
3218
|
+
})() : void 0;
|
|
3219
|
+
const widthFor = (field, path2) => textWidthMeasurer(styleFor(field, path2), options.textMeasurement);
|
|
3220
|
+
const citations = slideCitations(slide, options.slideIndex ?? 0, options.presentation);
|
|
3221
|
+
const citationMarker = citations ? (runPath) => citations.markers.get(runPath) : void 0;
|
|
3222
|
+
const richOptions = (style) => ({ style, textMeasurement: options.textMeasurement, ...citationMarker ? { citationMarker } : {}, ...rtl ? { direction: "rtl" } : {} });
|
|
3223
|
+
const fitPlacedText = (field, value, text2, box, size, minimum, path2, explicitAlignment) => {
|
|
3224
|
+
const style = styleFor(field, path2), rich = field === "text" && Array.isArray(value);
|
|
3225
|
+
if (!options.textMeasurement?.outlineBounds) return rich ? fitRichText(value, box, size, minimum, richOptions(style)) : fitText(text2, box, size, minimum, textWidthMeasurer(style, options.textMeasurement), textDirection);
|
|
3226
|
+
const alignment = explicitAlignment ?? alignmentFor(field);
|
|
3227
|
+
const richLayout = rich ? richTextLayouter(value, box, size, richOptions(style), minimum) : void 0;
|
|
3228
|
+
const measure = textWidthMeasurer(style, options.textMeasurement), floor = snapFontSizeUp(rich ? richMinimum(value, size, minimum) : minimum), start = Math.max(size, floor);
|
|
3229
|
+
for (let trial = 0; trial <= 64; trial++) {
|
|
3230
|
+
const raw = start - trial * scale, last = trial === 64 || raw <= floor, fontSize = last ? floor : gridFontSize(raw, floor);
|
|
3231
|
+
const fit = richLayout ? richLayout(fontSize) : fitText(text2, box, fontSize, fontSize, measure, textDirection);
|
|
3232
|
+
const richLines = "richLines" in fit ? fit.richLines : void 0;
|
|
3233
|
+
const lines = richLines ? richLines.map((line) => {
|
|
3234
|
+
let outline = null;
|
|
3235
|
+
for (const fragment of line.fragments) {
|
|
3236
|
+
if (fragment.kind === "tab") continue;
|
|
3237
|
+
const bounds = measureTextOutline(fragment.text, fragment.fontSize, fragment.style, options.textMeasurement);
|
|
3238
|
+
if (!bounds) continue;
|
|
3239
|
+
const next = { ...bounds, x: fragment.x + bounds.x, y: fragment.baselineShift + bounds.y };
|
|
3240
|
+
if (!outline) outline = next;
|
|
3241
|
+
else {
|
|
3242
|
+
const right = Math.max(outline.x + outline.width, next.x + next.width), bottom = Math.max(outline.y + outline.height, next.y + next.height);
|
|
3243
|
+
outline.x = Math.min(outline.x, next.x);
|
|
3244
|
+
outline.y = Math.min(outline.y, next.y);
|
|
3245
|
+
outline.width = right - outline.x;
|
|
3246
|
+
outline.height = bottom - outline.y;
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
return { width: line.width, y: line.y, baseline: line.baseline, height: line.height, outline };
|
|
3250
|
+
}) : fit.sourceLines.map((line, index) => {
|
|
3251
|
+
let outline = null;
|
|
3252
|
+
for (const segment of line.segments) {
|
|
3253
|
+
if (segment.kind === "tab") continue;
|
|
3254
|
+
const bounds = measureTextOutline(text2.slice(segment.start, segment.end), fontSize, style, options.textMeasurement);
|
|
3255
|
+
if (!bounds) continue;
|
|
3256
|
+
const next = { ...bounds, x: segment.x + bounds.x };
|
|
3257
|
+
if (!outline) outline = next;
|
|
3258
|
+
else {
|
|
3259
|
+
const right = Math.max(outline.x + outline.width, next.x + next.width), bottom = Math.max(outline.y + outline.height, next.y + next.height);
|
|
3260
|
+
outline.x = Math.min(outline.x, next.x);
|
|
3261
|
+
outline.y = Math.min(outline.y, next.y);
|
|
3262
|
+
outline.width = right - outline.x;
|
|
3263
|
+
outline.height = bottom - outline.y;
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
return { width: line.width, y: index * fit.lineHeight, baseline: fontSize + index * fit.lineHeight, height: fit.lineHeight, outline };
|
|
3267
|
+
});
|
|
3268
|
+
const placement = placeTextLines(lines, box, alignment, rasterPadding, fit.directions), result = { ...fit, placement, overflow: fit.overflow || placement.overflow };
|
|
3269
|
+
if (!result.overflow || last) return result;
|
|
3270
|
+
}
|
|
3271
|
+
throw new Error("Text placement did not evaluate its bounded floor trial.");
|
|
3272
|
+
};
|
|
3273
|
+
const fitContent = (field, value, text2, box, size, minimum, path2, numbering) => field === "text" ? fitPlacedText(field, value, text2, box, size, minimum, path2) : field === "items" || field === "bullets" ? fitList(value, box, size, minimum, { ...richOptions(styleFor(field, path2)), ...numbering !== void 0 ? { numbering } : bulletImage ? { bulletImage } : {} }) : fitText(text2, box, size, minimum, widthFor(field, path2), field === "code" ? void 0 : textDirection);
|
|
3274
|
+
const path = `slides.${options.slideIndex ?? 0}`;
|
|
3275
|
+
const annotationFit = (value, box, requestedSize, minFontSize, fitPath, alignment) => fitPlacedText("text", value, annotationText(value), box, requestedSize, minFontSize, fitPath, alignment);
|
|
3276
|
+
const annotationOptions = { scale, minFontSize: minSize, fit: annotationFit, textStyle: (fitPath) => styleFor("text", fitPath) };
|
|
3277
|
+
const slideImageDiagnostics = [];
|
|
3278
|
+
const slideImage = resolveSlideImage(slide, layout, options.presentation, width, height, path, padding, scale, slideImageDiagnostics, rtl);
|
|
3279
|
+
const area = { left: 0, top: 0, right: width, bottom: height };
|
|
3280
|
+
if (slideImage?.position === "left") area.left = slideImage.region.width;
|
|
3281
|
+
else if (slideImage?.position === "right") area.right = slideImage.region.x;
|
|
3282
|
+
else if (slideImage?.position === "top") area.top = slideImage.region.height;
|
|
3283
|
+
else if (slideImage?.position === "bottom") area.bottom = slideImage.region.y;
|
|
3284
|
+
const items = [], diagnostics = [];
|
|
3285
|
+
const measuredFurniture = layoutFurniture(slide, options);
|
|
3286
|
+
const furniture = measuredFurniture.configured || measuredFurniture.diagnostics.length ? measuredFurniture : void 0;
|
|
3287
|
+
if (furniture) diagnostics.push(...furniture.diagnostics);
|
|
3288
|
+
let bodyBottom = Math.min(area.bottom - padding, furniture && furniture.footerTop < height ? furniture.footerTop - gap * 0.5 : height - padding);
|
|
3289
|
+
const headingTop = Math.max(area.top + padding, furniture?.headerBottom ? furniture.headerBottom + gap * 0.5 : padding);
|
|
3290
|
+
let footnotes;
|
|
3291
|
+
if (citations) {
|
|
3292
|
+
footnotes = layoutFootnotes(citations, { ...annotationOptions, x: area.left + padding, width: area.right - area.left - padding * 2, bottom: bodyBottom, maxHeight: Math.max(minSize * 1.22 + 2 * scale, (bodyBottom - headingTop) * FOOTNOTE_MAX_RATIO), path });
|
|
3293
|
+
diagnostics.push(...footnotes.diagnostics);
|
|
3294
|
+
bodyBottom = footnotes.box.y - gap * 0.5;
|
|
3295
|
+
}
|
|
3296
|
+
const layoutPlaceholders = Array.isArray(layout.placeholders) ? layout.placeholders : [];
|
|
3297
|
+
const headingOnlyLayout = COVER_LAYOUT_IDS.has(String(layout.id ?? "")) || layoutPlaceholders.length > 0 && layoutPlaceholders.every((placeholder) => headings.has(placeholder.type ?? ""));
|
|
3298
|
+
const regions = Object.keys(slide).filter((key) => regionParts(key)).sort();
|
|
3299
|
+
const emptyPayload = (value) => value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
|
|
3300
|
+
const emptyHost = (host) => emptyPayload(host.blocks) && fields.every((field) => emptyPayload(host[field]));
|
|
3301
|
+
const hasBodyPayload = regions.some((key) => !emptyHost(record4(slide[key]))) || !emptyPayload(slide.blocks) || fields.some((field) => !emptyPayload(slide[field]));
|
|
3302
|
+
const isCover = !hasBodyPayload && (headingOnlyLayout || !layout.id && !layoutPlaceholders.some((placeholder) => !headings.has(placeholder.type ?? "")));
|
|
3303
|
+
coverGroup = isCover;
|
|
3304
|
+
let logo;
|
|
3305
|
+
if (isCover) {
|
|
3306
|
+
const resolved = resolveLogo(options.presentation, slide, { slot: "lockup", onDark: options.darkBackground, slideIndex: options.slideIndex });
|
|
3307
|
+
if (resolved) {
|
|
3308
|
+
const logoHeight = 56 * scale;
|
|
3309
|
+
const logoWidth = Math.max(scale, Math.min(4 * logoHeight, area.right - area.left - 2 * padding));
|
|
3310
|
+
const box = { x: round2(rtl ? area.right - padding - logoWidth : area.left + padding), y: round2(headingTop), width: round2(logoWidth), height: round2(logoHeight) };
|
|
3311
|
+
logo = { box, slot: "lockup", path: resolved.path, source: resolved.source, variant: resolved.variant, anchor: rtl ? "right" : "left" };
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
let y = logo ? logo.box.y + logo.box.height + gap : headingTop;
|
|
3315
|
+
const headingItems = [];
|
|
3316
|
+
for (const field of ["tag", "title", "subtitle"]) {
|
|
3317
|
+
if (!slide[field]) continue;
|
|
3318
|
+
const requested = (field === "title" ? 54 : field === "tag" ? 16 : 25) * scale;
|
|
3319
|
+
const maxHeight = Math.max(height * (field === "title" ? 0.26 : field === "subtitle" ? 0.12 : 0.045), minSize * 1.22 + 2 * rasterPadding);
|
|
3320
|
+
const box = { x: area.left + padding, y, width: area.right - area.left - padding * 2, height: maxHeight };
|
|
3321
|
+
const text2 = fitPlacedText(field, slide[field], String(slide[field]), box, requested, minSize, `${path}.${field}`);
|
|
3322
|
+
box.height = Math.min(maxHeight, Math.max(text2.lines.length * text2.lineHeight, text2.placement?.height ?? 0));
|
|
3323
|
+
if (furniture && box.y + box.height > bodyBottom + 0.01) diagnostics.push({ code: "text-overflow", path: `${path}.${field}`, message: "Repeated furniture leaves too little room for this heading. Change the header/footer or slide design." });
|
|
3324
|
+
const item = { path: `${path}.${field}`, field, type: "text", value: slide[field], payload: { text: slide[field] }, box, text: text2, textStyle: styleFor(field, `${path}.${field}`), composition, alignment: alignmentFor(field) };
|
|
3325
|
+
headingItems.push(item);
|
|
3326
|
+
items.push(item);
|
|
3327
|
+
y += box.height + gap * 0.5;
|
|
3328
|
+
}
|
|
3329
|
+
if (headingItems.length) {
|
|
3330
|
+
if (isCover) {
|
|
3331
|
+
const last = headingItems[headingItems.length - 1];
|
|
3332
|
+
const shift = Math.max(0, (bodyBottom - (last.box.y + last.box.height)) / 2);
|
|
3333
|
+
if (shift) for (const item of headingItems) {
|
|
3334
|
+
item.box.y += shift;
|
|
3335
|
+
if (item.text) item.text = translateTextFit(item.text, shift);
|
|
3336
|
+
}
|
|
3337
|
+
y = last.box.y + last.box.height + gap;
|
|
3338
|
+
} else y += gap * 0.5;
|
|
3339
|
+
}
|
|
3340
|
+
const contentBox = { x: area.left + padding, y, width: area.right - area.left - padding * 2, height: Math.max(scale, bodyBottom - y) };
|
|
3341
|
+
const collect = (host, basePath, depth = 0, ancestors = []) => {
|
|
3342
|
+
if (Array.isArray(host.blocks)) {
|
|
3343
|
+
if (depth >= MAX_COMPOSITION_DEPTH || ancestors.includes(host)) throw new RangeError(`Content groups must be acyclic and nest at most ${MAX_COMPOSITION_DEPTH} levels.`);
|
|
3344
|
+
const settings = record4(host.composition);
|
|
3345
|
+
assertComposition(settings);
|
|
3346
|
+
return [{
|
|
3347
|
+
field: "blocks",
|
|
3348
|
+
type: "group",
|
|
3349
|
+
value: host.blocks,
|
|
3350
|
+
path: basePath,
|
|
3351
|
+
payload: host,
|
|
3352
|
+
composition: settings,
|
|
3353
|
+
children: host.blocks.flatMap((block, index) => collect(record4(block), `${basePath}.blocks.${index}`, depth + 1, [...ancestors, host]))
|
|
3354
|
+
}];
|
|
3355
|
+
}
|
|
3356
|
+
const captioned = host.caption !== void 0 ? fields.filter((field) => host[field] !== void 0 && CAPTIONABLE_FIELDS.includes(field)) : [];
|
|
3357
|
+
return fields.filter((field) => host[field] !== void 0).map((field) => ({
|
|
3358
|
+
field,
|
|
3359
|
+
type: host.type ?? kind(field),
|
|
3360
|
+
value: host[field],
|
|
3361
|
+
path: `${basePath}.${field}`,
|
|
3362
|
+
payload: { type: host.type ?? kind(field), [field]: host[field], ...host.numbering !== void 0 && (field === "items" || field === "bullets") ? { numbering: host.numbering } : {} },
|
|
3363
|
+
...captioned.length === 1 && captioned[0] === field ? { caption: host.caption, captionPath: `${basePath}.caption` } : {}
|
|
3364
|
+
}));
|
|
3365
|
+
};
|
|
3366
|
+
const regionsInReadingOrder = visualReadingOrder(regions.map((key) => ({ key, box: regionBox(regionParts(key), contentBox, gap) }))).map((entry) => entry.key);
|
|
3367
|
+
const pending = regions.length ? regionsInReadingOrder.flatMap((key) => collect(record4(slide[key]), `${path}.${key}`).map((item) => ({ ...item, region: regionParts(key) }))) : Array.isArray(slide.blocks) ? slide.blocks.flatMap((block, index) => collect(record4(block), `${path}.blocks.${index}`)) : collect(slideImage?.replacesContent ? { ...slide, image: void 0 } : slide, path);
|
|
3368
|
+
const groups = [], flows = [];
|
|
3369
|
+
const decisions = options.explain ? [] : void 0;
|
|
3370
|
+
const inheritedSettings = (parent, own = {}) => ({
|
|
3371
|
+
minFontSize: parent.minFontSize,
|
|
3372
|
+
...own,
|
|
3373
|
+
overflow: parent.overflow === "error" ? "error" : own.overflow ?? parent.overflow
|
|
3374
|
+
});
|
|
3375
|
+
const inset = (box, settings) => {
|
|
3376
|
+
const amount = (settings.padding ?? 0) * Math.min(box.width, box.height);
|
|
3377
|
+
return { x: box.x + amount, y: box.y + amount, width: box.width - amount * 2, height: box.height - amount * 2 };
|
|
3378
|
+
};
|
|
3379
|
+
const acceptedBox = (box) => ({ x: round2(box.x), y: round2(box.y), width: round2(box.width), height: round2(box.height) });
|
|
3380
|
+
const payloadBox = (box) => {
|
|
3381
|
+
if (!hasCards) return box;
|
|
3382
|
+
const frame = acceptedBox(box), padding2 = Math.min(12 * scale, frame.width / 4, frame.height / 4);
|
|
3383
|
+
return acceptedBox({ x: frame.x + padding2, y: frame.y + padding2, width: frame.width - 2 * padding2, height: frame.height - 2 * padding2 });
|
|
3384
|
+
};
|
|
3385
|
+
const measureQuote = (node, box, settings) => layoutQuote(node.value, acceptedBox(box), {
|
|
3386
|
+
fonts: options.fonts,
|
|
3387
|
+
textMeasurement: options.textMeasurement,
|
|
3388
|
+
scale,
|
|
3389
|
+
minFontSize: settings.minFontSize,
|
|
3390
|
+
path: node.path,
|
|
3391
|
+
direction: textDirection
|
|
3392
|
+
});
|
|
3393
|
+
const measureCode = (node, box, settings) => layoutCode(node.value, acceptedBox(box), {
|
|
3394
|
+
fonts: options.fonts,
|
|
3395
|
+
textMeasurement: options.textMeasurement,
|
|
3396
|
+
scale,
|
|
3397
|
+
minFontSize: settings.minFontSize,
|
|
3398
|
+
path: node.path
|
|
3399
|
+
});
|
|
3400
|
+
const measureMetric = (node, box, settings) => layoutMetric(node.value, acceptedBox(box), {
|
|
3401
|
+
fonts: options.fonts,
|
|
3402
|
+
textMeasurement: options.textMeasurement,
|
|
3403
|
+
scale,
|
|
3404
|
+
minFontSize: settings.minFontSize,
|
|
3405
|
+
path: node.path,
|
|
3406
|
+
textRasterPadding: options.textRasterPadding,
|
|
3407
|
+
align: alignmentFor("metric"),
|
|
3408
|
+
direction: textDirection
|
|
3409
|
+
});
|
|
3410
|
+
const measureTimeline = (node, box, settings) => layoutTimeline(node.value, acceptedBox(box), {
|
|
3411
|
+
fonts: options.fonts,
|
|
3412
|
+
textMeasurement: options.textMeasurement,
|
|
3413
|
+
scale,
|
|
3414
|
+
minFontSize: settings.minFontSize,
|
|
3415
|
+
path: node.path,
|
|
3416
|
+
textRasterPadding: options.textRasterPadding,
|
|
3417
|
+
direction: textDirection
|
|
3418
|
+
});
|
|
3419
|
+
const captionOf = (node, box) => node.caption !== void 0 && node.captionPath ? layoutCaption(node.caption, box, node.captionPath, annotationOptions) : void 0;
|
|
3420
|
+
const leafScore = (node, box, settings, penalties) => {
|
|
3421
|
+
box = payloadBox(box);
|
|
3422
|
+
const captioned = captionOf(node, box);
|
|
3423
|
+
if (captioned) box = captioned.mediaBox;
|
|
3424
|
+
const text2 = contentText(node.field, node.value);
|
|
3425
|
+
let score = Math.abs(Math.log(box.width / box.height / 1.6)) + (captioned?.overflow ? 1e3 : 0);
|
|
3426
|
+
if (penalties) {
|
|
3427
|
+
penalties.cellProportions += Math.abs(Math.log(box.width / box.height / 1.6));
|
|
3428
|
+
if (captioned?.overflow) penalties.textOverflow += 1e3;
|
|
3429
|
+
}
|
|
3430
|
+
if (node.field === "quote" || node.field === "code" || node.field === "metric" || node.field === "timeline") {
|
|
3431
|
+
const internal = node.field === "quote" ? measureQuote(node, box, settings) : node.field === "code" ? measureCode(node, box, settings) : node.field === "metric" ? measureMetric(node, box, settings) : measureTimeline(node, box, settings);
|
|
3432
|
+
const reduction = internal.parts.reduce((sum, part) => sum + (part.fit ? (part.requestedFontSize - part.fit.fontSize) / scale : 0), 0);
|
|
3433
|
+
score += reduction + (internal.overflow ? 1e3 : 0);
|
|
3434
|
+
if (penalties) {
|
|
3435
|
+
penalties.fontReduction += reduction;
|
|
3436
|
+
penalties.textOverflow += internal.overflow ? 1e3 : 0;
|
|
3437
|
+
}
|
|
3438
|
+
} else if (text2) {
|
|
3439
|
+
const fit = fitContent(node.field, node.value, text2, box, 25 * scale, snapFontSizeUp((settings.minFontSize ?? 16) * scale), node.path, node.payload.numbering);
|
|
3440
|
+
const reduction = Math.max(0, snapFontSizeDown(25 * scale) - fit.fontSize) / scale;
|
|
3441
|
+
score += reduction + (fit.overflow ? 1e3 : 0);
|
|
3442
|
+
if (penalties) {
|
|
3443
|
+
penalties.fontReduction += reduction;
|
|
3444
|
+
penalties.textOverflow += fit.overflow ? 1e3 : 0;
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
if (node.field === "table" && tableOverflows(node.value, box, scale, settings, options, node.path)) {
|
|
3448
|
+
score += 1e3;
|
|
3449
|
+
if (penalties) penalties.tableOverflow += 1e3;
|
|
3450
|
+
}
|
|
3451
|
+
if (box.width < 100 * scale || box.height < 60 * scale) {
|
|
3452
|
+
score += 100;
|
|
3453
|
+
if (penalties) penalties.smallCells += 100;
|
|
3454
|
+
}
|
|
3455
|
+
return score;
|
|
3456
|
+
};
|
|
3457
|
+
const modeFor = (settings) => settings.mode ?? "auto";
|
|
3458
|
+
const defaultColumns = (count, area2, settings) => {
|
|
3459
|
+
const mode = modeFor(settings);
|
|
3460
|
+
return mode === "column" ? 1 : mode === "row" ? Math.max(1, count) : settings.columns ?? Math.max(1, Math.min(count, Math.ceil(Math.sqrt(count * area2.width / area2.height / 1.6))));
|
|
3461
|
+
};
|
|
3462
|
+
const scoreNode = (node, box, settings, penalties) => {
|
|
3463
|
+
if (!node.children) return leafScore(node, box, settings, penalties);
|
|
3464
|
+
const own = inheritedSettings(settings, node.composition), area2 = inset(box, own);
|
|
3465
|
+
const cols = defaultColumns(node.children.length, area2, own);
|
|
3466
|
+
const boxes = gridBoxes(node.children.length, area2, cols, (own.gap ?? 1 / 30) * Math.min(box.width, box.height), own.weights ?? [], modeFor(own) === "column");
|
|
3467
|
+
return node.children.reduce((score, child, index) => score + scoreNode(child, boxes[index], own, penalties), 0);
|
|
3468
|
+
};
|
|
3469
|
+
const arrange = (nodes, area2, settings, reserved = 0, gapOverride, containerPath = path, recorded = true) => {
|
|
3470
|
+
const count = Math.max(nodes.length, reserved);
|
|
3471
|
+
if (!count) return;
|
|
3472
|
+
const mode = modeFor(settings), localGap = (settings.gap ?? 1 / 30) * Math.min(area2.width, area2.height);
|
|
3473
|
+
const actualGap = gapOverride ?? (settings === rootSettings ? gap : localGap);
|
|
3474
|
+
let cols = defaultColumns(count, area2, settings);
|
|
3475
|
+
const hasRegions = nodes.some((node) => node.region);
|
|
3476
|
+
const candidates = decisions && recorded ? [] : void 0;
|
|
3477
|
+
if (mode === "auto" && !hasRegions) {
|
|
3478
|
+
let best = Infinity;
|
|
3479
|
+
for (let candidate = 1; candidate <= Math.min(count, settings.columns ?? 6); candidate++) {
|
|
3480
|
+
const boxes2 = gridBoxes(count, area2, candidate, actualGap, settings.weights ?? [], false);
|
|
3481
|
+
const emptySlots = (Math.ceil(count / candidate) * candidate - count) * 2;
|
|
3482
|
+
const penalties = candidates ? { cellProportions: 0, fontReduction: 0, textOverflow: 0, tableOverflow: 0, smallCells: 0, emptySlots } : void 0;
|
|
3483
|
+
const score = nodes.reduce((sum, node, i) => sum + scoreNode(node, boxes2[i], settings, penalties), 0) + emptySlots;
|
|
3484
|
+
if (penalties && candidates) candidates.push({ columns: candidate, rows: Math.ceil(count / candidate), score, penalties });
|
|
3485
|
+
if (score < best) {
|
|
3486
|
+
best = score;
|
|
3487
|
+
cols = candidate;
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
if (recorded) decisions?.push({
|
|
3492
|
+
path: containerPath,
|
|
3493
|
+
mode: hasRegions ? "regions" : mode,
|
|
3494
|
+
reason: hasRegions ? "promoted-regions" : settings === rootSettings && chartPrimary ? "chart-primary" : mode === "auto" ? "lowest-score" : "configured-mode",
|
|
3495
|
+
...hasRegions ? {} : { selectedColumns: cols },
|
|
3496
|
+
candidates: candidates ?? []
|
|
3497
|
+
});
|
|
3498
|
+
const grid = gridGeometry(count, area2, cols, actualGap, settings.weights ?? [], mode === "column");
|
|
3499
|
+
const mirrored = (box) => rtl ? { ...box, x: area2.x + area2.width - (box.x - area2.x) - box.width } : box;
|
|
3500
|
+
const boxes = grid.boxes.map(mirrored);
|
|
3501
|
+
if (recorded && !hasRegions) flows.push({ path: containerPath, box: { ...area2 }, composition: { ...settings }, columns: rtl ? grid.columns.map((track) => ({ offset: area2.width - track.offset - track.size, size: track.size })) : grid.columns, rows: grid.rows, gap: grid.gap, itemCount: nodes.length, slotCount: count });
|
|
3502
|
+
nodes.forEach((node, index) => {
|
|
3503
|
+
let box = node.region ? mirrored(regionBox(node.region, area2, actualGap)) : boxes[index];
|
|
3504
|
+
if (node.children) {
|
|
3505
|
+
const own = inheritedSettings(settings, node.composition), inner = inset(box, own);
|
|
3506
|
+
if (!node.synthetic) groups.push({ path: node.path, box, contentBox: inner, composition: own });
|
|
3507
|
+
arrange(node.children, inner, own, 0, (own.gap ?? 1 / 30) * Math.min(box.width, box.height), node.path, !node.synthetic);
|
|
3508
|
+
} else {
|
|
3509
|
+
const frameBox = hasCards ? acceptedBox(box) : void 0;
|
|
3510
|
+
box = payloadBox(box);
|
|
3511
|
+
const caption = captionOf(node, box);
|
|
3512
|
+
if (caption) {
|
|
3513
|
+
box = caption.mediaBox;
|
|
3514
|
+
diagnostics.push(...caption.diagnostics);
|
|
3515
|
+
}
|
|
3516
|
+
const textValue = contentText(node.field, node.value);
|
|
3517
|
+
const quoteLayout = node.field === "quote" ? measureQuote(node, box, settings) : void 0;
|
|
3518
|
+
const codeLayout = node.field === "code" ? measureCode(node, box, settings) : void 0;
|
|
3519
|
+
const metricLayout = node.field === "metric" ? measureMetric(node, box, settings) : void 0;
|
|
3520
|
+
const timelineLayout = node.field === "timeline" ? measureTimeline(node, box, settings) : void 0;
|
|
3521
|
+
const internal = quoteLayout ?? codeLayout ?? metricLayout ?? timelineLayout, body = internal?.parts.find((part) => part.role === "body" || part.role === "value");
|
|
3522
|
+
const text2 = internal ? body?.fit : textValue !== void 0 ? fitContent(node.field, node.value, textValue, box, 25 * scale, snapFontSizeUp((settings.minFontSize ?? 16) * scale), node.path, node.payload.numbering) : void 0;
|
|
3523
|
+
items.push({
|
|
3524
|
+
path: node.path,
|
|
3525
|
+
field: node.field,
|
|
3526
|
+
type: node.type,
|
|
3527
|
+
value: node.value,
|
|
3528
|
+
payload: node.payload,
|
|
3529
|
+
box: internal ? acceptedBox(box) : box,
|
|
3530
|
+
...frameBox ? { frameBox } : {},
|
|
3531
|
+
text: text2,
|
|
3532
|
+
textStyle: body?.style ?? styleFor(node.field, node.path),
|
|
3533
|
+
composition: settings,
|
|
3534
|
+
alignment: alignmentFor(node.field),
|
|
3535
|
+
...quoteLayout ? { quoteLayout } : {},
|
|
3536
|
+
...codeLayout ? { codeLayout } : {},
|
|
3537
|
+
...metricLayout ? { metricLayout } : {},
|
|
3538
|
+
...timelineLayout ? { timelineLayout } : {},
|
|
3539
|
+
...bulletImage && node.payload.numbering === void 0 && (node.field === "items" || node.field === "bullets") ? { bulletImage } : {},
|
|
3540
|
+
...caption ? { caption } : {}
|
|
3541
|
+
});
|
|
3542
|
+
if (box.width < 100 * scale || box.height < 60 * scale) diagnostics.push({ code: "small-cell", path: node.path, message: "Content cell is too small for comfortable reading; use fewer blocks or a different composition." });
|
|
3543
|
+
}
|
|
3544
|
+
});
|
|
3545
|
+
};
|
|
3546
|
+
const placeholders = Array.isArray(layout.placeholders) ? layout.placeholders.filter((p) => !headings.has(p.type)) : [];
|
|
3547
|
+
if (slideImage?.replacesContent) {
|
|
3548
|
+
const picture = placeholders.findIndex((p) => p.type === "picture");
|
|
3549
|
+
if (picture >= 0) placeholders.splice(picture, 1);
|
|
3550
|
+
}
|
|
3551
|
+
const ownMode = record4(slide.composition).mode;
|
|
3552
|
+
const direction = designHint("contentDirection")?.value;
|
|
3553
|
+
const directionMode = direction === "vertical" ? "column" : direction === "horizontal" ? "row" : void 0;
|
|
3554
|
+
const layoutDirectionMode = layout.slideLayoutDirection === "Vertical" ? "column" : layout.slideLayoutDirection === "Horizontal" ? "row" : "auto";
|
|
3555
|
+
const chartHint = designHint("chartPrimary")?.value ?? (typeof layout.contentTypeChartPrimary === "string" ? layout.contentTypeChartPrimary.toLowerCase() : void 0);
|
|
3556
|
+
const chartSide = chartHint === "left" || chartHint === "right" || chartHint === "top" || chartHint === "bottom" ? chartHint : void 0;
|
|
3557
|
+
const chartIndex = pending.findIndex((node) => !node.children && node.field === "chart");
|
|
3558
|
+
const chartPrimary = chartSide !== void 0 && !ownMode && !regions.length && chartIndex >= 0 && pending.some((node) => node.children || node.field !== "chart") ? chartSide : void 0;
|
|
3559
|
+
const rootSettings = chartPrimary ? { ...composition, mode: chartPrimary === "left" || chartPrimary === "right" ? "row" : "column", columns: void 0, weights: chartPrimary === "left" || chartPrimary === "top" ? [3, 2] : [2, 3] } : { ...composition, mode: composition.mode ?? directionMode ?? layoutDirectionMode };
|
|
3560
|
+
if (chartPrimary) {
|
|
3561
|
+
const primary = pending[chartIndex], rest = pending.filter((_, index) => index !== chartIndex);
|
|
3562
|
+
const container = { field: "blocks", type: "group", value: rest.map((node) => node.payload), path, payload: {}, children: rest, composition: {}, synthetic: true };
|
|
3563
|
+
arrange(chartPrimary === "left" || chartPrimary === "top" ? [primary, container] : [container, primary], contentBox, rootSettings, 0);
|
|
3564
|
+
} else arrange(pending, contentBox, rootSettings, composition.mode ? 0 : placeholders.length);
|
|
3565
|
+
if (listBullet?.value === "image" && !bulletImage && items.some((item) => (item.field === "items" || item.field === "bullets") && item.payload.numbering === void 0)) {
|
|
3566
|
+
diagnostics.push({ code: "unresolved-content", path: listBullet.path, message: "Picture bullets (listBullet: image) need design.logo or a primary organization logo; the marker glyph is drawn instead." });
|
|
3567
|
+
}
|
|
3568
|
+
const numberingDiagnostics = [];
|
|
3569
|
+
for (const item of items) {
|
|
3570
|
+
for (const key of ["x", "y", "width", "height"]) item.box[key] = round2(item.box[key]);
|
|
3571
|
+
if (item.field === "table" && tableOverflows(item.value, item.box, scale, item.composition, options, item.path)) diagnostics.push({ code: "text-overflow", path: item.path, message: "Table cells do not fit; use fewer rows, fewer columns, or split the table across slides." });
|
|
3572
|
+
if (item.quoteLayout) diagnostics.push(...item.quoteLayout.diagnostics);
|
|
3573
|
+
else if (item.codeLayout) diagnostics.push(...item.codeLayout.diagnostics);
|
|
3574
|
+
else if (item.metricLayout) diagnostics.push(...item.metricLayout.diagnostics);
|
|
3575
|
+
else if (item.timelineLayout) diagnostics.push(...item.timelineLayout.diagnostics);
|
|
3576
|
+
else if (item.text?.overflow) diagnostics.push({ code: "text-overflow", path: item.path, message: "Text exceeds its cell at the minimum font size; shorten it, increase its space, or split the slide." });
|
|
3577
|
+
for (const entry of item.text?.listEntries ?? []) {
|
|
3578
|
+
if (entry.marker.number?.adapted) numberingDiagnostics.push({ code: "numbering-adapted", path: entry.textPath ?? item.path, message: `Roman numerals stop at 3999; this entry is numbered ${entry.marker.number.value} in arabic, as PowerPoint draws it.` });
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
for (const group of groups) for (const box of [group.box, group.contentBox]) for (const key of ["x", "y", "width", "height"]) box[key] = round2(box[key]);
|
|
3582
|
+
const strictPaths = new Set(items.filter((item) => item.composition.overflow === "error").map((item) => item.path));
|
|
3583
|
+
const failures = diagnostics.filter((diagnostic) => {
|
|
3584
|
+
if (composition.overflow === "error") return true;
|
|
3585
|
+
let path2 = diagnostic.path;
|
|
3586
|
+
while (path2) {
|
|
3587
|
+
if (strictPaths.has(path2)) return true;
|
|
3588
|
+
const boundary = path2.lastIndexOf(".");
|
|
3589
|
+
if (boundary < 0) break;
|
|
3590
|
+
path2 = path2.slice(0, boundary);
|
|
3591
|
+
}
|
|
3592
|
+
return false;
|
|
3593
|
+
});
|
|
3594
|
+
const explanation = decisions ? {
|
|
3595
|
+
algorithm: "grid-score-v9",
|
|
3596
|
+
textMeasurement: options.textMeasurement ? "provided" : "estimated",
|
|
3597
|
+
textOutlines: options.textMeasurement?.outlineBounds ? "provided" : "unavailable",
|
|
3598
|
+
textRasterPadding: rasterPadding,
|
|
3599
|
+
decisions,
|
|
3600
|
+
unmeasuredPayloads: items.filter((item) => !headings.has(item.field) && !item.quoteLayout && !item.codeLayout && !item.metricLayout && !item.timelineLayout && !["text", "items", "bullets", "table"].includes(item.field)).map((item) => item.path)
|
|
3601
|
+
} : void 0;
|
|
3602
|
+
if (failures.length) throw new OPFCompositionError(failures, explanation);
|
|
3603
|
+
diagnostics.push(...slideImageDiagnostics, ...numberingDiagnostics);
|
|
3604
|
+
return { width, height, contentBox, items, groups, flows, diagnostics, composition, ...furniture ? { furniture } : {}, ...slideImage ? { slideImage } : {}, ...logo ? { logo } : {}, ...rtl ? { direction: "rtl" } : {}, ...footnotes ? { footnotes } : {}, ...explanation ? { explanation } : {} };
|
|
3605
|
+
}
|
|
3606
|
+
function resolveCanvasDimensions(input) {
|
|
3607
|
+
const presets = {
|
|
3608
|
+
widescreen: [40 / 3, 7.5],
|
|
3609
|
+
"16:9": [40 / 3, 7.5],
|
|
3610
|
+
standard: [10, 7.5],
|
|
3611
|
+
"4:3": [10, 7.5],
|
|
3612
|
+
"16:10": [10, 6.25],
|
|
3613
|
+
letter: [11, 8.5],
|
|
3614
|
+
a4: [11.69, 8.27]
|
|
3615
|
+
};
|
|
3616
|
+
const value = record4(input);
|
|
3617
|
+
const preset = presets[typeof input === "string" ? input : value.preset] ?? presets.widescreen;
|
|
3618
|
+
const width = (value.widthInches ?? preset[0]) * 96;
|
|
3619
|
+
const height = (value.heightInches ?? preset[1]) * 96;
|
|
3620
|
+
if (![width, height].every((n) => Number.isFinite(n) && n > 0)) throw new RangeError("Canvas dimensions must be finite and positive.");
|
|
3621
|
+
return { width, height };
|
|
3622
|
+
}
|
|
3623
|
+
|
|
3624
|
+
export { CAPTIONABLE_FIELDS, CAPTION_FONT_RATIO, CAPTION_MAX_RATIO, CHART_SERIES_MIN_DIFFERENCE, CHART_SERIES_MIN_LIGHTNESS_STEP, CITATION_MARKER_RAISE, CITATION_MARKER_SCALE, DEFAULT_CHART_LABEL_SEPARATOR, DEFAULT_FONT_SCHEME, DEFAULT_FURNITURE_DATE_FORMAT, DEFAULT_SLIDE_NUMBER_FORMAT, FONT_SIZE_GRID_PER_PX, FOOTNOTE_MAX_RATIO, MAX_COMPOSITION_DEPTH, MAX_NUMBERING_LEVELS, MAX_NUMBERING_VALUE, MAX_ROMAN_VALUE, NUMBERING_STYLES, NUMBERING_SUFFIXES, OPFCompositionError, PICTURE_BULLET_SCALE, annotationText, bareIdPattern, captionSettings, catalogIds, chartColorForFill, chartLabelText, chartOptionSupport, chartOptionTarget, chartPaletteForFill, citationMarkerText, collectCitations, colorContrast, composeSlide, deprecatedCatalogIds, fitList, fitRichText, fitText, formatChartLabelNumber, formatChartLabelPercent, formatFurnitureDate, formatListNumber, formatSlideNumber, intrinsicImageSize, isRecord2 as isRecord, layoutCaption, layoutCode, layoutFootnotes, layoutFurniture, layoutMetric, layoutQuote, layoutTable, layoutTimeline, listNumbers, measureText, measureTextOutline, normalizeHexColor, numberingAtLevel, numberingFindings, numberingStyleDraws, paragraphDirection, paragraphDirectionAt, parseIsoDate, pathFor, physicalAlignment, placeTextLines, promotedRegionKeys, readingRows, referencesSlide, resolveCanvasDimensions, resolveChartOptions, resolveColorRef, resolveFontFamilies, resolveFontSchemeReference, resolveLogo, resolveNumbering, resolveScriptFonts, resolveSlideDirection, resolveSocialProfile, resolveTextStyle, scriptFontRole, sliceNumberedItems, slideCitations, slideImageShape, snapFontSizeDown, snapFontSizeUp, tableGrid, tableRowBoundaries, textColorForFill, textWidthMeasurer, visitContentPayloads, visualReadingOrder, walkCitationRuns, wrapText };
|