@openpresentation/opf 0.11.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{catalogs-atkOF4Vy.d.ts → catalogs-DJ-B5ZyD.d.ts} +78 -8
- package/dist/catalogs.d.ts +2 -2
- package/dist/catalogs.js +1 -1
- package/dist/{chunk-FTWNWFIV.js → chunk-6UPHKXI5.js} +310 -34
- package/dist/{chunk-6FU2KICU.js → chunk-7LF37SG3.js} +7084 -5106
- package/dist/{chunk-EQHZ3ZO4.js → chunk-BP6WMSSS.js} +3 -3
- package/dist/{chunk-FWA5XB45.js → chunk-KWIOQ3QR.js} +45 -25
- package/dist/{chunk-GN52EE3P.js → chunk-LTSDDBNW.js} +425 -40
- package/dist/{chunk-CHKJ4QHQ.js → chunk-M4GTWRK7.js} +113 -32
- package/dist/chunk-NPYN4TZ5.js +3516 -0
- package/dist/{chunk-RS5GZ5RW.js → chunk-SDETQV7Y.js} +161 -0
- package/dist/{composition-Tv9Wm3Oh.d.ts → composition-DzLwY-Mi.d.ts} +200 -4
- package/dist/composition.d.ts +1 -1
- package/dist/composition.js +1 -1
- package/dist/docs.js +50 -20
- package/dist/examples.d.ts +1 -1
- package/dist/examples.js +11 -11
- package/dist/font-policy.d.ts +123 -0
- package/dist/font-policy.js +1 -0
- package/dist/index.d.ts +129 -10
- package/dist/index.js +280 -12
- package/dist/lint.d.ts +3 -3
- package/dist/lint.js +5 -5
- package/dist/pagination.d.ts +5 -1
- package/dist/pagination.js +5 -5
- package/dist/{presentation-Ct42Mitb.d.ts → presentation-bceTClm8.d.ts} +246 -22
- package/dist/repo-readme.js +1 -1
- package/dist/{schemas-CiVmUT2p.d.ts → schemas-X_NniU4A.d.ts} +2 -0
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.js +1 -1
- package/dist/spec/catalogs/audiences/academic.json +24 -0
- package/dist/spec/catalogs/audiences/all-hands.json +3 -1
- package/dist/spec/catalogs/audiences/board.json +3 -1
- package/dist/spec/catalogs/audiences/customer.json +25 -0
- package/dist/spec/catalogs/audiences/executive.json +24 -0
- package/dist/spec/catalogs/audiences/general-public.json +25 -0
- package/dist/spec/catalogs/audiences/index.json +73 -1
- package/dist/spec/catalogs/audiences/internal-team.json +25 -0
- package/dist/spec/catalogs/audiences/investor.json +25 -0
- package/dist/spec/catalogs/audiences/marketing.json +25 -0
- package/dist/spec/catalogs/audiences/media.json +24 -0
- package/dist/spec/catalogs/audiences/partner.json +25 -0
- package/dist/spec/catalogs/audiences/regulatory.json +25 -0
- package/dist/spec/catalogs/audiences/sales.json +25 -0
- package/dist/spec/catalogs/audiences/technical.json +25 -0
- package/dist/spec/catalogs/chart-types/100pct-bullet-bar-2x.json +5 -0
- package/dist/spec/catalogs/chart-types/100pct-bullet-bar-3x.json +5 -0
- package/dist/spec/catalogs/chart-types/100pct-bullet-bar.json +5 -0
- package/dist/spec/catalogs/chart-types/100pct-bullet-column-2x.json +5 -0
- package/dist/spec/catalogs/chart-types/100pct-bullet-column-3x.json +5 -0
- package/dist/spec/catalogs/chart-types/100pct-bullet-column.json +5 -0
- package/dist/spec/catalogs/chart-types/100pct-progress-bar.json +10 -0
- package/dist/spec/catalogs/chart-types/100pct-stacked-area-2x.json +10 -0
- package/dist/spec/catalogs/chart-types/100pct-stacked-area-3x.json +6 -1
- package/dist/spec/catalogs/chart-types/100pct-stacked-bar-2x.json +10 -0
- package/dist/spec/catalogs/chart-types/100pct-stacked-bar-3x.json +6 -1
- package/dist/spec/catalogs/chart-types/100pct-stacked-column-2x.json +10 -0
- package/dist/spec/catalogs/chart-types/100pct-stacked-column-3x.json +6 -1
- package/dist/spec/catalogs/chart-types/area.json +5 -0
- package/dist/spec/catalogs/chart-types/australia.json +10 -0
- package/dist/spec/catalogs/chart-types/bar.json +5 -0
- package/dist/spec/catalogs/chart-types/box-and-whisker-2x.json +10 -0
- package/dist/spec/catalogs/chart-types/box-and-whisker-3x.json +10 -0
- package/dist/spec/catalogs/chart-types/box-and-whisker.json +5 -0
- package/dist/spec/catalogs/chart-types/bullet-bar-2x.json +5 -0
- package/dist/spec/catalogs/chart-types/bullet-bar-3x.json +5 -0
- package/dist/spec/catalogs/chart-types/bullet-bar.json +5 -0
- package/dist/spec/catalogs/chart-types/bullet-column-2x.json +5 -0
- package/dist/spec/catalogs/chart-types/bullet-column-3x.json +5 -0
- package/dist/spec/catalogs/chart-types/bullet-column.json +5 -0
- package/dist/spec/catalogs/chart-types/canada.json +10 -0
- package/dist/spec/catalogs/chart-types/clustered-bar-2x.json +10 -0
- package/dist/spec/catalogs/chart-types/clustered-column.json +10 -0
- package/dist/spec/catalogs/chart-types/column.json +5 -0
- package/dist/spec/catalogs/chart-types/dot-plot-2x.json +5 -0
- package/dist/spec/catalogs/chart-types/dot-plot-3x.json +5 -0
- package/dist/spec/catalogs/chart-types/dot-plot-4x.json +5 -0
- package/dist/spec/catalogs/chart-types/dot-plot-5x.json +5 -0
- package/dist/spec/catalogs/chart-types/dot-plot-6x.json +5 -0
- package/dist/spec/catalogs/chart-types/dot-plot.json +5 -0
- package/dist/spec/catalogs/chart-types/doughnut.json +5 -0
- package/dist/spec/catalogs/chart-types/dumbbell.json +5 -0
- package/dist/spec/catalogs/chart-types/filled-radar.json +5 -0
- package/dist/spec/catalogs/chart-types/funnel.json +5 -0
- package/dist/spec/catalogs/chart-types/histogram.json +5 -0
- package/dist/spec/catalogs/chart-types/index.json +333 -233
- package/dist/spec/catalogs/chart-types/line-2x.json +10 -0
- package/dist/spec/catalogs/chart-types/line-3x.json +10 -0
- package/dist/spec/catalogs/chart-types/line-with-high-low-and-markers.json +5 -0
- package/dist/spec/catalogs/chart-types/line-with-high-low.json +5 -0
- package/dist/spec/catalogs/chart-types/line-with-markers-2x.json +10 -0
- package/dist/spec/catalogs/chart-types/line-with-markers-3x.json +10 -0
- package/dist/spec/catalogs/chart-types/line-with-markers.json +5 -0
- package/dist/spec/catalogs/chart-types/line.json +5 -0
- package/dist/spec/catalogs/chart-types/pareto.json +9 -14
- package/dist/spec/catalogs/chart-types/pie.json +5 -0
- package/dist/spec/catalogs/chart-types/radar-with-markers.json +5 -0
- package/dist/spec/catalogs/chart-types/radar.json +5 -0
- package/dist/spec/catalogs/chart-types/scatter.json +5 -0
- package/dist/spec/catalogs/chart-types/sparkline-2x.json +5 -0
- package/dist/spec/catalogs/chart-types/sparkline-3x.json +5 -0
- package/dist/spec/catalogs/chart-types/sparkline-4x.json +5 -0
- package/dist/spec/catalogs/chart-types/sparkline-5x.json +5 -0
- package/dist/spec/catalogs/chart-types/sparkline-6x.json +5 -0
- package/dist/spec/catalogs/chart-types/sparkline.json +5 -0
- package/dist/spec/catalogs/chart-types/stacked-area-2x.json +10 -0
- package/dist/spec/catalogs/chart-types/stacked-area-3x.json +6 -1
- package/dist/spec/catalogs/chart-types/stacked-bar-2x.json +10 -0
- package/dist/spec/catalogs/chart-types/stacked-bar-3x.json +6 -1
- package/dist/spec/catalogs/chart-types/stacked-column-2x.json +10 -0
- package/dist/spec/catalogs/chart-types/stacked-column-3x.json +6 -1
- package/dist/spec/catalogs/chart-types/stacked-line-2x.json +10 -0
- package/dist/spec/catalogs/chart-types/stacked-line-3x.json +6 -1
- package/dist/spec/catalogs/chart-types/stacked-line-with-markers-2x.json +10 -0
- package/dist/spec/catalogs/chart-types/stacked-line-with-markers-3x.json +6 -1
- package/dist/spec/catalogs/chart-types/treemap-2x.json +10 -0
- package/dist/spec/catalogs/chart-types/treemap-3x.json +10 -0
- package/dist/spec/catalogs/chart-types/treemap.json +5 -0
- package/dist/spec/catalogs/chart-types/united-kingdom.json +10 -0
- package/dist/spec/catalogs/chart-types/united-states.json +10 -0
- package/dist/spec/catalogs/chart-types/waterfall.json +5 -0
- package/dist/spec/catalogs/chart-types/world.json +6 -1
- package/dist/spec/catalogs/font-schemes/consolas.json +3 -0
- package/dist/spec/catalogs/font-schemes/courier-new.json +3 -0
- package/dist/spec/catalogs/languages/afrikaans.json +3 -0
- package/dist/spec/catalogs/languages/albanian.json +3 -0
- package/dist/spec/catalogs/languages/amharic.json +3 -0
- package/dist/spec/catalogs/languages/arabic.json +3 -0
- package/dist/spec/catalogs/languages/armenian.json +3 -0
- package/dist/spec/catalogs/languages/aymara.json +3 -0
- package/dist/spec/catalogs/languages/azerbaijani.json +3 -0
- package/dist/spec/catalogs/languages/bengali.json +3 -0
- package/dist/spec/catalogs/languages/berber-latin.json +3 -0
- package/dist/spec/catalogs/languages/bosnian-latin.json +3 -0
- package/dist/spec/catalogs/languages/bulgarian.json +3 -0
- package/dist/spec/catalogs/languages/catalan.json +3 -0
- package/dist/spec/catalogs/languages/cebuano.json +3 -0
- package/dist/spec/catalogs/languages/chinese-simplified.json +3 -0
- package/dist/spec/catalogs/languages/chinese-traditional.json +3 -0
- package/dist/spec/catalogs/languages/chittagonian.json +3 -0
- package/dist/spec/catalogs/languages/croatian.json +3 -0
- package/dist/spec/catalogs/languages/czech.json +3 -0
- package/dist/spec/catalogs/languages/danish.json +3 -0
- package/dist/spec/catalogs/languages/dutch.json +3 -0
- package/dist/spec/catalogs/languages/english-au.json +3 -0
- package/dist/spec/catalogs/languages/english-ca.json +3 -0
- package/dist/spec/catalogs/languages/english-gb.json +3 -0
- package/dist/spec/catalogs/languages/english-in.json +3 -0
- package/dist/spec/catalogs/languages/english-us.json +3 -0
- package/dist/spec/catalogs/languages/english.json +3 -0
- package/dist/spec/catalogs/languages/estonian.json +3 -0
- package/dist/spec/catalogs/languages/filipino.json +3 -0
- package/dist/spec/catalogs/languages/finnish.json +3 -0
- package/dist/spec/catalogs/languages/french.json +3 -0
- package/dist/spec/catalogs/languages/fulfulde.json +3 -0
- package/dist/spec/catalogs/languages/galician.json +3 -0
- package/dist/spec/catalogs/languages/georgian.json +3 -0
- package/dist/spec/catalogs/languages/german.json +3 -0
- package/dist/spec/catalogs/languages/greek.json +3 -0
- package/dist/spec/catalogs/languages/gujarati.json +3 -0
- package/dist/spec/catalogs/languages/hausa.json +3 -0
- package/dist/spec/catalogs/languages/hebrew.json +3 -0
- package/dist/spec/catalogs/languages/hindi.json +3 -0
- package/dist/spec/catalogs/languages/hungarian.json +3 -0
- package/dist/spec/catalogs/languages/igbo.json +3 -0
- package/dist/spec/catalogs/languages/indonesian.json +3 -0
- package/dist/spec/catalogs/languages/italian.json +3 -0
- package/dist/spec/catalogs/languages/japanese.json +3 -0
- package/dist/spec/catalogs/languages/kannada.json +3 -0
- package/dist/spec/catalogs/languages/kazakh.json +3 -0
- package/dist/spec/catalogs/languages/khmer.json +3 -0
- package/dist/spec/catalogs/languages/kinyarwanda.json +3 -0
- package/dist/spec/catalogs/languages/korean.json +3 -0
- package/dist/spec/catalogs/languages/kurmanji.json +3 -0
- package/dist/spec/catalogs/languages/latvian.json +3 -0
- package/dist/spec/catalogs/languages/lithuanian.json +3 -0
- package/dist/spec/catalogs/languages/macedonian.json +3 -0
- package/dist/spec/catalogs/languages/malagasy.json +3 -0
- package/dist/spec/catalogs/languages/malay.json +3 -0
- package/dist/spec/catalogs/languages/malayalam.json +3 -0
- package/dist/spec/catalogs/languages/maori.json +3 -0
- package/dist/spec/catalogs/languages/marathi.json +3 -0
- package/dist/spec/catalogs/languages/mongolian.json +3 -0
- package/dist/spec/catalogs/languages/nepali.json +3 -0
- package/dist/spec/catalogs/languages/norwegian.json +3 -0
- package/dist/spec/catalogs/languages/odia.json +3 -0
- package/dist/spec/catalogs/languages/oromo.json +3 -0
- package/dist/spec/catalogs/languages/pashto.json +3 -0
- package/dist/spec/catalogs/languages/persian.json +3 -0
- package/dist/spec/catalogs/languages/polish.json +3 -0
- package/dist/spec/catalogs/languages/portuguese.json +3 -0
- package/dist/spec/catalogs/languages/punjabi-gurmukhi.json +3 -0
- package/dist/spec/catalogs/languages/punjabi-shahmukhi.json +3 -0
- package/dist/spec/catalogs/languages/romanian.json +3 -0
- package/dist/spec/catalogs/languages/russian.json +3 -0
- package/dist/spec/catalogs/languages/serbian-cyrillic.json +3 -0
- package/dist/spec/catalogs/languages/serbian-latin.json +3 -0
- package/dist/spec/catalogs/languages/shona.json +3 -0
- package/dist/spec/catalogs/languages/slovak.json +3 -0
- package/dist/spec/catalogs/languages/slovenian.json +3 -0
- package/dist/spec/catalogs/languages/somali.json +3 -0
- package/dist/spec/catalogs/languages/spanish.json +3 -0
- package/dist/spec/catalogs/languages/swahili.json +3 -0
- package/dist/spec/catalogs/languages/swedish.json +3 -0
- package/dist/spec/catalogs/languages/tagalog.json +3 -0
- package/dist/spec/catalogs/languages/tajik.json +3 -0
- package/dist/spec/catalogs/languages/tamil.json +3 -0
- package/dist/spec/catalogs/languages/telugu.json +3 -0
- package/dist/spec/catalogs/languages/thai.json +3 -0
- package/dist/spec/catalogs/languages/turkish.json +3 -0
- package/dist/spec/catalogs/languages/ukrainian.json +3 -0
- package/dist/spec/catalogs/languages/urdu.json +3 -0
- package/dist/spec/catalogs/languages/uzbek-latin.json +3 -0
- package/dist/spec/catalogs/languages/vietnamese-quoc-ngu.json +3 -0
- package/dist/spec/catalogs/languages/xhosa.json +3 -0
- package/dist/spec/catalogs/languages/yoruba.json +3 -0
- package/dist/spec/catalogs/languages/zulu.json +3 -0
- package/dist/spec/catalogs/narratives/change-story.json +64 -0
- package/dist/spec/catalogs/narratives/data-story.json +66 -0
- package/dist/spec/catalogs/narratives/heros-journey.json +64 -0
- package/dist/spec/catalogs/narratives/index.json +81 -0
- package/dist/spec/catalogs/narratives/pyramid-principle.json +48 -0
- package/dist/spec/catalogs/narratives/situation-complication-resolution.json +56 -0
- package/dist/spec/catalogs/narratives/sparkline.json +56 -0
- package/dist/spec/catalogs/narratives/star-method.json +56 -0
- package/dist/spec/catalogs/narratives/vision-roadmap.json +65 -0
- package/dist/spec/catalogs/narratives/what-so-what-now-what.json +48 -0
- package/dist/spec/reference/engine-defaults.json +2 -2
- package/dist/spec/reference/font-policy.json +3435 -0
- package/dist/spec/reference/font-policy.schema.json +328 -0
- package/dist/spec/schemas/chart-type.schema.json +51 -11
- package/dist/spec/schemas/font-scheme.schema.json +91 -1
- package/dist/spec/schemas/language.schema.json +10 -5
- package/dist/spec/schemas/opf.schema.json +158 -20
- package/dist/spec-files.d.ts +1 -1
- package/dist/spec-files.js +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/validator.d.ts +3 -3
- package/dist/validator.js +4 -4
- package/package.json +6 -2
|
@@ -35,6 +35,112 @@ function tableRowBoundaries(value) {
|
|
|
35
35
|
return [...allowed];
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// src/furniture-fields.ts
|
|
39
|
+
var DEFAULT_FURNITURE_DATE_FORMAT = "M/d/yyyy";
|
|
40
|
+
var DEFAULT_SLIDE_NUMBER_FORMAT = "{current}";
|
|
41
|
+
var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
42
|
+
var weekdays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
43
|
+
var tokenWidths = { y: [1, 2, 4], M: [1, 2, 3, 4], d: [1, 2], E: [1, 2, 3, 4] };
|
|
44
|
+
function parseFurnitureDateFormat(pattern) {
|
|
45
|
+
if (!pattern) return "dateFormat must not be empty.";
|
|
46
|
+
const tokens = [];
|
|
47
|
+
const literal = (text) => {
|
|
48
|
+
const last = tokens[tokens.length - 1];
|
|
49
|
+
if (last?.kind === "literal") last.text += text;
|
|
50
|
+
else tokens.push({ kind: "literal", text });
|
|
51
|
+
};
|
|
52
|
+
for (let index = 0; index < pattern.length; ) {
|
|
53
|
+
const character = pattern.charAt(index);
|
|
54
|
+
if (character === "'") {
|
|
55
|
+
if (pattern[index + 1] === "'") {
|
|
56
|
+
literal("'");
|
|
57
|
+
index += 2;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
let end = index + 1, text = "";
|
|
61
|
+
for (; ; ) {
|
|
62
|
+
if (end >= pattern.length) return "dateFormat has an unterminated quoted literal.";
|
|
63
|
+
if (pattern[end] === "'") {
|
|
64
|
+
if (pattern[end + 1] === "'") {
|
|
65
|
+
text += "'";
|
|
66
|
+
end += 2;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
text += pattern[end];
|
|
72
|
+
end++;
|
|
73
|
+
}
|
|
74
|
+
literal(text);
|
|
75
|
+
index = end + 1;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (/[A-Za-z]/.test(character)) {
|
|
79
|
+
let end = index;
|
|
80
|
+
while (pattern[end] === character) end++;
|
|
81
|
+
const width = end - index;
|
|
82
|
+
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.`;
|
|
83
|
+
tokens.push({ kind: "field", letter: character, width });
|
|
84
|
+
index = end;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
literal(character);
|
|
88
|
+
index++;
|
|
89
|
+
}
|
|
90
|
+
return tokens;
|
|
91
|
+
}
|
|
92
|
+
function parseIsoDate(value) {
|
|
93
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
94
|
+
if (!match) return null;
|
|
95
|
+
const year = Number(match[1]), month = Number(match[2]), day = Number(match[3]);
|
|
96
|
+
const date = new Date(Date.UTC(2e3, month - 1, day));
|
|
97
|
+
date.setUTCFullYear(year);
|
|
98
|
+
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) return null;
|
|
99
|
+
return { year, month, day };
|
|
100
|
+
}
|
|
101
|
+
function formatFurnitureDate(iso, pattern) {
|
|
102
|
+
const date = parseIsoDate(iso);
|
|
103
|
+
if (!date) return { error: `'${iso}' is not an ISO YYYY-MM-DD calendar date.` };
|
|
104
|
+
const tokens = parseFurnitureDateFormat(pattern);
|
|
105
|
+
if (typeof tokens === "string") return { error: tokens };
|
|
106
|
+
const utc = new Date(Date.UTC(2e3, date.month - 1, date.day));
|
|
107
|
+
utc.setUTCFullYear(date.year);
|
|
108
|
+
const pad = (value, width) => String(value).padStart(width, "0");
|
|
109
|
+
let text = "";
|
|
110
|
+
for (const token of tokens) {
|
|
111
|
+
if (token.kind === "literal") {
|
|
112
|
+
text += token.text;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const { letter, width } = token;
|
|
116
|
+
if (letter === "y") text += width === 2 ? pad(date.year % 100, 2) : width === 4 ? pad(date.year, 4) : String(date.year);
|
|
117
|
+
else if (letter === "M") {
|
|
118
|
+
const name = months[date.month - 1] ?? "";
|
|
119
|
+
text += width >= 3 ? width === 4 ? name : name.slice(0, 3) : pad(date.month, width);
|
|
120
|
+
} else if (letter === "d") text += pad(date.day, width);
|
|
121
|
+
else {
|
|
122
|
+
const name = weekdays[utc.getUTCDay()] ?? "";
|
|
123
|
+
text += width === 4 ? name : name.slice(0, 3);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return { text };
|
|
127
|
+
}
|
|
128
|
+
function formatSlideNumber(format, current, total) {
|
|
129
|
+
if (!format.includes("{current}")) return { error: "slideNumberFormat must contain {current}." };
|
|
130
|
+
if (format.includes("{total}") && total === void 0) return { error: "slideNumberFormat uses {total}, which needs the rendered slide count." };
|
|
131
|
+
let text = "";
|
|
132
|
+
const fields2 = [];
|
|
133
|
+
for (const piece of format.split(/(\{current\}|\{total\})/)) {
|
|
134
|
+
if (piece === "{current}") {
|
|
135
|
+
const value = String(current);
|
|
136
|
+
fields2.push({ type: "slideNumber", start: text.length, end: text.length + value.length });
|
|
137
|
+
text += value;
|
|
138
|
+
} else if (piece === "{total}") text += String(total);
|
|
139
|
+
else text += piece;
|
|
140
|
+
}
|
|
141
|
+
return { text, fields: fields2 };
|
|
142
|
+
}
|
|
143
|
+
|
|
38
144
|
// src/color.ts
|
|
39
145
|
var schemeColorSlots = /* @__PURE__ */ new Set([
|
|
40
146
|
"accent1",
|
|
@@ -147,13 +253,25 @@ function chartColorForFill(fill, preferred) {
|
|
|
147
253
|
|
|
148
254
|
// src/composition.ts
|
|
149
255
|
var MAX_COMPOSITION_DEPTH = 32;
|
|
256
|
+
var FALLBACK_CODE_FAMILY = "Roboto Mono";
|
|
257
|
+
var DEFAULT_FONT_SCHEME = "aptos";
|
|
258
|
+
var DEFAULT_FONT_FAMILIES = { heading: "Aptos Display", body: "Aptos" };
|
|
259
|
+
function resolveFontSchemeReference(reference, lookup, path = "design.fontScheme") {
|
|
260
|
+
const overrides = typeof reference === "object" && reference !== null && !Array.isArray(reference) ? reference : void 0;
|
|
261
|
+
const id = typeof reference === "string" ? reference : typeof overrides?.id === "string" ? overrides.id : void 0;
|
|
262
|
+
const found = id === void 0 ? void 0 : lookup(id);
|
|
263
|
+
const base = record2(found ?? lookup(DEFAULT_FONT_SCHEME));
|
|
264
|
+
const scheme = overrides ? { ...base, ...overrides } : { ...base };
|
|
265
|
+
if (id === void 0 || found !== void 0) return { scheme };
|
|
266
|
+
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}'.` } };
|
|
267
|
+
}
|
|
150
268
|
function resolveFontFamilies(input) {
|
|
151
269
|
const scheme = record2(input);
|
|
152
270
|
const family = (value) => typeof value === "string" ? value : record2(value).family;
|
|
153
271
|
return {
|
|
154
|
-
heading: family(scheme.heading) ?? scheme.major ?? scheme.minor ??
|
|
155
|
-
body: family(scheme.body) ?? scheme.minor ?? scheme.major ??
|
|
156
|
-
code: family(scheme.code) ??
|
|
272
|
+
heading: family(scheme.heading) ?? scheme.major ?? scheme.minor ?? DEFAULT_FONT_FAMILIES.heading,
|
|
273
|
+
body: family(scheme.body) ?? scheme.minor ?? scheme.major ?? DEFAULT_FONT_FAMILIES.body,
|
|
274
|
+
code: family(scheme.code) ?? FALLBACK_CODE_FAMILY
|
|
157
275
|
};
|
|
158
276
|
}
|
|
159
277
|
function resolveTextStyle(style, measurement) {
|
|
@@ -219,6 +337,106 @@ var columns = ["left", "center", "right"];
|
|
|
219
337
|
var record2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
220
338
|
var kind = (field) => field === "items" ? "list" : field === "bullets" ? "text" : field;
|
|
221
339
|
var round = (value) => Math.round(value * 1e6) / 1e6 || value;
|
|
340
|
+
var SLIDE_IMAGE_POSITIONS = ["background", "top", "bottom", "left", "right"];
|
|
341
|
+
var SLIDE_IMAGE_BAND = 0.5;
|
|
342
|
+
var assetSource = (value) => typeof value === "string" ? value : record2(value).src;
|
|
343
|
+
var finite = (value, min, max) => typeof value === "number" && Number.isFinite(value) && value >= min && value <= max ? value : void 0;
|
|
344
|
+
var pathNumber = (value) => String(round(value));
|
|
345
|
+
var SLIDE_IMAGE_SHAPES = { rectangle: "rect", rounded: "roundRect", circle: "ellipse", hexagon: "hexagon" };
|
|
346
|
+
function slideImageShape(kind2, box, cornerRadius = 1 / 6) {
|
|
347
|
+
const { x, y, width: w, height: h } = box, r = x + w, b = y + h, ss = Math.min(w, h), n = pathNumber;
|
|
348
|
+
const rect = `M${n(x)} ${n(y)}H${n(r)}V${n(b)}H${n(x)}Z`;
|
|
349
|
+
if (kind2 === "rounded") {
|
|
350
|
+
const adj = Math.round(Math.min(0.5, Math.max(0, cornerRadius)) * 1e5), radius = ss * adj / 1e5;
|
|
351
|
+
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`;
|
|
352
|
+
return { kind: kind2, preset: "roundRect", adjust: { adj }, path };
|
|
353
|
+
}
|
|
354
|
+
if (kind2 === "circle") {
|
|
355
|
+
const cy = y + h / 2;
|
|
356
|
+
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` };
|
|
357
|
+
}
|
|
358
|
+
if (kind2 === "hexagon") {
|
|
359
|
+
const adj = 25e3, vf = 115470, a = Math.min(Math.max(adj, 0), 5e4 * w / ss);
|
|
360
|
+
const x1 = ss * a / 1e5, x2 = r - x1, vc = y + h / 2, dy1 = h / 2 * vf / 1e5 * Math.sin(Math.PI / 3);
|
|
361
|
+
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` };
|
|
362
|
+
}
|
|
363
|
+
return { kind: "rectangle", preset: "rect", adjust: {}, path: rect };
|
|
364
|
+
}
|
|
365
|
+
function resolveSlideImage(slide, layout, presentation, width, height, path, padding, scale, diagnostics) {
|
|
366
|
+
const own = record2(slide.design), deck = record2(record2(presentation).design);
|
|
367
|
+
const local = own.slideImage !== void 0;
|
|
368
|
+
const configured = local ? own.slideImage : deck.slideImage;
|
|
369
|
+
if (!configured || typeof configured !== "object" && typeof configured !== "string") return void 0;
|
|
370
|
+
const treatment = typeof configured === "object" && !Array.isArray(configured) && "position" in configured ? record2(configured) : void 0;
|
|
371
|
+
const alignment = typeof layout.slideImageAlignment === "string" ? layout.slideImageAlignment.toLowerCase() : void 0;
|
|
372
|
+
const position = treatment ? treatment.position : SLIDE_IMAGE_POSITIONS.find((value2) => value2 === alignment) ?? "background";
|
|
373
|
+
if (!SLIDE_IMAGE_POSITIONS.includes(position)) return void 0;
|
|
374
|
+
const designSource = treatment ? treatment.src : configured;
|
|
375
|
+
const root = slide.image, sameSource = root !== void 0 && designSource !== void 0 && assetSource(root) === assetSource(designSource);
|
|
376
|
+
if (!local && layout.slideImage !== true && !sameSource) return void 0;
|
|
377
|
+
const replacesContent = root !== void 0 && (designSource === void 0 || sameSource);
|
|
378
|
+
const value = replacesContent ? root : designSource;
|
|
379
|
+
const source = assetSource(value);
|
|
380
|
+
if (typeof source !== "string" || !source) return void 0;
|
|
381
|
+
const t = treatment ?? {};
|
|
382
|
+
const fill = t.fill === "crop" || t.fill === "fit" ? t.fill : (own.imageFill ?? deck.imageFill) === "fit" ? "fit" : "crop";
|
|
383
|
+
const share = finite(t.size, 0.1, 0.9) ?? SLIDE_IMAGE_BAND;
|
|
384
|
+
const band = { width: round(width * share), height: round(height * share) };
|
|
385
|
+
const region = position === "left" ? { x: 0, y: 0, width: band.width, height } : position === "right" ? { x: round(width - band.width), y: 0, width: band.width, height } : position === "top" ? { x: 0, y: 0, width, height: band.height } : position === "bottom" ? { x: 0, y: round(height - band.height), width, height: band.height } : { x: 0, y: 0, width, height };
|
|
386
|
+
const designPath = local ? `${path}.design.slideImage` : "design.slideImage";
|
|
387
|
+
const kind2 = Object.hasOwn(SLIDE_IMAGE_SHAPES, t.shape) ? t.shape : "rectangle";
|
|
388
|
+
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 };
|
|
389
|
+
const aspect = kind2 === "circle" ? 1 : finite(t.aspectRatio, Number.MIN_VALUE, 10);
|
|
390
|
+
if (aspect) {
|
|
391
|
+
const frameWidth = Math.min(box.width, box.height * aspect), frameHeight = frameWidth / aspect;
|
|
392
|
+
box = { x: box.x + (box.width - frameWidth) / 2, y: box.y + (box.height - frameHeight) / 2, width: frameWidth, height: frameHeight };
|
|
393
|
+
}
|
|
394
|
+
box = { x: round(box.x), y: round(box.y), width: round(box.width), height: round(box.height) };
|
|
395
|
+
const result = {
|
|
396
|
+
path: designPath,
|
|
397
|
+
sourcePath: replacesContent ? `${path}.image` : designPath,
|
|
398
|
+
value,
|
|
399
|
+
position,
|
|
400
|
+
fill,
|
|
401
|
+
region,
|
|
402
|
+
box,
|
|
403
|
+
replacesContent,
|
|
404
|
+
shape: slideImageShape(kind2, box, finite(t.cornerRadius, 0, 0.5))
|
|
405
|
+
};
|
|
406
|
+
if (typeof t.alt === "string") result.alt = t.alt;
|
|
407
|
+
const border = record2(t.border), borderWidth = finite(border.width, 0, 64);
|
|
408
|
+
if (border.color !== void 0 && borderWidth) result.border = { color: border.color, width: round(borderWidth * scale) };
|
|
409
|
+
const opacity = finite(t.opacity, 0, 1);
|
|
410
|
+
if (opacity !== void 0 && opacity < 1) result.opacity = opacity;
|
|
411
|
+
if (t.recolor === "grayscale") result.recolor = { type: "grayscale" };
|
|
412
|
+
else if (record2(t.recolor).dark !== void 0 && record2(t.recolor).light !== void 0) result.recolor = { type: "duotone", dark: t.recolor.dark, light: t.recolor.light };
|
|
413
|
+
const overlay = record2(t.overlay), overlayOpacity = finite(overlay.opacity, 0, 1);
|
|
414
|
+
if (overlay.color !== void 0 && overlayOpacity !== void 0) {
|
|
415
|
+
const edge = ["top", "bottom", "left", "right"].includes(overlay.edge) ? overlay.edge : void 0;
|
|
416
|
+
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." });
|
|
417
|
+
else {
|
|
418
|
+
const part = finite(overlay.size, 0.05, 1) ?? 0.3;
|
|
419
|
+
const bandBox = !edge ? box : edge === "top" ? { ...box, height: round(box.height * part) } : edge === "bottom" ? { ...box, y: round(box.y + box.height * (1 - part)), height: round(box.height * part) } : edge === "left" ? { ...box, width: round(box.width * part) } : { ...box, x: round(box.x + box.width * (1 - part)), width: round(box.width * part) };
|
|
420
|
+
result.overlay = { color: overlay.color, opacity: overlayOpacity, box: bandBox, shape: edge ? slideImageShape("rectangle", bandBox) : result.shape };
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return result;
|
|
424
|
+
}
|
|
425
|
+
var webUrl = /^https?:\/\/\S+$/i;
|
|
426
|
+
function resolveSocialProfile(platform, value, records = [], owner = "organization") {
|
|
427
|
+
const raw = String(value).trim().replace(/\s+/gu, " ");
|
|
428
|
+
const display = (url2) => url2.replace(/^https:\/\//i, "");
|
|
429
|
+
if (webUrl.test(raw)) return { text: display(raw), href: raw, resolved: false };
|
|
430
|
+
const platformRecord = records.find((item) => item?.id === platform);
|
|
431
|
+
const base = typeof platformRecord?.baseUrl === "string" && webUrl.test(platformRecord.baseUrl) ? `${platformRecord.baseUrl.replace(/\/+$/u, "")}/{handle}` : void 0;
|
|
432
|
+
const pattern = (owner === "organization" ? platformRecord?.companyUrlPattern : void 0) ?? platformRecord?.profileUrlPattern ?? base;
|
|
433
|
+
const prefix = platformRecord?.handlePrefix ?? "";
|
|
434
|
+
const handle = prefix && raw.startsWith(prefix) ? raw.slice(prefix.length) : raw;
|
|
435
|
+
if (!handle || typeof pattern !== "string" || !pattern.includes("{handle}")) return { text: raw, resolved: false };
|
|
436
|
+
const url = pattern.split("{handle}").join(handle);
|
|
437
|
+
if (!webUrl.test(url)) return { text: raw, resolved: false };
|
|
438
|
+
return { text: display(url), href: encodeURI(url), resolved: true };
|
|
439
|
+
}
|
|
222
440
|
function layoutFurniture(input, options = {}) {
|
|
223
441
|
const slide = record2(input), width = options.width ?? 1280, height = options.height ?? 720, scale = Math.min(width, height) / 720;
|
|
224
442
|
const settings = { ...record2(record2(options.layout).composition), ...record2(slide.composition) };
|
|
@@ -227,10 +445,14 @@ function layoutFurniture(input, options = {}) {
|
|
|
227
445
|
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.");
|
|
228
446
|
const number = options.slideNumber ?? (options.slideIndex ?? 0) + 1;
|
|
229
447
|
if (!Number.isSafeInteger(number) || number < 1) throw new RangeError("Displayed slide number must be a positive safe integer.");
|
|
448
|
+
const presentationSlides = options.presentation?.slides, slideCount = options.slideCount ?? (Array.isArray(presentationSlides) && presentationSlides.length ? presentationSlides.length : void 0);
|
|
449
|
+
if (slideCount !== void 0 && (!Number.isSafeInteger(slideCount) || slideCount < 1)) throw new RangeError("Displayed slide count must be a positive safe integer.");
|
|
450
|
+
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.");
|
|
230
451
|
const outlines = options.textMeasurement?.outlineBounds !== void 0, parts = [], diagnostics = [];
|
|
231
452
|
const sourceRoot = `slides.${options.slideIndex ?? 0}`, organizations = Array.isArray(options.presentation?.organization) ? options.presentation.organization : [options.presentation?.organization];
|
|
232
453
|
const primaryIndex = organizations.findIndex((item) => record2(item).role === "primary"), organizationIndex = primaryIndex >= 0 ? primaryIndex : organizations.findIndex(Boolean);
|
|
233
|
-
const organization = record2(organizations[organizationIndex]),
|
|
454
|
+
const organization = record2(organizations[organizationIndex]), organizationRoot = Array.isArray(options.presentation?.organization) ? `organization.${organizationIndex}` : "organization", organizationPath = `${organizationRoot}.name`;
|
|
455
|
+
const inlinePlatforms = record2(record2(options.presentation?.catalogs).socialPlatforms).records, platformRecords = [...Array.isArray(inlinePlatforms) ? inlinePlatforms : [], ...options.socialPlatforms ?? []];
|
|
234
456
|
const fontFamily = options.fonts?.body ?? "sans-serif";
|
|
235
457
|
let headerBottom = 0, footerTop = height, configured = false;
|
|
236
458
|
const error = (path, message, code = "text-overflow") => diagnostics.push({ code, path, message });
|
|
@@ -244,7 +466,7 @@ function layoutFurniture(input, options = {}) {
|
|
|
244
466
|
for (const [index, zone] of ["left", "center", "right"].entries()) {
|
|
245
467
|
const content = record2(record2(value)[zone]), path = `${root}.${zone}`, x = width * (0.07 + index * 0.3), zoneWidth = width * 0.26, zoneParts = [];
|
|
246
468
|
let y = 0;
|
|
247
|
-
const add = (field, text, generated = false, sourcePath) => {
|
|
469
|
+
const add = (field, text, generated = false, sourcePath, extras = {}) => {
|
|
248
470
|
if (text === void 0) return;
|
|
249
471
|
if (typeof text !== "string") throw new TypeError(`Furniture field ${path}.${field} requires string content.`);
|
|
250
472
|
const partPath = `${path}.${field}`, style = resolveTextStyle({ fontFamily, fontWeight: 400, italic: false, path: partPath }, options.textMeasurement);
|
|
@@ -274,7 +496,7 @@ function layoutFurniture(input, options = {}) {
|
|
|
274
496
|
if (!Number.isFinite(partHeight)) throw new RangeError("Furniture exceeds finite layout coordinates.");
|
|
275
497
|
const box = { x, y, width: zoneWidth, height: Math.max(scale, partHeight) }, placement = outlines ? placeTextLines(ink, box, zone, padding) : void 0;
|
|
276
498
|
const accepted = { ...fit, ...placement ? { placement } : {}, overflow: fit.overflow || !!placement?.overflow };
|
|
277
|
-
zoneParts.push({ type: "text", kind: kind2, zone, field, path: partPath, sourcePath: sourcePath ?? (!generated ? partPath : void 0), generated, text, style, requestedFontSize: 13 * scale, minFontSize: minimum, box, alignment: zone, fit: accepted });
|
|
499
|
+
zoneParts.push({ type: "text", kind: kind2, zone, field, path: partPath, sourcePath: sourcePath ?? (!generated ? partPath : void 0), generated, text, style, requestedFontSize: 13 * scale, minFontSize: minimum, box, alignment: zone, fit: accepted, ...extras.fields?.length ? { fields: extras.fields } : {}, ...extras.links?.length ? { links: extras.links } : {} });
|
|
278
500
|
if (accepted.overflow) error(partPath, "Repeated text exceeds its zone at the selected readability floor; change the furniture or slide design.");
|
|
279
501
|
y += box.height;
|
|
280
502
|
};
|
|
@@ -288,13 +510,37 @@ function layoutFurniture(input, options = {}) {
|
|
|
288
510
|
if (typeof organization.name === "string") add("organization", organization.name, true, organizationPath);
|
|
289
511
|
else error(`${path}.organization`, "Generated organization name needs a named organization in the presentation.", "unresolved-content");
|
|
290
512
|
}
|
|
513
|
+
if (content.socials === true) {
|
|
514
|
+
const links = Object.entries(record2(organization.socials)).filter(([, value2]) => typeof value2 === "string" && value2.trim()).map(([platform, value2]) => ({ platform, sourcePath: `${organizationRoot}.socials.${platform}`, ...resolveSocialProfile(platform, value2, platformRecords, "organization") }));
|
|
515
|
+
if (links.length) add("socials", links.map((link) => link.text).join("\n"), true, `${organizationRoot}.socials`, { links });
|
|
516
|
+
else error(`${path}.socials`, "Generated social profiles need a primary organization with socials.", "unresolved-content");
|
|
517
|
+
}
|
|
291
518
|
if (content.section === true) {
|
|
292
519
|
if (typeof slide.section === "string") add("section", slide.section, true, `${sourceRoot}.section`);
|
|
293
520
|
else error(`${path}.section`, "Generated section needs a literal slide section.", "unresolved-content");
|
|
294
521
|
}
|
|
295
|
-
|
|
296
|
-
if (content.
|
|
297
|
-
|
|
522
|
+
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.`);
|
|
523
|
+
if (content.slideNumber === true) {
|
|
524
|
+
const resolved = formatSlideNumber(content.slideNumberFormat ?? DEFAULT_SLIDE_NUMBER_FORMAT, number, slideCount);
|
|
525
|
+
if ("error" in resolved) error(`${path}.slideNumberFormat`, resolved.error, "unresolved-content");
|
|
526
|
+
else add("slideNumber", resolved.text, true, void 0, { fields: resolved.fields });
|
|
527
|
+
}
|
|
528
|
+
if (content.date === true) {
|
|
529
|
+
const format = content.dateFormat ?? DEFAULT_FURNITURE_DATE_FORMAT;
|
|
530
|
+
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");
|
|
531
|
+
else {
|
|
532
|
+
const resolved = formatFurnitureDate(options.date, format);
|
|
533
|
+
if ("error" in resolved) error(`${path}.dateFormat`, resolved.error, "unresolved-content");
|
|
534
|
+
else add("date", resolved.text, true, void 0, { fields: [{ type: "date", start: 0, end: resolved.text.length, format }] });
|
|
535
|
+
}
|
|
536
|
+
} else if (typeof content.date === "string") {
|
|
537
|
+
if (content.dateFormat === void 0) add("date", content.date);
|
|
538
|
+
else {
|
|
539
|
+
const resolved = formatFurnitureDate(content.date, content.dateFormat);
|
|
540
|
+
if ("error" in resolved) error(`${path}.${parseIsoDate(content.date) ? "dateFormat" : "date"}`, resolved.error, "unresolved-content");
|
|
541
|
+
else add("date", resolved.text, true, `${path}.date`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
298
544
|
zones.push(zoneParts);
|
|
299
545
|
}
|
|
300
546
|
const tallest = Math.max(0, ...zones.map((zone) => zone.reduce((sum, part) => sum + part.box.height, 0)));
|
|
@@ -931,9 +1177,24 @@ function richTextLayouter(input, box, requestedSize, options) {
|
|
|
931
1177
|
const text = whole.slice(a, b), normalSize = (entry.run.fontSize !== void 0 ? entry.run.fontSize * 4 / 3 : requestedSize) * ratio;
|
|
932
1178
|
const script = entry.run.superscript || entry.run.subscript;
|
|
933
1179
|
const size = normalSize * (script ? 0.7 : 1), baselineShift = entry.run.superscript ? -normalSize * 0.35 : entry.run.subscript ? normalSize * 0.2 : 0;
|
|
934
|
-
const
|
|
935
|
-
|
|
936
|
-
|
|
1180
|
+
const measure = textWidthMeasurer(entry.style, options.textMeasurement);
|
|
1181
|
+
let cursor = 0;
|
|
1182
|
+
for (const [index, segment] of text.split(" ").entries()) {
|
|
1183
|
+
if (index) {
|
|
1184
|
+
const tabStart = a + cursor, tabWidth = measure(" ", size) * 4;
|
|
1185
|
+
if (!Number.isFinite(tabWidth) || tabWidth <= 0) throw new RangeError("Rich text tabs require a positive finite measured space advance.");
|
|
1186
|
+
const next = (Math.floor(x / tabWidth + 1e-9) + 1) * tabWidth;
|
|
1187
|
+
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 });
|
|
1188
|
+
x = next;
|
|
1189
|
+
cursor++;
|
|
1190
|
+
}
|
|
1191
|
+
if (segment) {
|
|
1192
|
+
const width2 = measure(segment, size), segmentStart = a + cursor;
|
|
1193
|
+
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 });
|
|
1194
|
+
x += width2;
|
|
1195
|
+
cursor += segment.length;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
937
1198
|
}
|
|
938
1199
|
return result;
|
|
939
1200
|
};
|
|
@@ -1188,6 +1449,7 @@ function composeSlide(input, options = {}) {
|
|
|
1188
1449
|
const minSize = (composition.minFontSize ?? 16) * scale;
|
|
1189
1450
|
const rasterPadding = (options.textRasterPadding ?? 1) * scale;
|
|
1190
1451
|
if (!Number.isFinite(rasterPadding) || rasterPadding < 0) throw new RangeError("Text raster padding must be finite and nonnegative.");
|
|
1452
|
+
const alignmentFor = (field) => (field === "title" ? record2(slide.design).titleAlignment ?? options.titleAlignment : record2(slide.design).contentAlignment ?? options.contentAlignment) ?? "left";
|
|
1191
1453
|
const styleFor = (field, path2) => resolveTextStyle({
|
|
1192
1454
|
fontFamily: (field === "title" ? options.fonts?.heading : field === "code" ? options.fonts?.code : options.fonts?.body) ?? (field === "code" ? "monospace" : "sans-serif"),
|
|
1193
1455
|
fontWeight: field === "title" ? 700 : 400,
|
|
@@ -1197,7 +1459,7 @@ function composeSlide(input, options = {}) {
|
|
|
1197
1459
|
const fitPlacedText = (field, value, text, box, size, minimum, path2) => {
|
|
1198
1460
|
const style = styleFor(field, path2), rich = field === "text" && Array.isArray(value);
|
|
1199
1461
|
if (!options.textMeasurement?.outlineBounds) return rich ? fitRichText(value, box, size, minimum, { style, textMeasurement: options.textMeasurement }) : fitText(text, box, size, minimum, textWidthMeasurer(style, options.textMeasurement));
|
|
1200
|
-
const alignment = (field
|
|
1462
|
+
const alignment = alignmentFor(field);
|
|
1201
1463
|
const richLayout = rich ? richTextLayouter(value, box, size, { style, textMeasurement: options.textMeasurement }) : void 0;
|
|
1202
1464
|
const measure = textWidthMeasurer(style, options.textMeasurement), floor = rich ? richMinimum(value, size, minimum) : minimum, start = Math.max(size, floor);
|
|
1203
1465
|
for (let trial = 0; trial <= 64; trial++) {
|
|
@@ -1207,6 +1469,7 @@ function composeSlide(input, options = {}) {
|
|
|
1207
1469
|
const lines = richLines ? richLines.map((line) => {
|
|
1208
1470
|
let outline = null;
|
|
1209
1471
|
for (const fragment of line.fragments) {
|
|
1472
|
+
if (fragment.kind === "tab") continue;
|
|
1210
1473
|
const bounds = measureTextOutline(fragment.text, fragment.fontSize, fragment.style, options.textMeasurement);
|
|
1211
1474
|
if (!bounds) continue;
|
|
1212
1475
|
const next = { ...bounds, x: fragment.x + bounds.x, y: fragment.baselineShift + bounds.y };
|
|
@@ -1245,25 +1508,32 @@ function composeSlide(input, options = {}) {
|
|
|
1245
1508
|
};
|
|
1246
1509
|
const fitContent = (field, value, text, box, size, minimum, path2) => field === "text" ? fitPlacedText(field, value, text, box, size, minimum, path2) : field === "items" || field === "bullets" ? fitList(value, box, size, minimum, { style: styleFor(field, path2), textMeasurement: options.textMeasurement }) : fitText(text, box, size, minimum, widthFor(field, path2));
|
|
1247
1510
|
const path = `slides.${options.slideIndex ?? 0}`;
|
|
1511
|
+
const slideImageDiagnostics = [];
|
|
1512
|
+
const slideImage = resolveSlideImage(slide, layout, options.presentation, width, height, path, padding, scale, slideImageDiagnostics);
|
|
1513
|
+
const area = { left: 0, top: 0, right: width, bottom: height };
|
|
1514
|
+
if (slideImage?.position === "left") area.left = slideImage.region.width;
|
|
1515
|
+
else if (slideImage?.position === "right") area.right = slideImage.region.x;
|
|
1516
|
+
else if (slideImage?.position === "top") area.top = slideImage.region.height;
|
|
1517
|
+
else if (slideImage?.position === "bottom") area.bottom = slideImage.region.y;
|
|
1248
1518
|
const items = [], diagnostics = [];
|
|
1249
1519
|
const measuredFurniture = layoutFurniture(slide, options);
|
|
1250
1520
|
const furniture = measuredFurniture.configured || measuredFurniture.diagnostics.length ? measuredFurniture : void 0;
|
|
1251
1521
|
if (furniture) diagnostics.push(...furniture.diagnostics);
|
|
1252
|
-
const bodyBottom = Math.min(
|
|
1253
|
-
let y = Math.max(padding, furniture?.headerBottom ? furniture.headerBottom + gap * 0.5 : padding);
|
|
1522
|
+
const bodyBottom = Math.min(area.bottom - padding, furniture && furniture.footerTop < height ? furniture.footerTop - gap * 0.5 : height - padding);
|
|
1523
|
+
let y = Math.max(area.top + padding, furniture?.headerBottom ? furniture.headerBottom + gap * 0.5 : padding);
|
|
1254
1524
|
for (const field of ["tag", "title", "subtitle"]) {
|
|
1255
1525
|
if (!slide[field]) continue;
|
|
1256
1526
|
const requested = (field === "title" ? 54 : field === "tag" ? 16 : 25) * scale;
|
|
1257
1527
|
const maxHeight = Math.max(height * (field === "title" ? 0.26 : field === "subtitle" ? 0.12 : 0.045), minSize * 1.22 + 2 * rasterPadding);
|
|
1258
|
-
const box = { x: padding, y, width:
|
|
1528
|
+
const box = { x: area.left + padding, y, width: area.right - area.left - padding * 2, height: maxHeight };
|
|
1259
1529
|
const text = fitPlacedText(field, slide[field], String(slide[field]), box, requested, minSize, `${path}.${field}`);
|
|
1260
1530
|
box.height = Math.min(maxHeight, Math.max(text.lines.length * text.lineHeight, text.placement?.height ?? 0));
|
|
1261
1531
|
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." });
|
|
1262
|
-
items.push({ path: `${path}.${field}`, field, type: "text", value: slide[field], payload: { text: slide[field] }, box, text, textStyle: styleFor(field, `${path}.${field}`), composition });
|
|
1532
|
+
items.push({ path: `${path}.${field}`, field, type: "text", value: slide[field], payload: { text: slide[field] }, box, text, textStyle: styleFor(field, `${path}.${field}`), composition, alignment: alignmentFor(field) });
|
|
1263
1533
|
y += box.height + gap * 0.5;
|
|
1264
1534
|
}
|
|
1265
1535
|
if (items.length) y += gap * 0.5;
|
|
1266
|
-
const contentBox = { x: padding, y, width:
|
|
1536
|
+
const contentBox = { x: area.left + padding, y, width: area.right - area.left - padding * 2, height: Math.max(scale, bodyBottom - y) };
|
|
1267
1537
|
const collect = (host, basePath, depth = 0, ancestors = []) => {
|
|
1268
1538
|
if (Array.isArray(host.blocks)) {
|
|
1269
1539
|
if (depth >= MAX_COMPOSITION_DEPTH || ancestors.includes(host)) throw new RangeError(`Content groups must be acyclic and nest at most ${MAX_COMPOSITION_DEPTH} levels.`);
|
|
@@ -1282,7 +1552,7 @@ function composeSlide(input, options = {}) {
|
|
|
1282
1552
|
return fields.filter((field) => host[field] !== void 0).map((field) => ({ field, type: host.type ?? kind(field), value: host[field], path: `${basePath}.${field}`, payload: { type: host.type ?? kind(field), [field]: host[field] } }));
|
|
1283
1553
|
};
|
|
1284
1554
|
const regions = Object.keys(slide).filter((key) => regionParts(key)).sort();
|
|
1285
|
-
const pending = regions.length ? regions.flatMap((key) => collect(record2(slide[key]), `${path}.${key}`).map((item) => ({ ...item, region: regionParts(key) }))) : Array.isArray(slide.blocks) ? slide.blocks.flatMap((block, index) => collect(record2(block), `${path}.blocks.${index}`)) : collect(slide, path);
|
|
1555
|
+
const pending = regions.length ? regions.flatMap((key) => collect(record2(slide[key]), `${path}.${key}`).map((item) => ({ ...item, region: regionParts(key) }))) : Array.isArray(slide.blocks) ? slide.blocks.flatMap((block, index) => collect(record2(block), `${path}.blocks.${index}`)) : collect(slideImage?.replacesContent ? { ...slide, image: void 0 } : slide, path);
|
|
1286
1556
|
const groups = [], flows = [];
|
|
1287
1557
|
const decisions = options.explain ? [] : void 0;
|
|
1288
1558
|
const inheritedSettings = (parent, own = {}) => ({
|
|
@@ -1321,7 +1591,7 @@ function composeSlide(input, options = {}) {
|
|
|
1321
1591
|
minFontSize: settings.minFontSize,
|
|
1322
1592
|
path: node.path,
|
|
1323
1593
|
textRasterPadding: options.textRasterPadding,
|
|
1324
|
-
align:
|
|
1594
|
+
align: alignmentFor("metric")
|
|
1325
1595
|
});
|
|
1326
1596
|
const measureTimeline = (node, box, settings) => layoutTimeline(node.value, acceptedBox(box), {
|
|
1327
1597
|
fonts: options.fonts,
|
|
@@ -1364,29 +1634,29 @@ function composeSlide(input, options = {}) {
|
|
|
1364
1634
|
return score;
|
|
1365
1635
|
};
|
|
1366
1636
|
const modeFor = (settings) => settings.mode ?? "auto";
|
|
1367
|
-
const defaultColumns = (count,
|
|
1637
|
+
const defaultColumns = (count, area2, settings) => {
|
|
1368
1638
|
const mode = modeFor(settings);
|
|
1369
|
-
return mode === "column" ? 1 : mode === "row" ? Math.max(1, count) : settings.columns ?? Math.max(1, Math.min(count, Math.ceil(Math.sqrt(count *
|
|
1639
|
+
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))));
|
|
1370
1640
|
};
|
|
1371
1641
|
const scoreNode = (node, box, settings, penalties) => {
|
|
1372
1642
|
if (!node.children) return leafScore(node, box, settings, penalties);
|
|
1373
|
-
const own = inheritedSettings(settings, node.composition),
|
|
1374
|
-
const cols = defaultColumns(node.children.length,
|
|
1375
|
-
const boxes = gridBoxes(node.children.length,
|
|
1643
|
+
const own = inheritedSettings(settings, node.composition), area2 = inset(box, own);
|
|
1644
|
+
const cols = defaultColumns(node.children.length, area2, own);
|
|
1645
|
+
const boxes = gridBoxes(node.children.length, area2, cols, (own.gap ?? 1 / 30) * Math.min(box.width, box.height), own.weights ?? [], modeFor(own) === "column");
|
|
1376
1646
|
return node.children.reduce((score, child, index) => score + scoreNode(child, boxes[index], own, penalties), 0);
|
|
1377
1647
|
};
|
|
1378
|
-
const arrange = (nodes,
|
|
1648
|
+
const arrange = (nodes, area2, settings, reserved = 0, gapOverride, containerPath = path) => {
|
|
1379
1649
|
const count = Math.max(nodes.length, reserved);
|
|
1380
1650
|
if (!count) return;
|
|
1381
|
-
const mode = modeFor(settings), localGap = (settings.gap ?? 1 / 30) * Math.min(
|
|
1651
|
+
const mode = modeFor(settings), localGap = (settings.gap ?? 1 / 30) * Math.min(area2.width, area2.height);
|
|
1382
1652
|
const actualGap = gapOverride ?? (settings === rootSettings ? gap : localGap);
|
|
1383
|
-
let cols = defaultColumns(count,
|
|
1653
|
+
let cols = defaultColumns(count, area2, settings);
|
|
1384
1654
|
const hasRegions = nodes.some((node) => node.region);
|
|
1385
1655
|
const candidates = decisions ? [] : void 0;
|
|
1386
1656
|
if (mode === "auto" && !hasRegions) {
|
|
1387
1657
|
let best = Infinity;
|
|
1388
1658
|
for (let candidate = 1; candidate <= Math.min(count, settings.columns ?? 6); candidate++) {
|
|
1389
|
-
const boxes2 = gridBoxes(count,
|
|
1659
|
+
const boxes2 = gridBoxes(count, area2, candidate, actualGap, settings.weights ?? [], false);
|
|
1390
1660
|
const emptySlots = (Math.ceil(count / candidate) * candidate - count) * 2;
|
|
1391
1661
|
const penalties = candidates ? { cellProportions: 0, fontReduction: 0, textOverflow: 0, tableOverflow: 0, smallCells: 0, emptySlots } : void 0;
|
|
1392
1662
|
const score = nodes.reduce((sum, node, i) => sum + scoreNode(node, boxes2[i], settings, penalties), 0) + emptySlots;
|
|
@@ -1404,11 +1674,11 @@ function composeSlide(input, options = {}) {
|
|
|
1404
1674
|
...hasRegions ? {} : { selectedColumns: cols },
|
|
1405
1675
|
candidates: candidates ?? []
|
|
1406
1676
|
});
|
|
1407
|
-
const grid = gridGeometry(count,
|
|
1677
|
+
const grid = gridGeometry(count, area2, cols, actualGap, settings.weights ?? [], mode === "column");
|
|
1408
1678
|
const boxes = grid.boxes;
|
|
1409
|
-
if (!nodes.some((node) => node.region)) flows.push({ path: containerPath, box: { ...
|
|
1679
|
+
if (!nodes.some((node) => node.region)) flows.push({ path: containerPath, box: { ...area2 }, composition: { ...settings }, columns: grid.columns, rows: grid.rows, gap: grid.gap, itemCount: nodes.length, slotCount: count });
|
|
1410
1680
|
nodes.forEach((node, index) => {
|
|
1411
|
-
let box = node.region ? regionBox(node.region,
|
|
1681
|
+
let box = node.region ? regionBox(node.region, area2, actualGap) : boxes[index];
|
|
1412
1682
|
if (node.children) {
|
|
1413
1683
|
const own = inheritedSettings(settings, node.composition), inner = inset(box, own);
|
|
1414
1684
|
groups.push({ path: node.path, box, contentBox: inner, composition: own });
|
|
@@ -1434,6 +1704,7 @@ function composeSlide(input, options = {}) {
|
|
|
1434
1704
|
text,
|
|
1435
1705
|
textStyle: body?.style ?? styleFor(node.field, node.path),
|
|
1436
1706
|
composition: settings,
|
|
1707
|
+
alignment: alignmentFor(node.field),
|
|
1437
1708
|
...quoteLayout ? { quoteLayout } : {},
|
|
1438
1709
|
...codeLayout ? { codeLayout } : {},
|
|
1439
1710
|
...metricLayout ? { metricLayout } : {},
|
|
@@ -1444,6 +1715,10 @@ function composeSlide(input, options = {}) {
|
|
|
1444
1715
|
});
|
|
1445
1716
|
};
|
|
1446
1717
|
const placeholders = Array.isArray(layout.placeholders) ? layout.placeholders.filter((p) => !headings.has(p.type)) : [];
|
|
1718
|
+
if (slideImage?.replacesContent) {
|
|
1719
|
+
const picture = placeholders.findIndex((p) => p.type === "picture");
|
|
1720
|
+
if (picture >= 0) placeholders.splice(picture, 1);
|
|
1721
|
+
}
|
|
1447
1722
|
const rootSettings = { ...composition, mode: composition.mode ?? (layout.slideLayoutDirection === "Vertical" ? "column" : layout.slideLayoutDirection === "Horizontal" ? "row" : "auto") };
|
|
1448
1723
|
arrange(pending, contentBox, rootSettings, composition.mode ? 0 : placeholders.length);
|
|
1449
1724
|
for (const item of items) {
|
|
@@ -1477,7 +1752,8 @@ function composeSlide(input, options = {}) {
|
|
|
1477
1752
|
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)
|
|
1478
1753
|
} : void 0;
|
|
1479
1754
|
if (failures.length) throw new OPFCompositionError(failures, explanation);
|
|
1480
|
-
|
|
1755
|
+
diagnostics.push(...slideImageDiagnostics);
|
|
1756
|
+
return { width, height, contentBox, items, groups, flows, diagnostics, composition, ...furniture ? { furniture } : {}, ...slideImage ? { slideImage } : {}, ...explanation ? { explanation } : {} };
|
|
1481
1757
|
}
|
|
1482
1758
|
function resolveCanvasDimensions(input) {
|
|
1483
1759
|
const presets = {
|
|
@@ -1497,4 +1773,4 @@ function resolveCanvasDimensions(input) {
|
|
|
1497
1773
|
return { width, height };
|
|
1498
1774
|
}
|
|
1499
1775
|
|
|
1500
|
-
export { MAX_COMPOSITION_DEPTH, OPFCompositionError, chartColorForFill, colorContrast, composeSlide, fitList, fitRichText, fitText, layoutCode, layoutFurniture, layoutMetric, layoutQuote, layoutTable, layoutTimeline, measureText, measureTextOutline, normalizeHexColor, placeTextLines, resolveCanvasDimensions, resolveColorRef, resolveFontFamilies, resolveTextStyle, tableGrid, tableRowBoundaries, textColorForFill, textWidthMeasurer, wrapText };
|
|
1776
|
+
export { DEFAULT_FONT_SCHEME, DEFAULT_FURNITURE_DATE_FORMAT, DEFAULT_SLIDE_NUMBER_FORMAT, MAX_COMPOSITION_DEPTH, OPFCompositionError, chartColorForFill, colorContrast, composeSlide, fitList, fitRichText, fitText, formatFurnitureDate, formatSlideNumber, layoutCode, layoutFurniture, layoutMetric, layoutQuote, layoutTable, layoutTimeline, measureText, measureTextOutline, normalizeHexColor, placeTextLines, resolveCanvasDimensions, resolveColorRef, resolveFontFamilies, resolveFontSchemeReference, resolveSocialProfile, resolveTextStyle, slideImageShape, tableGrid, tableRowBoundaries, textColorForFill, textWidthMeasurer, wrapText };
|