@openpresentation/opf 0.11.3 → 0.11.4
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/{chunk-BVLWFMDX.js → chunk-3H2U7FDR.js} +224 -26
- package/dist/{chunk-57NWIYX3.js → chunk-3OJN7QAW.js} +2 -2
- package/dist/{chunk-JCXHSTSM.js → chunk-FDCWQHWO.js} +2 -2
- package/dist/{chunk-JTXCMVVP.js → chunk-KJKASEPE.js} +17 -13
- package/dist/{chunk-UDPSGTZI.js → chunk-NSVVQ52O.js} +6 -5
- package/dist/{composition-DzLwY-Mi.d.ts → composition-Dl-d9FRj.d.ts} +93 -6
- package/dist/composition.d.ts +1 -1
- package/dist/composition.js +1 -1
- package/dist/docs.js +13 -13
- package/dist/examples.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -7
- package/dist/lint.d.ts +1 -1
- package/dist/lint.js +4 -4
- package/dist/pagination.d.ts +1 -1
- package/dist/pagination.js +4 -4
- package/dist/{presentation-Cn6vTOCP.d.ts → presentation-j5PVrqfV.d.ts} +28 -16
- package/dist/repo-readme.js +1 -1
- package/dist/schemas.js +1 -1
- package/dist/spec/schemas/opf.schema.json +17 -13
- package/dist/types.d.ts +1 -1
- package/dist/validator.d.ts +1 -1
- package/dist/validator.js +3 -3
- package/package.json +1 -1
|
@@ -35,6 +35,114 @@ function tableRowBoundaries(value) {
|
|
|
35
35
|
return [...allowed];
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// src/image-aspect.ts
|
|
39
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
40
|
+
function sourceOf(value) {
|
|
41
|
+
if (typeof value === "string") return value;
|
|
42
|
+
return isRecord(value) && typeof value.src === "string" ? value.src : void 0;
|
|
43
|
+
}
|
|
44
|
+
var PREFIX_BYTES = 64 * 1024;
|
|
45
|
+
var JPEG_PREFIX_BYTES = 1024 * 1024;
|
|
46
|
+
function dataBytes(uri, limit) {
|
|
47
|
+
const match = /^data:([^;,]*)((?:;[^;,]*)*),/i.exec(uri);
|
|
48
|
+
if (!match) return void 0;
|
|
49
|
+
const type = (match[1] ?? "").toLowerCase(), start = match[0].length;
|
|
50
|
+
try {
|
|
51
|
+
if (/;base64/i.test(match[2] ?? "")) {
|
|
52
|
+
const chars = Math.ceil(limit / 3) * 4, text = uri.slice(start, start + chars + chars / 8).replace(/\s+/g, "").slice(0, chars);
|
|
53
|
+
return { type, bytes: Uint8Array.from(atob(text.slice(0, text.length - text.length % 4)), (char) => char.charCodeAt(0)) };
|
|
54
|
+
}
|
|
55
|
+
const prefix = uri.slice(start, start + limit).replace(/%[0-9a-f]?$/i, "");
|
|
56
|
+
return { type, bytes: new TextEncoder().encode(decodeURIComponent(prefix)) };
|
|
57
|
+
} catch {
|
|
58
|
+
return void 0;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
var at = (b, i) => b[i] ?? 0;
|
|
62
|
+
var u16be = (b, i) => at(b, i) << 8 | at(b, i + 1);
|
|
63
|
+
var u32be = (b, i) => (at(b, i) << 24 | at(b, i + 1) << 16 | at(b, i + 2) << 8 | at(b, i + 3)) >>> 0;
|
|
64
|
+
var u16le = (b, i) => at(b, i) | at(b, i + 1) << 8;
|
|
65
|
+
var u24le = (b, i) => at(b, i) | at(b, i + 1) << 8 | at(b, i + 2) << 16;
|
|
66
|
+
var ascii = (b, i, n) => String.fromCharCode(...b.subarray(i, i + n));
|
|
67
|
+
function rasterDimensions(b) {
|
|
68
|
+
if (b.length >= 24 && u32be(b, 0) === 2303741511 && ascii(b, 12, 4) === "IHDR") return { width: u32be(b, 16), height: u32be(b, 20) };
|
|
69
|
+
if (b.length >= 10 && (ascii(b, 0, 6) === "GIF87a" || ascii(b, 0, 6) === "GIF89a")) return { width: u16le(b, 6), height: u16le(b, 8) };
|
|
70
|
+
if (b.length >= 4 && b[0] === 255 && b[1] === 216) {
|
|
71
|
+
let i = 2;
|
|
72
|
+
while (i + 9 < b.length) {
|
|
73
|
+
if (at(b, i) !== 255) {
|
|
74
|
+
i++;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const marker = at(b, i + 1);
|
|
78
|
+
if (marker === 255) {
|
|
79
|
+
i++;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (marker === 216 || marker === 1 || marker >= 208 && marker <= 215) {
|
|
83
|
+
i += 2;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) return { height: u16be(b, i + 5), width: u16be(b, i + 7) };
|
|
87
|
+
i += 2 + u16be(b, i + 2);
|
|
88
|
+
}
|
|
89
|
+
return void 0;
|
|
90
|
+
}
|
|
91
|
+
if (b.length >= 30 && ascii(b, 0, 4) === "RIFF" && ascii(b, 8, 4) === "WEBP") {
|
|
92
|
+
const chunk = ascii(b, 12, 4);
|
|
93
|
+
if (chunk === "VP8X") return { width: u24le(b, 24) + 1, height: u24le(b, 27) + 1 };
|
|
94
|
+
if (chunk === "VP8L" && at(b, 20) === 47) {
|
|
95
|
+
const bits = u16le(b, 21) | u16le(b, 23) << 16;
|
|
96
|
+
return { width: (bits & 16383) + 1, height: (bits >>> 14 & 16383) + 1 };
|
|
97
|
+
}
|
|
98
|
+
if (chunk === "VP8 ") return { width: u16le(b, 26) & 16383, height: u16le(b, 28) & 16383 };
|
|
99
|
+
}
|
|
100
|
+
return void 0;
|
|
101
|
+
}
|
|
102
|
+
function svgDimensions(text) {
|
|
103
|
+
const root = /<svg\b([^>]*)>/i.exec(text)?.[1];
|
|
104
|
+
if (root === void 0) return void 0;
|
|
105
|
+
const attribute = (name) => new RegExp(`\\s${name}\\s*=\\s*["']([^"']*)["']`, "i").exec(root)?.[1];
|
|
106
|
+
const length = (value) => {
|
|
107
|
+
const match = value && /^\s*([0-9]*\.?[0-9]+)\s*(px|pt|in|cm|mm|em)?\s*$/i.exec(value);
|
|
108
|
+
return match ? Number(match[1]) : void 0;
|
|
109
|
+
};
|
|
110
|
+
const width = length(attribute("width")), height = length(attribute("height"));
|
|
111
|
+
if (width && height) return { width, height };
|
|
112
|
+
const box = attribute("viewBox")?.trim().split(/[\s,]+/).map(Number);
|
|
113
|
+
if (box && box.length === 4 && box.every(Number.isFinite)) {
|
|
114
|
+
const [, , width2 = 0, height2 = 0] = box;
|
|
115
|
+
if (width2 > 0 && height2 > 0) return { width: width2, height: height2 };
|
|
116
|
+
}
|
|
117
|
+
return void 0;
|
|
118
|
+
}
|
|
119
|
+
var cache = /* @__PURE__ */ new Map();
|
|
120
|
+
var CACHE_ENTRIES = 64;
|
|
121
|
+
function readAspect(source) {
|
|
122
|
+
const first = dataBytes(source, PREFIX_BYTES);
|
|
123
|
+
if (!first) return void 0;
|
|
124
|
+
let dimensions;
|
|
125
|
+
if (first.type.startsWith("image/svg")) dimensions = svgDimensions(new TextDecoder().decode(first.bytes));
|
|
126
|
+
else {
|
|
127
|
+
dimensions = rasterDimensions(first.bytes);
|
|
128
|
+
if (!dimensions && first.bytes.length >= 2 && first.bytes[0] === 255 && first.bytes[1] === 216) {
|
|
129
|
+
const more = dataBytes(source, JPEG_PREFIX_BYTES);
|
|
130
|
+
if (more) dimensions = rasterDimensions(more.bytes);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return dimensions && dimensions.width > 0 && dimensions.height > 0 ? dimensions.width / dimensions.height : void 0;
|
|
134
|
+
}
|
|
135
|
+
function intrinsicImageAspect(value, assets) {
|
|
136
|
+
let source = sourceOf(value);
|
|
137
|
+
for (let hop = 0; source?.startsWith("asset:") && hop < 8; hop++) source = sourceOf(isRecord(assets) ? assets[source.slice(6)] : void 0);
|
|
138
|
+
if (!source?.startsWith("data:")) return void 0;
|
|
139
|
+
if (cache.has(source)) return cache.get(source);
|
|
140
|
+
const aspect = readAspect(source);
|
|
141
|
+
if (cache.size >= CACHE_ENTRIES) cache.delete(cache.keys().next().value);
|
|
142
|
+
cache.set(source, aspect);
|
|
143
|
+
return aspect;
|
|
144
|
+
}
|
|
145
|
+
|
|
38
146
|
// src/furniture-fields.ts
|
|
39
147
|
var DEFAULT_FURNITURE_DATE_FORMAT = "M/d/yyyy";
|
|
40
148
|
var DEFAULT_SLIDE_NUMBER_FORMAT = "{current}";
|
|
@@ -268,10 +376,12 @@ function resolveFontSchemeReference(reference, lookup, path = "design.fontScheme
|
|
|
268
376
|
function resolveFontFamilies(input) {
|
|
269
377
|
const scheme = record2(input);
|
|
270
378
|
const family = (value) => typeof value === "string" ? value : record2(value).family;
|
|
379
|
+
const accent = family(scheme.accent);
|
|
271
380
|
return {
|
|
272
381
|
heading: family(scheme.heading) ?? scheme.major ?? scheme.minor ?? DEFAULT_FONT_FAMILIES.heading,
|
|
273
382
|
body: family(scheme.body) ?? scheme.minor ?? scheme.major ?? DEFAULT_FONT_FAMILIES.body,
|
|
274
|
-
code: family(scheme.code) ?? FALLBACK_CODE_FAMILY
|
|
383
|
+
code: family(scheme.code) ?? FALLBACK_CODE_FAMILY,
|
|
384
|
+
...typeof accent === "string" && accent ? { accent } : {}
|
|
275
385
|
};
|
|
276
386
|
}
|
|
277
387
|
function resolveTextStyle(style, measurement) {
|
|
@@ -438,6 +548,45 @@ function resolveSlideImage(slide, layout, presentation, width, height, path, pad
|
|
|
438
548
|
}
|
|
439
549
|
return result;
|
|
440
550
|
}
|
|
551
|
+
var LOGO_LOCKUP_CHAINS = {
|
|
552
|
+
dark: ["light", "default", "stackedLight", "stacked", "wordmarkLight", "wordmark", "iconLight", "icon", "dark", "stackedDark", "wordmarkDark", "iconDark"],
|
|
553
|
+
light: ["dark", "default", "stackedDark", "stacked", "wordmarkDark", "wordmark", "iconDark", "icon", "light", "stackedLight", "wordmarkLight", "iconLight"]
|
|
554
|
+
};
|
|
555
|
+
var LOGO_SET_KEYS = new Set(LOGO_LOCKUP_CHAINS.dark);
|
|
556
|
+
function resolveLogo(presentation, slide, options = {}) {
|
|
557
|
+
const slot = options.slot === "icon" || options.slot === "stacked" ? options.slot : "lockup";
|
|
558
|
+
if (options.slot !== void 0 && slot !== options.slot) throw new RangeError("Logo slot must be lockup, icon or stacked.");
|
|
559
|
+
const tone = options.onDark === true ? "dark" : "light";
|
|
560
|
+
const lockup = LOGO_LOCKUP_CHAINS[tone];
|
|
561
|
+
const chain = slot === "icon" ? [tone === "dark" ? "iconLight" : "iconDark", "icon", ...lockup] : slot === "stacked" ? [tone === "dark" ? "stackedLight" : "stackedDark", "stacked", ...lockup] : lockup;
|
|
562
|
+
const usable = (value) => {
|
|
563
|
+
const source = assetSource(value);
|
|
564
|
+
return typeof source === "string" && source.length > 0;
|
|
565
|
+
};
|
|
566
|
+
const pick = (value, path) => {
|
|
567
|
+
if (value === void 0 || value === null || value === false) return null;
|
|
568
|
+
if (typeof value === "string" || typeof value === "object" && !Array.isArray(value) && "src" in record2(value)) {
|
|
569
|
+
return usable(value) ? { source: value, path, variant: "default", slot } : null;
|
|
570
|
+
}
|
|
571
|
+
const set = record2(value);
|
|
572
|
+
if (!Object.keys(set).some((key) => LOGO_SET_KEYS.has(key))) return null;
|
|
573
|
+
for (const key of chain) {
|
|
574
|
+
const variant = set[key];
|
|
575
|
+
if (variant !== void 0 && usable(variant)) return { source: variant, path: `${path}.${key}`, variant: key, slot };
|
|
576
|
+
}
|
|
577
|
+
return null;
|
|
578
|
+
};
|
|
579
|
+
const deck = record2(presentation), slideIndex = Number.isSafeInteger(options.slideIndex) && options.slideIndex >= 0 ? options.slideIndex : 0;
|
|
580
|
+
const own = pick(record2(record2(slide).design).logo, `slides.${slideIndex}.design.logo`);
|
|
581
|
+
if (own) return own;
|
|
582
|
+
const shared = pick(record2(deck.design).logo, "design.logo");
|
|
583
|
+
if (shared) return shared;
|
|
584
|
+
const organizations = Array.isArray(deck.organization) ? deck.organization : [deck.organization];
|
|
585
|
+
const primaryIndex = organizations.findIndex((item) => record2(item).role === "primary");
|
|
586
|
+
const index = primaryIndex >= 0 ? primaryIndex : organizations.findIndex(Boolean);
|
|
587
|
+
if (index < 0) return null;
|
|
588
|
+
return pick(record2(organizations[index]).logo, Array.isArray(deck.organization) ? `organization.${index}.logo` : "organization.logo");
|
|
589
|
+
}
|
|
441
590
|
var webUrl = /^https?:\/\/\S+$/i;
|
|
442
591
|
function resolveSocialProfile(platform, value, records = [], owner = "organization") {
|
|
443
592
|
const raw = String(value).trim().replace(/\s+/gu, " ");
|
|
@@ -516,8 +665,20 @@ function layoutFurniture(input, options = {}) {
|
|
|
516
665
|
if (accepted.overflow) error(partPath, "Repeated text exceeds its zone at the selected readability floor; change the furniture or slide design.");
|
|
517
666
|
y += box.height;
|
|
518
667
|
};
|
|
668
|
+
const imageBox = (source) => {
|
|
669
|
+
const imageHeight = Math.max(32 * scale, Math.min(height * 0.05, 72 * scale)), imageWidth = Math.min(zoneWidth, imageHeight * (intrinsicImageAspect(source, record2(options.presentation).assets) ?? 1));
|
|
670
|
+
return { x: zone === "left" ? x : zone === "center" ? x + (zoneWidth - imageWidth) / 2 : x + zoneWidth - imageWidth, y, width: imageWidth, height: imageHeight };
|
|
671
|
+
};
|
|
672
|
+
if (content.logo === true) {
|
|
673
|
+
const resolved = resolveLogo(options.presentation, slide, { slot: "icon", onDark: options.darkBackground, slideIndex: options.slideIndex });
|
|
674
|
+
if (resolved) {
|
|
675
|
+
const box = imageBox(resolved.source);
|
|
676
|
+
zoneParts.push({ type: "image", kind: kind2, zone, field: "logo", path: `${path}.logo`, sourcePath: resolved.path, generated: true, image: resolved.source, box, alignment: zone });
|
|
677
|
+
y += box.height;
|
|
678
|
+
} else error(`${path}.logo`, "Generated logo needs design.logo or a primary organization logo.", "unresolved-content");
|
|
679
|
+
}
|
|
519
680
|
if (content.image !== void 0) {
|
|
520
|
-
const box =
|
|
681
|
+
const box = imageBox(content.image);
|
|
521
682
|
zoneParts.push({ type: "image", kind: kind2, zone, field: "image", path: `${path}.image`, sourcePath: `${path}.image`, generated: false, image: content.image, box, alignment: zone });
|
|
522
683
|
y += box.height;
|
|
523
684
|
}
|
|
@@ -644,7 +805,7 @@ function layoutQuote(value, box, options = {}) {
|
|
|
644
805
|
[source(bodyPath, quote.text, 1)],
|
|
645
806
|
{ x: box.x + 18, y: box.y + 18, width: box.width - 36, height: box.height - (footer ? 94 : 36) },
|
|
646
807
|
28,
|
|
647
|
-
options.fonts?.heading ?? "sans-serif",
|
|
808
|
+
options.fonts?.accent ?? options.fonts?.heading ?? "sans-serif",
|
|
648
809
|
600,
|
|
649
810
|
bodyPath
|
|
650
811
|
);
|
|
@@ -780,15 +941,15 @@ function layoutMetric(value, box, options = {}) {
|
|
|
780
941
|
const width = Math.max(...lineInk(part, fit).map((line) => Math.max(line.width, line.outline ? line.outline.x + line.outline.width : 0) - Math.min(0, line.outline?.x ?? 0)));
|
|
781
942
|
return width > 0 ? width + 2 * rasterPadding : 0;
|
|
782
943
|
};
|
|
783
|
-
const
|
|
944
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
784
945
|
const measure = (part, size, width) => {
|
|
785
946
|
const key = JSON.stringify([part.role, size, width]);
|
|
786
|
-
let fit =
|
|
947
|
+
let fit = cache2.get(key);
|
|
787
948
|
if (!fit) {
|
|
788
949
|
fit = fitCodeText(part.text, { ...box, width: hasOutlines ? Math.max(Number.MIN_VALUE, width - 2 * rasterPadding) : width }, size, size, scale, textWidthMeasurer(part.style, options.textMeasurement));
|
|
789
950
|
fit = place(part, fit, width);
|
|
790
951
|
if (!Number.isFinite(occupied(fit))) throw new RangeError("Metric text layout exceeds finite coordinates.");
|
|
791
|
-
|
|
952
|
+
cache2.set(key, fit);
|
|
792
953
|
}
|
|
793
954
|
return fit;
|
|
794
955
|
};
|
|
@@ -904,10 +1065,10 @@ function layoutTimeline(value, box, options = {}) {
|
|
|
904
1065
|
const metadata = [], events = timeline.events.map(() => []);
|
|
905
1066
|
for (const part of source) if (part.eventIndex === void 0) metadata.push(part);
|
|
906
1067
|
else events[part.eventIndex].push(part);
|
|
907
|
-
const
|
|
1068
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
908
1069
|
const measure = (part, size, width, alignment) => {
|
|
909
1070
|
const key = JSON.stringify([part.path, size, width, alignment]);
|
|
910
|
-
let measured =
|
|
1071
|
+
let measured = cache2.get(key);
|
|
911
1072
|
if (measured) return measured;
|
|
912
1073
|
const fit = fitText(part.text, { width: Math.max(Number.MIN_VALUE, width - (outlines ? 2 * padding : 0)), height: Number.MAX_VALUE }, size, size, textWidthMeasurer(part.style, options.textMeasurement));
|
|
913
1074
|
const ink = fit.sourceLines.map((line, index) => {
|
|
@@ -930,7 +1091,7 @@ function layoutTimeline(value, box, options = {}) {
|
|
|
930
1091
|
const height = placement?.height ?? fit.sourceLines.length * fit.lineHeight;
|
|
931
1092
|
if (!Number.isFinite(height)) throw new RangeError("Timeline text exceeds finite layout coordinates.");
|
|
932
1093
|
measured = { fit, ink, height };
|
|
933
|
-
|
|
1094
|
+
cache2.set(key, measured);
|
|
934
1095
|
return measured;
|
|
935
1096
|
};
|
|
936
1097
|
let selected, attempts = 0;
|
|
@@ -1267,6 +1428,7 @@ function richTextLayouter(input, box, requestedSize, options) {
|
|
|
1267
1428
|
};
|
|
1268
1429
|
return layout;
|
|
1269
1430
|
}
|
|
1431
|
+
var PICTURE_BULLET_SCALE = 0.65;
|
|
1270
1432
|
function fitList(input, box, requestedSize = 25, minFontSize = 16, options = { style: { fontFamily: "sans-serif", fontWeight: 400 } }) {
|
|
1271
1433
|
if (!Array.isArray(input) || ![box.width, box.height, requestedSize, minFontSize].every((value) => Number.isFinite(value) && value > 0)) throw new RangeError("List content, dimensions and font sizes must be valid.");
|
|
1272
1434
|
const source = input.map((value, index) => {
|
|
@@ -1300,6 +1462,7 @@ function fitList(input, box, requestedSize = 25, minFontSize = 16, options = { s
|
|
|
1300
1462
|
y += description.height;
|
|
1301
1463
|
}
|
|
1302
1464
|
overflow ||= text.overflow || !!description?.overflow;
|
|
1465
|
+
const marker = { text: ["\u2022", "\u25E6", "\u25AA"][item.level % 3], x: box.x + offset, y: textBox.y + (text.richLines[0]?.baseline ?? fontSize), fontSize, style: resolveTextStyle(style, options.textMeasurement), indent }, side = fontSize * PICTURE_BULLET_SCALE;
|
|
1303
1466
|
entries.push({
|
|
1304
1467
|
index: item.index,
|
|
1305
1468
|
level: item.level,
|
|
@@ -1311,7 +1474,8 @@ function fitList(input, box, requestedSize = 25, minFontSize = 16, options = { s
|
|
|
1311
1474
|
textBox,
|
|
1312
1475
|
description,
|
|
1313
1476
|
descriptionBox,
|
|
1314
|
-
marker
|
|
1477
|
+
marker,
|
|
1478
|
+
...options.bulletImage ? { bulletImage: options.bulletImage, bulletBox: { x: marker.x, y: marker.y - side, width: side, height: side } } : {}
|
|
1315
1479
|
});
|
|
1316
1480
|
if (item.index < source.length - 1) y += fontSize * 0.28;
|
|
1317
1481
|
}
|
|
@@ -1465,12 +1629,20 @@ function composeSlide(input, options = {}) {
|
|
|
1465
1629
|
const minSize = (composition.minFontSize ?? 16) * scale;
|
|
1466
1630
|
const rasterPadding = (options.textRasterPadding ?? 1) * scale;
|
|
1467
1631
|
if (!Number.isFinite(rasterPadding) || rasterPadding < 0) throw new RangeError("Text raster padding must be finite and nonnegative.");
|
|
1468
|
-
|
|
1632
|
+
let coverGroup = false;
|
|
1633
|
+
const alignmentFor = (field) => (field === "title" || coverGroup && (field === "tag" || field === "subtitle") && record2(slide.design).contentAlignment === void 0 ? record2(slide.design).titleAlignment ?? options.titleAlignment : record2(slide.design).contentAlignment ?? options.contentAlignment) ?? "left";
|
|
1469
1634
|
const styleFor = (field, path2) => resolveTextStyle({
|
|
1470
|
-
fontFamily: (field === "title" ? options.fonts?.heading : field === "code" ? options.fonts?.code : options.fonts?.body) ?? (field === "code" ? "monospace" : "sans-serif"),
|
|
1635
|
+
fontFamily: (field === "title" ? options.fonts?.heading : field === "code" ? options.fonts?.code : field === "tag" ? options.fonts?.accent ?? options.fonts?.body : options.fonts?.body) ?? (field === "code" ? "monospace" : "sans-serif"),
|
|
1471
1636
|
fontWeight: field === "title" ? 700 : 400,
|
|
1472
1637
|
path: path2
|
|
1473
1638
|
}, options.textMeasurement);
|
|
1639
|
+
const slideDesign = record2(slide.design), deckDesign = record2(record2(options.presentation).design);
|
|
1640
|
+
const designHint = (field) => slideDesign[field] !== void 0 ? { value: slideDesign[field], path: `slides.${options.slideIndex ?? 0}.design.${field}` } : deckDesign[field] !== void 0 ? { value: deckDesign[field], path: `design.${field}` } : void 0;
|
|
1641
|
+
const listBullet = designHint("listBullet");
|
|
1642
|
+
const bulletImage = listBullet?.value === "image" ? (() => {
|
|
1643
|
+
const resolved = resolveLogo(options.presentation, slide, { slot: "icon", onDark: options.darkBackground, slideIndex: options.slideIndex });
|
|
1644
|
+
return resolved ? { source: resolved.source, path: resolved.path } : void 0;
|
|
1645
|
+
})() : void 0;
|
|
1474
1646
|
const widthFor = (field, path2) => textWidthMeasurer(styleFor(field, path2), options.textMeasurement);
|
|
1475
1647
|
const fitPlacedText = (field, value, text, box, size, minimum, path2) => {
|
|
1476
1648
|
const style = styleFor(field, path2), rich = field === "text" && Array.isArray(value);
|
|
@@ -1522,7 +1694,7 @@ function composeSlide(input, options = {}) {
|
|
|
1522
1694
|
}
|
|
1523
1695
|
throw new Error("Text placement did not evaluate its bounded floor trial.");
|
|
1524
1696
|
};
|
|
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));
|
|
1697
|
+
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, ...bulletImage ? { bulletImage } : {} }) : fitText(text, box, size, minimum, widthFor(field, path2));
|
|
1526
1698
|
const path = `slides.${options.slideIndex ?? 0}`;
|
|
1527
1699
|
const slideImageDiagnostics = [];
|
|
1528
1700
|
const slideImage = resolveSlideImage(slide, layout, options.presentation, width, height, path, padding, scale, slideImageDiagnostics);
|
|
@@ -1544,7 +1716,17 @@ function composeSlide(input, options = {}) {
|
|
|
1544
1716
|
const emptyHost = (host) => emptyPayload(host.blocks) && fields.every((field) => emptyPayload(host[field]));
|
|
1545
1717
|
const hasBodyPayload = regions.some((key) => !emptyHost(record2(slide[key]))) || !emptyPayload(slide.blocks) || fields.some((field) => !emptyPayload(slide[field]));
|
|
1546
1718
|
const isCover = !hasBodyPayload && (headingOnlyLayout || !layout.id && !layoutPlaceholders.some((placeholder) => !headings.has(placeholder.type ?? "")));
|
|
1547
|
-
|
|
1719
|
+
coverGroup = isCover;
|
|
1720
|
+
let logo;
|
|
1721
|
+
if (isCover) {
|
|
1722
|
+
const resolved = resolveLogo(options.presentation, slide, { slot: "lockup", onDark: options.darkBackground, slideIndex: options.slideIndex });
|
|
1723
|
+
if (resolved) {
|
|
1724
|
+
const logoHeight = 56 * scale;
|
|
1725
|
+
const box = { x: round(area.left + padding), y: round(headingTop), width: round(Math.max(scale, Math.min(4 * logoHeight, area.right - area.left - 2 * padding))), height: round(logoHeight) };
|
|
1726
|
+
logo = { box, slot: "lockup", path: resolved.path, source: resolved.source, variant: resolved.variant, anchor: "left" };
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
let y = logo ? logo.box.y + logo.box.height + gap : headingTop;
|
|
1548
1730
|
const headingItems = [];
|
|
1549
1731
|
for (const field of ["tag", "title", "subtitle"]) {
|
|
1550
1732
|
if (!slide[field]) continue;
|
|
@@ -1681,14 +1863,14 @@ function composeSlide(input, options = {}) {
|
|
|
1681
1863
|
const boxes = gridBoxes(node.children.length, area2, cols, (own.gap ?? 1 / 30) * Math.min(box.width, box.height), own.weights ?? [], modeFor(own) === "column");
|
|
1682
1864
|
return node.children.reduce((score, child, index) => score + scoreNode(child, boxes[index], own, penalties), 0);
|
|
1683
1865
|
};
|
|
1684
|
-
const arrange = (nodes, area2, settings, reserved = 0, gapOverride, containerPath = path) => {
|
|
1866
|
+
const arrange = (nodes, area2, settings, reserved = 0, gapOverride, containerPath = path, recorded = true) => {
|
|
1685
1867
|
const count = Math.max(nodes.length, reserved);
|
|
1686
1868
|
if (!count) return;
|
|
1687
1869
|
const mode = modeFor(settings), localGap = (settings.gap ?? 1 / 30) * Math.min(area2.width, area2.height);
|
|
1688
1870
|
const actualGap = gapOverride ?? (settings === rootSettings ? gap : localGap);
|
|
1689
1871
|
let cols = defaultColumns(count, area2, settings);
|
|
1690
1872
|
const hasRegions = nodes.some((node) => node.region);
|
|
1691
|
-
const candidates = decisions ? [] : void 0;
|
|
1873
|
+
const candidates = decisions && recorded ? [] : void 0;
|
|
1692
1874
|
if (mode === "auto" && !hasRegions) {
|
|
1693
1875
|
let best = Infinity;
|
|
1694
1876
|
for (let candidate = 1; candidate <= Math.min(count, settings.columns ?? 6); candidate++) {
|
|
@@ -1703,22 +1885,22 @@ function composeSlide(input, options = {}) {
|
|
|
1703
1885
|
}
|
|
1704
1886
|
}
|
|
1705
1887
|
}
|
|
1706
|
-
decisions?.push({
|
|
1888
|
+
if (recorded) decisions?.push({
|
|
1707
1889
|
path: containerPath,
|
|
1708
1890
|
mode: hasRegions ? "regions" : mode,
|
|
1709
|
-
reason: hasRegions ? "promoted-regions" : mode === "auto" ? "lowest-score" : "configured-mode",
|
|
1891
|
+
reason: hasRegions ? "promoted-regions" : settings === rootSettings && chartPrimary ? "chart-primary" : mode === "auto" ? "lowest-score" : "configured-mode",
|
|
1710
1892
|
...hasRegions ? {} : { selectedColumns: cols },
|
|
1711
1893
|
candidates: candidates ?? []
|
|
1712
1894
|
});
|
|
1713
1895
|
const grid = gridGeometry(count, area2, cols, actualGap, settings.weights ?? [], mode === "column");
|
|
1714
1896
|
const boxes = grid.boxes;
|
|
1715
|
-
if (
|
|
1897
|
+
if (recorded && !hasRegions) flows.push({ path: containerPath, box: { ...area2 }, composition: { ...settings }, columns: grid.columns, rows: grid.rows, gap: grid.gap, itemCount: nodes.length, slotCount: count });
|
|
1716
1898
|
nodes.forEach((node, index) => {
|
|
1717
1899
|
let box = node.region ? regionBox(node.region, area2, actualGap) : boxes[index];
|
|
1718
1900
|
if (node.children) {
|
|
1719
1901
|
const own = inheritedSettings(settings, node.composition), inner = inset(box, own);
|
|
1720
|
-
groups.push({ path: node.path, box, contentBox: inner, composition: own });
|
|
1721
|
-
arrange(node.children, inner, own, 0, (own.gap ?? 1 / 30) * Math.min(box.width, box.height), node.path);
|
|
1902
|
+
if (!node.synthetic) groups.push({ path: node.path, box, contentBox: inner, composition: own });
|
|
1903
|
+
arrange(node.children, inner, own, 0, (own.gap ?? 1 / 30) * Math.min(box.width, box.height), node.path, !node.synthetic);
|
|
1722
1904
|
} else {
|
|
1723
1905
|
const frameBox = hasCards ? acceptedBox(box) : void 0;
|
|
1724
1906
|
box = payloadBox(box);
|
|
@@ -1744,7 +1926,8 @@ function composeSlide(input, options = {}) {
|
|
|
1744
1926
|
...quoteLayout ? { quoteLayout } : {},
|
|
1745
1927
|
...codeLayout ? { codeLayout } : {},
|
|
1746
1928
|
...metricLayout ? { metricLayout } : {},
|
|
1747
|
-
...timelineLayout ? { timelineLayout } : {}
|
|
1929
|
+
...timelineLayout ? { timelineLayout } : {},
|
|
1930
|
+
...bulletImage && (node.field === "items" || node.field === "bullets") ? { bulletImage } : {}
|
|
1748
1931
|
});
|
|
1749
1932
|
if (box.width < 100 * scale || box.height < 60 * scale) diagnostics.push({ code: "small-cell", path: node.path, message: "Content cell is too small for comfortable reading; use fewer blocks or a different composition." });
|
|
1750
1933
|
}
|
|
@@ -1755,8 +1938,23 @@ function composeSlide(input, options = {}) {
|
|
|
1755
1938
|
const picture = placeholders.findIndex((p) => p.type === "picture");
|
|
1756
1939
|
if (picture >= 0) placeholders.splice(picture, 1);
|
|
1757
1940
|
}
|
|
1758
|
-
const
|
|
1759
|
-
|
|
1941
|
+
const ownMode = record2(slide.composition).mode;
|
|
1942
|
+
const direction = designHint("contentDirection")?.value;
|
|
1943
|
+
const directionMode = direction === "vertical" ? "column" : direction === "horizontal" ? "row" : void 0;
|
|
1944
|
+
const layoutDirectionMode = layout.slideLayoutDirection === "Vertical" ? "column" : layout.slideLayoutDirection === "Horizontal" ? "row" : "auto";
|
|
1945
|
+
const chartHint = designHint("chartPrimary")?.value ?? (typeof layout.contentTypeChartPrimary === "string" ? layout.contentTypeChartPrimary.toLowerCase() : void 0);
|
|
1946
|
+
const chartSide = chartHint === "left" || chartHint === "right" || chartHint === "top" || chartHint === "bottom" ? chartHint : void 0;
|
|
1947
|
+
const chartIndex = pending.findIndex((node) => !node.children && node.field === "chart");
|
|
1948
|
+
const chartPrimary = chartSide !== void 0 && !ownMode && !regions.length && chartIndex >= 0 && pending.some((node) => node.children || node.field !== "chart") ? chartSide : void 0;
|
|
1949
|
+
const rootSettings = chartPrimary ? { ...composition, mode: chartPrimary === "left" || chartPrimary === "right" ? "row" : "column", columns: void 0, weights: chartPrimary === "left" || chartPrimary === "top" ? [3, 2] : [2, 3] } : { ...composition, mode: composition.mode ?? directionMode ?? layoutDirectionMode };
|
|
1950
|
+
if (chartPrimary) {
|
|
1951
|
+
const primary = pending[chartIndex], rest = pending.filter((_, index) => index !== chartIndex);
|
|
1952
|
+
const container = { field: "blocks", type: "group", value: rest.map((node) => node.payload), path, payload: {}, children: rest, composition: {}, synthetic: true };
|
|
1953
|
+
arrange(chartPrimary === "left" || chartPrimary === "top" ? [primary, container] : [container, primary], contentBox, rootSettings, 0);
|
|
1954
|
+
} else arrange(pending, contentBox, rootSettings, composition.mode ? 0 : placeholders.length);
|
|
1955
|
+
if (listBullet?.value === "image" && !bulletImage && items.some((item) => item.field === "items" || item.field === "bullets")) {
|
|
1956
|
+
diagnostics.push({ code: "unresolved-content", path: listBullet.path, message: "Picture bullets (listBullet: image) need design.logo or a primary organization logo; the marker glyph is drawn instead." });
|
|
1957
|
+
}
|
|
1760
1958
|
for (const item of items) {
|
|
1761
1959
|
for (const key of ["x", "y", "width", "height"]) item.box[key] = round(item.box[key]);
|
|
1762
1960
|
if (item.field === "table" && tableOverflows(item.value, item.box, scale, item.composition, options, item.path)) diagnostics.push({ code: "text-overflow", path: item.path, message: "Table cells do not fit; use fewer rows, fewer columns, or split the table across slides." });
|
|
@@ -1789,7 +1987,7 @@ function composeSlide(input, options = {}) {
|
|
|
1789
1987
|
} : void 0;
|
|
1790
1988
|
if (failures.length) throw new OPFCompositionError(failures, explanation);
|
|
1791
1989
|
diagnostics.push(...slideImageDiagnostics);
|
|
1792
|
-
return { width, height, contentBox, items, groups, flows, diagnostics, composition, ...furniture ? { furniture } : {}, ...slideImage ? { slideImage } : {}, ...explanation ? { explanation } : {} };
|
|
1990
|
+
return { width, height, contentBox, items, groups, flows, diagnostics, composition, ...furniture ? { furniture } : {}, ...slideImage ? { slideImage } : {}, ...logo ? { logo } : {}, ...explanation ? { explanation } : {} };
|
|
1793
1991
|
}
|
|
1794
1992
|
function resolveCanvasDimensions(input) {
|
|
1795
1993
|
const presets = {
|
|
@@ -1809,4 +2007,4 @@ function resolveCanvasDimensions(input) {
|
|
|
1809
2007
|
return { width, height };
|
|
1810
2008
|
}
|
|
1811
2009
|
|
|
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 };
|
|
2010
|
+
export { DEFAULT_FONT_SCHEME, DEFAULT_FURNITURE_DATE_FORMAT, DEFAULT_SLIDE_NUMBER_FORMAT, MAX_COMPOSITION_DEPTH, OPFCompositionError, PICTURE_BULLET_SCALE, chartColorForFill, colorContrast, composeSlide, fitList, fitRichText, fitText, formatFurnitureDate, formatSlideNumber, layoutCode, layoutFurniture, layoutMetric, layoutQuote, layoutTable, layoutTimeline, measureText, measureTextOutline, normalizeHexColor, placeTextLines, resolveCanvasDimensions, resolveColorRef, resolveFontFamilies, resolveFontSchemeReference, resolveLogo, resolveSocialProfile, resolveTextStyle, slideImageShape, tableGrid, tableRowBoundaries, textColorForFill, textWidthMeasurer, wrapText };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { validatePresentation, validationDefinition, validateCatalogRecord } from './chunk-
|
|
2
|
-
import { schemas, schemaEntries } from './chunk-
|
|
1
|
+
import { validatePresentation, validationDefinition, validateCatalogRecord } from './chunk-FDCWQHWO.js';
|
|
2
|
+
import { schemas, schemaEntries } from './chunk-KJKASEPE.js';
|
|
3
3
|
import { catalogKinds, catalogEntries, catalogSchemaNames } from './chunk-AEUSVEUY.js';
|
|
4
4
|
import { parseTree, printParseErrorCode, getNodePath } from 'jsonc-parser';
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { MAX_COMPOSITION_DEPTH, tableGrid } from './chunk-
|
|
2
|
-
import { schemas, schemaEntries } from './chunk-
|
|
1
|
+
import { MAX_COMPOSITION_DEPTH, tableGrid } from './chunk-3H2U7FDR.js';
|
|
2
|
+
import { schemas, schemaEntries } from './chunk-KJKASEPE.js';
|
|
3
3
|
import { catalogSchemaNames } from './chunk-AEUSVEUY.js';
|
|
4
4
|
import Ajv2020 from 'ajv/dist/2020.js';
|
|
5
5
|
import addFormats from 'ajv-formats';
|
|
@@ -847,7 +847,7 @@ Deck-level concerns that aren't part of the storyline (audience, tone, takeaway,
|
|
|
847
847
|
},
|
|
848
848
|
"logo": {
|
|
849
849
|
"$ref": "#/$defs/Asset",
|
|
850
|
-
"description": "Source for the organization's logo image. Accepts an HTTPS URL, data URI, relative path (resolved against the OPF file location), local path, or 'asset:<id>' reference. Common formats are SVG (preferred for vector logos), PNG (with transparency), or JPG. The primary organization's logo is
|
|
850
|
+
"description": "Source for the organization's logo image. Accepts an HTTPS URL, data URI, relative path (resolved against the OPF file location), local path, or 'asset:<id>' reference. Common formats are SVG (preferred for vector logos), PNG (with transparency), or JPG. The primary organization's logo (role 'primary', else the first organization) is the fallback source wherever the deck logo is drawn: a slide's design.logo, then design.logo, then this value. It is therefore drawn on cover and section slides and by header/footer zones with logo: true, exactly as design.logo would be (see design.logo and docs/design-resolution.md).",
|
|
851
851
|
"examples": [
|
|
852
852
|
"https://cdn.acme.com/logo.svg",
|
|
853
853
|
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9V3iWggAAAAASUVORK5CYII=",
|
|
@@ -947,7 +947,7 @@ Deck-level concerns that aren't part of the storyline (audience, tone, takeaway,
|
|
|
947
947
|
},
|
|
948
948
|
"photo": {
|
|
949
949
|
"$ref": "#/$defs/Asset",
|
|
950
|
-
"description": "Source for the speaker's headshot image. Accepts an HTTPS URL, data URI, relative path (resolved against the OPF file location), local path, or 'asset:<id>' reference. Common formats are JPG or PNG; SVG is not appropriate for photographic content.
|
|
950
|
+
"description": "Source for the speaker's headshot image. Accepts an HTTPS URL, data URI, relative path (resolved against the OPF file location), local path, or 'asset:<id>' reference. Common formats are JPG or PNG; SVG is not appropriate for photographic content. Authoring metadata for hosts and layouts (cover and bio slides built by a host): the reference renderer and exporter do not draw it, because no slide field or layout slot places a speaker; it round-trips through PPTX provenance.",
|
|
951
951
|
"examples": [
|
|
952
952
|
"https://cdn.acme.com/headshots/alice.jpg",
|
|
953
953
|
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAr/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AJ//2Q==",
|
|
@@ -1355,7 +1355,7 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
|
|
|
1355
1355
|
"$ref": "#/$defs/LogoSet"
|
|
1356
1356
|
}
|
|
1357
1357
|
],
|
|
1358
|
-
"description": "Deck logo assets used by
|
|
1358
|
+
"description": "Deck logo assets used by covers, section dividers, headers, footers and picture bullets. A string or Asset object is the default logo source; the LogoSet object form provides light/dark, stacked, icon, and wordmark variants (see LogoSet for the selection order). Source precedence: a slide's design.logo, then this value, then the primary organization's logo; absence inherits. Placement (reference engines, vetoable): on a cover or section slide (no body payload on a heading-only layout such as title, title-subtitle or section-divider, or no layout) the lockup variant is drawn at the top-left of the free area, inside the slide padding and below any header furniture, in a box 56 reference pixels tall and at most four times as wide; the tag/title/subtitle group then centers in the remaining span. Content slides never get an automatic logo. Header and footer zones draw the icon variant when they set logo: true, and design.listBullet 'image' draws it as the picture bullet. composeSlide returns the cover box as geometry.logo.",
|
|
1359
1359
|
"examples": [
|
|
1360
1360
|
"asset:acme-logo",
|
|
1361
1361
|
{
|
|
@@ -1634,7 +1634,7 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
|
|
|
1634
1634
|
"horizontal",
|
|
1635
1635
|
"vertical"
|
|
1636
1636
|
],
|
|
1637
|
-
"description": "Axis along which parallel body
|
|
1637
|
+
"description": "Axis along which parallel body content is arranged. Sets the root arrangement mode of blocks and root payloads when no composition.mode is set on the slide or on its layout record: 'vertical' is column, 'horizontal' is row. Precedence: composition.mode (the slide's own, else the layout record's geometry contract), then this value (slide design, then deck design), then the layout record's slideLayoutDirection, then automatic selection. A layout that carries a composition.mode keeps its grid or direction whatever this value says. Promoted regions (left, top:left, ...) keep their explicit geometry and nested groups keep their own composition."
|
|
1638
1638
|
},
|
|
1639
1639
|
"chartPrimary": {
|
|
1640
1640
|
"type": "string",
|
|
@@ -1645,7 +1645,7 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
|
|
|
1645
1645
|
"left",
|
|
1646
1646
|
"right"
|
|
1647
1647
|
],
|
|
1648
|
-
"description": "
|
|
1648
|
+
"description": "Where the primary chart sits relative to supporting content. Effective value: slide design, then deck design, then the layout record's contentTypeChartPrimary. When the slide has no promoted regions and no composition.mode of its own, and its root nodes mix at least one chart with other content, the first chart becomes a primary track and the other nodes form one synthetic sub-grid: 'left'/'right' is a two-track row and 'top'/'bottom' a two-track column, weighted 3:2 in favor of the chart; explicit root columns and weights are ignored while it applies. 'none' (the default) keeps the ordinary automatic grid with equal weight."
|
|
1649
1649
|
},
|
|
1650
1650
|
"imageFill": {
|
|
1651
1651
|
"type": "string",
|
|
@@ -1661,7 +1661,7 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
|
|
|
1661
1661
|
"character",
|
|
1662
1662
|
"image"
|
|
1663
1663
|
],
|
|
1664
|
-
"description": "
|
|
1664
|
+
"description": "Marker style for items and bullets lists. 'character' (the default) draws the glyph marker. 'image' draws the deck's icon logo (a slide's design.logo, then design.logo, then the primary organization's logo; light variants on dark backgrounds) as a picture bullet: a square of the marker's font size whose bottom sits on the marker baseline, so list geometry does not change. Without a logo the glyph is drawn and the engine reports unresolved-content at this field."
|
|
1665
1665
|
}
|
|
1666
1666
|
}
|
|
1667
1667
|
},
|
|
@@ -2098,7 +2098,7 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
|
|
|
2098
2098
|
},
|
|
2099
2099
|
"accent": {
|
|
2100
2100
|
"$ref": "#/$defs/Font",
|
|
2101
|
-
"description": "Abstract role: font used for accent text
|
|
2101
|
+
"description": "Abstract role: font used for accent text. When set, the slide tag (eyebrow) and the quote body use this family instead of the body and heading families; nothing else changes. resolveFontFamilies() returns it as accent only when the scheme defines it. No direct OOXML slot: PPTX export writes it on those runs, and the theme fonts stay major/minor."
|
|
2102
2102
|
},
|
|
2103
2103
|
"code": {
|
|
2104
2104
|
"$ref": "#/$defs/Font",
|
|
@@ -2365,7 +2365,7 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
|
|
|
2365
2365
|
},
|
|
2366
2366
|
"color": {
|
|
2367
2367
|
"type": "string",
|
|
2368
|
-
"description": "Fixed solid fill color,
|
|
2368
|
+
"description": "Fixed solid fill color: a hex string, a color-scheme slot or role name, or a var:<id> variable reference (a ColorRef, resolved against the effective color scheme and the deck variables). Use { type: 'theme', slot: ... } for PowerPoint's four theme-controlled background choices.",
|
|
2369
2369
|
"examples": [
|
|
2370
2370
|
"#FFFFFF",
|
|
2371
2371
|
"#0F172A",
|
|
@@ -2414,7 +2414,7 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
|
|
|
2414
2414
|
"properties": {
|
|
2415
2415
|
"color": {
|
|
2416
2416
|
"type": "string",
|
|
2417
|
-
"description": "Stop color
|
|
2417
|
+
"description": "Stop color: a hex string, a color-scheme slot or role name, or a var:<id> variable reference (a ColorRef).",
|
|
2418
2418
|
"examples": [
|
|
2419
2419
|
"#1E40AF",
|
|
2420
2420
|
"#22D3EE"
|
|
@@ -2519,14 +2519,14 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
|
|
|
2519
2519
|
},
|
|
2520
2520
|
"foregroundColor": {
|
|
2521
2521
|
"type": "string",
|
|
2522
|
-
"description": "Foreground color for the pattern,
|
|
2522
|
+
"description": "Foreground color for the pattern: a hex string, a color-scheme slot or role name, or a var:<id> variable reference (a ColorRef).",
|
|
2523
2523
|
"examples": [
|
|
2524
2524
|
"#0F172A"
|
|
2525
2525
|
]
|
|
2526
2526
|
},
|
|
2527
2527
|
"backgroundColor": {
|
|
2528
2528
|
"type": "string",
|
|
2529
|
-
"description": "Background color behind the pattern,
|
|
2529
|
+
"description": "Background color behind the pattern: a hex string, a color-scheme slot or role name, or a var:<id> variable reference (a ColorRef).",
|
|
2530
2530
|
"examples": [
|
|
2531
2531
|
"#FFFFFF"
|
|
2532
2532
|
]
|
|
@@ -2543,7 +2543,7 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
|
|
|
2543
2543
|
},
|
|
2544
2544
|
"LogoSet": {
|
|
2545
2545
|
"type": "object",
|
|
2546
|
-
"description": "Deck logo variants surfaced by
|
|
2546
|
+
"description": "Deck logo variants surfaced by covers, section dividers, headers, footers and picture bullets. Organization identity lives in organization; this object only controls visual rendering assets. Engines select one variant per slot and background tone (resolveLogo in @openpresentation/opf): same-tone variants first, neutral ones next, the opposite tone last. Lockup on a dark background: light, default, stackedLight, stacked, wordmarkLight, wordmark, iconLight, icon, then dark, stackedDark, wordmarkDark, iconDark; on a light background: dark, default, stackedDark, stacked, wordmarkDark, wordmark, iconDark, icon, then light, stackedLight, wordmarkLight, iconLight. The icon slot (headers, footers, picture bullets) tries iconLight or iconDark for the tone, then icon, then the lockup chain; the stacked slot tries stackedLight or stackedDark, then stacked, then the lockup chain.",
|
|
2547
2547
|
"additionalProperties": false,
|
|
2548
2548
|
"properties": {
|
|
2549
2549
|
"default": {
|
|
@@ -2636,8 +2636,12 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
|
|
|
2636
2636
|
},
|
|
2637
2637
|
"HeaderFooterItem": {
|
|
2638
2638
|
"type": "object",
|
|
2639
|
-
"description": "One header/footer zone. Every configured field renders; fields in one zone stack top to bottom in the order image, text, organization, section, slide number, date. Put a date and a slide number in different zones to keep each on the zone's single line.",
|
|
2639
|
+
"description": "One header/footer zone. Every configured field renders; fields in one zone stack top to bottom in the order logo, image, text, organization, socials, section, slide number, date. Put a date and a slide number in different zones to keep each on the zone's single line.",
|
|
2640
2640
|
"properties": {
|
|
2641
|
+
"logo": {
|
|
2642
|
+
"type": "boolean",
|
|
2643
|
+
"description": "Whether to render the deck's icon logo in this zone: a slide's design.logo, then design.logo, then the primary organization's logo (LogoSet icon variants first, light ones on dark backgrounds). It is a generated image part in the same box as image; without a logo the engine reports unresolved-content at this field. Use image for a literal picture that is not the deck logo."
|
|
2644
|
+
},
|
|
2641
2645
|
"text": {
|
|
2642
2646
|
"type": "string",
|
|
2643
2647
|
"description": "Literal text rendered in this zone.",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { assertValidPresentation, visitContentPayloads } from './chunk-
|
|
2
|
-
import { DEFAULT_FONT_SCHEME, resolveFontSchemeReference, resolveFontFamilies, resolveCanvasDimensions, composeSlide, tableRowBoundaries } from './chunk-
|
|
1
|
+
import { assertValidPresentation, visitContentPayloads } from './chunk-FDCWQHWO.js';
|
|
2
|
+
import { DEFAULT_FONT_SCHEME, resolveFontSchemeReference, resolveFontFamilies, resolveCanvasDimensions, composeSlide, tableRowBoundaries } from './chunk-3H2U7FDR.js';
|
|
3
3
|
import { catalogs } from './chunk-AEUSVEUY.js';
|
|
4
4
|
|
|
5
5
|
// src/pagination.ts
|
|
@@ -95,6 +95,7 @@ function paginateSlide(input, options = {}) {
|
|
|
95
95
|
return composeSlide(withReadability(slide, true), { ...options, slideNumber: (options.slideNumber ?? sourceIndex + 1) + pageIndex });
|
|
96
96
|
};
|
|
97
97
|
const initial = geometry(source);
|
|
98
|
+
const fit = (diagnostics) => diagnostics.filter((issue) => issue.code === "text-overflow" || issue.code === "small-cell" || !!initial.furniture?.diagnostics.includes(issue));
|
|
98
99
|
const repeatedMappings = (pageIndex) => {
|
|
99
100
|
if (!initial.furniture) return [];
|
|
100
101
|
const paths = new Set(initial.items.filter((item) => headingFields.has(item.field)).map((item) => item.path));
|
|
@@ -104,7 +105,7 @@ function paginateSlide(input, options = {}) {
|
|
|
104
105
|
}
|
|
105
106
|
return [...paths].map((sourcePath) => ({ sourcePath, outputPath: sourcePath.startsWith(`${sourceBase}.`) ? `slides.${sourceIndex + pageIndex}${sourcePath.slice(sourceBase.length)}` : sourcePath }));
|
|
106
107
|
};
|
|
107
|
-
if (!initial.diagnostics.length) return { slides: [source], pages: [{ slideIndex: sourceIndex, mappings: initial.items.map((item) => ({ sourcePath: item.path, outputPath: item.path })), ...initial.furniture ? { repeatedMappings: repeatedMappings(0) } : {} }] };
|
|
108
|
+
if (!fit(initial.diagnostics).length) return { slides: [source], pages: [{ slideIndex: sourceIndex, mappings: initial.items.map((item) => ({ sourcePath: item.path, outputPath: item.path })), ...initial.furniture ? { repeatedMappings: repeatedMappings(0) } : {} }] };
|
|
108
109
|
const headerIssues = initial.diagnostics.filter((issue) => headingFields.has(issue.path.slice(sourceBase.length + 1)) || initial.furniture?.diagnostics.includes(issue));
|
|
109
110
|
if (headerIssues.length) throw new OPFPaginationError("Repeated headings or header/footer content cannot fit or resolve. Change the repeated content or slide design before pagination.", headerIssues);
|
|
110
111
|
const leaves = initial.items.filter((item) => !headingFields.has(item.field)).map((item) => leafFor(item.path, item.field, item.value));
|
|
@@ -170,12 +171,12 @@ function paginateSlide(input, options = {}) {
|
|
|
170
171
|
if (pageIndex > 0) delete slide.notes;
|
|
171
172
|
return { slide, mappings };
|
|
172
173
|
};
|
|
173
|
-
const diagnosticsFor = (portions) => geometry(project(portions, slides.length).slide, slides.length).diagnostics;
|
|
174
|
+
const diagnosticsFor = (portions) => fit(geometry(project(portions, slides.length).slide, slides.length).diagnostics);
|
|
174
175
|
const finish = () => {
|
|
175
176
|
if (!selected.size) return;
|
|
176
177
|
if (slides.length >= maxSlides) throw new OPFPaginationError(`Pagination needs more than ${maxSlides} slides. No partial result was returned.`);
|
|
177
178
|
const { slide, mappings } = project(selected, slides.length);
|
|
178
|
-
const issues = geometry(slide, slides.length).diagnostics;
|
|
179
|
+
const issues = fit(geometry(slide, slides.length).diagnostics);
|
|
179
180
|
if (issues.length) throw new OPFPaginationError("A continuation page does not fit at its final page number. No partial result was returned.", issues);
|
|
180
181
|
assignPageIds(slide, slides.length);
|
|
181
182
|
assertValidPresentation({ slides: [slide] });
|