@openpresentation/opf 0.17.0 → 0.18.3

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.
Files changed (67) hide show
  1. package/README.md +4 -4
  2. package/dist/browser.js +38 -0
  3. package/dist/{catalog-refs-C4K4y-vh.d.ts → catalog-refs-BSfGELpt.d.ts} +1 -1
  4. package/dist/{catalog-schemas-g7RFQBdc.d.ts → catalog-schemas-CinPh5WA.d.ts} +1 -1
  5. package/dist/catalog.d.ts +2 -2
  6. package/dist/{chart-data-DTxdRRT1.d.ts → chart-data-BnNqgnC-.d.ts} +1 -1
  7. package/dist/{chunk-RA64UQKI.js → chunk-2PEWUUBR.js} +97 -73
  8. package/dist/{chunk-TCD2FB2K.js → chunk-ECP7QPGK.js} +5 -5
  9. package/dist/{chunk-O2CX5NEH.js → chunk-FO3WGS4R.js} +6 -5
  10. package/dist/{chunk-N4EVOFEA.js → chunk-GVOLSWAJ.js} +4 -4
  11. package/dist/{chunk-WWYWFVOI.js → chunk-IM337HP5.js} +151 -25
  12. package/dist/{chunk-YT7FMQER.js → chunk-JCOSZTOI.js} +1 -1
  13. package/dist/{chunk-IPW5BS3X.js → chunk-K5EG3UMS.js} +190 -172
  14. package/dist/{chunk-NLNYNBTS.js → chunk-KA3ZPFVT.js} +84 -26
  15. package/dist/{chunk-M4UR3NPN.js → chunk-L5YACLWK.js} +2 -2
  16. package/dist/{chunk-CW7GEAV3.js → chunk-MBU42S5Y.js} +291 -133
  17. package/dist/{chunk-BIC3SWPK.js → chunk-MSFE2HUK.js} +30 -5
  18. package/dist/{chunk-ESRYBYLY.js → chunk-PNINP5IF.js} +1 -1
  19. package/dist/{chunk-IS5H5JKH.js → chunk-R7KIASS4.js} +1 -1
  20. package/dist/{chunk-PPROJP2E.js → chunk-TRELE6I6.js} +1 -1
  21. package/dist/{chunk-O3CVNJTL.js → chunk-WHHLGDCQ.js} +1 -1
  22. package/dist/{chunk-O263APNI.js → chunk-ZCFSDSUX.js} +3 -3
  23. package/dist/{composition-BbZ_9C-c.d.ts → composition-C-OoqvAr.d.ts} +80 -40
  24. package/dist/composition.d.ts +3 -3
  25. package/dist/composition.js +1 -1
  26. package/dist/conversion-4CLOEG6O.js +21 -0
  27. package/dist/{conversion-SBpTOwdg.d.ts → conversion-BxZbBd_0.d.ts} +96 -38
  28. package/dist/convert.d.ts +4 -4
  29. package/dist/convert.js +7 -7
  30. package/dist/data.d.ts +7 -7
  31. package/dist/data.js +2 -2
  32. package/dist/{deck-report-Bx3naFlF.d.ts → deck-report-DiDf_Vew.d.ts} +5 -5
  33. package/dist/deck.d.ts +8 -8
  34. package/dist/deck.js +10 -10
  35. package/dist/diff.d.ts +1 -1
  36. package/dist/diff.js +5 -5
  37. package/dist/docs.js +35 -29
  38. package/dist/examples.d.ts +1 -1
  39. package/dist/examples.js +181 -214
  40. package/dist/{finding-CDQ_wH8j.d.ts → finding-CT_f_vbD.d.ts} +1 -1
  41. package/dist/format.js +2 -2
  42. package/dist/index.d.ts +153 -20
  43. package/dist/index.js +62 -15
  44. package/dist/markdown.d.ts +12 -8
  45. package/dist/markdown.js +7 -7
  46. package/dist/node-engine.d.ts +37 -17
  47. package/dist/node-engine.js +39 -16
  48. package/dist/{pagination-BhX4M48Y.d.ts → pagination-BaUjDrTz.d.ts} +7 -4
  49. package/dist/pagination.d.ts +4 -4
  50. package/dist/pagination.js +4 -4
  51. package/dist/patch.d.ts +1 -1
  52. package/dist/patch.js +4 -4
  53. package/dist/{presentation-fb8CZmRq.d.ts → presentation-81rNihF0.d.ts} +271 -384
  54. package/dist/repo-readme.js +1 -1
  55. package/dist/schemas.d.ts +4 -2
  56. package/dist/schemas.js +1 -1
  57. package/dist/spec/README.md +1 -1
  58. package/dist/spec/schemas/opf.schema.json +67 -72
  59. package/dist/types.d.ts +3 -3
  60. package/dist/{validation-types-iZbXfES6.d.ts → validation-types-PbGDrNUr.d.ts} +12 -7
  61. package/dist/validator.d.ts +8 -8
  62. package/dist/validator.js +3 -3
  63. package/dist/yaml.d.ts +6 -6
  64. package/dist/yaml.js +7 -7
  65. package/package.json +14 -12
  66. package/dist/node.d.ts +0 -60
  67. package/dist/node.js +0 -54
@@ -425,6 +425,109 @@ function primaryOrganization(presentation) {
425
425
  return organizations.find((item) => item.role === "primary") ?? organizations[0];
426
426
  }
427
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
+
428
531
  // src/slide-variables.ts
429
532
  var SLIDE_SCOPED_BUILTINS = ["slide.number", "slide.section", "deck.slideCount"];
430
533
  function isSlideScopedBuiltin(name) {
@@ -476,18 +579,34 @@ function slideTokenSpans(text2) {
476
579
  return Array.from(text2.matchAll(slideTokenSpanPattern), (match) => [match.index, match.index + match[0].length]);
477
580
  }
478
581
  var skippedKeys = /* @__PURE__ */ new Set(["extensions"]);
479
- function substitute(value, values, root) {
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 = {}) {
480
596
  if (typeof value === "string") {
597
+ const logo = substituteLogo(value, logos);
598
+ if (logo !== value) return logo;
481
599
  const result = substituteSlideTokens(value, values);
482
600
  return result.changed ? result.text : value;
483
601
  }
484
602
  if (Array.isArray(value)) {
485
603
  let changed2 = false;
486
- const out2 = value.map((entry) => {
487
- const next = substitute(entry, values, false);
604
+ const out2 = [];
605
+ for (const entry of value) {
606
+ const next = substitute(entry, values, false, logos);
488
607
  if (next !== entry) changed2 = true;
489
- return next;
490
- });
608
+ if (next !== OMIT) out2.push(next);
609
+ }
491
610
  return changed2 ? out2 : value;
492
611
  }
493
612
  if (!isRecord(value)) return value;
@@ -500,14 +619,14 @@ function substitute(value, values, root) {
500
619
  const design = {};
501
620
  let designChanged = false;
502
621
  for (const [designKey, designEntry] of Object.entries(entry)) {
503
- const designNext = designKey === "header" || designKey === "footer" ? designEntry : substitute(designEntry, values, false);
622
+ const designNext = designKey === "header" || designKey === "footer" || designKey === "logo" ? designEntry : substitute(designEntry, values, false, logos);
504
623
  if (designNext !== designEntry) designChanged = true;
505
- design[designKey] = designNext;
624
+ if (designNext !== OMIT) design[designKey] = designNext;
506
625
  }
507
626
  next = designChanged ? design : entry;
508
- } else next = substitute(entry, values, false);
627
+ } else next = substitute(entry, values, false, logos);
509
628
  if (next !== entry) changed = true;
510
- out[key] = next;
629
+ if (next !== OMIT) out[key] = next;
511
630
  }
512
631
  return changed ? out : value;
513
632
  }
@@ -515,8 +634,10 @@ function resolveSlideVariables(slide, values) {
515
634
  if (!isRecord(slide)) return slide;
516
635
  const full = { slideNumber: values?.slideNumber, ...values?.slideCount !== void 0 ? { slideCount: values.slideCount } : {}, section: slide.section };
517
636
  checkValues(full);
518
- if (!quickCheck.test(JSON.stringify(slide))) return slide;
519
- return substitute(slide, full, true);
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);
520
641
  }
521
642
  function usesSlideBuiltin(value, name) {
522
643
  const pattern = new RegExp(String.raw`(?:^|[^\\])\{\{\s*${name.replace(".", String.raw`\.`)}\s*(?:\|[^{}]*)?\}\}`);
@@ -543,9 +664,9 @@ var OPFVariableError = class extends Error {
543
664
  }
544
665
  };
545
666
  var idPattern = /^[a-z][a-z0-9-]*$/;
546
- var nameSource = String.raw`[a-z][a-z0-9-]*|(?:deck|speaker|organization|slide)(?:\.[A-Za-z0-9_-]+){1,2}`;
667
+ var nameSource = String.raw`[a-z][a-z0-9-]*|(?:deck|speaker|organization|slide)(?:\.[A-Za-z0-9_-]+){1,3}`;
547
668
  var referencePattern = new RegExp(String.raw`^var:(${nameSource})$`);
548
- var builtinNamePattern = /^(?:speakers|(?:deck|speaker|organization|slide)(?:\.[A-Za-z0-9_-]+){1,2})$/;
669
+ var builtinNamePattern = /^(?:speakers|(?:deck|speaker|organization|slide)(?:\.[A-Za-z0-9_-]+){1,3})$/;
549
670
  var builtinUsePattern = /\{\{\s*(?:speakers\b|(?:deck|speaker|organization|slide)\.)|^var:(?:speakers$|(?:deck|speaker|organization|slide)\.)/;
550
671
  var hexPattern = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
551
672
  var urlPattern = /^(?:https?:\/\/|mailto:|tel:)\S+$/;
@@ -562,7 +683,7 @@ var SAMPLES = {
562
683
  list: ["Sample item"]
563
684
  };
564
685
  var hasOwn = (value, key) => Object.hasOwn(value, key);
565
- var clone = (value) => value === void 0 ? value : JSON.parse(JSON.stringify(value));
686
+ var clone2 = (value) => value === void 0 ? value : JSON.parse(JSON.stringify(value));
566
687
  function isTemplate(presentation) {
567
688
  return isRecord(presentation) && presentation.template === true;
568
689
  }
@@ -578,7 +699,7 @@ function coerceVariableValue(kind2, raw) {
578
699
  if (typeof raw === "string") return { ok: true, value: raw };
579
700
  if (typeof raw === "number" && Number.isFinite(raw)) return { ok: true, value: String(raw) };
580
701
  if (typeof raw === "boolean") return { ok: true, value: String(raw) };
581
- if (Array.isArray(raw) && raw.every((run) => typeof run === "string" || isRecord(run) && typeof run.text === "string")) return { ok: true, value: clone(raw) };
702
+ if (Array.isArray(raw) && raw.every((run) => typeof run === "string" || isRecord(run) && typeof run.text === "string")) return { ok: true, value: clone2(raw) };
582
703
  return fail("expected text: a string or an array of text runs");
583
704
  }
584
705
  case "number": {
@@ -598,7 +719,7 @@ function coerceVariableValue(kind2, raw) {
598
719
  }
599
720
  case "image": {
600
721
  if (typeof raw === "string" && raw.trim()) return { ok: true, value: raw.trim() };
601
- if (isRecord(raw) && typeof raw.src === "string" && raw.src.trim()) return { ok: true, value: clone(raw) };
722
+ if (isRecord(raw) && typeof raw.src === "string" && raw.src.trim()) return { ok: true, value: clone2(raw) };
602
723
  return fail("expected an image source: a string or an object with src");
603
724
  }
604
725
  case "url":
@@ -744,7 +865,7 @@ function inlineText(effective, format, path, context) {
744
865
  }
745
866
  var DECK_FIELDS = { name: "text", description: "text", author: "text" };
746
867
  var SPEAKER_FIELDS = { name: "text", title: "text", email: "text", phone: "text", bio: "text", photo: "image" };
747
- var ORGANIZATION_FIELDS = { name: "text", legalName: "text", tagline: "text", domain: "text", email: "text", phone: "text", logo: "image" };
868
+ var ORGANIZATION_FIELDS = { name: "text", legalName: "text", tagline: "text", domain: "text", email: "text", phone: "text" };
748
869
  function isBuiltinName(name) {
749
870
  return builtinNamePattern.test(name);
750
871
  }
@@ -768,6 +889,11 @@ function builtinSource(name, doc) {
768
889
  }
769
890
  const parts = name.split(".");
770
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
+ }
771
897
  const fields2 = builtinFields(root);
772
898
  if (root === "deck") {
773
899
  const field2 = parts[1];
@@ -780,6 +906,7 @@ function builtinSource(name, doc) {
780
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))}.` };
781
907
  }
782
908
  let entity;
909
+ if (parts.length > 3) return { ok: false, message: `'${name}' is not a built-in variable.` };
783
910
  if (parts.length === 2) entity = root === "speaker" ? primarySpeaker(doc) : primaryOrganization(doc);
784
911
  else {
785
912
  const id = parts[1];
@@ -864,7 +991,7 @@ function interpolate(text2, path, context) {
864
991
  });
865
992
  return { value, changed };
866
993
  }
867
- var OMIT = /* @__PURE__ */ Symbol("omit");
994
+ var OMIT2 = /* @__PURE__ */ Symbol("omit");
868
995
  var Splice = class {
869
996
  constructor(values) {
870
997
  this.values = values;
@@ -875,6 +1002,8 @@ function reduceString(text2, path, parentIsArray, context) {
875
1002
  const reference = referencePattern.exec(text2);
876
1003
  if (reference) {
877
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);
878
1007
  const effective = lookupVariable(id, path, context);
879
1008
  if (effective && effective.declaration.kind !== "color") {
880
1009
  context.uses.push({ id, path, form: "reference" });
@@ -883,9 +1012,9 @@ function reduceString(text2, path, parentIsArray, context) {
883
1012
  }
884
1013
  const value = effective.status === "unfilled" ? sampleValue(effective.declaration) : effective.value;
885
1014
  if (effective.declaration.kind === "list" && parentIsArray) {
886
- return context.shape ? { kind: "replace", value: value[0] ?? "" } : { kind: "splice", values: clone(value) };
1015
+ return context.shape ? { kind: "replace", value: value[0] ?? "" } : { kind: "splice", values: clone2(value) };
887
1016
  }
888
- return { kind: "replace", value: clone(value) };
1017
+ return { kind: "replace", value: clone2(value) };
889
1018
  }
890
1019
  if (effective) {
891
1020
  context.uses.push({ id, path, form: "reference" });
@@ -897,13 +1026,31 @@ function reduceString(text2, path, parentIsArray, context) {
897
1026
  const result = interpolate(text2, path, context);
898
1027
  return result.changed ? { kind: "replace", value: result.value } : { kind: "keep" };
899
1028
  }
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
+ }
900
1047
  var skippedKeys2 = /* @__PURE__ */ new Set(["extensions"]);
901
1048
  var skippedRootKeys = /* @__PURE__ */ new Set(["variables", "catalogs", "$schema", "template"]);
902
1049
  function walk(value, path, parentIsArray, context) {
903
1050
  if (typeof value === "string") {
904
1051
  const reduced = reduceString(value, path, parentIsArray, context);
905
1052
  if (reduced.kind === "keep") return value;
906
- if (reduced.kind === "omit") return OMIT;
1053
+ if (reduced.kind === "omit") return OMIT2;
907
1054
  if (reduced.kind === "splice") return new Splice(reduced.values);
908
1055
  return reduced.value;
909
1056
  }
@@ -912,7 +1059,7 @@ function walk(value, path, parentIsArray, context) {
912
1059
  const out = [];
913
1060
  value.forEach((entry, index) => {
914
1061
  const next = walk(entry, `${path}/${index}`, true, context);
915
- if (next === OMIT) {
1062
+ if (next === OMIT2) {
916
1063
  changed = true;
917
1064
  return;
918
1065
  }
@@ -935,7 +1082,7 @@ function walk(value, path, parentIsArray, context) {
935
1082
  continue;
936
1083
  }
937
1084
  const next = walk(entry, pathFor(path || "/", key), false, context);
938
- if (next === OMIT) {
1085
+ if (next === OMIT2) {
939
1086
  changed = true;
940
1087
  continue;
941
1088
  }
@@ -1131,6 +1278,15 @@ function listBuiltinVariables(presentation) {
1131
1278
  const available = name !== "slide.section" || slides.some((slide) => isRecord(slide) && typeof slide.section === "string" && slide.section !== "");
1132
1279
  out.push({ name, kind: "text", label: SLIDE_SCOPED_LABELS[name], scope: "slide", available, uses: built.context.uses.filter((use) => use.id === name) });
1133
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
+ }
1289
+ }
1134
1290
  return out;
1135
1291
  }
1136
1292
 
@@ -4108,7 +4264,6 @@ var imagePayload = (host) => {
4108
4264
  for (const key of IMAGE_OPTION_KEYS) if (host[key] !== void 0) payload[key] = host[key];
4109
4265
  return payload;
4110
4266
  };
4111
- var assetSource = (value) => typeof value === "string" ? value : record6(value).src;
4112
4267
  var finite = (value, min, max) => typeof value === "number" && Number.isFinite(value) && value >= min && value <= max ? value : void 0;
4113
4268
  var pathNumber = (value) => String(round2(value));
4114
4269
  var IMAGE_SHAPES = { rectangle: "rect", rounded: "roundRect", circle: "ellipse", hexagon: "hexagon" };
@@ -4228,45 +4383,6 @@ function resolveBackgroundImage(slide, presentation, themeBackground, width, hei
4228
4383
  if (aspect) result.picture = fitImage(box, background.fit, aspect, background.focus);
4229
4384
  return result;
4230
4385
  }
4231
- var LOGO_LOCKUP_CHAINS = {
4232
- dark: ["light", "default", "stackedLight", "stacked", "wordmarkLight", "wordmark", "iconLight", "icon", "dark", "stackedDark", "wordmarkDark", "iconDark"],
4233
- light: ["dark", "default", "stackedDark", "stacked", "wordmarkDark", "wordmark", "iconDark", "icon", "light", "stackedLight", "wordmarkLight", "iconLight"]
4234
- };
4235
- var LOGO_SET_KEYS = new Set(LOGO_LOCKUP_CHAINS.dark);
4236
- function resolveLogo(presentation, slide, options = {}) {
4237
- const slot = options.slot === "icon" || options.slot === "stacked" ? options.slot : "lockup";
4238
- if (options.slot !== void 0 && slot !== options.slot) throw new RangeError("Logo slot must be lockup, icon or stacked.");
4239
- const tone = options.onDark === true ? "dark" : "light";
4240
- const lockup = LOGO_LOCKUP_CHAINS[tone];
4241
- const chain = slot === "icon" ? [tone === "dark" ? "iconLight" : "iconDark", "icon", ...lockup] : slot === "stacked" ? [tone === "dark" ? "stackedLight" : "stackedDark", "stacked", ...lockup] : lockup;
4242
- const usable = (value) => {
4243
- const source = assetSource(value);
4244
- return typeof source === "string" && source.length > 0;
4245
- };
4246
- const pick = (value, path) => {
4247
- if (value === void 0 || value === null || value === false) return null;
4248
- if (typeof value === "string" || typeof value === "object" && !Array.isArray(value) && "src" in record6(value)) {
4249
- return usable(value) ? { source: value, path, variant: "default", slot } : null;
4250
- }
4251
- const set2 = record6(value);
4252
- if (!Object.keys(set2).some((key) => LOGO_SET_KEYS.has(key))) return null;
4253
- for (const key of chain) {
4254
- const variant = set2[key];
4255
- if (variant !== void 0 && usable(variant)) return { source: variant, path: `${path}.${key}`, variant: key, slot };
4256
- }
4257
- return null;
4258
- };
4259
- const deck = record6(presentation), slideIndex = Number.isSafeInteger(options.slideIndex) && options.slideIndex >= 0 ? options.slideIndex : 0;
4260
- const own2 = pick(record6(record6(slide).design).logo, `slides.${slideIndex}.design.logo`);
4261
- if (own2) return own2;
4262
- const shared = pick(record6(deck.design).logo, "design.logo");
4263
- if (shared) return shared;
4264
- const organizations = Array.isArray(deck.organization) ? deck.organization : [deck.organization];
4265
- const primaryIndex = organizations.findIndex((item) => record6(item).role === "primary");
4266
- const index = primaryIndex >= 0 ? primaryIndex : organizations.findIndex(Boolean);
4267
- if (index < 0) return null;
4268
- return pick(record6(organizations[index]).logo, Array.isArray(deck.organization) ? `organization.${index}.logo` : "organization.logo");
4269
- }
4270
4386
  var webUrl = /^https?:\/\/\S+$/i;
4271
4387
  function resolveSocialProfile(platform, value, owner = "organization") {
4272
4388
  const raw = String(value).trim().replace(/\s+/gu, " ");
@@ -4282,6 +4398,17 @@ function resolveSocialProfile(platform, value, owner = "organization") {
4282
4398
  if (!webUrl.test(url)) return { text: raw, resolved: false };
4283
4399
  return { text: display(url), href: encodeURI(url), resolved: true };
4284
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
+ }
4285
4412
  function layoutFurniture(input, options = {}) {
4286
4413
  const slide = record6(input), width = options.width ?? 1280, height = options.height ?? 720, scale = Math.min(width, height) / 720;
4287
4414
  const settings = { ...record6(record6(options.layout).composition), ...record6(slide.composition) };
@@ -4296,8 +4423,8 @@ function layoutFurniture(input, options = {}) {
4296
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.");
4297
4424
  const outlines = options.textMeasurement?.outlineBounds !== void 0, parts = [], diagnostics = [];
4298
4425
  const sourceRoot = `slides.${options.slideIndex ?? 0}`, organizations = Array.isArray(options.presentation?.organization) ? options.presentation.organization : [options.presentation?.organization];
4299
- const primaryIndex = organizations.findIndex((item) => record6(item).role === "primary"), organizationIndex = primaryIndex >= 0 ? primaryIndex : organizations.findIndex(Boolean);
4300
- const organization = record6(organizations[organizationIndex]), organizationRoot = Array.isArray(options.presentation?.organization) ? `organization.${organizationIndex}` : "organization";
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";
4301
4428
  const fontFamily = options.fontFamilies?.body ?? "sans-serif";
4302
4429
  let headerBottom = 0, footerTop = height, configured = false;
4303
4430
  const error = (path, message, code = "text-overflow") => diagnostics.push({ code, path, message });
@@ -4310,58 +4437,27 @@ function layoutFurniture(input, options = {}) {
4310
4437
  const zones = [];
4311
4438
  for (const [index, zone] of ["left", "center", "right"].entries()) {
4312
4439
  const side = furnitureRtl && zone !== "center" ? zone === "left" ? "right" : "left" : zone;
4313
- const content = record6(record6(value)[zone]), path = `${root}.${zone}`, x = width * (0.07 + (furnitureRtl ? 2 - index : index) * 0.3), zoneWidth = width * 0.26, zoneParts = [];
4314
- let y = 0;
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 = [];
4315
4442
  const add = (field, text2, generated = false, sourcePath, extras = {}) => {
4316
4443
  if (text2 === void 0) return;
4317
4444
  if (typeof text2 !== "string") throw new TypeError(`Furniture field ${path}.${field} requires string content.`);
4318
- const partPath = `${path}.${field}`, style = resolveTextStyle({ fontFamily, fontWeight: 400, italic: false, path: partPath }, options.textMeasurement);
4319
- const fit = fitText(text2, { width: Math.max(Number.MIN_VALUE, zoneWidth - (outlines ? 2 * padding : 0)), height: Number.MAX_VALUE }, size, size, textWidthMeasurer(style, options.textMeasurement));
4320
- const ink = fit.sourceLines.map((line, index2) => {
4321
- let outline = null;
4322
- if (outlines) {
4323
- for (const segment2 of line.segments) if (segment2.kind === "text") {
4324
- const bounds = measureTextOutline(text2.slice(segment2.start, segment2.end), size, style, options.textMeasurement);
4325
- if (bounds) {
4326
- const next = { ...bounds, x: bounds.x + segment2.x };
4327
- if (!outline) outline = next;
4328
- else {
4329
- const right = Math.max(outline.x + outline.width, next.x + next.width), bottom = Math.max(outline.y + outline.height, next.y + next.height);
4330
- outline.x = Math.min(outline.x, next.x);
4331
- outline.y = Math.min(outline.y, next.y);
4332
- outline.width = right - outline.x;
4333
- outline.height = bottom - outline.y;
4334
- }
4335
- }
4336
- }
4337
- }
4338
- return { width: line.width, y: index2 * fit.lineHeight, baseline: size + index2 * fit.lineHeight, height: fit.lineHeight, outline };
4339
- });
4340
- const natural = outlines ? placeTextLines(ink, { x, y, width: zoneWidth, height: Number.MAX_VALUE }, side, padding) : void 0;
4341
- const partHeight = natural?.height ?? fit.sourceLines.length * fit.lineHeight;
4342
- if (!Number.isFinite(partHeight)) throw new RangeError("Furniture exceeds finite layout coordinates.");
4343
- const box = { x, y, width: zoneWidth, height: Math.max(scale, partHeight) }, placement = outlines ? placeTextLines(ink, box, side, padding) : void 0;
4344
- const accepted = { ...fit, ...placement ? { placement } : {}, overflow: fit.overflow || !!placement?.overflow };
4345
- 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 } : {} });
4346
- if (accepted.overflow) error(partPath, "Repeated text exceeds its zone at the selected readability floor; change the furniture or slide design.");
4347
- 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) });
4348
4447
  };
4349
- const imageBox = (source) => {
4448
+ const addImage = (source, sourcePath, reference) => {
4350
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));
4351
- return { x: side === "left" ? x : side === "center" ? x + (zoneWidth - imageWidth) / 2 : x + zoneWidth - imageWidth, y, width: imageWidth, height: imageHeight };
4450
+ pending.push({ type: "image", sourcePath, image: source, ...reference !== void 0 ? { reference } : {}, width: imageWidth, height: imageHeight });
4352
4451
  };
4353
- if (content.logo === true) {
4354
- const resolved = resolveLogo(options.presentation, slide, { slot: "icon", onDark: options.darkBackground, slideIndex: options.slideIndex });
4355
- if (resolved) {
4356
- const box = imageBox(resolved.source);
4357
- zoneParts.push({ type: "image", kind: kind2, zone, field: "logo", path: `${path}.logo`, sourcePath: resolved.path, generated: true, image: resolved.source, box, alignment: side });
4358
- y += box.height;
4359
- } else error(`${path}.logo`, "Generated logo needs design.logo or a primary organization logo.", "unresolved-content");
4360
- }
4361
4452
  if (content.image !== void 0) {
4362
- const box = imageBox(content.image);
4363
- zoneParts.push({ type: "image", kind: kind2, zone, field: "image", path: `${path}.image`, sourcePath: `${path}.image`, generated: false, image: content.image, box, alignment: side });
4364
- y += box.height;
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
+ }
4365
4461
  }
4366
4462
  if (typeof content.text === "string") {
4367
4463
  const resolved = substituteSlideTokens(content.text, { slideNumber: number, ...slideCount !== void 0 ? { slideCount } : {}, section: slide.section });
@@ -4390,9 +4486,71 @@ function layoutFurniture(input, options = {}) {
4390
4486
  else add("date", resolved.text, true, `${path}.date`);
4391
4487
  }
4392
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
+ });
4393
4551
  zones.push(zoneParts);
4394
4552
  }
4395
- const tallest = Math.max(0, ...zones.map((zone) => zone.reduce((sum, part) => sum + part.box.height, 0)));
4553
+ const tallest = Math.max(0, ...zones.flat().map((part) => part.box.y + part.box.height));
4396
4554
  if (!tallest) continue;
4397
4555
  const top = kind2 === "header" ? height * 0.025 : Math.max(height * 0.025, height - height * 0.04 - tallest);
4398
4556
  if (kind2 === "header") headerBottom = top + tallest;
@@ -4522,13 +4680,13 @@ function layoutQuote(value, box, options = {}) {
4522
4680
  path
4523
4681
  ) : void 0;
4524
4682
  const footerRequested = attribution?.requestedFontSize ?? gridFontSize(17 * scale, minimum);
4525
- const usable = (area) => [area.x, area.y, area.width, area.height].every(Number.isFinite) && area.width > 0 && area.height > 0;
4526
- const fit = (part, area, size = part.requestedFontSize, floor = minimum) => !usable(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);
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);
4527
4685
  const heightOf = (fit2) => "richLines" in fit2 ? fit2.height : fit2.lines.length * fit2.lineHeight;
4528
4686
  const inner = { x: box.x + 18, y: box.y + 18, width: box.width - 36, height: box.height - 36 };
4529
4687
  let photo, photoFits = true;
4530
4688
  if (!hasBlock) body.fit = fit(body, body.box);
4531
- else if (usable(inner) && inner.height > 18) {
4689
+ else if (usable2(inner) && inner.height > 18) {
4532
4690
  const available = inner.height - 18;
4533
4691
  const preferredBody = fit(body, inner, body.requestedFontSize, body.requestedFontSize);
4534
4692
  const minimumBody = body.requestedFontSize === minimum ? preferredBody : fit(body, inner, minimum, minimum);
@@ -4550,8 +4708,8 @@ function layoutQuote(value, box, options = {}) {
4550
4708
  const bodyFit = fit(body, bodyBox);
4551
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;
4552
4710
  const textBox = attribution ? hasPhoto ? { ...textArea, y: footerBox.y + Math.max(0, footerHeight - naturalHeight) / 2, height: Math.min(naturalHeight, footerHeight) } : footerBox : void 0;
4553
- const footerFit = attribution && natural && usable(footerBox) ? { ...natural, overflow: natural.overflow || naturalHeight > footerHeight + 0.01 } : void 0;
4554
- const fitsPhoto = !hasPhoto || diameter <= footerHeight + 0.01 && (!attribution || usable(textArea));
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));
4555
4713
  const overflow = !bodyFit || bodyFit.overflow || (attribution ? !footerFit || footerFit.overflow : false) || !fitsPhoto;
4556
4714
  const score = (body.requestedFontSize - (bodyFit?.fontSize ?? minimum) + footerRequested - size) / scale;
4557
4715
  if (!selected || !overflow && (selected.overflow || score < selected.score) || overflow && selected.overflow) {
@@ -4577,7 +4735,7 @@ function layoutQuote(value, box, options = {}) {
4577
4735
  } else if (part.fit.overflow) {
4578
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.`);
4579
4737
  }
4580
- if (usable(area) && (area.x < box.x || area.y < box.y || area.x + area.width > box.x + box.width + 0.01 || area.y + area.height > box.y + box.height + 0.01)) {
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)) {
4581
4739
  report("part-outside-cell", part.path, [part.role], `Quote ${part.role} extends outside its cell; increase the cell size or change the arrangement.`);
4582
4740
  }
4583
4741
  }
@@ -4632,7 +4790,7 @@ function layoutMetric(value, box, options = {}) {
4632
4790
  const primary = parts[0], metadata = parts.filter((part) => part !== primary && part.visible), unit = metadata.find((part) => part.role === "unit");
4633
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;
4634
4792
  const alignment = physicalAlignment(requestedAlignment, metricDirection);
4635
- const usable = (area) => [area.x, area.y, area.width, area.height, area.x + area.width, area.y + area.height].every(Number.isFinite) && area.width > 0 && area.height > 0;
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;
4636
4794
  const occupied = (fit) => fit.placement?.height ?? fit.lines.length * fit.lineHeight;
4637
4795
  const gap = 8 * scale, primaryGap = 12 * scale;
4638
4796
  const lineInk = (part, fit) => fit.sourceLines.map((line, index) => {
@@ -4676,7 +4834,7 @@ function layoutMetric(value, box, options = {}) {
4676
4834
  return fit;
4677
4835
  };
4678
4836
  const fitValue = (area, singleLine) => {
4679
- if (!usable(area)) return void 0;
4837
+ if (!usable2(area)) return void 0;
4680
4838
  for (let step = 0; step <= 76; step++) {
4681
4839
  const size = gridFontSize(primary.requestedFontSize - step * scale, minimum);
4682
4840
  const natural = measure(primary, size, area.width);
@@ -4717,7 +4875,7 @@ function layoutMetric(value, box, options = {}) {
4717
4875
  allocations.push({ part, box: { ...box, y, height }, fit: { ...natural, overflow: !!natural.placement?.overflow || natural.sourceLines.some((line) => line.width > box.width + 0.01) } });
4718
4876
  y += height + gap;
4719
4877
  }
4720
- const overflow = !valueFit || valueFit.overflow || arrangement === "inline-unit" && valueFit.lines.length !== 1 || primaryHeight > area.height + 0.01 || allocations.some(({ box: area2, fit }) => !fit || fit.overflow || !usable(area2) || area2.y + area2.height > box.y + box.height + 0.01);
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);
4721
4879
  const score = allocations.reduce((sum, { part, fit }) => sum + (part.requestedFontSize - (fit?.fontSize ?? minimum)) / scale, 0);
4722
4880
  const candidate = { arrangement, allocations, score, overflow };
4723
4881
  if (!selected || !overflow && (selected.overflow || score < selected.score) || overflow && selected.overflow) selected = candidate;
@@ -4745,9 +4903,9 @@ function layoutMetric(value, box, options = {}) {
4745
4903
  }
4746
4904
  const report = (reason, part, roles, message) => diagnostics.push({ code: "text-overflow", reason, path: part.path, parts: roles, message });
4747
4905
  for (const part of parts.filter((part2) => part2.visible)) {
4748
- if (!part.fit || !usable(part.box)) report("invalid-part-box", part, [part.role], `Metric ${part.role} has no usable space after metadata; increase the cell or change the arrangement.`);
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.`);
4749
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.`);
4750
- if (usable(part.box) && (part.box.x < box.x || part.box.y < box.y || part.box.x + part.box.width > box.x + box.width + 0.01 || part.box.y + part.box.height > box.y + box.height + 0.01)) {
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)) {
4751
4909
  report("part-outside-cell", part, [part.role], `Metric ${part.role} extends outside its cell; increase the cell or change the arrangement.`);
4752
4910
  }
4753
4911
  }
@@ -5019,9 +5177,9 @@ function layoutCode(value, box, options = {}) {
5019
5177
  if (code.language) add("language", code.language, `${path}.language`);
5020
5178
  if (!parts.length) add("language", "code", path, true);
5021
5179
  const body = add("body", code.source, shorthand ? path : `${path}.source`), headers = parts.filter((part) => part !== body);
5022
- const usable = (area) => [area.x, area.y, area.width, area.height].every(Number.isFinite) && area.width > 0 && area.height > 0;
5023
- const fit = (part, area, size = part.requestedFontSize, floor = minimum) => usable(area) ? fitCodeText(part.text, area, size, floor, scale, textWidthMeasurer(part.style, options.textMeasurement)) : void 0;
5024
- if (usable(inner)) {
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)) {
5025
5183
  const combinations = headers.reduce((choices, part) => choices.flatMap((choice) => [.../* @__PURE__ */ new Set([part.requestedFontSize, minimum])].map((size) => [...choice, { part, size }])), [[]]);
5026
5184
  let selected;
5027
5185
  for (const combination of combinations) {
@@ -5030,12 +5188,12 @@ function layoutCode(value, box, options = {}) {
5030
5188
  for (const [index, { part, size }] of combination.entries()) {
5031
5189
  const measured = fitCodeText(part.text, inner, size, size, scale, textWidthMeasurer(part.style, options.textMeasurement));
5032
5190
  const area2 = { ...inner, y, height: measured.lines.length * measured.lineHeight };
5033
- allocations.push({ part, box: area2, fit: usable(area2) ? { ...measured, overflow: measured.sourceLines.some((line) => line.width > area2.width + 0.01) } : void 0 });
5191
+ allocations.push({ part, box: area2, fit: usable2(area2) ? { ...measured, overflow: measured.sourceLines.some((line) => line.width > area2.width + 0.01) } : void 0 });
5034
5192
  y += area2.height + (index < headers.length - 1 ? 8 : 12);
5035
5193
  }
5036
5194
  const area = { ...inner, y, height: inner.y + inner.height - y };
5037
5195
  allocations.push({ part: body, box: area, fit: fit(body, area) });
5038
- const overflow = allocations.some(({ box: box2, fit: fit2 }) => !fit2 || fit2.overflow || !usable(box2) || box2.y + box2.height > inner.y + inner.height + 0.01);
5196
+ const overflow = allocations.some(({ box: box2, fit: fit2 }) => !fit2 || fit2.overflow || !usable2(box2) || box2.y + box2.height > inner.y + inner.height + 0.01);
5039
5197
  const score = allocations.reduce((sum, { part, fit: fit2 }) => sum + (part.requestedFontSize - (fit2?.fontSize ?? minimum)) / scale, 0);
5040
5198
  if (!selected || !overflow && (selected.overflow || score < selected.score) || overflow && selected.overflow) selected = { allocations, score, overflow };
5041
5199
  }
@@ -5049,7 +5207,7 @@ function layoutCode(value, box, options = {}) {
5049
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.`);
5050
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.`);
5051
5209
  const area = part.box;
5052
- if (usable(area) && (area.x < box.x || area.y < box.y || area.x + area.width > box.x + box.width + 0.01 || area.y + area.height > box.y + box.height + 0.01)) {
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)) {
5053
5211
  report("part-outside-cell", part, [part.role], `Code ${part.role} extends outside its cell; increase the cell or change the arrangement.`);
5054
5212
  }
5055
5213
  }
@@ -5434,7 +5592,7 @@ function composeSlide(input, options = {}) {
5434
5592
  }, options.textMeasurement);
5435
5593
  const listBullet = hints.listBullet !== void 0 ? { value: hints.listBullet, path: hints.paths.listBullet } : void 0;
5436
5594
  const bulletImage = listBullet?.value === "image" ? (() => {
5437
- const resolved = resolveLogo(options.presentation, slide, { slot: "icon", onDark: options.darkBackground, slideIndex: options.slideIndex });
5595
+ const resolved = resolveLogo(options.presentation, slide, { shape: "icon", onDark: options.darkBackground, slideIndex: options.slideIndex });
5438
5596
  return resolved ? { source: resolved.source, path: resolved.path } : void 0;
5439
5597
  })() : void 0;
5440
5598
  const widthFor = (field, path2) => textWidthMeasurer(styleFor(field, path2), options.textMeasurement);
@@ -5549,12 +5707,12 @@ function composeSlide(input, options = {}) {
5549
5707
  coverGroup = isCover;
5550
5708
  let logo;
5551
5709
  if (isCover) {
5552
- const resolved = resolveLogo(options.presentation, slide, { slot: "lockup", onDark: options.darkBackground, slideIndex: options.slideIndex });
5710
+ const resolved = resolveLogo(options.presentation, slide, { shape: "full", onDark: options.darkBackground, slideIndex: options.slideIndex });
5553
5711
  if (resolved) {
5554
5712
  const logoHeight = 56 * scale;
5555
5713
  const logoWidth = Math.max(scale, Math.min(4 * logoHeight, area.right - area.left - 2 * padding));
5556
5714
  const box = { x: round2(rtl ? area.right - padding - logoWidth : area.left + padding), y: round2(headingTop), width: round2(logoWidth), height: round2(logoHeight) };
5557
- logo = { box, slot: "lockup", path: resolved.path, source: resolved.source, variant: resolved.variant, anchor: rtl ? "right" : "left" };
5715
+ logo = { box, shape: resolved.shape, path: resolved.path, source: resolved.source, variant: resolved.variant, reference: resolved.reference, anchor: rtl ? "right" : "left" };
5558
5716
  }
5559
5717
  }
5560
5718
  let y = logo ? logo.box.y + logo.box.height + gap : headingTop;
@@ -5891,7 +6049,7 @@ function composeSlide(input, options = {}) {
5891
6049
  slots.sort((a, b) => order.get(a.path) - order.get(b.path));
5892
6050
  } else arrange(pending, contentBox, rootSettings, composition.mode ? 0 : placeholders.length);
5893
6051
  if (listBullet?.value === "image" && !bulletImage && items.some((item) => (item.field === "items" || item.field === "bullets") && item.payload.numbering === void 0)) {
5894
- 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." });
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." });
5895
6053
  }
5896
6054
  const numberingDiagnostics = [];
5897
6055
  for (const item of items) {
@@ -5953,4 +6111,4 @@ function resolveCanvasDimensions(input) {
5953
6111
  return { width, height };
5954
6112
  }
5955
6113
 
5956
- 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, LANGUAGES, LAYOUT_BODY_KINDS, 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 };
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 };