@openpresentation/opf 0.16.0 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -2
- package/dist/browser.js +38 -0
- package/dist/{catalog-refs-_anZs-Qz.d.ts → catalog-refs-BSfGELpt.d.ts} +1 -1
- package/dist/{catalog-schemas-BZCjgAxu.d.ts → catalog-schemas-CinPh5WA.d.ts} +1 -1
- package/dist/catalog.d.ts +2 -2
- package/dist/catalog.js +3 -23712
- package/dist/{chunk-BSYQDWT4.js → chunk-64YSJZDH.js} +28 -15
- package/dist/{chunk-WEDGVC5W.js → chunk-6WW2EDUW.js} +108 -108
- package/dist/{chunk-5UEUAW57.js → chunk-CJPDTLGD.js} +1 -1
- package/dist/{chunk-FQSIIADJ.js → chunk-DBYZI75U.js} +30 -5
- package/dist/{chunk-PX5AWL7Y.js → chunk-JT3QXA22.js} +99 -29
- package/dist/{chunk-6OR326EA.js → chunk-JXVS7BJK.js} +1 -1
- package/dist/chunk-KXBZ6ZSB.js +991 -0
- package/dist/{chunk-5PDN7BN6.js → chunk-MBU42S5Y.js} +427 -167
- package/dist/{chunk-K4DQQIT4.js → chunk-NS3VYB57.js} +5 -5
- package/dist/chunk-QLFWWHRF.js +919 -0
- package/dist/{chunk-5TO6I3GB.js → chunk-RAKEVM52.js} +1 -1
- package/dist/{chunk-NAWQR7MN.js → chunk-VCGRMNY2.js} +1 -1
- package/dist/chunk-WCG3THTD.js +23712 -0
- package/dist/{chunk-UM24JFUE.js → chunk-YWXB4XAT.js} +2 -2
- package/dist/{chunk-3XDRQYWZ.js → chunk-YZXTXX2D.js} +1 -1
- package/dist/{chunk-SMLMI6IZ.js → chunk-ZCFSDSUX.js} +3 -3
- package/dist/{chunk-KINQFU6X.js → chunk-ZOHFLP3O.js} +19 -12
- package/dist/{composition-D4ADF5c-.d.ts → composition-C-OoqvAr.d.ts} +227 -167
- package/dist/composition.d.ts +2 -2
- package/dist/composition.js +1 -1
- package/dist/conversion-C5foedh6.d.ts +502 -0
- package/dist/conversion-KGDVJM75.js +21 -0
- package/dist/convert.d.ts +3 -3
- package/dist/convert.js +7 -7
- package/dist/data.d.ts +6 -6
- package/dist/data.js +2 -2
- package/dist/deck-report-wszX7uo8.d.ts +40 -0
- package/dist/deck.d.ts +21 -51
- package/dist/deck.js +10 -10
- package/dist/diff.js +5 -5
- package/dist/docs.js +33 -27
- package/dist/examples.d.ts +1 -1
- package/dist/examples.js +418 -451
- package/dist/format.js +2 -2
- package/dist/index.d.ts +179 -16
- package/dist/index.js +51 -796
- package/dist/markdown.d.ts +10 -6
- package/dist/markdown.js +7 -7
- package/dist/node-engine.d.ts +114 -0
- package/dist/node-engine.js +44 -0
- package/dist/{pagination-DzZPFheM.d.ts → pagination-BaUjDrTz.d.ts} +25 -8
- package/dist/pagination.d.ts +3 -3
- package/dist/pagination.js +4 -4
- package/dist/patch.js +4 -4
- package/dist/{presentation-COrFuj2g.d.ts → presentation-HondgJ3l.d.ts} +278 -454
- package/dist/repo-readme.js +1 -1
- package/dist/schemas.d.ts +4 -2
- package/dist/schemas.js +1 -1
- package/dist/spec/catalogs/manifest.json +1 -1
- package/dist/spec/schemas/opf.schema.json +74 -100
- package/dist/types.d.ts +2 -2
- package/dist/{validation-types-HxIgJfKx.d.ts → validation-types-PbGDrNUr.d.ts} +23 -7
- package/dist/validator.d.ts +6 -6
- package/dist/validator.js +3 -3
- package/dist/yaml.d.ts +4 -4
- package/dist/yaml.js +7 -7
- package/package.json +37 -2
|
@@ -319,7 +319,6 @@ function visitContentPayloads(slide, slidePath, visit) {
|
|
|
319
319
|
|
|
320
320
|
// src/furniture-fields.ts
|
|
321
321
|
var DEFAULT_FURNITURE_DATE_FORMAT = "M/d/yyyy";
|
|
322
|
-
var DEFAULT_SLIDE_NUMBER_FORMAT = "{current}";
|
|
323
322
|
var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
324
323
|
var weekdays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
325
324
|
var tokenWidths = { y: [1, 2, 4], M: [1, 2, 3, 4], d: [1, 2], E: [1, 2, 3, 4] };
|
|
@@ -407,21 +406,6 @@ function formatFurnitureDate(iso, pattern) {
|
|
|
407
406
|
}
|
|
408
407
|
return { text: text2 };
|
|
409
408
|
}
|
|
410
|
-
function formatSlideNumber(format, current, total) {
|
|
411
|
-
if (!format.includes("{current}")) return { error: "slideNumberFormat must contain {current}." };
|
|
412
|
-
if (format.includes("{total}") && total === void 0) return { error: "slideNumberFormat uses {total}, which needs the rendered slide count." };
|
|
413
|
-
let text2 = "";
|
|
414
|
-
const fields2 = [];
|
|
415
|
-
for (const piece of format.split(/(\{current\}|\{total\})/)) {
|
|
416
|
-
if (piece === "{current}") {
|
|
417
|
-
const value = String(current);
|
|
418
|
-
fields2.push({ type: "slideNumber", start: text2.length, end: text2.length + value.length });
|
|
419
|
-
text2 += value;
|
|
420
|
-
} else if (piece === "{total}") text2 += String(total);
|
|
421
|
-
else text2 += piece;
|
|
422
|
-
}
|
|
423
|
-
return { text: text2, fields: fields2 };
|
|
424
|
-
}
|
|
425
409
|
|
|
426
410
|
// src/deck-metadata.ts
|
|
427
411
|
function recordsOf(value) {
|
|
@@ -441,6 +425,232 @@ function primaryOrganization(presentation) {
|
|
|
441
425
|
return organizations.find((item) => item.role === "primary") ?? organizations[0];
|
|
442
426
|
}
|
|
443
427
|
|
|
428
|
+
// src/logos.ts
|
|
429
|
+
var LOGO_SHAPES = ["full", "stacked", "icon", "wordmark"];
|
|
430
|
+
function isLogoShape(value) {
|
|
431
|
+
return typeof value === "string" && LOGO_SHAPES.includes(value);
|
|
432
|
+
}
|
|
433
|
+
var NAME_PATTERN = /^(?:var:)?(organization(?:\.[A-Za-z0-9_-]+){1,3})$/;
|
|
434
|
+
function parseLogoName(name, presentation) {
|
|
435
|
+
const match = NAME_PATTERN.exec(name);
|
|
436
|
+
if (!match) return null;
|
|
437
|
+
const parts = match[1].split(".");
|
|
438
|
+
const shapes = LOGO_SHAPES.map((shape) => `'${shape}'`).join(", ");
|
|
439
|
+
if (parts.length === 2) return parts[1] === "logo" ? {} : null;
|
|
440
|
+
if (parts.length === 3) {
|
|
441
|
+
if (parts[2] === "logo") return { organization: parts[1] };
|
|
442
|
+
if (parts[1] !== "logo") return null;
|
|
443
|
+
if (isLogoShape(parts[2])) return { shape: parts[2] };
|
|
444
|
+
if (organizationIndex(presentation, "logo") >= 0) return null;
|
|
445
|
+
return { error: `'${parts.join(".")}' is not a built-in variable: '${parts[2]}' is not a logo shape; the shapes are ${shapes}.` };
|
|
446
|
+
}
|
|
447
|
+
if (parts[2] !== "logo") return null;
|
|
448
|
+
if (isLogoShape(parts[3])) return { organization: parts[1], shape: parts[3] };
|
|
449
|
+
return { error: `'${parts.join(".")}' is not a built-in variable: '${parts[3]}' is not a logo shape; the shapes are ${shapes}.` };
|
|
450
|
+
}
|
|
451
|
+
function logoReferenceName(reference, shape = reference.shape) {
|
|
452
|
+
return `organization${reference.organization !== void 0 ? `.${reference.organization}` : ""}.logo${shape && shape !== "full" ? `.${shape}` : ""}`;
|
|
453
|
+
}
|
|
454
|
+
function organizationIndex(presentation, id) {
|
|
455
|
+
if (!isRecord(presentation)) return -1;
|
|
456
|
+
const raw = presentation.organization;
|
|
457
|
+
const list = Array.isArray(raw) ? raw : [raw];
|
|
458
|
+
if (id !== void 0) return list.findIndex((entry) => isRecord(entry) && entry.id === id);
|
|
459
|
+
const primary = list.findIndex((entry) => isRecord(entry) && entry.role === "primary");
|
|
460
|
+
return primary >= 0 ? primary : list.findIndex((entry) => isRecord(entry));
|
|
461
|
+
}
|
|
462
|
+
var usable = (value) => {
|
|
463
|
+
const source = typeof value === "string" ? value : isRecord(value) ? value.src : void 0;
|
|
464
|
+
return typeof source === "string" && source.trim().length > 0;
|
|
465
|
+
};
|
|
466
|
+
var isAsset = (value) => typeof value === "string" || isRecord(value) && "src" in value;
|
|
467
|
+
function shapeChain(shape) {
|
|
468
|
+
const chain = [shape, "full", "wordmark", "stacked", "icon"];
|
|
469
|
+
return chain.filter((entry, index) => chain.indexOf(entry) === index);
|
|
470
|
+
}
|
|
471
|
+
function pickOrganizationLogo(logo, shape, onDark, root) {
|
|
472
|
+
if (logo === void 0 || logo === null) return null;
|
|
473
|
+
if (isAsset(logo)) return usable(logo) ? { source: logo, path: `${root}.logo`, variant: "default" } : null;
|
|
474
|
+
if (!isRecord(logo)) return null;
|
|
475
|
+
const tones = onDark ? ["onDark", "onLight"] : ["onLight", "onDark"];
|
|
476
|
+
for (const candidate of shapeChain(shape)) {
|
|
477
|
+
const entry = logo[candidate];
|
|
478
|
+
if (entry === void 0 || entry === null) continue;
|
|
479
|
+
if (isAsset(entry)) {
|
|
480
|
+
if (usable(entry)) return { source: entry, path: `${root}.logo.${candidate}`, variant: "default" };
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
if (!isRecord(entry)) continue;
|
|
484
|
+
for (const tone of tones) if (usable(entry[tone])) return { source: entry[tone], path: `${root}.logo.${candidate}.${tone}`, variant: tone };
|
|
485
|
+
}
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
function resolveOrganizationLogo(presentation, reference, options = {}) {
|
|
489
|
+
const shape = reference.shape ?? options.shape ?? "full";
|
|
490
|
+
const name = logoReferenceName(reference, shape);
|
|
491
|
+
const index = organizationIndex(presentation, reference.organization);
|
|
492
|
+
if (index < 0) {
|
|
493
|
+
if (reference.organization !== void 0) return { ok: false, reason: "unknown", message: `'${name}' names no organization with id '${reference.organization}'.` };
|
|
494
|
+
return { ok: false, reason: "missing", message: `'${name}' has no value: the deck has no organization with a logo.` };
|
|
495
|
+
}
|
|
496
|
+
const deck = presentation;
|
|
497
|
+
const root = Array.isArray(deck.organization) ? `organization.${index}` : "organization";
|
|
498
|
+
const organization = Array.isArray(deck.organization) ? deck.organization[index] : deck.organization;
|
|
499
|
+
const picked = pickOrganizationLogo(organization.logo, shape, options.onDark === true, root);
|
|
500
|
+
if (!picked) return { ok: false, reason: "missing", message: `'${name}' has no value: ${root} has no logo.` };
|
|
501
|
+
return { ok: true, logo: { ...picked, shape, reference: `var:${name}` } };
|
|
502
|
+
}
|
|
503
|
+
function resolveLogo(presentation, slide, options = {}) {
|
|
504
|
+
if (options.shape !== void 0 && !isLogoShape(options.shape)) throw new RangeError("Logo shape must be full, stacked, icon or wordmark.");
|
|
505
|
+
const onDark = options.onDark === true;
|
|
506
|
+
const resolve = (reference, designPath) => {
|
|
507
|
+
const result = resolveOrganizationLogo(presentation, reference, { ...options.shape ? { shape: options.shape } : {}, onDark });
|
|
508
|
+
if (!result.ok) return null;
|
|
509
|
+
return designPath ? { ...result.logo, designPath } : result.logo;
|
|
510
|
+
};
|
|
511
|
+
if (options.reference !== void 0) {
|
|
512
|
+
const parsed = typeof options.reference === "string" ? parseLogoName(options.reference, presentation) : null;
|
|
513
|
+
if (!parsed || "error" in parsed) throw new RangeError(`'${String(options.reference)}' is not a logo reference such as 'var:organization.logo.icon'.`);
|
|
514
|
+
return resolve(parsed);
|
|
515
|
+
}
|
|
516
|
+
const slideIndex = Number.isSafeInteger(options.slideIndex) && options.slideIndex >= 0 ? options.slideIndex : 0;
|
|
517
|
+
const levels = [
|
|
518
|
+
[isRecord(slide) && isRecord(slide.design) ? slide.design.logo : void 0, `slides.${slideIndex}.design.logo`],
|
|
519
|
+
[isRecord(presentation) && isRecord(presentation.design) ? presentation.design.logo : void 0, "design.logo"]
|
|
520
|
+
];
|
|
521
|
+
for (const [value, path] of levels) {
|
|
522
|
+
if (value === false) return null;
|
|
523
|
+
if (typeof value !== "string") continue;
|
|
524
|
+
const parsed = parseLogoName(value, presentation);
|
|
525
|
+
if (!parsed || "error" in parsed || !value.startsWith("var:")) continue;
|
|
526
|
+
return resolve(parsed, path);
|
|
527
|
+
}
|
|
528
|
+
return resolve({});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/slide-variables.ts
|
|
532
|
+
var SLIDE_SCOPED_BUILTINS = ["slide.number", "slide.section", "deck.slideCount"];
|
|
533
|
+
function isSlideScopedBuiltin(name) {
|
|
534
|
+
return SLIDE_SCOPED_BUILTINS.includes(name);
|
|
535
|
+
}
|
|
536
|
+
function isSlideScopedName(name) {
|
|
537
|
+
return name === "deck.slideCount" || name.startsWith("slide.");
|
|
538
|
+
}
|
|
539
|
+
var SLIDE_TOKEN_BODY = String.raw`\s*(?:slide(?:\.[A-Za-z0-9_-]+){1,2}|deck\.slideCount)\s*(?:\|[^{}]*)?\}\}`;
|
|
540
|
+
var slideTokenAhead = new RegExp(`^${SLIDE_TOKEN_BODY}`);
|
|
541
|
+
var slideTokenPattern = new RegExp(String.raw`\\\{\{(?=${SLIDE_TOKEN_BODY})|\{\{\s*(slide\.number|slide\.section|deck\.slideCount)\s*(?:\|[^{}]*)?\}\}`, "g");
|
|
542
|
+
var slideTokenSpanPattern = new RegExp(String.raw`\\?\{\{${SLIDE_TOKEN_BODY}`, "g");
|
|
543
|
+
var quickCheck = /\{\{\s*(?:slide\.|deck\.slideCount)/;
|
|
544
|
+
function slideTokenFollows(text2) {
|
|
545
|
+
return slideTokenAhead.test(text2);
|
|
546
|
+
}
|
|
547
|
+
function checkValues(values) {
|
|
548
|
+
if (!isRecord(values)) throw new TypeError("Slide variable values must be an object with slideNumber and slideCount.");
|
|
549
|
+
if (!Number.isSafeInteger(values.slideNumber) || values.slideNumber < 1) throw new RangeError("slideNumber must be a positive safe integer.");
|
|
550
|
+
if (values.slideCount !== void 0 && (!Number.isSafeInteger(values.slideCount) || values.slideCount < 1)) throw new RangeError("slideCount must be a positive safe integer.");
|
|
551
|
+
}
|
|
552
|
+
function substituteSlideTokens(text2, values) {
|
|
553
|
+
if (!text2.includes("{{")) return { text: text2, fields: [], changed: false };
|
|
554
|
+
const fields2 = [];
|
|
555
|
+
let out = "";
|
|
556
|
+
let last = 0;
|
|
557
|
+
let changed = false;
|
|
558
|
+
slideTokenPattern.lastIndex = 0;
|
|
559
|
+
for (let match = slideTokenPattern.exec(text2); match; match = slideTokenPattern.exec(text2)) {
|
|
560
|
+
const name = match[1];
|
|
561
|
+
let value;
|
|
562
|
+
if (name === void 0) value = "{{";
|
|
563
|
+
else if (name === "slide.number") value = String(values.slideNumber);
|
|
564
|
+
else if (name === "deck.slideCount") {
|
|
565
|
+
if (values.slideCount === void 0) continue;
|
|
566
|
+
value = String(values.slideCount);
|
|
567
|
+
} else value = typeof values.section === "string" ? values.section : "";
|
|
568
|
+
out += text2.slice(last, match.index);
|
|
569
|
+
if (name === "slide.number") fields2.push({ type: "slideNumber", start: out.length, end: out.length + value.length });
|
|
570
|
+
out += value;
|
|
571
|
+
last = match.index + match[0].length;
|
|
572
|
+
changed = true;
|
|
573
|
+
}
|
|
574
|
+
if (!changed) return { text: text2, fields: fields2, changed };
|
|
575
|
+
return { text: out + text2.slice(last), fields: fields2, changed };
|
|
576
|
+
}
|
|
577
|
+
function slideTokenSpans(text2) {
|
|
578
|
+
if (!text2.includes("{{")) return [];
|
|
579
|
+
return Array.from(text2.matchAll(slideTokenSpanPattern), (match) => [match.index, match.index + match[0].length]);
|
|
580
|
+
}
|
|
581
|
+
var skippedKeys = /* @__PURE__ */ new Set(["extensions"]);
|
|
582
|
+
var logoReferencePattern = /^var:organization(?:.[A-Za-z0-9_-]+){1,3}$/;
|
|
583
|
+
var OMIT = /* @__PURE__ */ Symbol("omit");
|
|
584
|
+
function clone(value) {
|
|
585
|
+
return typeof value === "object" && value !== null ? structuredClone(value) : value;
|
|
586
|
+
}
|
|
587
|
+
function substituteLogo(value, logos) {
|
|
588
|
+
if (logos.presentation === void 0 || !logoReferencePattern.test(value)) return value;
|
|
589
|
+
const reference = parseLogoName(value, logos.presentation);
|
|
590
|
+
if (!reference || "error" in reference) return value;
|
|
591
|
+
const result = resolveOrganizationLogo(logos.presentation, reference, { onDark: logos.darkBackground === true });
|
|
592
|
+
if (result.ok) return clone(result.logo.source);
|
|
593
|
+
return result.reason === "missing" ? OMIT : value;
|
|
594
|
+
}
|
|
595
|
+
function substitute(value, values, root, logos = {}) {
|
|
596
|
+
if (typeof value === "string") {
|
|
597
|
+
const logo = substituteLogo(value, logos);
|
|
598
|
+
if (logo !== value) return logo;
|
|
599
|
+
const result = substituteSlideTokens(value, values);
|
|
600
|
+
return result.changed ? result.text : value;
|
|
601
|
+
}
|
|
602
|
+
if (Array.isArray(value)) {
|
|
603
|
+
let changed2 = false;
|
|
604
|
+
const out2 = [];
|
|
605
|
+
for (const entry of value) {
|
|
606
|
+
const next = substitute(entry, values, false, logos);
|
|
607
|
+
if (next !== entry) changed2 = true;
|
|
608
|
+
if (next !== OMIT) out2.push(next);
|
|
609
|
+
}
|
|
610
|
+
return changed2 ? out2 : value;
|
|
611
|
+
}
|
|
612
|
+
if (!isRecord(value)) return value;
|
|
613
|
+
let changed = false;
|
|
614
|
+
const out = {};
|
|
615
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
616
|
+
let next = entry;
|
|
617
|
+
if (skippedKeys.has(key)) next = entry;
|
|
618
|
+
else if (root && key === "design" && isRecord(entry)) {
|
|
619
|
+
const design = {};
|
|
620
|
+
let designChanged = false;
|
|
621
|
+
for (const [designKey, designEntry] of Object.entries(entry)) {
|
|
622
|
+
const designNext = designKey === "header" || designKey === "footer" || designKey === "logo" ? designEntry : substitute(designEntry, values, false, logos);
|
|
623
|
+
if (designNext !== designEntry) designChanged = true;
|
|
624
|
+
if (designNext !== OMIT) design[designKey] = designNext;
|
|
625
|
+
}
|
|
626
|
+
next = designChanged ? design : entry;
|
|
627
|
+
} else next = substitute(entry, values, false, logos);
|
|
628
|
+
if (next !== entry) changed = true;
|
|
629
|
+
if (next !== OMIT) out[key] = next;
|
|
630
|
+
}
|
|
631
|
+
return changed ? out : value;
|
|
632
|
+
}
|
|
633
|
+
function resolveSlideVariables(slide, values) {
|
|
634
|
+
if (!isRecord(slide)) return slide;
|
|
635
|
+
const full = { slideNumber: values?.slideNumber, ...values?.slideCount !== void 0 ? { slideCount: values.slideCount } : {}, section: slide.section };
|
|
636
|
+
checkValues(full);
|
|
637
|
+
const logos = values.presentation !== void 0 ? { presentation: values.presentation, darkBackground: values.darkBackground === true } : {};
|
|
638
|
+
const text2 = JSON.stringify(slide);
|
|
639
|
+
if (!quickCheck.test(text2) && !(logos.presentation !== void 0 && text2.includes('"var:organization.'))) return slide;
|
|
640
|
+
return substitute(slide, full, true, logos);
|
|
641
|
+
}
|
|
642
|
+
function usesSlideBuiltin(value, name) {
|
|
643
|
+
const pattern = new RegExp(String.raw`(?:^|[^\\])\{\{\s*${name.replace(".", String.raw`\.`)}\s*(?:\|[^{}]*)?\}\}`);
|
|
644
|
+
const visit = (entry) => {
|
|
645
|
+
if (typeof entry === "string") return entry.includes("{{") && pattern.test(entry);
|
|
646
|
+
if (Array.isArray(entry)) return entry.some(visit);
|
|
647
|
+
if (!isRecord(entry)) return false;
|
|
648
|
+
for (const [key, child] of Object.entries(entry)) if (!skippedKeys.has(key) && visit(child)) return true;
|
|
649
|
+
return false;
|
|
650
|
+
};
|
|
651
|
+
return visit(value);
|
|
652
|
+
}
|
|
653
|
+
|
|
444
654
|
// src/variables.ts
|
|
445
655
|
var VARIABLE_KINDS = ["color", "text", "number", "date", "image", "url", "list"];
|
|
446
656
|
var DEFAULT_VARIABLE_DATE_FORMAT = "MMMM d, yyyy";
|
|
@@ -454,10 +664,10 @@ var OPFVariableError = class extends Error {
|
|
|
454
664
|
}
|
|
455
665
|
};
|
|
456
666
|
var idPattern = /^[a-z][a-z0-9-]*$/;
|
|
457
|
-
var nameSource = String.raw`[a-z][a-z0-9-]*|(?:deck|speaker|organization)(?:\.[A-Za-z0-9_-]+){1,
|
|
667
|
+
var nameSource = String.raw`[a-z][a-z0-9-]*|(?:deck|speaker|organization|slide)(?:\.[A-Za-z0-9_-]+){1,3}`;
|
|
458
668
|
var referencePattern = new RegExp(String.raw`^var:(${nameSource})$`);
|
|
459
|
-
var builtinNamePattern = /^(?:speakers|(?:deck|speaker|organization)(?:\.[A-Za-z0-9_-]+){1,
|
|
460
|
-
var builtinUsePattern = /\{\{\s*(?:speakers\b|(?:deck|speaker|organization)\.)|^var:(?:speakers$|(?:deck|speaker|organization)\.)/;
|
|
669
|
+
var builtinNamePattern = /^(?:speakers|(?:deck|speaker|organization|slide)(?:\.[A-Za-z0-9_-]+){1,3})$/;
|
|
670
|
+
var builtinUsePattern = /\{\{\s*(?:speakers\b|(?:deck|speaker|organization|slide)\.)|^var:(?:speakers$|(?:deck|speaker|organization|slide)\.)/;
|
|
461
671
|
var hexPattern = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
|
462
672
|
var urlPattern = /^(?:https?:\/\/|mailto:|tel:)\S+$/;
|
|
463
673
|
var datePrefixPattern = /^(\d{4}-\d{2}-\d{2})(?:[T ].*)?$/;
|
|
@@ -473,7 +683,7 @@ var SAMPLES = {
|
|
|
473
683
|
list: ["Sample item"]
|
|
474
684
|
};
|
|
475
685
|
var hasOwn = (value, key) => Object.hasOwn(value, key);
|
|
476
|
-
var
|
|
686
|
+
var clone2 = (value) => value === void 0 ? value : JSON.parse(JSON.stringify(value));
|
|
477
687
|
function isTemplate(presentation) {
|
|
478
688
|
return isRecord(presentation) && presentation.template === true;
|
|
479
689
|
}
|
|
@@ -489,7 +699,7 @@ function coerceVariableValue(kind2, raw) {
|
|
|
489
699
|
if (typeof raw === "string") return { ok: true, value: raw };
|
|
490
700
|
if (typeof raw === "number" && Number.isFinite(raw)) return { ok: true, value: String(raw) };
|
|
491
701
|
if (typeof raw === "boolean") return { ok: true, value: String(raw) };
|
|
492
|
-
if (Array.isArray(raw) && raw.every((run) => typeof run === "string" || isRecord(run) && typeof run.text === "string")) return { ok: true, value:
|
|
702
|
+
if (Array.isArray(raw) && raw.every((run) => typeof run === "string" || isRecord(run) && typeof run.text === "string")) return { ok: true, value: clone2(raw) };
|
|
493
703
|
return fail("expected text: a string or an array of text runs");
|
|
494
704
|
}
|
|
495
705
|
case "number": {
|
|
@@ -509,7 +719,7 @@ function coerceVariableValue(kind2, raw) {
|
|
|
509
719
|
}
|
|
510
720
|
case "image": {
|
|
511
721
|
if (typeof raw === "string" && raw.trim()) return { ok: true, value: raw.trim() };
|
|
512
|
-
if (isRecord(raw) && typeof raw.src === "string" && raw.src.trim()) return { ok: true, value:
|
|
722
|
+
if (isRecord(raw) && typeof raw.src === "string" && raw.src.trim()) return { ok: true, value: clone2(raw) };
|
|
513
723
|
return fail("expected an image source: a string or an object with src");
|
|
514
724
|
}
|
|
515
725
|
case "url":
|
|
@@ -560,7 +770,7 @@ function usesBuiltins(value, root = true) {
|
|
|
560
770
|
if (Array.isArray(value)) return value.some((entry) => usesBuiltins(entry, false));
|
|
561
771
|
if (!isRecord(value)) return false;
|
|
562
772
|
for (const [key, entry] of Object.entries(value)) {
|
|
563
|
-
if (
|
|
773
|
+
if (skippedKeys2.has(key) || root && skippedRootKeys.has(key)) continue;
|
|
564
774
|
if (usesBuiltins(entry, false)) return true;
|
|
565
775
|
}
|
|
566
776
|
return false;
|
|
@@ -655,7 +865,7 @@ function inlineText(effective, format, path, context) {
|
|
|
655
865
|
}
|
|
656
866
|
var DECK_FIELDS = { name: "text", description: "text", author: "text" };
|
|
657
867
|
var SPEAKER_FIELDS = { name: "text", title: "text", email: "text", phone: "text", bio: "text", photo: "image" };
|
|
658
|
-
var ORGANIZATION_FIELDS = { name: "text", legalName: "text", tagline: "text", domain: "text", email: "text", phone: "text"
|
|
868
|
+
var ORGANIZATION_FIELDS = { name: "text", legalName: "text", tagline: "text", domain: "text", email: "text", phone: "text" };
|
|
659
869
|
function isBuiltinName(name) {
|
|
660
870
|
return builtinNamePattern.test(name);
|
|
661
871
|
}
|
|
@@ -672,13 +882,22 @@ function builtinSource(name, doc) {
|
|
|
672
882
|
const names = speakersOf(doc).map((speaker) => present(speaker.name)).filter((entry) => typeof entry === "string");
|
|
673
883
|
return { ok: true, kind: "list", raw: names.length ? names : void 0 };
|
|
674
884
|
}
|
|
885
|
+
const list = (names) => names.map((entry) => `'${entry}'`).join(", ");
|
|
886
|
+
if (isSlideScopedName(name)) {
|
|
887
|
+
if (isSlideScopedBuiltin(name)) return { ok: false, message: `'var:${name}' is not supported: '${name}' is a slide-scoped built-in, written only as the inline token '{{${name}}}'.` };
|
|
888
|
+
return { ok: false, message: `'${name}' is not a built-in variable; the slide-scoped built-ins are ${list(SLIDE_SCOPED_BUILTINS)}.` };
|
|
889
|
+
}
|
|
675
890
|
const parts = name.split(".");
|
|
676
891
|
const root = parts[0];
|
|
892
|
+
if (root === "organization") {
|
|
893
|
+
const logo = parseLogoName(name, doc);
|
|
894
|
+
if (logo && "error" in logo) return { ok: false, message: logo.error };
|
|
895
|
+
if (logo) return { ok: false, message: `'{{${name}}}' is not supported: '${name}' is an image, used only as a whole field, 'var:${name}', which resolves for each slide's background.` };
|
|
896
|
+
}
|
|
677
897
|
const fields2 = builtinFields(root);
|
|
678
|
-
const list = (names) => names.map((entry) => `'${entry}'`).join(", ");
|
|
679
898
|
if (root === "deck") {
|
|
680
899
|
const field2 = parts[1];
|
|
681
|
-
if (parts.length !== 2 || !Object.hasOwn(fields2, field2)) return { ok: false, message: `'${name}' is not a built-in variable; the deck built-ins are ${list(Object.keys(fields2).map((key) => `deck.${key}`))}.` };
|
|
900
|
+
if (parts.length !== 2 || !Object.hasOwn(fields2, field2)) return { ok: false, message: `'${name}' is not a built-in variable; the deck built-ins are ${list(Object.keys(fields2).map((key) => `deck.${key}`))} and the slide-scoped 'deck.slideCount'.` };
|
|
682
901
|
const value = doc[field2];
|
|
683
902
|
return { ok: true, kind: "text", raw: field2 === "author" && Array.isArray(value) ? present(value.filter((entry) => typeof entry === "string").join(", ")) : present(value) };
|
|
684
903
|
}
|
|
@@ -687,6 +906,7 @@ function builtinSource(name, doc) {
|
|
|
687
906
|
return { ok: false, message: `'${name}' is not a built-in variable: '${field}' is not a ${root} field; the fields are ${list(Object.keys(fields2))}.` };
|
|
688
907
|
}
|
|
689
908
|
let entity;
|
|
909
|
+
if (parts.length > 3) return { ok: false, message: `'${name}' is not a built-in variable.` };
|
|
690
910
|
if (parts.length === 2) entity = root === "speaker" ? primarySpeaker(doc) : primaryOrganization(doc);
|
|
691
911
|
else {
|
|
692
912
|
const id = parts[1];
|
|
@@ -742,15 +962,16 @@ function lookupVariable(id, path, context) {
|
|
|
742
962
|
}
|
|
743
963
|
function interpolate(text2, path, context) {
|
|
744
964
|
let changed = false;
|
|
745
|
-
const value = text2.replace(tokenPattern, (match, id, format) => {
|
|
965
|
+
const value = text2.replace(tokenPattern, (match, id, format, offset) => {
|
|
746
966
|
if (id === void 0) {
|
|
747
|
-
if (context.unescape) {
|
|
967
|
+
if (context.unescape && !slideTokenFollows(text2.slice(offset + match.length))) {
|
|
748
968
|
changed = true;
|
|
749
969
|
return "{{";
|
|
750
970
|
}
|
|
751
971
|
return match;
|
|
752
972
|
}
|
|
753
973
|
context.uses.push({ id, path, form: "token" });
|
|
974
|
+
if (isSlideScopedBuiltin(id)) return match;
|
|
754
975
|
const effective = lookupVariable(id, path, context);
|
|
755
976
|
if (!effective) {
|
|
756
977
|
if (!isBuiltinName(id)) diag(context, { code: "variable-unknown", severity: "warning", path, id, message: `'{{${id}}}' names no declared variable; declare '${id}' in the top-level variables map, or write '\\{{' for literal braces.` }, `unk:${id}:${path}`);
|
|
@@ -770,7 +991,7 @@ function interpolate(text2, path, context) {
|
|
|
770
991
|
});
|
|
771
992
|
return { value, changed };
|
|
772
993
|
}
|
|
773
|
-
var
|
|
994
|
+
var OMIT2 = /* @__PURE__ */ Symbol("omit");
|
|
774
995
|
var Splice = class {
|
|
775
996
|
constructor(values) {
|
|
776
997
|
this.values = values;
|
|
@@ -781,6 +1002,8 @@ function reduceString(text2, path, parentIsArray, context) {
|
|
|
781
1002
|
const reference = referencePattern.exec(text2);
|
|
782
1003
|
if (reference) {
|
|
783
1004
|
const id = reference[1];
|
|
1005
|
+
const logo = id.startsWith("organization.") ? parseLogoName(id, context.doc) : null;
|
|
1006
|
+
if (logo) return reduceLogo(id, logo, path, context);
|
|
784
1007
|
const effective = lookupVariable(id, path, context);
|
|
785
1008
|
if (effective && effective.declaration.kind !== "color") {
|
|
786
1009
|
context.uses.push({ id, path, form: "reference" });
|
|
@@ -789,9 +1012,9 @@ function reduceString(text2, path, parentIsArray, context) {
|
|
|
789
1012
|
}
|
|
790
1013
|
const value = effective.status === "unfilled" ? sampleValue(effective.declaration) : effective.value;
|
|
791
1014
|
if (effective.declaration.kind === "list" && parentIsArray) {
|
|
792
|
-
return context.shape ? { kind: "replace", value: value[0] ?? "" } : { kind: "splice", values:
|
|
1015
|
+
return context.shape ? { kind: "replace", value: value[0] ?? "" } : { kind: "splice", values: clone2(value) };
|
|
793
1016
|
}
|
|
794
|
-
return { kind: "replace", value:
|
|
1017
|
+
return { kind: "replace", value: clone2(value) };
|
|
795
1018
|
}
|
|
796
1019
|
if (effective) {
|
|
797
1020
|
context.uses.push({ id, path, form: "reference" });
|
|
@@ -803,13 +1026,31 @@ function reduceString(text2, path, parentIsArray, context) {
|
|
|
803
1026
|
const result = interpolate(text2, path, context);
|
|
804
1027
|
return result.changed ? { kind: "replace", value: result.value } : { kind: "keep" };
|
|
805
1028
|
}
|
|
806
|
-
|
|
1029
|
+
function slideReachable(path) {
|
|
1030
|
+
return path.startsWith("/slides/") || path.startsWith("/design/header/") || path.startsWith("/design/footer/") || path === "/design/logo";
|
|
1031
|
+
}
|
|
1032
|
+
function reduceLogo(id, logo, path, context) {
|
|
1033
|
+
context.uses.push({ id, path, form: "reference" });
|
|
1034
|
+
if ("error" in logo) {
|
|
1035
|
+
diag(context, { code: "variable-unknown-builtin", severity: "error", path, id, message: logo.error }, `ubi:${id}:${path}`);
|
|
1036
|
+
return { kind: "keep" };
|
|
1037
|
+
}
|
|
1038
|
+
const result = resolveOrganizationLogo(context.doc, logo);
|
|
1039
|
+
if (!result.ok) {
|
|
1040
|
+
if (result.reason === "unknown") diag(context, { code: "variable-unknown-builtin", severity: "error", path, id, message: result.message }, `ubi:${id}:${path}`);
|
|
1041
|
+
else diag(context, missingBuiltin(id, path), `bim:${id}:${path}`);
|
|
1042
|
+
}
|
|
1043
|
+
if (slideReachable(path)) return { kind: "keep" };
|
|
1044
|
+
if (result.ok) return { kind: "replace", value: clone2(result.logo.source) };
|
|
1045
|
+
return context.shape ? { kind: "keep" } : { kind: "omit" };
|
|
1046
|
+
}
|
|
1047
|
+
var skippedKeys2 = /* @__PURE__ */ new Set(["extensions"]);
|
|
807
1048
|
var skippedRootKeys = /* @__PURE__ */ new Set(["variables", "catalogs", "$schema", "template"]);
|
|
808
1049
|
function walk(value, path, parentIsArray, context) {
|
|
809
1050
|
if (typeof value === "string") {
|
|
810
1051
|
const reduced = reduceString(value, path, parentIsArray, context);
|
|
811
1052
|
if (reduced.kind === "keep") return value;
|
|
812
|
-
if (reduced.kind === "omit") return
|
|
1053
|
+
if (reduced.kind === "omit") return OMIT2;
|
|
813
1054
|
if (reduced.kind === "splice") return new Splice(reduced.values);
|
|
814
1055
|
return reduced.value;
|
|
815
1056
|
}
|
|
@@ -818,7 +1059,7 @@ function walk(value, path, parentIsArray, context) {
|
|
|
818
1059
|
const out = [];
|
|
819
1060
|
value.forEach((entry, index) => {
|
|
820
1061
|
const next = walk(entry, `${path}/${index}`, true, context);
|
|
821
|
-
if (next ===
|
|
1062
|
+
if (next === OMIT2) {
|
|
822
1063
|
changed = true;
|
|
823
1064
|
return;
|
|
824
1065
|
}
|
|
@@ -836,12 +1077,12 @@ function walk(value, path, parentIsArray, context) {
|
|
|
836
1077
|
let changed = false;
|
|
837
1078
|
const entries = [];
|
|
838
1079
|
for (const [key, entry] of Object.entries(value)) {
|
|
839
|
-
if (
|
|
1080
|
+
if (skippedKeys2.has(key) || path === "" && skippedRootKeys.has(key)) {
|
|
840
1081
|
entries.push([key, entry]);
|
|
841
1082
|
continue;
|
|
842
1083
|
}
|
|
843
1084
|
const next = walk(entry, pathFor(path || "/", key), false, context);
|
|
844
|
-
if (next ===
|
|
1085
|
+
if (next === OMIT2) {
|
|
845
1086
|
changed = true;
|
|
846
1087
|
continue;
|
|
847
1088
|
}
|
|
@@ -940,6 +1181,21 @@ function rebuildVariables(presentation, built, keepAll) {
|
|
|
940
1181
|
}
|
|
941
1182
|
return out.length ? Object.fromEntries(out) : void 0;
|
|
942
1183
|
}
|
|
1184
|
+
function slideSectionDiagnostics(presentation, uses) {
|
|
1185
|
+
const paths = uses.filter((use) => use.id === "slide.section").map((use) => use.path);
|
|
1186
|
+
if (!paths.length || !Array.isArray(presentation.slides)) return [];
|
|
1187
|
+
const out = [];
|
|
1188
|
+
presentation.slides.forEach((slide, index) => {
|
|
1189
|
+
if (!isRecord(slide) || typeof slide.section === "string" && slide.section !== "") return;
|
|
1190
|
+
const own2 = `/slides/${index}/`;
|
|
1191
|
+
const design = isRecord(slide.design) ? slide.design : {};
|
|
1192
|
+
const inherited = ["header", "footer"].filter((kind2) => design[kind2] === void 0).map((kind2) => `/design/${kind2}/`);
|
|
1193
|
+
const where = paths.find((path) => path.startsWith(own2) || inherited.some((prefix) => path.startsWith(prefix)));
|
|
1194
|
+
if (where === void 0) return;
|
|
1195
|
+
out.push({ code: "variable-builtin-missing", severity: "warning", path: `/slides/${index}`, id: "slide.section", message: `Slide ${index + 1} has no section, so '{{slide.section}}' (used at ${where}) resolves to nothing on it; give the slide a section or remove the token.` });
|
|
1196
|
+
});
|
|
1197
|
+
return out;
|
|
1198
|
+
}
|
|
943
1199
|
function finish(presentation, built, options) {
|
|
944
1200
|
const { context } = built;
|
|
945
1201
|
const complete = built.unfilled.length === 0;
|
|
@@ -958,6 +1214,7 @@ function finish(presentation, built, options) {
|
|
|
958
1214
|
}
|
|
959
1215
|
}
|
|
960
1216
|
}
|
|
1217
|
+
if (context.shape) context.diagnostics.push(...slideSectionDiagnostics(presentation, context.uses));
|
|
961
1218
|
const diagnostics = context.diagnostics;
|
|
962
1219
|
if (options.strict && diagnostics.some((entry) => entry.severity === "error")) throw new OPFVariableError(diagnostics);
|
|
963
1220
|
return { presentation: result, diagnostics, unfilled: built.unfilled, examplesUsed: built.examplesUsed, complete };
|
|
@@ -991,6 +1248,7 @@ function listVariables(presentation, values = {}) {
|
|
|
991
1248
|
};
|
|
992
1249
|
});
|
|
993
1250
|
}
|
|
1251
|
+
var SLIDE_SCOPED_LABELS = { "slide.number": "Slide number", "slide.section": "Section", "deck.slideCount": "Slide count" };
|
|
994
1252
|
var FIELD_LABELS = { name: "name", legalName: "legal name", tagline: "tagline", domain: "domain", email: "email", phone: "phone", logo: "logo", title: "title", bio: "bio", photo: "photo", description: "description", author: "author" };
|
|
995
1253
|
function listBuiltinVariables(presentation) {
|
|
996
1254
|
if (!isRecord(presentation)) return [];
|
|
@@ -1013,7 +1271,21 @@ function listBuiltinVariables(presentation) {
|
|
|
1013
1271
|
const source = builtinSource(name, presentation);
|
|
1014
1272
|
if (!source.ok) continue;
|
|
1015
1273
|
const coerced = source.raw === void 0 ? void 0 : coerceVariableValue(source.kind, source.raw);
|
|
1016
|
-
out.push({ name, kind: source.kind, label, ...coerced?.ok ? { value: coerced.value } : {}, available: coerced?.ok === true, uses: built.context.uses.filter((use) => use.id === name) });
|
|
1274
|
+
out.push({ name, kind: source.kind, label, scope: "deck", ...coerced?.ok ? { value: coerced.value } : {}, available: coerced?.ok === true, uses: built.context.uses.filter((use) => use.id === name) });
|
|
1275
|
+
}
|
|
1276
|
+
const slides = Array.isArray(presentation.slides) ? presentation.slides : [];
|
|
1277
|
+
for (const name of SLIDE_SCOPED_BUILTINS) {
|
|
1278
|
+
const available = name !== "slide.section" || slides.some((slide) => isRecord(slide) && typeof slide.section === "string" && slide.section !== "");
|
|
1279
|
+
out.push({ name, kind: "text", label: SLIDE_SCOPED_LABELS[name], scope: "slide", available, uses: built.context.uses.filter((use) => use.id === name) });
|
|
1280
|
+
}
|
|
1281
|
+
const logoReferences = [{ reference: {}, label: "Organization" }];
|
|
1282
|
+
for (const entry of organizationsOf(presentation)) if (typeof entry.id === "string" && entry.id !== "") logoReferences.push({ reference: { organization: entry.id }, label: `Organization ${entry.id}` });
|
|
1283
|
+
for (const { reference, label } of logoReferences) {
|
|
1284
|
+
for (const shape of LOGO_SHAPES) {
|
|
1285
|
+
const name = logoReferenceName(reference, shape);
|
|
1286
|
+
const available = resolveOrganizationLogo(presentation, reference, { shape }).ok;
|
|
1287
|
+
out.push({ name, kind: "image", label: `${label} logo${shape === "full" ? "" : ` (${shape})`}`, scope: "slide", available, uses: built.context.uses.filter((use) => use.id === name || shape === "full" && use.id === `${name}.full`) });
|
|
1288
|
+
}
|
|
1017
1289
|
}
|
|
1018
1290
|
return out;
|
|
1019
1291
|
}
|
|
@@ -3992,7 +4264,6 @@ var imagePayload = (host) => {
|
|
|
3992
4264
|
for (const key of IMAGE_OPTION_KEYS) if (host[key] !== void 0) payload[key] = host[key];
|
|
3993
4265
|
return payload;
|
|
3994
4266
|
};
|
|
3995
|
-
var assetSource = (value) => typeof value === "string" ? value : record6(value).src;
|
|
3996
4267
|
var finite = (value, min, max) => typeof value === "number" && Number.isFinite(value) && value >= min && value <= max ? value : void 0;
|
|
3997
4268
|
var pathNumber = (value) => String(round2(value));
|
|
3998
4269
|
var IMAGE_SHAPES = { rectangle: "rect", rounded: "roundRect", circle: "ellipse", hexagon: "hexagon" };
|
|
@@ -4112,45 +4383,6 @@ function resolveBackgroundImage(slide, presentation, themeBackground, width, hei
|
|
|
4112
4383
|
if (aspect) result.picture = fitImage(box, background.fit, aspect, background.focus);
|
|
4113
4384
|
return result;
|
|
4114
4385
|
}
|
|
4115
|
-
var LOGO_LOCKUP_CHAINS = {
|
|
4116
|
-
dark: ["light", "default", "stackedLight", "stacked", "wordmarkLight", "wordmark", "iconLight", "icon", "dark", "stackedDark", "wordmarkDark", "iconDark"],
|
|
4117
|
-
light: ["dark", "default", "stackedDark", "stacked", "wordmarkDark", "wordmark", "iconDark", "icon", "light", "stackedLight", "wordmarkLight", "iconLight"]
|
|
4118
|
-
};
|
|
4119
|
-
var LOGO_SET_KEYS = new Set(LOGO_LOCKUP_CHAINS.dark);
|
|
4120
|
-
function resolveLogo(presentation, slide, options = {}) {
|
|
4121
|
-
const slot = options.slot === "icon" || options.slot === "stacked" ? options.slot : "lockup";
|
|
4122
|
-
if (options.slot !== void 0 && slot !== options.slot) throw new RangeError("Logo slot must be lockup, icon or stacked.");
|
|
4123
|
-
const tone = options.onDark === true ? "dark" : "light";
|
|
4124
|
-
const lockup = LOGO_LOCKUP_CHAINS[tone];
|
|
4125
|
-
const chain = slot === "icon" ? [tone === "dark" ? "iconLight" : "iconDark", "icon", ...lockup] : slot === "stacked" ? [tone === "dark" ? "stackedLight" : "stackedDark", "stacked", ...lockup] : lockup;
|
|
4126
|
-
const usable = (value) => {
|
|
4127
|
-
const source = assetSource(value);
|
|
4128
|
-
return typeof source === "string" && source.length > 0;
|
|
4129
|
-
};
|
|
4130
|
-
const pick = (value, path) => {
|
|
4131
|
-
if (value === void 0 || value === null || value === false) return null;
|
|
4132
|
-
if (typeof value === "string" || typeof value === "object" && !Array.isArray(value) && "src" in record6(value)) {
|
|
4133
|
-
return usable(value) ? { source: value, path, variant: "default", slot } : null;
|
|
4134
|
-
}
|
|
4135
|
-
const set2 = record6(value);
|
|
4136
|
-
if (!Object.keys(set2).some((key) => LOGO_SET_KEYS.has(key))) return null;
|
|
4137
|
-
for (const key of chain) {
|
|
4138
|
-
const variant = set2[key];
|
|
4139
|
-
if (variant !== void 0 && usable(variant)) return { source: variant, path: `${path}.${key}`, variant: key, slot };
|
|
4140
|
-
}
|
|
4141
|
-
return null;
|
|
4142
|
-
};
|
|
4143
|
-
const deck = record6(presentation), slideIndex = Number.isSafeInteger(options.slideIndex) && options.slideIndex >= 0 ? options.slideIndex : 0;
|
|
4144
|
-
const own2 = pick(record6(record6(slide).design).logo, `slides.${slideIndex}.design.logo`);
|
|
4145
|
-
if (own2) return own2;
|
|
4146
|
-
const shared = pick(record6(deck.design).logo, "design.logo");
|
|
4147
|
-
if (shared) return shared;
|
|
4148
|
-
const organizations = Array.isArray(deck.organization) ? deck.organization : [deck.organization];
|
|
4149
|
-
const primaryIndex = organizations.findIndex((item) => record6(item).role === "primary");
|
|
4150
|
-
const index = primaryIndex >= 0 ? primaryIndex : organizations.findIndex(Boolean);
|
|
4151
|
-
if (index < 0) return null;
|
|
4152
|
-
return pick(record6(organizations[index]).logo, Array.isArray(deck.organization) ? `organization.${index}.logo` : "organization.logo");
|
|
4153
|
-
}
|
|
4154
4386
|
var webUrl = /^https?:\/\/\S+$/i;
|
|
4155
4387
|
function resolveSocialProfile(platform, value, owner = "organization") {
|
|
4156
4388
|
const raw = String(value).trim().replace(/\s+/gu, " ");
|
|
@@ -4166,6 +4398,17 @@ function resolveSocialProfile(platform, value, owner = "organization") {
|
|
|
4166
4398
|
if (!webUrl.test(url)) return { text: raw, resolved: false };
|
|
4167
4399
|
return { text: display(url), href: encodeURI(url), resolved: true };
|
|
4168
4400
|
}
|
|
4401
|
+
var FURNITURE_GAP = 12;
|
|
4402
|
+
var FURNITURE_IMAGE_SHARE = 0.4;
|
|
4403
|
+
var SPACED_WORD_CHARACTER = /^[\p{L}\p{N}\p{M}]$/u;
|
|
4404
|
+
var UNSPACED_SCRIPT = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Thai}\p{Script=Lao}\p{Script=Khmer}\p{Script=Myanmar}\p{Script=Tibetan}]/u;
|
|
4405
|
+
function breaksInsideWord(text2, fit) {
|
|
4406
|
+
return fit.sourceLines.some((line) => {
|
|
4407
|
+
if (line.boundary !== "soft" || line.nextStart <= 0 || line.nextStart >= text2.length) return false;
|
|
4408
|
+
const before = [...text2.slice(0, line.nextStart)].at(-1) ?? "", after = [...text2.slice(line.nextStart, line.nextStart + 2)][0] ?? "";
|
|
4409
|
+
return SPACED_WORD_CHARACTER.test(before) && SPACED_WORD_CHARACTER.test(after) && !UNSPACED_SCRIPT.test(before) && !UNSPACED_SCRIPT.test(after);
|
|
4410
|
+
});
|
|
4411
|
+
}
|
|
4169
4412
|
function layoutFurniture(input, options = {}) {
|
|
4170
4413
|
const slide = record6(input), width = options.width ?? 1280, height = options.height ?? 720, scale = Math.min(width, height) / 720;
|
|
4171
4414
|
const settings = { ...record6(record6(options.layout).composition), ...record6(slide.composition) };
|
|
@@ -4180,9 +4423,8 @@ function layoutFurniture(input, options = {}) {
|
|
|
4180
4423
|
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.");
|
|
4181
4424
|
const outlines = options.textMeasurement?.outlineBounds !== void 0, parts = [], diagnostics = [];
|
|
4182
4425
|
const sourceRoot = `slides.${options.slideIndex ?? 0}`, organizations = Array.isArray(options.presentation?.organization) ? options.presentation.organization : [options.presentation?.organization];
|
|
4183
|
-
const primaryIndex = organizations.findIndex((item) => record6(item).role === "primary"),
|
|
4184
|
-
const organization = record6(organizations[
|
|
4185
|
-
const speakers = Array.isArray(options.presentation?.speaker) ? options.presentation.speaker : [options.presentation?.speaker], speaker = record6(speakers[0]), speakerRoot = Array.isArray(options.presentation?.speaker) ? "speaker.0" : "speaker";
|
|
4426
|
+
const primaryIndex = organizations.findIndex((item) => record6(item).role === "primary"), organizationIndex2 = primaryIndex >= 0 ? primaryIndex : organizations.findIndex(Boolean);
|
|
4427
|
+
const organization = record6(organizations[organizationIndex2]), organizationRoot = Array.isArray(options.presentation?.organization) ? `organization.${organizationIndex2}` : "organization";
|
|
4186
4428
|
const fontFamily = options.fontFamilies?.body ?? "sans-serif";
|
|
4187
4429
|
let headerBottom = 0, footerTop = height, configured = false;
|
|
4188
4430
|
const error = (path, message, code = "text-overflow") => diagnostics.push({ code, path, message });
|
|
@@ -4195,83 +4437,39 @@ function layoutFurniture(input, options = {}) {
|
|
|
4195
4437
|
const zones = [];
|
|
4196
4438
|
for (const [index, zone] of ["left", "center", "right"].entries()) {
|
|
4197
4439
|
const side = furnitureRtl && zone !== "center" ? zone === "left" ? "right" : "left" : zone;
|
|
4198
|
-
const content = record6(record6(value)[zone]), path = `${root}.${zone}`, x = width * (0.07 + (furnitureRtl ? 2 - index : index) * 0.3), zoneWidth = width * 0.26
|
|
4199
|
-
|
|
4440
|
+
const content = record6(record6(value)[zone]), path = `${root}.${zone}`, x = width * (0.07 + (furnitureRtl ? 2 - index : index) * 0.3), zoneWidth = width * 0.26;
|
|
4441
|
+
const pending = [];
|
|
4200
4442
|
const add = (field, text2, generated = false, sourcePath, extras = {}) => {
|
|
4201
4443
|
if (text2 === void 0) return;
|
|
4202
4444
|
if (typeof text2 !== "string") throw new TypeError(`Furniture field ${path}.${field} requires string content.`);
|
|
4203
|
-
const partPath = `${path}.${field}
|
|
4204
|
-
|
|
4205
|
-
const ink = fit.sourceLines.map((line, index2) => {
|
|
4206
|
-
let outline = null;
|
|
4207
|
-
if (outlines) {
|
|
4208
|
-
for (const segment2 of line.segments) if (segment2.kind === "text") {
|
|
4209
|
-
const bounds = measureTextOutline(text2.slice(segment2.start, segment2.end), size, style, options.textMeasurement);
|
|
4210
|
-
if (bounds) {
|
|
4211
|
-
const next = { ...bounds, x: bounds.x + segment2.x };
|
|
4212
|
-
if (!outline) outline = next;
|
|
4213
|
-
else {
|
|
4214
|
-
const right = Math.max(outline.x + outline.width, next.x + next.width), bottom = Math.max(outline.y + outline.height, next.y + next.height);
|
|
4215
|
-
outline.x = Math.min(outline.x, next.x);
|
|
4216
|
-
outline.y = Math.min(outline.y, next.y);
|
|
4217
|
-
outline.width = right - outline.x;
|
|
4218
|
-
outline.height = bottom - outline.y;
|
|
4219
|
-
}
|
|
4220
|
-
}
|
|
4221
|
-
}
|
|
4222
|
-
}
|
|
4223
|
-
return { width: line.width, y: index2 * fit.lineHeight, baseline: size + index2 * fit.lineHeight, height: fit.lineHeight, outline };
|
|
4224
|
-
});
|
|
4225
|
-
const natural = outlines ? placeTextLines(ink, { x, y, width: zoneWidth, height: Number.MAX_VALUE }, side, padding) : void 0;
|
|
4226
|
-
const partHeight = natural?.height ?? fit.sourceLines.length * fit.lineHeight;
|
|
4227
|
-
if (!Number.isFinite(partHeight)) throw new RangeError("Furniture exceeds finite layout coordinates.");
|
|
4228
|
-
const box = { x, y, width: zoneWidth, height: Math.max(scale, partHeight) }, placement = outlines ? placeTextLines(ink, box, side, padding) : void 0;
|
|
4229
|
-
const accepted = { ...fit, ...placement ? { placement } : {}, overflow: fit.overflow || !!placement?.overflow };
|
|
4230
|
-
zoneParts.push({ type: "text", kind: kind2, zone, field, path: partPath, sourcePath: sourcePath ?? (!generated ? partPath : void 0), generated, text: text2, style, requestedFontSize: size, minFontSize: minimum, box, alignment: side, fit: accepted, ...extras.fields?.length ? { fields: extras.fields } : {}, ...extras.links?.length ? { links: extras.links } : {} });
|
|
4231
|
-
if (accepted.overflow) error(partPath, "Repeated text exceeds its zone at the selected readability floor; change the furniture or slide design.");
|
|
4232
|
-
y += box.height;
|
|
4445
|
+
const partPath = `${path}.${field}`;
|
|
4446
|
+
pending.push({ type: "text", field, text: text2, generated, sourcePath: sourcePath ?? (!generated ? partPath : void 0), extras, partPath, style: resolveTextStyle({ fontFamily, fontWeight: 400, italic: false, path: partPath }, options.textMeasurement) });
|
|
4233
4447
|
};
|
|
4234
|
-
const
|
|
4448
|
+
const addImage = (source, sourcePath, reference) => {
|
|
4235
4449
|
const imageHeight = Math.max(32 * scale, Math.min(height * 0.05, 72 * scale)), imageWidth = Math.min(zoneWidth, imageHeight * (intrinsicImageAspect(source, record6(options.presentation).assets) ?? 1));
|
|
4236
|
-
|
|
4450
|
+
pending.push({ type: "image", sourcePath, image: source, ...reference !== void 0 ? { reference } : {}, width: imageWidth, height: imageHeight });
|
|
4237
4451
|
};
|
|
4238
|
-
if (content.logo === true) {
|
|
4239
|
-
const resolved = resolveLogo(options.presentation, slide, { slot: "icon", onDark: options.darkBackground, slideIndex: options.slideIndex });
|
|
4240
|
-
if (resolved) {
|
|
4241
|
-
const box = imageBox(resolved.source);
|
|
4242
|
-
zoneParts.push({ type: "image", kind: kind2, zone, field: "logo", path: `${path}.logo`, sourcePath: resolved.path, generated: true, image: resolved.source, box, alignment: side });
|
|
4243
|
-
y += box.height;
|
|
4244
|
-
} else error(`${path}.logo`, "Generated logo needs design.logo or a primary organization logo.", "unresolved-content");
|
|
4245
|
-
}
|
|
4246
4452
|
if (content.image !== void 0) {
|
|
4247
|
-
const
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
}
|
|
4256
|
-
if (content.speaker === true) {
|
|
4257
|
-
if (typeof speaker.name === "string" && speaker.name.trim()) add("speaker", typeof speaker.title === "string" && speaker.title.trim() ? `${speaker.name}, ${speaker.title}` : speaker.name, true, `${speakerRoot}.name`);
|
|
4258
|
-
else error(`${path}.speaker`, "Generated speaker needs a named speaker in the presentation.", "unresolved-content");
|
|
4453
|
+
const logo = typeof content.image === "string" && content.image.startsWith("var:organization.") ? parseLogoName(content.image, options.presentation) : null;
|
|
4454
|
+
if (!logo) addImage(content.image, `${path}.image`);
|
|
4455
|
+
else if ("error" in logo) error(`${path}.image`, logo.error, "unresolved-content");
|
|
4456
|
+
else {
|
|
4457
|
+
const resolved = resolveOrganizationLogo(options.presentation, logo, { onDark: options.darkBackground === true });
|
|
4458
|
+
if (resolved.ok) addImage(resolved.logo.source, resolved.logo.path, content.image);
|
|
4459
|
+
else error(`${path}.image`, `${resolved.message} Give the organization a logo, or change the zone image.`, "unresolved-content");
|
|
4460
|
+
}
|
|
4259
4461
|
}
|
|
4462
|
+
if (typeof content.text === "string") {
|
|
4463
|
+
const resolved = substituteSlideTokens(content.text, { slideNumber: number, ...slideCount !== void 0 ? { slideCount } : {}, section: slide.section });
|
|
4464
|
+
if (slideCount === void 0 && usesSlideBuiltin(content.text, "deck.slideCount")) error(`${path}.text`, "{{deck.slideCount}} needs the rendered slide count (the slideCount option or the presentation slides).", "unresolved-content");
|
|
4465
|
+
add("text", resolved.text, false, void 0, { fields: resolved.fields });
|
|
4466
|
+
} else add("text", content.text);
|
|
4260
4467
|
if (content.socials === true) {
|
|
4261
4468
|
const links = Object.entries(record6(organization.socials)).filter(([, value2]) => typeof value2 === "string" && value2.trim()).map(([platform, value2]) => ({ platform, sourcePath: `${organizationRoot}.socials.${platform}`, ...resolveSocialProfile(platform, value2, "organization") }));
|
|
4262
4469
|
if (links.length) add("socials", links.map((link) => link.text).join("\n"), true, `${organizationRoot}.socials`, { links });
|
|
4263
4470
|
else error(`${path}.socials`, "Generated social profiles need a primary organization with socials.", "unresolved-content");
|
|
4264
4471
|
}
|
|
4265
|
-
if (content.
|
|
4266
|
-
if (typeof slide.section === "string") add("section", slide.section, true, `${sourceRoot}.section`);
|
|
4267
|
-
else error(`${path}.section`, "Generated section needs a literal slide section.", "unresolved-content");
|
|
4268
|
-
}
|
|
4269
|
-
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.`);
|
|
4270
|
-
if (content.slideNumber === true) {
|
|
4271
|
-
const resolved = formatSlideNumber(content.slideNumberFormat ?? DEFAULT_SLIDE_NUMBER_FORMAT, number, slideCount);
|
|
4272
|
-
if ("error" in resolved) error(`${path}.slideNumberFormat`, resolved.error, "unresolved-content");
|
|
4273
|
-
else add("slideNumber", resolved.text, true, void 0, { fields: resolved.fields });
|
|
4274
|
-
}
|
|
4472
|
+
if (content.dateFormat !== void 0 && typeof content.dateFormat !== "string") throw new TypeError(`Furniture setting ${path}.dateFormat requires a string.`);
|
|
4275
4473
|
if (content.date === true) {
|
|
4276
4474
|
const format = content.dateFormat ?? DEFAULT_FURNITURE_DATE_FORMAT;
|
|
4277
4475
|
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");
|
|
@@ -4288,9 +4486,71 @@ function layoutFurniture(input, options = {}) {
|
|
|
4288
4486
|
else add("date", resolved.text, true, `${path}.date`);
|
|
4289
4487
|
}
|
|
4290
4488
|
}
|
|
4489
|
+
const fitPart = (part, boxWidth) => {
|
|
4490
|
+
const fit = fitText(part.text, { width: Math.max(Number.MIN_VALUE, boxWidth - (outlines ? 2 * padding : 0)), height: Number.MAX_VALUE }, size, size, textWidthMeasurer(part.style, options.textMeasurement));
|
|
4491
|
+
const ink = fit.sourceLines.map((line, index2) => {
|
|
4492
|
+
let outline = null;
|
|
4493
|
+
if (outlines) {
|
|
4494
|
+
for (const segment2 of line.segments) if (segment2.kind === "text") {
|
|
4495
|
+
const bounds = measureTextOutline(part.text.slice(segment2.start, segment2.end), size, part.style, options.textMeasurement);
|
|
4496
|
+
if (bounds) {
|
|
4497
|
+
const next = { ...bounds, x: bounds.x + segment2.x };
|
|
4498
|
+
if (!outline) outline = next;
|
|
4499
|
+
else {
|
|
4500
|
+
const right = Math.max(outline.x + outline.width, next.x + next.width), bottom = Math.max(outline.y + outline.height, next.y + next.height);
|
|
4501
|
+
outline.x = Math.min(outline.x, next.x);
|
|
4502
|
+
outline.y = Math.min(outline.y, next.y);
|
|
4503
|
+
outline.width = right - outline.x;
|
|
4504
|
+
outline.height = bottom - outline.y;
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4507
|
+
}
|
|
4508
|
+
}
|
|
4509
|
+
return { width: line.width, y: index2 * fit.lineHeight, baseline: size + index2 * fit.lineHeight, height: fit.lineHeight, outline };
|
|
4510
|
+
});
|
|
4511
|
+
const natural = outlines ? placeTextLines(ink, { x: 0, y: 0, width: boxWidth, height: Number.MAX_VALUE }, side, padding) : void 0;
|
|
4512
|
+
const partHeight = natural?.height ?? fit.sourceLines.length * fit.lineHeight;
|
|
4513
|
+
if (!Number.isFinite(partHeight)) throw new RangeError("Furniture exceeds finite layout coordinates.");
|
|
4514
|
+
return { fit, ink, height: Math.max(scale, partHeight) };
|
|
4515
|
+
};
|
|
4516
|
+
const gap = FURNITURE_GAP * scale, gaps = gap * Math.max(0, pending.length - 1);
|
|
4517
|
+
const widths = pending.map((part) => part.type === "image" ? pending.length > 1 ? Math.min(part.width, zoneWidth * FURNITURE_IMAGE_SHARE) : part.width : pending.length === 1 ? zoneWidth : Math.max(scale, ...fitPart(part, 1e7).ink.map((line) => Math.max(line.width + (outlines ? 2 * padding : 0), line.outline ? line.outline.width + 2 * padding : 0))));
|
|
4518
|
+
if (pending.length > 1 && widths.reduce((sum, entry) => sum + entry, 0) + gaps > zoneWidth + 0.01) {
|
|
4519
|
+
const texts = pending.map((_, index2) => index2).filter((index2) => pending[index2].type === "text").sort((a, b) => widths[a] - widths[b]);
|
|
4520
|
+
let remaining = zoneWidth - gaps - pending.reduce((sum, part, index2) => sum + (part.type === "image" ? widths[index2] : 0), 0), count = texts.length;
|
|
4521
|
+
if (remaining < scale * Math.max(1, count)) error(path, `The parts of this zone (${pending.map((part) => part.type === "image" ? "image" : part.field).join(", ")}) are wider side by side than the zone; remove a part or move one to another zone.`);
|
|
4522
|
+
for (const index2 of texts) {
|
|
4523
|
+
const share = Math.max(scale, remaining / count);
|
|
4524
|
+
widths[index2] = Math.min(widths[index2], share);
|
|
4525
|
+
remaining -= widths[index2];
|
|
4526
|
+
count--;
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
const measured = pending.map((part, index2) => part.type === "image" ? { part, width: widths[index2], height: part.height } : { part, width: widths[index2], ...fitPart(part, widths[index2]) });
|
|
4530
|
+
const row = Math.max(0, ...measured.map((entry) => entry.height));
|
|
4531
|
+
const total = measured.reduce((sum, entry) => sum + entry.width, 0) + gaps;
|
|
4532
|
+
let cursor = side === "left" || total > zoneWidth + 0.01 ? x : side === "center" ? x + (zoneWidth - total) / 2 : x + zoneWidth - total;
|
|
4533
|
+
const placed = /* @__PURE__ */ new Map();
|
|
4534
|
+
for (const index2 of furnitureRtl ? [...measured.keys()].reverse() : [...measured.keys()]) {
|
|
4535
|
+
placed.set(index2, cursor);
|
|
4536
|
+
cursor += measured[index2].width + gap;
|
|
4537
|
+
}
|
|
4538
|
+
const zoneParts = measured.map((entry, index2) => {
|
|
4539
|
+
const box = { x: placed.get(index2), y: (row - entry.height) / 2, width: entry.width, height: entry.height };
|
|
4540
|
+
if (entry.part.type === "image") {
|
|
4541
|
+
const part2 = entry.part;
|
|
4542
|
+
return { type: "image", kind: kind2, zone, field: "image", path: `${path}.image`, sourcePath: part2.sourcePath, generated: false, image: part2.image, ...part2.reference !== void 0 ? { reference: part2.reference } : {}, box, alignment: side };
|
|
4543
|
+
}
|
|
4544
|
+
const part = entry.part, { fit, ink } = entry;
|
|
4545
|
+
const placement = outlines ? placeTextLines(ink, box, side, padding) : void 0;
|
|
4546
|
+
const accepted = { ...fit, ...placement ? { placement } : {}, overflow: fit.overflow || !!placement?.overflow };
|
|
4547
|
+
if (accepted.overflow) error(part.partPath, "Repeated text exceeds its zone at the selected readability floor; change the furniture or slide design.");
|
|
4548
|
+
else if (pending.length > 1 && breaksInsideWord(part.text, fit)) error(part.partPath, "A word of this repeated text is wider than its share of the zone row at the selected readability floor and would break inside the word; shorten the text, move a part to another zone, or widen the canvas.");
|
|
4549
|
+
return { type: "text", kind: kind2, zone, field: part.field, path: part.partPath, ...part.sourcePath !== void 0 ? { sourcePath: part.sourcePath } : {}, generated: part.generated, text: part.text, style: part.style, requestedFontSize: size, minFontSize: minimum, box, alignment: side, fit: accepted, ...part.extras.fields?.length ? { fields: part.extras.fields } : {}, ...part.extras.links?.length ? { links: part.extras.links } : {} };
|
|
4550
|
+
});
|
|
4291
4551
|
zones.push(zoneParts);
|
|
4292
4552
|
}
|
|
4293
|
-
const tallest = Math.max(0, ...zones.
|
|
4553
|
+
const tallest = Math.max(0, ...zones.flat().map((part) => part.box.y + part.box.height));
|
|
4294
4554
|
if (!tallest) continue;
|
|
4295
4555
|
const top = kind2 === "header" ? height * 0.025 : Math.max(height * 0.025, height - height * 0.04 - tallest);
|
|
4296
4556
|
if (kind2 === "header") headerBottom = top + tallest;
|
|
@@ -4420,13 +4680,13 @@ function layoutQuote(value, box, options = {}) {
|
|
|
4420
4680
|
path
|
|
4421
4681
|
) : void 0;
|
|
4422
4682
|
const footerRequested = attribution?.requestedFontSize ?? gridFontSize(17 * scale, minimum);
|
|
4423
|
-
const
|
|
4424
|
-
const fit = (part, area, size = part.requestedFontSize, floor = minimum) => !
|
|
4683
|
+
const usable2 = (area) => [area.x, area.y, area.width, area.height].every(Number.isFinite) && area.width > 0 && area.height > 0;
|
|
4684
|
+
const fit = (part, area, size = part.requestedFontSize, floor = minimum) => !usable2(area) ? void 0 : part.runs ? fitRichText(part.runs, area, size, floor, { style: part.style, textMeasurement: options.textMeasurement, ...options.citationMarker ? { citationMarker: options.citationMarker } : {}, ...options.direction ? { direction: options.direction } : {} }) : fitText(part.text, area, size, floor, textWidthMeasurer(part.style, options.textMeasurement), options.direction);
|
|
4425
4685
|
const heightOf = (fit2) => "richLines" in fit2 ? fit2.height : fit2.lines.length * fit2.lineHeight;
|
|
4426
4686
|
const inner = { x: box.x + 18, y: box.y + 18, width: box.width - 36, height: box.height - 36 };
|
|
4427
4687
|
let photo, photoFits = true;
|
|
4428
4688
|
if (!hasBlock) body.fit = fit(body, body.box);
|
|
4429
|
-
else if (
|
|
4689
|
+
else if (usable2(inner) && inner.height > 18) {
|
|
4430
4690
|
const available = inner.height - 18;
|
|
4431
4691
|
const preferredBody = fit(body, inner, body.requestedFontSize, body.requestedFontSize);
|
|
4432
4692
|
const minimumBody = body.requestedFontSize === minimum ? preferredBody : fit(body, inner, minimum, minimum);
|
|
@@ -4448,8 +4708,8 @@ function layoutQuote(value, box, options = {}) {
|
|
|
4448
4708
|
const bodyFit = fit(body, bodyBox);
|
|
4449
4709
|
const photoBox = hasPhoto ? { x: rtl ? inner.x + inner.width - diameter : inner.x, y: footerBox.y + (footerHeight - diameter) / 2, width: diameter, height: diameter } : void 0;
|
|
4450
4710
|
const textBox = attribution ? hasPhoto ? { ...textArea, y: footerBox.y + Math.max(0, footerHeight - naturalHeight) / 2, height: Math.min(naturalHeight, footerHeight) } : footerBox : void 0;
|
|
4451
|
-
const footerFit = attribution && natural &&
|
|
4452
|
-
const fitsPhoto = !hasPhoto || diameter <= footerHeight + 0.01 && (!attribution ||
|
|
4711
|
+
const footerFit = attribution && natural && usable2(footerBox) ? { ...natural, overflow: natural.overflow || naturalHeight > footerHeight + 0.01 } : void 0;
|
|
4712
|
+
const fitsPhoto = !hasPhoto || diameter <= footerHeight + 0.01 && (!attribution || usable2(textArea));
|
|
4453
4713
|
const overflow = !bodyFit || bodyFit.overflow || (attribution ? !footerFit || footerFit.overflow : false) || !fitsPhoto;
|
|
4454
4714
|
const score = (body.requestedFontSize - (bodyFit?.fontSize ?? minimum) + footerRequested - size) / scale;
|
|
4455
4715
|
if (!selected || !overflow && (selected.overflow || score < selected.score) || overflow && selected.overflow) {
|
|
@@ -4475,7 +4735,7 @@ function layoutQuote(value, box, options = {}) {
|
|
|
4475
4735
|
} else if (part.fit.overflow) {
|
|
4476
4736
|
report("text-fit", part.path, [part.role], `Quote ${part.role} exceeds its available space at the readability floor; increase its space or change the arrangement.`);
|
|
4477
4737
|
}
|
|
4478
|
-
if (
|
|
4738
|
+
if (usable2(area) && (area.x < box.x || area.y < box.y || area.x + area.width > box.x + box.width + 0.01 || area.y + area.height > box.y + box.height + 0.01)) {
|
|
4479
4739
|
report("part-outside-cell", part.path, [part.role], `Quote ${part.role} extends outside its cell; increase the cell size or change the arrangement.`);
|
|
4480
4740
|
}
|
|
4481
4741
|
}
|
|
@@ -4530,7 +4790,7 @@ function layoutMetric(value, box, options = {}) {
|
|
|
4530
4790
|
const primary = parts[0], metadata = parts.filter((part) => part !== primary && part.visible), unit = metadata.find((part) => part.role === "unit");
|
|
4531
4791
|
const metricDirection = options.direction === "rtl" ? paragraphDirection(["label", "description", "unit"].map((role) => parts.find((part) => part.role === role)?.text).find(Boolean) ?? primary.text, "rtl") : void 0;
|
|
4532
4792
|
const alignment = physicalAlignment(requestedAlignment, metricDirection);
|
|
4533
|
-
const
|
|
4793
|
+
const usable2 = (area) => [area.x, area.y, area.width, area.height, area.x + area.width, area.y + area.height].every(Number.isFinite) && area.width > 0 && area.height > 0;
|
|
4534
4794
|
const occupied = (fit) => fit.placement?.height ?? fit.lines.length * fit.lineHeight;
|
|
4535
4795
|
const gap = 8 * scale, primaryGap = 12 * scale;
|
|
4536
4796
|
const lineInk = (part, fit) => fit.sourceLines.map((line, index) => {
|
|
@@ -4574,7 +4834,7 @@ function layoutMetric(value, box, options = {}) {
|
|
|
4574
4834
|
return fit;
|
|
4575
4835
|
};
|
|
4576
4836
|
const fitValue = (area, singleLine) => {
|
|
4577
|
-
if (!
|
|
4837
|
+
if (!usable2(area)) return void 0;
|
|
4578
4838
|
for (let step = 0; step <= 76; step++) {
|
|
4579
4839
|
const size = gridFontSize(primary.requestedFontSize - step * scale, minimum);
|
|
4580
4840
|
const natural = measure(primary, size, area.width);
|
|
@@ -4615,7 +4875,7 @@ function layoutMetric(value, box, options = {}) {
|
|
|
4615
4875
|
allocations.push({ part, box: { ...box, y, height }, fit: { ...natural, overflow: !!natural.placement?.overflow || natural.sourceLines.some((line) => line.width > box.width + 0.01) } });
|
|
4616
4876
|
y += height + gap;
|
|
4617
4877
|
}
|
|
4618
|
-
const overflow = !valueFit || valueFit.overflow || arrangement === "inline-unit" && valueFit.lines.length !== 1 || primaryHeight > area.height + 0.01 || allocations.some(({ box: area2, fit }) => !fit || fit.overflow || !
|
|
4878
|
+
const overflow = !valueFit || valueFit.overflow || arrangement === "inline-unit" && valueFit.lines.length !== 1 || primaryHeight > area.height + 0.01 || allocations.some(({ box: area2, fit }) => !fit || fit.overflow || !usable2(area2) || area2.y + area2.height > box.y + box.height + 0.01);
|
|
4619
4879
|
const score = allocations.reduce((sum, { part, fit }) => sum + (part.requestedFontSize - (fit?.fontSize ?? minimum)) / scale, 0);
|
|
4620
4880
|
const candidate = { arrangement, allocations, score, overflow };
|
|
4621
4881
|
if (!selected || !overflow && (selected.overflow || score < selected.score) || overflow && selected.overflow) selected = candidate;
|
|
@@ -4643,9 +4903,9 @@ function layoutMetric(value, box, options = {}) {
|
|
|
4643
4903
|
}
|
|
4644
4904
|
const report = (reason, part, roles, message) => diagnostics.push({ code: "text-overflow", reason, path: part.path, parts: roles, message });
|
|
4645
4905
|
for (const part of parts.filter((part2) => part2.visible)) {
|
|
4646
|
-
if (!part.fit || !
|
|
4906
|
+
if (!part.fit || !usable2(part.box)) report("invalid-part-box", part, [part.role], `Metric ${part.role} has no usable space after metadata; increase the cell or change the arrangement.`);
|
|
4647
4907
|
else if (part.fit.overflow) report("text-fit", part, [part.role], `Metric ${part.role} exceeds its space at the readability floor; increase the cell or change the arrangement.`);
|
|
4648
|
-
if (
|
|
4908
|
+
if (usable2(part.box) && (part.box.x < box.x || part.box.y < box.y || part.box.x + part.box.width > box.x + box.width + 0.01 || part.box.y + part.box.height > box.y + box.height + 0.01)) {
|
|
4649
4909
|
report("part-outside-cell", part, [part.role], `Metric ${part.role} extends outside its cell; increase the cell or change the arrangement.`);
|
|
4650
4910
|
}
|
|
4651
4911
|
}
|
|
@@ -4917,9 +5177,9 @@ function layoutCode(value, box, options = {}) {
|
|
|
4917
5177
|
if (code.language) add("language", code.language, `${path}.language`);
|
|
4918
5178
|
if (!parts.length) add("language", "code", path, true);
|
|
4919
5179
|
const body = add("body", code.source, shorthand ? path : `${path}.source`), headers = parts.filter((part) => part !== body);
|
|
4920
|
-
const
|
|
4921
|
-
const fit = (part, area, size = part.requestedFontSize, floor = minimum) =>
|
|
4922
|
-
if (
|
|
5180
|
+
const usable2 = (area) => [area.x, area.y, area.width, area.height].every(Number.isFinite) && area.width > 0 && area.height > 0;
|
|
5181
|
+
const fit = (part, area, size = part.requestedFontSize, floor = minimum) => usable2(area) ? fitCodeText(part.text, area, size, floor, scale, textWidthMeasurer(part.style, options.textMeasurement)) : void 0;
|
|
5182
|
+
if (usable2(inner)) {
|
|
4923
5183
|
const combinations = headers.reduce((choices, part) => choices.flatMap((choice) => [.../* @__PURE__ */ new Set([part.requestedFontSize, minimum])].map((size) => [...choice, { part, size }])), [[]]);
|
|
4924
5184
|
let selected;
|
|
4925
5185
|
for (const combination of combinations) {
|
|
@@ -4928,12 +5188,12 @@ function layoutCode(value, box, options = {}) {
|
|
|
4928
5188
|
for (const [index, { part, size }] of combination.entries()) {
|
|
4929
5189
|
const measured = fitCodeText(part.text, inner, size, size, scale, textWidthMeasurer(part.style, options.textMeasurement));
|
|
4930
5190
|
const area2 = { ...inner, y, height: measured.lines.length * measured.lineHeight };
|
|
4931
|
-
allocations.push({ part, box: area2, fit:
|
|
5191
|
+
allocations.push({ part, box: area2, fit: usable2(area2) ? { ...measured, overflow: measured.sourceLines.some((line) => line.width > area2.width + 0.01) } : void 0 });
|
|
4932
5192
|
y += area2.height + (index < headers.length - 1 ? 8 : 12);
|
|
4933
5193
|
}
|
|
4934
5194
|
const area = { ...inner, y, height: inner.y + inner.height - y };
|
|
4935
5195
|
allocations.push({ part: body, box: area, fit: fit(body, area) });
|
|
4936
|
-
const overflow = allocations.some(({ box: box2, fit: fit2 }) => !fit2 || fit2.overflow || !
|
|
5196
|
+
const overflow = allocations.some(({ box: box2, fit: fit2 }) => !fit2 || fit2.overflow || !usable2(box2) || box2.y + box2.height > inner.y + inner.height + 0.01);
|
|
4937
5197
|
const score = allocations.reduce((sum, { part, fit: fit2 }) => sum + (part.requestedFontSize - (fit2?.fontSize ?? minimum)) / scale, 0);
|
|
4938
5198
|
if (!selected || !overflow && (selected.overflow || score < selected.score) || overflow && selected.overflow) selected = { allocations, score, overflow };
|
|
4939
5199
|
}
|
|
@@ -4947,7 +5207,7 @@ function layoutCode(value, box, options = {}) {
|
|
|
4947
5207
|
if (!part.fit) report("invalid-part-box", part, [part.role], `Code ${part.role} has no usable space after metadata and insets; increase the cell or change the arrangement.`);
|
|
4948
5208
|
else if (part.fit.overflow) report("text-fit", part, [part.role], `Code ${part.role} exceeds its space at the readability floor; increase its space or change the arrangement.`);
|
|
4949
5209
|
const area = part.box;
|
|
4950
|
-
if (
|
|
5210
|
+
if (usable2(area) && (area.x < box.x || area.y < box.y || area.x + area.width > box.x + box.width + 0.01 || area.y + area.height > box.y + box.height + 0.01)) {
|
|
4951
5211
|
report("part-outside-cell", part, [part.role], `Code ${part.role} extends outside its cell; increase the cell or change the arrangement.`);
|
|
4952
5212
|
}
|
|
4953
5213
|
}
|
|
@@ -5332,7 +5592,7 @@ function composeSlide(input, options = {}) {
|
|
|
5332
5592
|
}, options.textMeasurement);
|
|
5333
5593
|
const listBullet = hints.listBullet !== void 0 ? { value: hints.listBullet, path: hints.paths.listBullet } : void 0;
|
|
5334
5594
|
const bulletImage = listBullet?.value === "image" ? (() => {
|
|
5335
|
-
const resolved = resolveLogo(options.presentation, slide, {
|
|
5595
|
+
const resolved = resolveLogo(options.presentation, slide, { shape: "icon", onDark: options.darkBackground, slideIndex: options.slideIndex });
|
|
5336
5596
|
return resolved ? { source: resolved.source, path: resolved.path } : void 0;
|
|
5337
5597
|
})() : void 0;
|
|
5338
5598
|
const widthFor = (field, path2) => textWidthMeasurer(styleFor(field, path2), options.textMeasurement);
|
|
@@ -5447,12 +5707,12 @@ function composeSlide(input, options = {}) {
|
|
|
5447
5707
|
coverGroup = isCover;
|
|
5448
5708
|
let logo;
|
|
5449
5709
|
if (isCover) {
|
|
5450
|
-
const resolved = resolveLogo(options.presentation, slide, {
|
|
5710
|
+
const resolved = resolveLogo(options.presentation, slide, { shape: "full", onDark: options.darkBackground, slideIndex: options.slideIndex });
|
|
5451
5711
|
if (resolved) {
|
|
5452
5712
|
const logoHeight = 56 * scale;
|
|
5453
5713
|
const logoWidth = Math.max(scale, Math.min(4 * logoHeight, area.right - area.left - 2 * padding));
|
|
5454
5714
|
const box = { x: round2(rtl ? area.right - padding - logoWidth : area.left + padding), y: round2(headingTop), width: round2(logoWidth), height: round2(logoHeight) };
|
|
5455
|
-
logo = { box,
|
|
5715
|
+
logo = { box, shape: resolved.shape, path: resolved.path, source: resolved.source, variant: resolved.variant, reference: resolved.reference, anchor: rtl ? "right" : "left" };
|
|
5456
5716
|
}
|
|
5457
5717
|
}
|
|
5458
5718
|
let y = logo ? logo.box.y + logo.box.height + gap : headingTop;
|
|
@@ -5789,7 +6049,7 @@ function composeSlide(input, options = {}) {
|
|
|
5789
6049
|
slots.sort((a, b) => order.get(a.path) - order.get(b.path));
|
|
5790
6050
|
} else arrange(pending, contentBox, rootSettings, composition.mode ? 0 : placeholders.length);
|
|
5791
6051
|
if (listBullet?.value === "image" && !bulletImage && items.some((item) => (item.field === "items" || item.field === "bullets") && item.payload.numbering === void 0)) {
|
|
5792
|
-
diagnostics.push({ code: "unresolved-content", path: listBullet.path, message: "Picture bullets (listBullet: image) need
|
|
6052
|
+
diagnostics.push({ code: "unresolved-content", path: listBullet.path, message: "Picture bullets (listBullet: image) need an organization logo (the primary organization, or the one design.logo names); the marker glyph is drawn instead." });
|
|
5793
6053
|
}
|
|
5794
6054
|
const numberingDiagnostics = [];
|
|
5795
6055
|
for (const item of items) {
|
|
@@ -5851,4 +6111,4 @@ function resolveCanvasDimensions(input) {
|
|
|
5851
6111
|
return { width, height };
|
|
5852
6112
|
}
|
|
5853
6113
|
|
|
5854
|
-
export { CAPTIONABLE_FIELDS, CAPTION_FONT_RATIO, CAPTION_MAX_RATIO, CHART_HIGHLIGHT_MUTED_MIN_CONTRAST, CHART_HIGHLIGHT_MUTED_MIX, CHART_SERIES_MIN_DIFFERENCE, CHART_SERIES_MIN_LIGHTNESS_STEP, CHART_TYPES, CITATION_MARKER_RAISE, CITATION_MARKER_SCALE, CODE_HIGHLIGHT_LANGUAGES, CODE_HIGHLIGHT_MAX_LENGTH, CODE_PANEL_BACKGROUND, CODE_PANEL_FOREGROUND, CODE_SYNTAX_MIN_CONTRAST, DARK_BACKGROUND_LUMINANCE, DEFAULT_CHART_LABEL_SEPARATOR, DEFAULT_FURNITURE_DATE_FORMAT,
|
|
6114
|
+
export { CAPTIONABLE_FIELDS, CAPTION_FONT_RATIO, CAPTION_MAX_RATIO, CHART_HIGHLIGHT_MUTED_MIN_CONTRAST, CHART_HIGHLIGHT_MUTED_MIX, CHART_SERIES_MIN_DIFFERENCE, CHART_SERIES_MIN_LIGHTNESS_STEP, CHART_TYPES, CITATION_MARKER_RAISE, CITATION_MARKER_SCALE, CODE_HIGHLIGHT_LANGUAGES, CODE_HIGHLIGHT_MAX_LENGTH, CODE_PANEL_BACKGROUND, CODE_PANEL_FOREGROUND, CODE_SYNTAX_MIN_CONTRAST, DARK_BACKGROUND_LUMINANCE, DEFAULT_CHART_LABEL_SEPARATOR, DEFAULT_FURNITURE_DATE_FORMAT, DEFAULT_VARIABLE_DATE_FORMAT, DESIGN_HINT_KEYS, ENGINE_DEFAULT_CHART_TYPES, ENGINE_DEFAULT_COLOR_SCHEME, ENGINE_DEFAULT_FONT_SCHEME, ENGINE_DEFAULT_THEME, FONT_SIZE_GRID_PER_PX, FOOTNOTE_MAX_RATIO, FURNITURE_GAP, FURNITURE_IMAGE_SHARE, LANGUAGES, LAYOUT_BODY_KINDS, LOGO_SHAPES, MAX_COMPOSITION_DEPTH, MAX_NUMBERING_LEVELS, MAX_NUMBERING_VALUE, MAX_PLACEHOLDER_GROUP_DEPTH, MAX_ROMAN_VALUE, METRIC_TREND_MIN_CONTRAST, METRIC_TREND_SHAPES, NUMBERING_STYLES, NUMBERING_SUFFIXES, OPFCompositionError, OPFVariableError, PATTERN_PRESETS, PATTERN_PRESET_ALIASES, PATTERN_TILE_SIZE, PICTURE_BULLET_SCALE, SLIDE_SCOPED_BUILTINS, SOCIAL_PLATFORMS, SURFACE_ALT_MIN_CONTRAST, SURFACE_ALT_MIX, TIMELINE_OUTLINE_MIN_CONTRAST, TIMELINE_STATUSES, TIMELINE_TEXT_MIN_CONTRAST, VARIABLE_KINDS, annotationText, captionSettings, chartColorForFill, chartHighlightColors, chartHighlightMarks, chartLabelText, chartNumber, chartNumberFixesByCell, chartOptionSupport, chartOptionTarget, chartPaletteForFill, chartPrimaryLayout, citationMarkerText, codeHighlightBands, codeHighlightColors, codeHighlightLines, codeHighlightSlice, codeLineNumbers, codeLineRuns, codeSyntaxPalette, codeSyntaxPaletteForScheme, coerceVariableValue, collectCitations, colorContrast, composeSlide, datasetDiagnostics, defaultSlideBackground, fitImage, fitList, fitRichText, fitText, formatChartLabelNumber, formatChartLabelPercent, formatDataNumber, formatFurnitureDate, formatListNumber, formatVariableNumber, fromExcelNumberFormat, hasContentVariables, hasPlaceholderGroups, imageBackground, imageShape, inlineChartData, inlineDatasets, inlineTableData, instantiateForValidation, intrinsicImageAspect, intrinsicImageSize, isDarkColor, isDatasetRef, isPlaceholderGroup, isRecord, isTemplate, isXYChartType, layoutCaption, layoutCode, layoutContent, layoutFootnotes, layoutFurniture, layoutLeaves, layoutMetric, layoutQuote, layoutSlots, layoutStructure, layoutTable, layoutTimeline, layoutWatermark, listBuiltinVariables, listNumbers, listVariables, measureText, measureTextOutline, metricTrendColor, metricTrendMark, metricTrendPoints, normalizeHexColor, normalizeLanguageFamily, numberFormatError, numberingAtLevel, numberingFindings, numberingStyleDraws, paragraphDirection, paragraphDirectionAt, pathFor, patternBitmap, patternRuns, physicalAlignment, placeTextLines, promotedRegionKeys, readingRows, referencesSlide, resolveCanvasDimensions, resolveChartData, resolveChartOptions, resolveCodeLanguage, resolveColorRef, resolveColorRoles, resolveDesignHints, resolveDesignRecords, resolveFontFamilies, resolveFontScheme, resolveLogo, resolveNumbering, resolvePatternPreset, resolveScriptFonts, resolveSlideDirection, resolveSlideVariables, resolveSocialProfile, resolveTableData, resolveTextStyle, resolveVariables, scriptFontRole, sliceNumberedItems, slideCitations, slideTokenSpans, snapFontSizeDown, snapFontSizeUp, suggestChartNumberFix, surfaceAltColor, tableCellDisplayValue, tableGrid, tableRowBoundaries, textColorForFill, textWidthMeasurer, timelineMarkerShapes, timelineTextColor, toExcelNumberFormat, tokenizeCode, unusedDatasets, usesSlideBuiltin, variableDeclarations, visitContentPayloads, visualReadingOrder, walkCitationRuns, wrapText };
|