@openpresentation/opf 0.11.0 → 0.11.2
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-6FU2KICU.js → chunk-7LF37SG3.js} +7084 -5106
- package/dist/{chunk-FTWNWFIV.js → chunk-BVLWFMDX.js} +348 -36
- package/dist/{chunk-CHKJ4QHQ.js → chunk-DJRZMGKY.js} +113 -32
- package/dist/{chunk-EQHZ3ZO4.js → chunk-K7IWYODS.js} +3 -3
- package/dist/chunk-KZ6BT5UG.js +3591 -0
- package/dist/{chunk-GN52EE3P.js → chunk-LTSDDBNW.js} +425 -40
- package/dist/{chunk-RS5GZ5RW.js → chunk-SDETQV7Y.js} +161 -0
- package/dist/{chunk-FWA5XB45.js → chunk-SMYEM3HV.js} +45 -25
- 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 +127 -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 +3510 -0
- package/dist/spec/reference/font-policy.schema.json +338 -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) {
|
|
@@ -201,6 +319,21 @@ function placeTextLines(lines, box, alignment = "left", rasterPadding = 0) {
|
|
|
201
319
|
}
|
|
202
320
|
return { alignment, rasterPadding, lines: placed, height, overflow };
|
|
203
321
|
}
|
|
322
|
+
function translateTextFit(fit, dy) {
|
|
323
|
+
if (!dy || !fit.placement) return fit;
|
|
324
|
+
return {
|
|
325
|
+
...fit,
|
|
326
|
+
placement: {
|
|
327
|
+
...fit.placement,
|
|
328
|
+
lines: fit.placement.lines.map((line) => ({
|
|
329
|
+
...line,
|
|
330
|
+
y: line.y + dy,
|
|
331
|
+
baseline: line.baseline + dy,
|
|
332
|
+
outline: line.outline ? { ...line.outline, y: line.outline.y + dy } : line.outline
|
|
333
|
+
}))
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
}
|
|
204
337
|
var OPFCompositionError = class extends Error {
|
|
205
338
|
constructor(diagnostics, explanation) {
|
|
206
339
|
super("Slide content does not fit its composition.");
|
|
@@ -214,11 +347,112 @@ var OPFCompositionError = class extends Error {
|
|
|
214
347
|
};
|
|
215
348
|
var fields = ["text", "items", "bullets", "image", "video", "chart", "table", "code", "metric", "quote", "timeline"];
|
|
216
349
|
var headings = /* @__PURE__ */ new Set(["title", "subtitle", "tag"]);
|
|
350
|
+
var COVER_LAYOUT_IDS = /* @__PURE__ */ new Set(["title", "title-subtitle"]);
|
|
217
351
|
var rows = ["top", "middle", "bottom"];
|
|
218
352
|
var columns = ["left", "center", "right"];
|
|
219
353
|
var record2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
220
354
|
var kind = (field) => field === "items" ? "list" : field === "bullets" ? "text" : field;
|
|
221
355
|
var round = (value) => Math.round(value * 1e6) / 1e6 || value;
|
|
356
|
+
var SLIDE_IMAGE_POSITIONS = ["background", "top", "bottom", "left", "right"];
|
|
357
|
+
var SLIDE_IMAGE_BAND = 0.5;
|
|
358
|
+
var assetSource = (value) => typeof value === "string" ? value : record2(value).src;
|
|
359
|
+
var finite = (value, min, max) => typeof value === "number" && Number.isFinite(value) && value >= min && value <= max ? value : void 0;
|
|
360
|
+
var pathNumber = (value) => String(round(value));
|
|
361
|
+
var SLIDE_IMAGE_SHAPES = { rectangle: "rect", rounded: "roundRect", circle: "ellipse", hexagon: "hexagon" };
|
|
362
|
+
function slideImageShape(kind2, box, cornerRadius = 1 / 6) {
|
|
363
|
+
const { x, y, width: w, height: h } = box, r = x + w, b = y + h, ss = Math.min(w, h), n = pathNumber;
|
|
364
|
+
const rect = `M${n(x)} ${n(y)}H${n(r)}V${n(b)}H${n(x)}Z`;
|
|
365
|
+
if (kind2 === "rounded") {
|
|
366
|
+
const adj = Math.round(Math.min(0.5, Math.max(0, cornerRadius)) * 1e5), radius = ss * adj / 1e5;
|
|
367
|
+
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`;
|
|
368
|
+
return { kind: kind2, preset: "roundRect", adjust: { adj }, path };
|
|
369
|
+
}
|
|
370
|
+
if (kind2 === "circle") {
|
|
371
|
+
const cy = y + h / 2;
|
|
372
|
+
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` };
|
|
373
|
+
}
|
|
374
|
+
if (kind2 === "hexagon") {
|
|
375
|
+
const adj = 25e3, vf = 115470, a = Math.min(Math.max(adj, 0), 5e4 * w / ss);
|
|
376
|
+
const x1 = ss * a / 1e5, x2 = r - x1, vc = y + h / 2, dy1 = h / 2 * vf / 1e5 * Math.sin(Math.PI / 3);
|
|
377
|
+
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` };
|
|
378
|
+
}
|
|
379
|
+
return { kind: "rectangle", preset: "rect", adjust: {}, path: rect };
|
|
380
|
+
}
|
|
381
|
+
function resolveSlideImage(slide, layout, presentation, width, height, path, padding, scale, diagnostics) {
|
|
382
|
+
const own = record2(slide.design), deck = record2(record2(presentation).design);
|
|
383
|
+
const local = own.slideImage !== void 0;
|
|
384
|
+
const configured = local ? own.slideImage : deck.slideImage;
|
|
385
|
+
if (!configured || typeof configured !== "object" && typeof configured !== "string") return void 0;
|
|
386
|
+
const treatment = typeof configured === "object" && !Array.isArray(configured) && "position" in configured ? record2(configured) : void 0;
|
|
387
|
+
const alignment = typeof layout.slideImageAlignment === "string" ? layout.slideImageAlignment.toLowerCase() : void 0;
|
|
388
|
+
const position = treatment ? treatment.position : SLIDE_IMAGE_POSITIONS.find((value2) => value2 === alignment) ?? "background";
|
|
389
|
+
if (!SLIDE_IMAGE_POSITIONS.includes(position)) return void 0;
|
|
390
|
+
const designSource = treatment ? treatment.src : configured;
|
|
391
|
+
const root = slide.image, sameSource = root !== void 0 && designSource !== void 0 && assetSource(root) === assetSource(designSource);
|
|
392
|
+
if (!local && layout.slideImage !== true && !sameSource) return void 0;
|
|
393
|
+
const replacesContent = root !== void 0 && (designSource === void 0 || sameSource);
|
|
394
|
+
const value = replacesContent ? root : designSource;
|
|
395
|
+
const source = assetSource(value);
|
|
396
|
+
if (typeof source !== "string" || !source) return void 0;
|
|
397
|
+
const t = treatment ?? {};
|
|
398
|
+
const fill = t.fill === "crop" || t.fill === "fit" ? t.fill : (own.imageFill ?? deck.imageFill) === "fit" ? "fit" : "crop";
|
|
399
|
+
const share = finite(t.size, 0.1, 0.9) ?? SLIDE_IMAGE_BAND;
|
|
400
|
+
const band = { width: round(width * share), height: round(height * share) };
|
|
401
|
+
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 };
|
|
402
|
+
const designPath = local ? `${path}.design.slideImage` : "design.slideImage";
|
|
403
|
+
const kind2 = Object.hasOwn(SLIDE_IMAGE_SHAPES, t.shape) ? t.shape : "rectangle";
|
|
404
|
+
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 };
|
|
405
|
+
const aspect = kind2 === "circle" ? 1 : finite(t.aspectRatio, Number.MIN_VALUE, 10);
|
|
406
|
+
if (aspect) {
|
|
407
|
+
const frameWidth = Math.min(box.width, box.height * aspect), frameHeight = frameWidth / aspect;
|
|
408
|
+
box = { x: box.x + (box.width - frameWidth) / 2, y: box.y + (box.height - frameHeight) / 2, width: frameWidth, height: frameHeight };
|
|
409
|
+
}
|
|
410
|
+
box = { x: round(box.x), y: round(box.y), width: round(box.width), height: round(box.height) };
|
|
411
|
+
const result = {
|
|
412
|
+
path: designPath,
|
|
413
|
+
sourcePath: replacesContent ? `${path}.image` : designPath,
|
|
414
|
+
value,
|
|
415
|
+
position,
|
|
416
|
+
fill,
|
|
417
|
+
region,
|
|
418
|
+
box,
|
|
419
|
+
replacesContent,
|
|
420
|
+
shape: slideImageShape(kind2, box, finite(t.cornerRadius, 0, 0.5))
|
|
421
|
+
};
|
|
422
|
+
if (typeof t.alt === "string") result.alt = t.alt;
|
|
423
|
+
const border = record2(t.border), borderWidth = finite(border.width, 0, 64);
|
|
424
|
+
if (border.color !== void 0 && borderWidth) result.border = { color: border.color, width: round(borderWidth * scale) };
|
|
425
|
+
const opacity = finite(t.opacity, 0, 1);
|
|
426
|
+
if (opacity !== void 0 && opacity < 1) result.opacity = opacity;
|
|
427
|
+
if (t.recolor === "grayscale") result.recolor = { type: "grayscale" };
|
|
428
|
+
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 };
|
|
429
|
+
const overlay = record2(t.overlay), overlayOpacity = finite(overlay.opacity, 0, 1);
|
|
430
|
+
if (overlay.color !== void 0 && overlayOpacity !== void 0) {
|
|
431
|
+
const edge = ["top", "bottom", "left", "right"].includes(overlay.edge) ? overlay.edge : void 0;
|
|
432
|
+
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." });
|
|
433
|
+
else {
|
|
434
|
+
const part = finite(overlay.size, 0.05, 1) ?? 0.3;
|
|
435
|
+
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) };
|
|
436
|
+
result.overlay = { color: overlay.color, opacity: overlayOpacity, box: bandBox, shape: edge ? slideImageShape("rectangle", bandBox) : result.shape };
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return result;
|
|
440
|
+
}
|
|
441
|
+
var webUrl = /^https?:\/\/\S+$/i;
|
|
442
|
+
function resolveSocialProfile(platform, value, records = [], owner = "organization") {
|
|
443
|
+
const raw = String(value).trim().replace(/\s+/gu, " ");
|
|
444
|
+
const display = (url2) => url2.replace(/^https:\/\//i, "");
|
|
445
|
+
if (webUrl.test(raw)) return { text: display(raw), href: raw, resolved: false };
|
|
446
|
+
const platformRecord = records.find((item) => item?.id === platform);
|
|
447
|
+
const base = typeof platformRecord?.baseUrl === "string" && webUrl.test(platformRecord.baseUrl) ? `${platformRecord.baseUrl.replace(/\/+$/u, "")}/{handle}` : void 0;
|
|
448
|
+
const pattern = (owner === "organization" ? platformRecord?.companyUrlPattern : void 0) ?? platformRecord?.profileUrlPattern ?? base;
|
|
449
|
+
const prefix = platformRecord?.handlePrefix ?? "";
|
|
450
|
+
const handle = prefix && raw.startsWith(prefix) ? raw.slice(prefix.length) : raw;
|
|
451
|
+
if (!handle || typeof pattern !== "string" || !pattern.includes("{handle}")) return { text: raw, resolved: false };
|
|
452
|
+
const url = pattern.split("{handle}").join(handle);
|
|
453
|
+
if (!webUrl.test(url)) return { text: raw, resolved: false };
|
|
454
|
+
return { text: display(url), href: encodeURI(url), resolved: true };
|
|
455
|
+
}
|
|
222
456
|
function layoutFurniture(input, options = {}) {
|
|
223
457
|
const slide = record2(input), width = options.width ?? 1280, height = options.height ?? 720, scale = Math.min(width, height) / 720;
|
|
224
458
|
const settings = { ...record2(record2(options.layout).composition), ...record2(slide.composition) };
|
|
@@ -227,10 +461,14 @@ function layoutFurniture(input, options = {}) {
|
|
|
227
461
|
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
462
|
const number = options.slideNumber ?? (options.slideIndex ?? 0) + 1;
|
|
229
463
|
if (!Number.isSafeInteger(number) || number < 1) throw new RangeError("Displayed slide number must be a positive safe integer.");
|
|
464
|
+
const presentationSlides = options.presentation?.slides, slideCount = options.slideCount ?? (Array.isArray(presentationSlides) && presentationSlides.length ? presentationSlides.length : void 0);
|
|
465
|
+
if (slideCount !== void 0 && (!Number.isSafeInteger(slideCount) || slideCount < 1)) throw new RangeError("Displayed slide count must be a positive safe integer.");
|
|
466
|
+
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
467
|
const outlines = options.textMeasurement?.outlineBounds !== void 0, parts = [], diagnostics = [];
|
|
231
468
|
const sourceRoot = `slides.${options.slideIndex ?? 0}`, organizations = Array.isArray(options.presentation?.organization) ? options.presentation.organization : [options.presentation?.organization];
|
|
232
469
|
const primaryIndex = organizations.findIndex((item) => record2(item).role === "primary"), organizationIndex = primaryIndex >= 0 ? primaryIndex : organizations.findIndex(Boolean);
|
|
233
|
-
const organization = record2(organizations[organizationIndex]),
|
|
470
|
+
const organization = record2(organizations[organizationIndex]), organizationRoot = Array.isArray(options.presentation?.organization) ? `organization.${organizationIndex}` : "organization", organizationPath = `${organizationRoot}.name`;
|
|
471
|
+
const inlinePlatforms = record2(record2(options.presentation?.catalogs).socialPlatforms).records, platformRecords = [...Array.isArray(inlinePlatforms) ? inlinePlatforms : [], ...options.socialPlatforms ?? []];
|
|
234
472
|
const fontFamily = options.fonts?.body ?? "sans-serif";
|
|
235
473
|
let headerBottom = 0, footerTop = height, configured = false;
|
|
236
474
|
const error = (path, message, code = "text-overflow") => diagnostics.push({ code, path, message });
|
|
@@ -244,7 +482,7 @@ function layoutFurniture(input, options = {}) {
|
|
|
244
482
|
for (const [index, zone] of ["left", "center", "right"].entries()) {
|
|
245
483
|
const content = record2(record2(value)[zone]), path = `${root}.${zone}`, x = width * (0.07 + index * 0.3), zoneWidth = width * 0.26, zoneParts = [];
|
|
246
484
|
let y = 0;
|
|
247
|
-
const add = (field, text, generated = false, sourcePath) => {
|
|
485
|
+
const add = (field, text, generated = false, sourcePath, extras = {}) => {
|
|
248
486
|
if (text === void 0) return;
|
|
249
487
|
if (typeof text !== "string") throw new TypeError(`Furniture field ${path}.${field} requires string content.`);
|
|
250
488
|
const partPath = `${path}.${field}`, style = resolveTextStyle({ fontFamily, fontWeight: 400, italic: false, path: partPath }, options.textMeasurement);
|
|
@@ -274,7 +512,7 @@ function layoutFurniture(input, options = {}) {
|
|
|
274
512
|
if (!Number.isFinite(partHeight)) throw new RangeError("Furniture exceeds finite layout coordinates.");
|
|
275
513
|
const box = { x, y, width: zoneWidth, height: Math.max(scale, partHeight) }, placement = outlines ? placeTextLines(ink, box, zone, padding) : void 0;
|
|
276
514
|
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 });
|
|
515
|
+
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
516
|
if (accepted.overflow) error(partPath, "Repeated text exceeds its zone at the selected readability floor; change the furniture or slide design.");
|
|
279
517
|
y += box.height;
|
|
280
518
|
};
|
|
@@ -288,13 +526,37 @@ function layoutFurniture(input, options = {}) {
|
|
|
288
526
|
if (typeof organization.name === "string") add("organization", organization.name, true, organizationPath);
|
|
289
527
|
else error(`${path}.organization`, "Generated organization name needs a named organization in the presentation.", "unresolved-content");
|
|
290
528
|
}
|
|
529
|
+
if (content.socials === true) {
|
|
530
|
+
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") }));
|
|
531
|
+
if (links.length) add("socials", links.map((link) => link.text).join("\n"), true, `${organizationRoot}.socials`, { links });
|
|
532
|
+
else error(`${path}.socials`, "Generated social profiles need a primary organization with socials.", "unresolved-content");
|
|
533
|
+
}
|
|
291
534
|
if (content.section === true) {
|
|
292
535
|
if (typeof slide.section === "string") add("section", slide.section, true, `${sourceRoot}.section`);
|
|
293
536
|
else error(`${path}.section`, "Generated section needs a literal slide section.", "unresolved-content");
|
|
294
537
|
}
|
|
295
|
-
|
|
296
|
-
if (content.
|
|
297
|
-
|
|
538
|
+
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.`);
|
|
539
|
+
if (content.slideNumber === true) {
|
|
540
|
+
const resolved = formatSlideNumber(content.slideNumberFormat ?? DEFAULT_SLIDE_NUMBER_FORMAT, number, slideCount);
|
|
541
|
+
if ("error" in resolved) error(`${path}.slideNumberFormat`, resolved.error, "unresolved-content");
|
|
542
|
+
else add("slideNumber", resolved.text, true, void 0, { fields: resolved.fields });
|
|
543
|
+
}
|
|
544
|
+
if (content.date === true) {
|
|
545
|
+
const format = content.dateFormat ?? DEFAULT_FURNITURE_DATE_FORMAT;
|
|
546
|
+
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");
|
|
547
|
+
else {
|
|
548
|
+
const resolved = formatFurnitureDate(options.date, format);
|
|
549
|
+
if ("error" in resolved) error(`${path}.dateFormat`, resolved.error, "unresolved-content");
|
|
550
|
+
else add("date", resolved.text, true, void 0, { fields: [{ type: "date", start: 0, end: resolved.text.length, format }] });
|
|
551
|
+
}
|
|
552
|
+
} else if (typeof content.date === "string") {
|
|
553
|
+
if (content.dateFormat === void 0) add("date", content.date);
|
|
554
|
+
else {
|
|
555
|
+
const resolved = formatFurnitureDate(content.date, content.dateFormat);
|
|
556
|
+
if ("error" in resolved) error(`${path}.${parseIsoDate(content.date) ? "dateFormat" : "date"}`, resolved.error, "unresolved-content");
|
|
557
|
+
else add("date", resolved.text, true, `${path}.date`);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
298
560
|
zones.push(zoneParts);
|
|
299
561
|
}
|
|
300
562
|
const tallest = Math.max(0, ...zones.map((zone) => zone.reduce((sum, part) => sum + part.box.height, 0)));
|
|
@@ -931,9 +1193,24 @@ function richTextLayouter(input, box, requestedSize, options) {
|
|
|
931
1193
|
const text = whole.slice(a, b), normalSize = (entry.run.fontSize !== void 0 ? entry.run.fontSize * 4 / 3 : requestedSize) * ratio;
|
|
932
1194
|
const script = entry.run.superscript || entry.run.subscript;
|
|
933
1195
|
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
|
-
|
|
1196
|
+
const measure = textWidthMeasurer(entry.style, options.textMeasurement);
|
|
1197
|
+
let cursor = 0;
|
|
1198
|
+
for (const [index, segment] of text.split(" ").entries()) {
|
|
1199
|
+
if (index) {
|
|
1200
|
+
const tabStart = a + cursor, tabWidth = measure(" ", size) * 4;
|
|
1201
|
+
if (!Number.isFinite(tabWidth) || tabWidth <= 0) throw new RangeError("Rich text tabs require a positive finite measured space advance.");
|
|
1202
|
+
const next = (Math.floor(x / tabWidth + 1e-9) + 1) * tabWidth;
|
|
1203
|
+
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 });
|
|
1204
|
+
x = next;
|
|
1205
|
+
cursor++;
|
|
1206
|
+
}
|
|
1207
|
+
if (segment) {
|
|
1208
|
+
const width2 = measure(segment, size), segmentStart = a + cursor;
|
|
1209
|
+
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 });
|
|
1210
|
+
x += width2;
|
|
1211
|
+
cursor += segment.length;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
937
1214
|
}
|
|
938
1215
|
return result;
|
|
939
1216
|
};
|
|
@@ -1188,6 +1465,7 @@ function composeSlide(input, options = {}) {
|
|
|
1188
1465
|
const minSize = (composition.minFontSize ?? 16) * scale;
|
|
1189
1466
|
const rasterPadding = (options.textRasterPadding ?? 1) * scale;
|
|
1190
1467
|
if (!Number.isFinite(rasterPadding) || rasterPadding < 0) throw new RangeError("Text raster padding must be finite and nonnegative.");
|
|
1468
|
+
const alignmentFor = (field) => (field === "title" ? record2(slide.design).titleAlignment ?? options.titleAlignment : record2(slide.design).contentAlignment ?? options.contentAlignment) ?? "left";
|
|
1191
1469
|
const styleFor = (field, path2) => resolveTextStyle({
|
|
1192
1470
|
fontFamily: (field === "title" ? options.fonts?.heading : field === "code" ? options.fonts?.code : options.fonts?.body) ?? (field === "code" ? "monospace" : "sans-serif"),
|
|
1193
1471
|
fontWeight: field === "title" ? 700 : 400,
|
|
@@ -1197,7 +1475,7 @@ function composeSlide(input, options = {}) {
|
|
|
1197
1475
|
const fitPlacedText = (field, value, text, box, size, minimum, path2) => {
|
|
1198
1476
|
const style = styleFor(field, path2), rich = field === "text" && Array.isArray(value);
|
|
1199
1477
|
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
|
|
1478
|
+
const alignment = alignmentFor(field);
|
|
1201
1479
|
const richLayout = rich ? richTextLayouter(value, box, size, { style, textMeasurement: options.textMeasurement }) : void 0;
|
|
1202
1480
|
const measure = textWidthMeasurer(style, options.textMeasurement), floor = rich ? richMinimum(value, size, minimum) : minimum, start = Math.max(size, floor);
|
|
1203
1481
|
for (let trial = 0; trial <= 64; trial++) {
|
|
@@ -1207,6 +1485,7 @@ function composeSlide(input, options = {}) {
|
|
|
1207
1485
|
const lines = richLines ? richLines.map((line) => {
|
|
1208
1486
|
let outline = null;
|
|
1209
1487
|
for (const fragment of line.fragments) {
|
|
1488
|
+
if (fragment.kind === "tab") continue;
|
|
1210
1489
|
const bounds = measureTextOutline(fragment.text, fragment.fontSize, fragment.style, options.textMeasurement);
|
|
1211
1490
|
if (!bounds) continue;
|
|
1212
1491
|
const next = { ...bounds, x: fragment.x + bounds.x, y: fragment.baselineShift + bounds.y };
|
|
@@ -1245,25 +1524,53 @@ function composeSlide(input, options = {}) {
|
|
|
1245
1524
|
};
|
|
1246
1525
|
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
1526
|
const path = `slides.${options.slideIndex ?? 0}`;
|
|
1527
|
+
const slideImageDiagnostics = [];
|
|
1528
|
+
const slideImage = resolveSlideImage(slide, layout, options.presentation, width, height, path, padding, scale, slideImageDiagnostics);
|
|
1529
|
+
const area = { left: 0, top: 0, right: width, bottom: height };
|
|
1530
|
+
if (slideImage?.position === "left") area.left = slideImage.region.width;
|
|
1531
|
+
else if (slideImage?.position === "right") area.right = slideImage.region.x;
|
|
1532
|
+
else if (slideImage?.position === "top") area.top = slideImage.region.height;
|
|
1533
|
+
else if (slideImage?.position === "bottom") area.bottom = slideImage.region.y;
|
|
1248
1534
|
const items = [], diagnostics = [];
|
|
1249
1535
|
const measuredFurniture = layoutFurniture(slide, options);
|
|
1250
1536
|
const furniture = measuredFurniture.configured || measuredFurniture.diagnostics.length ? measuredFurniture : void 0;
|
|
1251
1537
|
if (furniture) diagnostics.push(...furniture.diagnostics);
|
|
1252
|
-
const bodyBottom = Math.min(
|
|
1253
|
-
|
|
1538
|
+
const bodyBottom = Math.min(area.bottom - padding, furniture && furniture.footerTop < height ? furniture.footerTop - gap * 0.5 : height - padding);
|
|
1539
|
+
const headingTop = Math.max(area.top + padding, furniture?.headerBottom ? furniture.headerBottom + gap * 0.5 : padding);
|
|
1540
|
+
const layoutPlaceholders = Array.isArray(layout.placeholders) ? layout.placeholders : [];
|
|
1541
|
+
const headingOnlyLayout = COVER_LAYOUT_IDS.has(String(layout.id ?? "")) || layoutPlaceholders.length > 0 && layoutPlaceholders.every((placeholder) => headings.has(placeholder.type ?? ""));
|
|
1542
|
+
const regions = Object.keys(slide).filter((key) => regionParts(key)).sort();
|
|
1543
|
+
const emptyPayload = (value) => value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
|
|
1544
|
+
const emptyHost = (host) => emptyPayload(host.blocks) && fields.every((field) => emptyPayload(host[field]));
|
|
1545
|
+
const hasBodyPayload = regions.some((key) => !emptyHost(record2(slide[key]))) || !emptyPayload(slide.blocks) || fields.some((field) => !emptyPayload(slide[field]));
|
|
1546
|
+
const isCover = !hasBodyPayload && (headingOnlyLayout || !layout.id && !layoutPlaceholders.some((placeholder) => !headings.has(placeholder.type ?? "")));
|
|
1547
|
+
let y = headingTop;
|
|
1548
|
+
const headingItems = [];
|
|
1254
1549
|
for (const field of ["tag", "title", "subtitle"]) {
|
|
1255
1550
|
if (!slide[field]) continue;
|
|
1256
1551
|
const requested = (field === "title" ? 54 : field === "tag" ? 16 : 25) * scale;
|
|
1257
1552
|
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:
|
|
1553
|
+
const box = { x: area.left + padding, y, width: area.right - area.left - padding * 2, height: maxHeight };
|
|
1259
1554
|
const text = fitPlacedText(field, slide[field], String(slide[field]), box, requested, minSize, `${path}.${field}`);
|
|
1260
1555
|
box.height = Math.min(maxHeight, Math.max(text.lines.length * text.lineHeight, text.placement?.height ?? 0));
|
|
1261
1556
|
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
|
-
|
|
1557
|
+
const item = { path: `${path}.${field}`, field, type: "text", value: slide[field], payload: { text: slide[field] }, box, text, textStyle: styleFor(field, `${path}.${field}`), composition, alignment: alignmentFor(field) };
|
|
1558
|
+
headingItems.push(item);
|
|
1559
|
+
items.push(item);
|
|
1263
1560
|
y += box.height + gap * 0.5;
|
|
1264
1561
|
}
|
|
1265
|
-
if (
|
|
1266
|
-
|
|
1562
|
+
if (headingItems.length) {
|
|
1563
|
+
if (isCover) {
|
|
1564
|
+
const last = headingItems[headingItems.length - 1];
|
|
1565
|
+
const shift = Math.max(0, (bodyBottom - (last.box.y + last.box.height)) / 2);
|
|
1566
|
+
if (shift) for (const item of headingItems) {
|
|
1567
|
+
item.box.y += shift;
|
|
1568
|
+
if (item.text) item.text = translateTextFit(item.text, shift);
|
|
1569
|
+
}
|
|
1570
|
+
y = last.box.y + last.box.height + gap;
|
|
1571
|
+
} else y += gap * 0.5;
|
|
1572
|
+
}
|
|
1573
|
+
const contentBox = { x: area.left + padding, y, width: area.right - area.left - padding * 2, height: Math.max(scale, bodyBottom - y) };
|
|
1267
1574
|
const collect = (host, basePath, depth = 0, ancestors = []) => {
|
|
1268
1575
|
if (Array.isArray(host.blocks)) {
|
|
1269
1576
|
if (depth >= MAX_COMPOSITION_DEPTH || ancestors.includes(host)) throw new RangeError(`Content groups must be acyclic and nest at most ${MAX_COMPOSITION_DEPTH} levels.`);
|
|
@@ -1281,8 +1588,7 @@ function composeSlide(input, options = {}) {
|
|
|
1281
1588
|
}
|
|
1282
1589
|
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
1590
|
};
|
|
1284
|
-
const
|
|
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);
|
|
1591
|
+
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
1592
|
const groups = [], flows = [];
|
|
1287
1593
|
const decisions = options.explain ? [] : void 0;
|
|
1288
1594
|
const inheritedSettings = (parent, own = {}) => ({
|
|
@@ -1321,7 +1627,7 @@ function composeSlide(input, options = {}) {
|
|
|
1321
1627
|
minFontSize: settings.minFontSize,
|
|
1322
1628
|
path: node.path,
|
|
1323
1629
|
textRasterPadding: options.textRasterPadding,
|
|
1324
|
-
align:
|
|
1630
|
+
align: alignmentFor("metric")
|
|
1325
1631
|
});
|
|
1326
1632
|
const measureTimeline = (node, box, settings) => layoutTimeline(node.value, acceptedBox(box), {
|
|
1327
1633
|
fonts: options.fonts,
|
|
@@ -1364,29 +1670,29 @@ function composeSlide(input, options = {}) {
|
|
|
1364
1670
|
return score;
|
|
1365
1671
|
};
|
|
1366
1672
|
const modeFor = (settings) => settings.mode ?? "auto";
|
|
1367
|
-
const defaultColumns = (count,
|
|
1673
|
+
const defaultColumns = (count, area2, settings) => {
|
|
1368
1674
|
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 *
|
|
1675
|
+
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
1676
|
};
|
|
1371
1677
|
const scoreNode = (node, box, settings, penalties) => {
|
|
1372
1678
|
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,
|
|
1679
|
+
const own = inheritedSettings(settings, node.composition), area2 = inset(box, own);
|
|
1680
|
+
const cols = defaultColumns(node.children.length, area2, own);
|
|
1681
|
+
const boxes = gridBoxes(node.children.length, area2, cols, (own.gap ?? 1 / 30) * Math.min(box.width, box.height), own.weights ?? [], modeFor(own) === "column");
|
|
1376
1682
|
return node.children.reduce((score, child, index) => score + scoreNode(child, boxes[index], own, penalties), 0);
|
|
1377
1683
|
};
|
|
1378
|
-
const arrange = (nodes,
|
|
1684
|
+
const arrange = (nodes, area2, settings, reserved = 0, gapOverride, containerPath = path) => {
|
|
1379
1685
|
const count = Math.max(nodes.length, reserved);
|
|
1380
1686
|
if (!count) return;
|
|
1381
|
-
const mode = modeFor(settings), localGap = (settings.gap ?? 1 / 30) * Math.min(
|
|
1687
|
+
const mode = modeFor(settings), localGap = (settings.gap ?? 1 / 30) * Math.min(area2.width, area2.height);
|
|
1382
1688
|
const actualGap = gapOverride ?? (settings === rootSettings ? gap : localGap);
|
|
1383
|
-
let cols = defaultColumns(count,
|
|
1689
|
+
let cols = defaultColumns(count, area2, settings);
|
|
1384
1690
|
const hasRegions = nodes.some((node) => node.region);
|
|
1385
1691
|
const candidates = decisions ? [] : void 0;
|
|
1386
1692
|
if (mode === "auto" && !hasRegions) {
|
|
1387
1693
|
let best = Infinity;
|
|
1388
1694
|
for (let candidate = 1; candidate <= Math.min(count, settings.columns ?? 6); candidate++) {
|
|
1389
|
-
const boxes2 = gridBoxes(count,
|
|
1695
|
+
const boxes2 = gridBoxes(count, area2, candidate, actualGap, settings.weights ?? [], false);
|
|
1390
1696
|
const emptySlots = (Math.ceil(count / candidate) * candidate - count) * 2;
|
|
1391
1697
|
const penalties = candidates ? { cellProportions: 0, fontReduction: 0, textOverflow: 0, tableOverflow: 0, smallCells: 0, emptySlots } : void 0;
|
|
1392
1698
|
const score = nodes.reduce((sum, node, i) => sum + scoreNode(node, boxes2[i], settings, penalties), 0) + emptySlots;
|
|
@@ -1404,11 +1710,11 @@ function composeSlide(input, options = {}) {
|
|
|
1404
1710
|
...hasRegions ? {} : { selectedColumns: cols },
|
|
1405
1711
|
candidates: candidates ?? []
|
|
1406
1712
|
});
|
|
1407
|
-
const grid = gridGeometry(count,
|
|
1713
|
+
const grid = gridGeometry(count, area2, cols, actualGap, settings.weights ?? [], mode === "column");
|
|
1408
1714
|
const boxes = grid.boxes;
|
|
1409
|
-
if (!nodes.some((node) => node.region)) flows.push({ path: containerPath, box: { ...
|
|
1715
|
+
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
1716
|
nodes.forEach((node, index) => {
|
|
1411
|
-
let box = node.region ? regionBox(node.region,
|
|
1717
|
+
let box = node.region ? regionBox(node.region, area2, actualGap) : boxes[index];
|
|
1412
1718
|
if (node.children) {
|
|
1413
1719
|
const own = inheritedSettings(settings, node.composition), inner = inset(box, own);
|
|
1414
1720
|
groups.push({ path: node.path, box, contentBox: inner, composition: own });
|
|
@@ -1434,6 +1740,7 @@ function composeSlide(input, options = {}) {
|
|
|
1434
1740
|
text,
|
|
1435
1741
|
textStyle: body?.style ?? styleFor(node.field, node.path),
|
|
1436
1742
|
composition: settings,
|
|
1743
|
+
alignment: alignmentFor(node.field),
|
|
1437
1744
|
...quoteLayout ? { quoteLayout } : {},
|
|
1438
1745
|
...codeLayout ? { codeLayout } : {},
|
|
1439
1746
|
...metricLayout ? { metricLayout } : {},
|
|
@@ -1444,6 +1751,10 @@ function composeSlide(input, options = {}) {
|
|
|
1444
1751
|
});
|
|
1445
1752
|
};
|
|
1446
1753
|
const placeholders = Array.isArray(layout.placeholders) ? layout.placeholders.filter((p) => !headings.has(p.type)) : [];
|
|
1754
|
+
if (slideImage?.replacesContent) {
|
|
1755
|
+
const picture = placeholders.findIndex((p) => p.type === "picture");
|
|
1756
|
+
if (picture >= 0) placeholders.splice(picture, 1);
|
|
1757
|
+
}
|
|
1447
1758
|
const rootSettings = { ...composition, mode: composition.mode ?? (layout.slideLayoutDirection === "Vertical" ? "column" : layout.slideLayoutDirection === "Horizontal" ? "row" : "auto") };
|
|
1448
1759
|
arrange(pending, contentBox, rootSettings, composition.mode ? 0 : placeholders.length);
|
|
1449
1760
|
for (const item of items) {
|
|
@@ -1477,7 +1788,8 @@ function composeSlide(input, options = {}) {
|
|
|
1477
1788
|
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
1789
|
} : void 0;
|
|
1479
1790
|
if (failures.length) throw new OPFCompositionError(failures, explanation);
|
|
1480
|
-
|
|
1791
|
+
diagnostics.push(...slideImageDiagnostics);
|
|
1792
|
+
return { width, height, contentBox, items, groups, flows, diagnostics, composition, ...furniture ? { furniture } : {}, ...slideImage ? { slideImage } : {}, ...explanation ? { explanation } : {} };
|
|
1481
1793
|
}
|
|
1482
1794
|
function resolveCanvasDimensions(input) {
|
|
1483
1795
|
const presets = {
|
|
@@ -1497,4 +1809,4 @@ function resolveCanvasDimensions(input) {
|
|
|
1497
1809
|
return { width, height };
|
|
1498
1810
|
}
|
|
1499
1811
|
|
|
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 };
|
|
1812
|
+
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 };
|