@openpresentation/opf 0.15.1 → 0.17.0

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 (86) hide show
  1. package/README.md +19 -1
  2. package/dist/{catalog-refs-BSfGELpt.d.ts → catalog-refs-C4K4y-vh.d.ts} +1 -1
  3. package/dist/{catalog-schemas-CinPh5WA.d.ts → catalog-schemas-g7RFQBdc.d.ts} +1 -1
  4. package/dist/catalog.d.ts +2 -2
  5. package/dist/catalog.js +3 -23224
  6. package/dist/{chart-data-BnNqgnC-.d.ts → chart-data-DTxdRRT1.d.ts} +1 -1
  7. package/dist/{chunk-3JG4YXWF.js → chunk-BIC3SWPK.js} +12 -5
  8. package/dist/{chunk-O744SXXA.js → chunk-CW7GEAV3.js} +350 -141
  9. package/dist/{chunk-374R35TJ.js → chunk-ESRYBYLY.js} +1 -1
  10. package/dist/chunk-IPW5BS3X.js +973 -0
  11. package/dist/{chunk-3RKB4RDJ.js → chunk-IS5H5JKH.js} +4 -2
  12. package/dist/{chunk-TWQJLI6E.js → chunk-M4UR3NPN.js} +2 -2
  13. package/dist/chunk-N4EVOFEA.js +58 -0
  14. package/dist/{chunk-QDI75P2M.js → chunk-NLNYNBTS.js} +217 -20
  15. package/dist/{chunk-U4J75TRG.js → chunk-O263APNI.js} +5 -2
  16. package/dist/{chunk-UBFJ2QHH.js → chunk-O2CX5NEH.js} +36 -14
  17. package/dist/{chunk-5FTUIDUH.js → chunk-O3CVNJTL.js} +1 -1
  18. package/dist/{chunk-VGAOJEY7.js → chunk-PPROJP2E.js} +1 -1
  19. package/dist/{chunk-2CJPC4HI.js → chunk-RA64UQKI.js} +203 -53
  20. package/dist/chunk-TCD2FB2K.js +319 -0
  21. package/dist/chunk-WCG3THTD.js +23712 -0
  22. package/dist/chunk-WWYWFVOI.js +793 -0
  23. package/dist/{chunk-ZPU46TFA.js → chunk-YT7FMQER.js} +1 -1
  24. package/dist/{composition-D2tghM82.d.ts → composition-BbZ_9C-c.d.ts} +238 -139
  25. package/dist/composition.d.ts +3 -3
  26. package/dist/composition.js +1 -1
  27. package/dist/conversion-SBpTOwdg.d.ts +444 -0
  28. package/dist/convert.d.ts +4 -4
  29. package/dist/convert.js +7 -7
  30. package/dist/data.d.ts +28 -11
  31. package/dist/data.js +2 -2
  32. package/dist/deck-report-Bx3naFlF.d.ts +40 -0
  33. package/dist/deck.d.ts +47 -0
  34. package/dist/deck.js +12 -0
  35. package/dist/diff.d.ts +1 -1
  36. package/dist/diff.js +5 -5
  37. package/dist/docs.js +33 -27
  38. package/dist/examples.d.ts +1 -1
  39. package/dist/examples.js +3229 -985
  40. package/dist/{finding-CT_f_vbD.d.ts → finding-CDQ_wH8j.d.ts} +1 -1
  41. package/dist/format.js +2 -2
  42. package/dist/index.d.ts +43 -12
  43. package/dist/index.js +15 -805
  44. package/dist/markdown.d.ts +7 -8
  45. package/dist/markdown.js +7 -7
  46. package/dist/node-engine.d.ts +94 -0
  47. package/dist/node-engine.js +21 -0
  48. package/dist/node.d.ts +60 -0
  49. package/dist/node.js +54 -0
  50. package/dist/{pagination-wedbe7eq.d.ts → pagination-BhX4M48Y.d.ts} +21 -7
  51. package/dist/pagination.d.ts +4 -4
  52. package/dist/pagination.js +4 -4
  53. package/dist/patch.d.ts +1 -1
  54. package/dist/patch.js +4 -4
  55. package/dist/{presentation-CDKQVkyI.d.ts → presentation-fb8CZmRq.d.ts} +28 -83
  56. package/dist/repo-readme.js +1 -1
  57. package/dist/schemas.d.ts +14 -0
  58. package/dist/schemas.js +1 -1
  59. package/dist/spec/catalogs/layouts/business-model-canvas.json +66 -1
  60. package/dist/spec/catalogs/layouts/capability-map.json +53 -3
  61. package/dist/spec/catalogs/layouts/chart-2x.json +32 -8
  62. package/dist/spec/catalogs/layouts/chart-3x-bottom-vertical-title-left.json +16 -10
  63. package/dist/spec/catalogs/layouts/chart-3x-left-title-center.json +16 -10
  64. package/dist/spec/catalogs/layouts/chart-3x-left.json +16 -10
  65. package/dist/spec/catalogs/layouts/chart-3x-right-title-center.json +16 -10
  66. package/dist/spec/catalogs/layouts/chart-3x-right.json +16 -10
  67. package/dist/spec/catalogs/layouts/chart-3x-top-vertical-title-left.json +16 -10
  68. package/dist/spec/catalogs/layouts/chart-3x.json +48 -12
  69. package/dist/spec/catalogs/layouts/dashboard.json +37 -5
  70. package/dist/spec/catalogs/layouts/image-2x.json +32 -8
  71. package/dist/spec/catalogs/layouts/image-3x.json +48 -12
  72. package/dist/spec/catalogs/layouts/index.json +1 -1
  73. package/dist/spec/catalogs/layouts/kpi-scorecard.json +48 -3
  74. package/dist/spec/catalogs/layouts/logic-tree.json +69 -0
  75. package/dist/spec/catalogs/layouts/market-size.json +26 -2
  76. package/dist/spec/catalogs/layouts/org-chart.json +53 -6
  77. package/dist/spec/catalogs/manifest.json +3 -3
  78. package/dist/spec/schemas/layout.schema.json +172 -5
  79. package/dist/spec/schemas/opf.schema.json +27 -43
  80. package/dist/types.d.ts +10 -32
  81. package/dist/{validation-types-BOcY9GTs.d.ts → validation-types-iZbXfES6.d.ts} +19 -8
  82. package/dist/validator.d.ts +8 -8
  83. package/dist/validator.js +3 -3
  84. package/dist/yaml.d.ts +6 -6
  85. package/dist/yaml.js +8 -321
  86. package/package.json +38 -1
@@ -1,6 +1,8 @@
1
- import { isRecord, same } from './chunk-VGAOJEY7.js';
1
+ import { isRecord, same } from './chunk-PPROJP2E.js';
2
2
  import { parseDocument, YAMLMap, Scalar, stringify } from 'yaml';
3
3
 
4
+ // src/not-checked.ts
5
+ var NOT_CHECKED = { syntax: "checked", schema: "not-run", references: "not-run", policy: "not-run", accessibility: "not-run", content: "not-run", layout: "not-run", backgroundPixels: "not-read", imageBytes: "embedded-only", nativeExport: "not-checked" };
4
6
  function splitLines(source) {
5
7
  const lines = [];
6
8
  let start = 0;
@@ -189,4 +191,4 @@ var OPFMarkdownError = class extends Error {
189
191
  }
190
192
  };
191
193
 
192
- export { Ctx, DECIMAL, OPFMarkdownError, csvField, lineRange, readCsv, readYamlMapping, scalarText, splitLines, writeYaml };
194
+ export { Ctx, DECIMAL, NOT_CHECKED, OPFMarkdownError, csvField, lineRange, readCsv, readYamlMapping, scalarText, splitLines, writeYaml };
@@ -1,5 +1,5 @@
1
- import { jsonEqual, formatPointer } from './chunk-5FTUIDUH.js';
2
- import { annotationText } from './chunk-O744SXXA.js';
1
+ import { jsonEqual, formatPointer } from './chunk-O3CVNJTL.js';
2
+ import { annotationText } from './chunk-CW7GEAV3.js';
3
3
 
4
4
  // src/diff-match.ts
5
5
  var DEFAULT_MATCH_THRESHOLD = 0.5;
@@ -0,0 +1,58 @@
1
+ import { fromMarkdown, toMarkdown } from './chunk-BIC3SWPK.js';
2
+ import { fromYaml, toYaml } from './chunk-TCD2FB2K.js';
3
+ import { NOT_CHECKED } from './chunk-IS5H5JKH.js';
4
+ import { parseSource, validate, OPFValidationError } from './chunk-NLNYNBTS.js';
5
+ import { checkCatalogsOption } from './chunk-SPFV27O5.js';
6
+
7
+ // src/deck-report.ts
8
+ var DECK_FORMATS = ["json", "yaml", "markdown"];
9
+ function deckFormatOf(filename) {
10
+ if (filename === void 0) return "json";
11
+ if (/\.opf\.md$/i.test(filename)) return "markdown";
12
+ if (/\.ya?ml$/i.test(filename)) return "yaml";
13
+ return "json";
14
+ }
15
+ function resolveFormat(caller, options) {
16
+ if (options.format === void 0) return deckFormatOf(options.filename);
17
+ if (!DECK_FORMATS.includes(options.format)) throw new TypeError(`${caller}: unknown format ${JSON.stringify(options.format)}. Formats: ${DECK_FORMATS.join(", ")}.`);
18
+ return options.format;
19
+ }
20
+ function readDeckReport(text, options = {}, caller = "parse") {
21
+ if (typeof text !== "string") throw new TypeError(`${caller} expects a string.`);
22
+ checkCatalogsOption(options.catalogs, caller);
23
+ const format = resolveFormat(caller, options);
24
+ const { catalogs } = options;
25
+ const check = options.validate ?? true;
26
+ const validateOptions = typeof check === "object" ? { ...catalogs && check.catalogs === void 0 ? { catalogs } : {}, ...check } : { only: ["format", "references"], ...catalogs ? { catalogs } : {} };
27
+ const checked = check === false ? false : validateOptions;
28
+ if (format === "yaml") return { ...fromYaml(text, { validate: checked, catalogs }), format };
29
+ if (format === "markdown") return { ...fromMarkdown(text, { validate: checked, catalogs }), format };
30
+ if (checked === false) {
31
+ const parsed = parseSource(text);
32
+ return { ...report(parsed.syntax), presentation: parsed.syntax.length ? {} : parsed.value, format };
33
+ }
34
+ const result = validate(text, checked);
35
+ const broken = result.findings.some((found) => found.ruleId === "opf/json-syntax");
36
+ return { ...result, presentation: broken ? {} : JSON.parse(text.charCodeAt(0) === 65279 ? text.slice(1) : text), format };
37
+ }
38
+ function report(findings) {
39
+ const counts = { error: 0, warning: 0, info: 0 };
40
+ for (const found of findings) counts[found.severity]++;
41
+ return { valid: counts.error === 0, schemaValid: null, findings, counts, checks: NOT_CHECKED };
42
+ }
43
+
44
+ // src/deck.ts
45
+ function parse(text, options = {}) {
46
+ const { presentation, format: _format, ...result } = readDeckReport(text, { ...options, validate: true }, "parse");
47
+ if (!result.valid) throw new OPFValidationError(result);
48
+ return presentation;
49
+ }
50
+ function stringify(presentation, options = {}) {
51
+ const format = resolveFormat("stringify", options);
52
+ if (format === "yaml") return toYaml(presentation, { schemaComment: options.schemaComment === true }).yaml;
53
+ if (format === "markdown") return toMarkdown(presentation).markdown;
54
+ return `${JSON.stringify(presentation, null, 2)}
55
+ `;
56
+ }
57
+
58
+ export { DECK_FORMATS, deckFormatOf, parse, readDeckReport, stringify };
@@ -1,7 +1,7 @@
1
- import { promotedRegionKeys, VARIABLE_KINDS, resolveChartData, chartPaletteForFill, imageBackground, tableGrid, timelineTextColor, visitContentPayloads, readingRows, visualReadingOrder, intrinsicImageSize, textWidthMeasurer, normalizeHexColor, resolveColorRef, resolveDesignRecords, resolveColorRoles, resolveFontFamilies, resolveCanvasDimensions, defaultSlideBackground, isRecord, hasContentVariables, isTemplate, instantiateForValidation, unusedDatasets, MAX_COMPOSITION_DEPTH, datasetDiagnostics, pathFor, collectCitations, numberingFindings, walkCitationRuns, resolveTableData, resolveChartOptions, chartOptionTarget, codeHighlightLines, chartNumberFixesByCell, OPFCompositionError, composeSlide, resolveScriptFonts } from './chunk-O744SXXA.js';
1
+ import { promotedRegionKeys, VARIABLE_KINDS, resolveChartData, chartPaletteForFill, imageBackground, tableGrid, timelineTextColor, visitContentPayloads, readingRows, visualReadingOrder, intrinsicImageSize, textWidthMeasurer, normalizeHexColor, resolveColorRef, resolveDesignRecords, resolveFontFamilies, resolveColorRoles, resolveCanvasDimensions, resolveSlideVariables, defaultSlideBackground, isRecord, hasContentVariables, isTemplate, instantiateForValidation, resolveScriptFonts, unusedDatasets, MAX_COMPOSITION_DEPTH, datasetDiagnostics, pathFor, collectCitations, numberingFindings, walkCitationRuns, resolveTableData, resolveChartOptions, chartOptionTarget, codeHighlightLines, chartNumberFixesByCell, MAX_PLACEHOLDER_GROUP_DEPTH, OPFCompositionError, composeSlide } from './chunk-CW7GEAV3.js';
2
2
  import { catalogSchemaNames } from './chunk-TRDXRRQK.js';
3
3
  import { resolveReference, checkCatalogsOption, provenanceOf, unresolvedReference, OPFUnresolvedReferenceError, catalogReferenceSites, parseReference, catalogGroupDeclared, pointerPath, catalogRecords, catalogKinds, catalogGroupSource, registeredCatalog } from './chunk-SPFV27O5.js';
4
- import { schemaEntries, schemas } from './chunk-2CJPC4HI.js';
4
+ import { schemaEntries, schemas } from './chunk-RA64UQKI.js';
5
5
  import Ajv2020 from 'ajv/dist/2020.js';
6
6
  import addFormats from 'ajv-formats';
7
7
  import { parseTree, printParseErrorCode, getNodePath } from 'jsonc-parser';
@@ -864,6 +864,33 @@ function dataUnionErrors(errors, value) {
864
864
  return { ...error, message: field === "fields" ? "'fields' applies only to a dataset-backed table; add 'dataset' or remove 'fields'" : datasetTableMessage(field) };
865
865
  });
866
866
  }
867
+ var placeholderHeadings = /* @__PURE__ */ new Set(["title", "subtitle", "tag"]);
868
+ function placeholderGroupIssues(value) {
869
+ const issues = [];
870
+ if (!isRecord(value) || !Array.isArray(value.placeholders)) return issues;
871
+ const walk = (entries, path, depth) => entries.forEach((entry, index) => {
872
+ if (!isRecord(entry)) return;
873
+ const at2 = `${path}/${index}`;
874
+ if (entry.type === "group") {
875
+ if (depth >= MAX_PLACEHOLDER_GROUP_DEPTH) issues.push(semanticIssue(at2, `placeholder groups nest at most ${MAX_PLACEHOLDER_GROUP_DEPTH} levels inside a layout record; this group would be level ${depth + 1}`, { code: "layout-placeholder-group", depth: depth + 1, maxDepth: MAX_PLACEHOLDER_GROUP_DEPTH }));
876
+ else if (Array.isArray(entry.placeholders)) walk(entry.placeholders, `${at2}/placeholders`, depth + 1);
877
+ return;
878
+ }
879
+ if (depth === 0) return;
880
+ if (typeof entry.type === "string" && placeholderHeadings.has(entry.type)) issues.push(semanticIssue(`${at2}/type`, `a placeholder group holds body regions only; a '${entry.type}' placeholder belongs at the record's top level`, { code: "layout-placeholder-group" }));
881
+ if (hasOwn(entry, "placement")) issues.push(semanticIssue(`${at2}/placement`, "placement is only valid on a top-level image placeholder; a region inside a placeholder group cannot bleed to a slide edge", { code: "image-placement-invalid" }));
882
+ });
883
+ walk(value.placeholders, "/placeholders", 0);
884
+ return issues;
885
+ }
886
+ function withPlaceholderGroupIssues(errors, value) {
887
+ const issues = placeholderGroupIssues(value);
888
+ const entries = issues.map((issue2) => issue2.path.replace(/\/(type|placement)$/, ""));
889
+ const covered = (error) => entries.some((entry) => error.path === entry || error.path.startsWith(`${entry}/`));
890
+ const kept = errors.filter((error) => !covered(error));
891
+ const reported = [...kept.filter((error) => error.keyword !== "if"), ...issues].map((error) => error.path);
892
+ return [...kept.filter((error) => error.keyword !== "if" || !reported.some((path) => path === error.path || path.startsWith(`${error.path}/`))), ...issues];
893
+ }
867
894
  function validateAgainstSchema(value, schemaOrKind = "presentation", options = {}) {
868
895
  const resolved = resolveValidator(schemaOrKind);
869
896
  if (resolved.schemaName === "presentation") {
@@ -895,7 +922,8 @@ function validateAgainstSchema(value, schemaOrKind = "presentation", options = {
895
922
  }
896
923
  const valid = resolved.validate(subject) === true;
897
924
  const ajvErrors = valid ? [] : typedVariableErrors(resolved.validate.errors ?? [], subject);
898
- const errors = (resolved.schemaName === "presentation" ? dataUnionErrors(ajvErrors, subject) : ajvErrors).map(toIssue);
925
+ const mapped = (resolved.schemaName === "presentation" ? dataUnionErrors(ajvErrors, subject) : ajvErrors).map(toIssue);
926
+ const errors = resolved.schemaName === "layout" ? withPlaceholderGroupIssues(mapped, subject) : mapped;
899
927
  const warnings = [];
900
928
  if (resolved.schemaName === "presentation") {
901
929
  errors.push(...presentationSemanticIssues(subject));
@@ -1918,7 +1946,7 @@ var languageRule = rule(
1918
1946
  "info",
1919
1947
  "The presentation does not declare its language.",
1920
1948
  "Screen readers and text-to-speech choose pronunciation and hyphenation from the declared language; spell checkers and translation tools use it too.",
1921
- { standard: "WCAG 2.2 SC 3.1.1 Language of Page, level A", approximations: "Only the presentation-level `language` is checked, not the language of individual runs (OPF has no per-run language)." }
1949
+ { standard: "WCAG 2.2 SC 3.1.1 Language of Page, level A", approximations: "Only the presentation-level `language` is checked, not the language of individual runs (`TextRun.lang`; opf/language-tag checks how those tags are written)." }
1922
1950
  );
1923
1951
  var languageRules = [
1924
1952
  {
@@ -1944,6 +1972,105 @@ var accessibilityRules = [
1944
1972
  ...languageRules
1945
1973
  ];
1946
1974
 
1975
+ // src/language-tag.ts
1976
+ var REGION_ALIASES = { UK: "GB" };
1977
+ var privateRegion = /^(?:AA|Q[M-Z]|X[A-Z]|ZZ)$/;
1978
+ var regionNames;
1979
+ var assignedRegion = (region) => {
1980
+ if (privateRegion.test(region)) return true;
1981
+ regionNames ??= new Intl.DisplayNames(["en"], { type: "region", fallback: "code" });
1982
+ try {
1983
+ return regionNames.of(region) !== region;
1984
+ } catch {
1985
+ return false;
1986
+ }
1987
+ };
1988
+ var canonical = (tag) => {
1989
+ try {
1990
+ return Intl.getCanonicalLocales(tag)[0];
1991
+ } catch {
1992
+ return void 0;
1993
+ }
1994
+ };
1995
+ function regionSubtag(tag) {
1996
+ const subtags = tag.split("-");
1997
+ for (let index = 1; index < subtags.length; index++) {
1998
+ const subtag = subtags[index];
1999
+ if (/^(?:[a-z]{2}|\d{3})$/i.test(subtag)) return { region: subtag.toUpperCase(), index };
2000
+ if (!/^[a-z]{3,4}$/i.test(subtag)) return void 0;
2001
+ }
2002
+ return void 0;
2003
+ }
2004
+ function languageTagProblem(tag) {
2005
+ if (/^[ix]-/i.test(tag)) return void 0;
2006
+ const parsed = canonical(tag);
2007
+ if (parsed === void 0) return { message: `'${tag}' is not a well-formed BCP 47 language tag (subtags are letters and digits joined by hyphens, such as 'en-US' or 'zh-Hans').`, caseOnly: false };
2008
+ const found = regionSubtag(tag);
2009
+ if (found) {
2010
+ const alias = REGION_ALIASES[found.region];
2011
+ if (alias || !assignedRegion(found.region)) {
2012
+ const subtags = tag.split("-");
2013
+ subtags[found.index] = alias ?? found.region;
2014
+ const suggestion = alias ? canonical(subtags.join("-")) : void 0;
2015
+ return {
2016
+ message: `'${tag}' names the region '${found.region}', which is not an assigned region${suggestion ? `; '${suggestion}' is the tag for ${new Intl.DisplayNames(["en"], { type: "region" }).of(alias)}` : ""}.`,
2017
+ ...suggestion ? { suggestion } : {},
2018
+ caseOnly: false
2019
+ };
2020
+ }
2021
+ }
2022
+ if (parsed === tag) return void 0;
2023
+ if (parsed.toLowerCase() === tag.toLowerCase())
2024
+ return { message: `'${tag}' is not in canonical case; the canonical form is '${parsed}' (language lower case, script title case, region upper case).`, suggestion: parsed, caseOnly: true };
2025
+ const replacement = found && regionSubtag(parsed)?.region;
2026
+ if (found && replacement && replacement !== found.region) {
2027
+ const subtags = tag.split("-");
2028
+ subtags[found.index] = replacement;
2029
+ return { message: `'${tag}' names the region '${found.region}', which is deprecated; use '${replacement}'.`, suggestion: subtags.join("-"), caseOnly: false };
2030
+ }
2031
+ return void 0;
2032
+ }
2033
+
2034
+ // src/rules-language.ts
2035
+ var languageTagRule = rule(
2036
+ "language-tag",
2037
+ "content",
2038
+ "warning",
2039
+ "A language tag is malformed, uses an unassigned region or is not in canonical case.",
2040
+ "Engines read the deck language and each run's `lang` as BCP 47 tags to choose script fonts, text direction, proofing language and the PowerPoint `lang` attribute. A tag such as `en-a`, or a region that does not exist (`en-UK`; the United Kingdom is `GB`), is guessed at or dropped, and a wrong-case tag (`EN-us`) is not what a locale lookup expects.",
2041
+ {
2042
+ standard: "BCP 47 (RFC 5646, section 2.1) and the platform locale data (Intl.getCanonicalLocales, Intl.DisplayNames)",
2043
+ approximations: "Checks the root `language` (a tag string, or the `bcp47` of an inline Language object) and `lang` on the TextRun objects of titles, subtitles, tags, text, lists, tables, quotes, metrics and timelines. A region is assigned when the runtime's CLDR data names it (ISO 3166-1 codes and UN M.49 areas such as 419); private-use regions (`AA`, `QM`-`QZ`, `XA`-`XZ`) and `ZZ` are accepted, as are private-use (`x-...`) tags. Only the spelling of a tag is checked, not whether the language subtag is assigned or whether the language suits the text. The schema rejects tags with characters other than letters, digits and hyphens (`en_US` is a schema error, with no suggestion), and `en-UK` as the deck language is already an error of the schema rule, so those are not reported twice."
2044
+ }
2045
+ );
2046
+ var report = (context, tag, path, slide) => {
2047
+ if (typeof tag !== "string") return;
2048
+ const problem = languageTagProblem(tag);
2049
+ if (!problem) return;
2050
+ context.report(languageTagRule, {
2051
+ path,
2052
+ slide,
2053
+ message: problem.message,
2054
+ help: problem.suggestion ? `Write '${problem.suggestion}'.` : 'Write a well-formed BCP 47 tag with an assigned region, such as "en-US", "pt-BR" or "es-419".',
2055
+ ...problem.suggestion ? { fixes: [{ id: "use-canonical-tag", title: `Use '${problem.suggestion}'`, kind: "patch", safe: problem.caseOnly, patch: [{ op: "replace", path, value: problem.suggestion }] }] } : {}
2056
+ });
2057
+ };
2058
+ var isEnUk = (tag) => typeof tag === "string" && tag.toLowerCase() === "en-uk";
2059
+ var languageTagRules = [
2060
+ {
2061
+ info: languageTagRule,
2062
+ run(context) {
2063
+ const language = context.document.language;
2064
+ if (typeof language === "string") {
2065
+ if (!isEnUk(language)) report(context, language, "/language");
2066
+ } else if (language !== void 0 && !isEnUk(rec(language).bcp47)) report(context, rec(language).bcp47, "/language/bcp47");
2067
+ for (const slide of context.slides)
2068
+ for (const tv of slide.texts)
2069
+ if (typeof tv.value !== "string") for (const run of runsOf(tv)) report(context, run.style.lang, `${run.path}/lang`, slide);
2070
+ }
2071
+ }
2072
+ ];
2073
+
1947
2074
  // src/rules-layout-content.ts
1948
2075
  var PX_TO_PT2 = 0.75;
1949
2076
  var round2 = (value, places = 1) => Math.round(value * 10 ** places) / 10 ** places;
@@ -2287,13 +2414,14 @@ var formatRules = [
2287
2414
  rule("caption-unsupported-payload", "format", "error", "A caption is on a payload that cannot carry one.", "A caption belongs to exactly one image, chart, table or video; a group, text or code block has no place to draw it.", { standard: SCHEMA }),
2288
2415
  rule("image-option-unsupported-payload", "format", "error", "An image option is on a payload that is not an image.", "`fit`, `focus`, the image treatments (`shape`, `cornerRadius`, `border`, `opacity`, `recolor`, `overlay`, `aspectRatio`) and `placement` describe how a picture is drawn; a group, text or chart block has no picture for them to apply to.", { standard: SCHEMA }),
2289
2416
  rule("image-placement-invalid", "format", "error", "An image placement cannot be honoured.", "A placed image bleeds to a slide edge and the rest of the slide composes beside it. Only a top-level block (slides.N.blocks.I) can do that, and a slide edge holds one placed image; a block in a group or a promoted region, or a second block on the same edge, has nowhere to go.", { standard: SCHEMA }),
2417
+ rule("layout-placeholder-group", "format", "error", "A layout record's placeholder group breaks a nesting rule.", "A placeholder group arranges body regions inside one cell of its layout record. Groups nest at most three levels inside a record, and a group holds body regions and groups only: the headings (title, subtitle, tag) stay at the record's top level, where the engine places them. Composition cannot use a record that breaks either rule.", { standard: SCHEMA }),
2290
2418
  rule("dataset-unknown", "format", "error", "A chart or table names a dataset that does not exist.", "The data cannot be drawn without the dataset it refers to.", { standard: SCHEMA }),
2291
2419
  rule("dataset-field-unknown", "format", "error", "A table asks a dataset for a column it does not have.", "The column cannot be shown when the dataset has no column of that name.", { standard: SCHEMA }),
2292
2420
  rule("data-column-duplicate", "format", "error", "A dataset or table has two columns with the same name.", "Series, mappings and fields address columns by name, so a repeated name makes them ambiguous.", { standard: SCHEMA }),
2293
2421
  rule("chart-mapping-unknown-column", "format", "error", "A chart mapping names a column the data does not have.", "The series cannot be built from a column that is not there.", { standard: SCHEMA }),
2294
2422
  rule("number-format-invalid", "format", "error", "A number format cannot be read.", "A format code that no engine can parse is drawn as the plain number or fails to export.", { standard: "Excel number format codes, as far as OPF supports them" }),
2295
2423
  rule("variable-invalid-value", "format", "error", "A variable value does not match its declared type.", "A number variable given text, or a date given a non-date, cannot be filled into the deck.", { standard: SCHEMA }),
2296
- rule("variable-unknown-builtin", "format", "error", "A token names a built-in variable that does not exist.", "The `deck.`, `speaker.` and `organization.` names (and `speakers`) are reserved for built-in variables; a name under them that is not a built-in can never be filled, so the token would show literally.", { standard: SCHEMA }),
2424
+ rule("variable-unknown-builtin", "format", "error", "A token names a built-in variable that does not exist.", "The `deck.`, `speaker.`, `organization.` and `slide.` names (and `speakers`) are reserved for built-in variables; a name under them that is not a built-in can never be filled, so the token would show literally. The slide-scoped `slide.number`, `slide.section` and `deck.slideCount` are inline tokens only, so `var:slide.*` and `var:deck.slideCount` are reported too.", { standard: SCHEMA }),
2297
2425
  rule("chart-highlight-unknown-name", "format", "error", "A chart highlight names a series or category the chart does not have.", "`chart.highlight` emphasises marks by name; a name that matches no series or category in the chart data highlights nothing, so the chart is not drawn as authored.", { standard: SCHEMA }),
2298
2426
  rule("variable-format", "format", "error", "A variable cannot be formatted.", "A number or date format that does not apply to the value leaves the token unreadable.", { standard: SCHEMA }),
2299
2427
  rule(
@@ -2377,11 +2505,11 @@ var dataRules = [
2377
2505
  rule("chart-highlight-adapted", "content", "warning", "A chart highlight is adapted by the chart type.", "A highlight the chart type cannot show as authored (a category highlight on a chart without categories, for example) is adapted by every engine, so the emphasis is not exactly what was written.", { standard: SCHEMA }),
2378
2506
  rule("code-highlight-out-of-range", "content", "warning", "A code highlight marks a line the code does not have.", "Marked lines count from 1 by line break in code.source; an entry past the last line marks nothing.", { standard: SCHEMA }),
2379
2507
  rule("code-highlight-range-reversed", "content", "warning", "A code highlight range ends before it starts.", "A range written end before start marks nothing; write the smaller line number first.", { standard: SCHEMA }),
2380
- rule("variable-builtin-missing", "content", "warning", "A built-in variable has no value in this document.", "A `{{deck.*}}`, `{{speaker.*}}` or `{{organization.*}}` token whose field the document does not set resolves to nothing, so the text shows a gap.", { standard: SCHEMA }),
2508
+ rule("variable-builtin-missing", "content", "warning", "A built-in variable has no value in this document.", "A `{{deck.*}}`, `{{speaker.*}}` or `{{organization.*}}` token whose field the document does not set resolves to nothing, so the text shows a gap. `{{slide.section}}` is checked per slide: a slide without a `section` that uses it, in its own text or in the header or footer it inherits, is reported at that slide.", { standard: SCHEMA }),
2381
2509
  rule("duration-outside-narrative", "content", "warning", "The target duration is outside the range the narrative suits.", "A narrative records the talk lengths it suits. A deck whose duration is outside that range probably needs a different narrative or a different length; nothing is drawn or exported differently.", { standard: SCHEMA }),
2382
2510
  rule("narrative-duration-range", "content", "warning", "An inline narrative duration range has min greater than max.", "A range with its bounds swapped matches no duration, so the duration check cannot help.", { standard: "spec/schemas/narrative.schema.json" })
2383
2511
  ];
2384
- var implementations = [...accessibilityRules, ...layoutContentRules];
2512
+ var implementations = [...accessibilityRules, ...layoutContentRules, ...languageTagRules];
2385
2513
  var ruleImplementations = implementations;
2386
2514
  var infos = [
2387
2515
  ...formatRules,
@@ -3117,6 +3245,67 @@ function parseSource(source) {
3117
3245
  };
3118
3246
  }
3119
3247
 
3248
+ // src/script-measurement.ts
3249
+ function scriptProfile(presentation, slideIndex, options, language) {
3250
+ let resolved;
3251
+ try {
3252
+ resolved = resolveScriptFonts(presentation, { slideIndex, ...options.catalogs !== void 0 ? { catalogs: options.catalogs } : {}, ...language === void 0 ? {} : { language } });
3253
+ } catch {
3254
+ resolved = void 0;
3255
+ }
3256
+ const slots = (role) => {
3257
+ const latin = options.fontFamilies[role];
3258
+ const slot = (key) => !resolved || resolved.sources[key] === "latin" || resolved.sources[key] === "schemeFamily" ? latin : resolved[role][key] ?? latin;
3259
+ return { latin, eastAsian: slot("eastAsian"), complexScript: slot("complexScript") };
3260
+ };
3261
+ return {
3262
+ heading: slots("heading"),
3263
+ body: slots("body"),
3264
+ ...resolved?.supplement ? { supplement: resolved.supplement } : {},
3265
+ script: resolved?.script ?? "Zzzz",
3266
+ ...resolved ? { scriptRole: resolved.scriptRole, bcp47: resolved.bcp47, lang: resolved.lang, languageSource: resolved.languageSource, direction: resolved.direction } : {},
3267
+ rtl: resolved?.rtl === true,
3268
+ serif: options.serif
3269
+ };
3270
+ }
3271
+ function slideScriptMeasurement(presentation, slideIndex, measurement, options) {
3272
+ const forScripts = measurement.forScripts;
3273
+ if (typeof forScripts !== "function") return measurement;
3274
+ const deckProfile = scriptProfile(presentation, slideIndex, options);
3275
+ const deck = forScripts.call(measurement, deckProfile);
3276
+ const languages = /* @__PURE__ */ new Map();
3277
+ const forLanguage = (lang) => {
3278
+ if (typeof lang !== "string" || !lang) return void 0;
3279
+ if (!languages.has(lang)) {
3280
+ let own2;
3281
+ try {
3282
+ const profile = scriptProfile(presentation, slideIndex, options, lang);
3283
+ own2 = profile.languageSource === "option" && profile.bcp47?.toLowerCase() !== deckProfile.bcp47?.toLowerCase() ? forScripts.call(measurement, profile) : void 0;
3284
+ } catch {
3285
+ own2 = void 0;
3286
+ }
3287
+ languages.set(lang, own2);
3288
+ }
3289
+ return languages.get(lang);
3290
+ };
3291
+ const pick = (style) => forLanguage(style?.lang) ?? deck;
3292
+ const scripted = { ...deck, measure: (text, size, style) => pick(style).measure(text, size, style) };
3293
+ if (typeof deck.resolveStyle === "function") scripted.resolveStyle = (style) => deck.resolveStyle.call(deck, style);
3294
+ if (typeof deck.outlineBounds === "function")
3295
+ scripted.outlineBounds = (text, size, style) => {
3296
+ const own2 = pick(style);
3297
+ return typeof own2.outlineBounds === "function" ? own2.outlineBounds(text, size, style) : deck.outlineBounds.call(deck, text, size, style);
3298
+ };
3299
+ return scripted;
3300
+ }
3301
+ function resolveSlideScriptMeasurement(presentation, slideIndex, measurement, options = {}) {
3302
+ if (typeof measurement.forScripts !== "function") return measurement;
3303
+ const { fontScheme } = resolveDesignRecords(presentation, slideIndex, options.catalogs !== void 0 ? { catalogs: options.catalogs } : {});
3304
+ const resolved = resolveFontFamilies(fontScheme);
3305
+ const fontFamilies = { heading: options.fontFamilies?.heading ?? resolved.heading, body: options.fontFamilies?.body ?? resolved.body };
3306
+ return slideScriptMeasurement(presentation, slideIndex, measurement, { ...options.catalogs !== void 0 ? { catalogs: options.catalogs } : {}, fontFamilies, serif: fontScheme.type === "serif" });
3307
+ }
3308
+
3120
3309
  // src/slide-context.ts
3121
3310
  var rec2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
3122
3311
  function resolveSlideContext(presentation, index, options = {}) {
@@ -3152,7 +3341,8 @@ function resolveSlideContext(presentation, index, options = {}) {
3152
3341
  ...options.fonts?.textMeasurement ? { textMeasurement: options.fonts.textMeasurement } : {},
3153
3342
  ...options.date !== void 0 ? { date: options.date } : {}
3154
3343
  };
3155
- return { options: composeOptions, diagnostics, resolved: { theme, colorScheme, fontScheme, fontSchemePath: records.fontSchemePath, ...layout ? { layout } : {}, provenance: { ...layoutProvenance ? { layout: layoutProvenance } : {}, ...records.provenance } } };
3344
+ const resolvedSlide = resolveSlideVariables(slide, { slideNumber: composeOptions.slideNumber, slideCount: composeOptions.slideCount });
3345
+ return { slide: resolvedSlide, options: composeOptions, diagnostics, resolved: { theme, colorScheme, fontScheme, fontSchemePath: records.fontSchemePath, ...layout ? { layout } : {}, provenance: { ...layoutProvenance ? { layout: layoutProvenance } : {}, ...records.provenance } } };
3156
3346
  }
3157
3347
 
3158
3348
  // src/rule-slides.ts
@@ -3188,10 +3378,17 @@ function buildSlides(document, options) {
3188
3378
  const slides = Array.isArray(document.slides) ? document.slides : [];
3189
3379
  return slides.map((slide, index) => {
3190
3380
  const path = pointer2("slides", index);
3191
- const measurement = lazy(() => measurementFor(options, index));
3381
+ const baseContext = lazy(() => resolveSlideContext(document, index, { catalogs: options.catalogs, date: "2000-01-01" }));
3382
+ const measurement = lazy(() => {
3383
+ const host = measurementFor(options, index);
3384
+ if (!host) return void 0;
3385
+ const { resolved: resolved2 } = baseContext();
3386
+ return slideScriptMeasurement(document, index, host, { catalogs: options.catalogs, fontFamilies: resolveFontFamilies(resolved2.fontScheme), serif: resolved2.fontScheme.type === "serif" });
3387
+ });
3192
3388
  const resolved = lazy(() => {
3193
3389
  const textMeasurement = measurement();
3194
- return resolveSlideContext(document, index, { catalogs: options.catalogs, ...textMeasurement ? { fonts: { textMeasurement } } : {}, date: "2000-01-01" });
3390
+ const slideContext = baseContext();
3391
+ return textMeasurement ? { ...slideContext, options: { ...slideContext.options, textMeasurement } } : slideContext;
3195
3392
  });
3196
3393
  const design = lazy(() => resolveDesign(document, index, resolved()));
3197
3394
  const layout = lazy(() => resolved().options.layout);
@@ -3199,14 +3396,14 @@ function buildSlides(document, options) {
3199
3396
  const result = { layoutDiagnostics: [] };
3200
3397
  const compose = (input, withLayout) => composeSlide(input, { ...resolved().options, layout: withLayout });
3201
3398
  try {
3202
- result.composition = compose(slide, layout());
3399
+ result.composition = compose(resolved().slide, layout());
3203
3400
  result.layoutDiagnostics.push(...result.composition.diagnostics.map((diagnostic) => ({ diagnostic, strict: false })));
3204
3401
  } catch (error) {
3205
3402
  if (error instanceof OPFCompositionError) {
3206
3403
  result.layoutDiagnostics.push(...error.diagnostics.map((diagnostic) => ({ diagnostic, strict: true })));
3207
3404
  try {
3208
3405
  const relaxedLayout = layout();
3209
- result.composition = compose(relax(slide), relaxedLayout ? relax(relaxedLayout) : void 0);
3406
+ result.composition = compose(relax(resolved().slide), relaxedLayout ? relax(relaxedLayout) : void 0);
3210
3407
  } catch (second) {
3211
3408
  result.layoutError = second instanceof Error ? second.message : String(second);
3212
3409
  }
@@ -3248,12 +3445,12 @@ var OPFValidationError = class extends Error {
3248
3445
  report;
3249
3446
  /** The error-severity findings of the report. */
3250
3447
  findings;
3251
- constructor(report) {
3252
- const errors = report.findings.filter((entry) => entry.severity === "error");
3448
+ constructor(report2) {
3449
+ const errors = report2.findings.filter((entry) => entry.severity === "error");
3253
3450
  const first = errors[0];
3254
3451
  super(first ? `OPF validation failed at ${first.path || "/"}: ${first.message}` : "OPF validation failed");
3255
3452
  this.name = "OPFValidationError";
3256
- this.report = report;
3453
+ this.report = report2;
3257
3454
  this.findings = errors;
3258
3455
  }
3259
3456
  };
@@ -3486,8 +3683,8 @@ function validate(input, options = {}) {
3486
3683
  }
3487
3684
  function assertValid(value, options = {}) {
3488
3685
  if (typeof value === "string") throw new TypeError("assertValid takes a parsed presentation. Use validate(text) for JSON text.");
3489
- const report = validate(value, options);
3490
- if (!report.valid) throw new OPFValidationError(report);
3686
+ const report2 = validate(value, options);
3687
+ if (!report2.valid) throw new OPFValidationError(report2);
3491
3688
  }
3492
3689
  function validateCatalogRecord(kind, value) {
3493
3690
  const kinds = Object.keys(catalogSchemaNames);
@@ -3508,8 +3705,8 @@ function validateCatalogRecord(kind, value) {
3508
3705
  return { valid: tally.error === 0, schemaValid: engine.valid, findings, counts: tally, checks };
3509
3706
  }
3510
3707
  function assertValidCatalogRecord(kind, value) {
3511
- const report = validateCatalogRecord(kind, value);
3512
- if (!report.valid) throw new OPFValidationError(report);
3708
+ const report2 = validateCatalogRecord(kind, value);
3709
+ if (!report2.valid) throw new OPFValidationError(report2);
3513
3710
  }
3514
3711
 
3515
- export { DEFAULT_CHART_PALETTE, DEFAULT_VALIDATION_THRESHOLDS, OPFValidationError, assertValid, assertValidCatalogRecord, findValidationRule, resolveSlideContext, validate, validateCatalogRecord, validationCategories, validationRules };
3712
+ export { DEFAULT_CHART_PALETTE, DEFAULT_VALIDATION_THRESHOLDS, OPFValidationError, assertValid, assertValidCatalogRecord, findValidationRule, parseSource, resolveSlideContext, resolveSlideScriptMeasurement, validate, validateCatalogRecord, validationCategories, validationRules };
@@ -1,4 +1,4 @@
1
- import { chartNumber } from './chunk-O744SXXA.js';
1
+ import { chartNumber } from './chunk-CW7GEAV3.js';
2
2
 
3
3
  // src/data.ts
4
4
  var OPFDataImportError = class extends Error {
@@ -138,7 +138,10 @@ function importData(input, options) {
138
138
  if (circular && numeric < 0) fail(`Row ${i + 1}: pie and donut values cannot be negative.`);
139
139
  return numeric;
140
140
  }));
141
- return { chart: { type: options.chartType ?? "column", data: { columns: names, rows } } };
141
+ return { chart: { type: options.chartType ?? "column", data: {
142
+ columns: names,
143
+ rows
144
+ } } };
142
145
  }
143
146
 
144
147
  export { OPFDataImportError, importData, parseTabularData };
@@ -1,5 +1,5 @@
1
- import { assertValid, resolveSlideContext } from './chunk-QDI75P2M.js';
2
- import { composeSlide, codeHighlightSlice, tableRowBoundaries, visitContentPayloads, sliceNumberedItems } from './chunk-O744SXXA.js';
1
+ import { assertValid, resolveSlideScriptMeasurement, resolveSlideContext } from './chunk-NLNYNBTS.js';
2
+ import { resolveSlideVariables, usesSlideBuiltin, composeSlide, codeHighlightSlice, tableRowBoundaries, visitContentPayloads, slideTokenSpans, sliceNumberedItems } from './chunk-CW7GEAV3.js';
3
3
  import { checkCatalogsOption } from './chunk-SPFV27O5.js';
4
4
 
5
5
  // src/pagination.ts
@@ -39,6 +39,10 @@ var deckContext = (presentation) => {
39
39
  const deck = isRecord(presentation) ? presentation : {};
40
40
  return { ...Array.isArray(deck.references) ? { references: deck.references } : {}, ...isRecord(deck.datasets) ? { datasets: deck.datasets } : {} };
41
41
  };
42
+ function keepTokensWhole(boundaries, text) {
43
+ const spans = slideTokenSpans(text);
44
+ return spans.length ? boundaries.filter((boundary) => !spans.some(([start, end]) => boundary > start && boundary < end)) : boundaries;
45
+ }
42
46
  function leafFor(path, field, value) {
43
47
  const leaf = { path, field, value };
44
48
  let text;
@@ -62,7 +66,7 @@ function leafFor(path, field, value) {
62
66
  }
63
67
  if (text !== void 0) {
64
68
  leaf.unit = "utf16";
65
- leaf.boundaries = [0, ...Array.from(graphemes.segment(text), (segment) => segment.index + segment.segment.length)];
69
+ leaf.boundaries = keepTokensWhole([0, ...Array.from(graphemes.segment(text), (segment) => segment.index + segment.segment.length)], text);
66
70
  } else if (field === "items" || field === "bullets") {
67
71
  leaf.unit = "items";
68
72
  leaf.boundaries = Array.from({ length: value.length + 1 }, (_, i) => i);
@@ -103,11 +107,25 @@ function paginateSlide(input, options = {}) {
103
107
  };
104
108
  source = withReadability(source);
105
109
  let evaluations = 0;
106
- const { fonts, minFontSize: _minFontSize, maxSlides: _maxSlides, reservedIds: _reservedIds, ...engineOptions } = options;
107
- const composeOptions = { ...engineOptions, ...fonts?.textMeasurement ? { textMeasurement: fonts.textMeasurement } : {} };
110
+ const { fonts, catalogs, minFontSize: _minFontSize, maxSlides: _maxSlides, reservedIds: _reservedIds, ...engineOptions } = options;
111
+ const textMeasurement = fonts?.textMeasurement && options.presentation !== void 0 ? resolveSlideScriptMeasurement(options.presentation, sourceIndex, fonts.textMeasurement, { ...catalogs !== void 0 ? { catalogs } : {}, ...options.fontFamilies ? { fontFamilies: options.fontFamilies } : {} }) : fonts?.textMeasurement;
112
+ const composeOptions = { ...engineOptions, ...textMeasurement ? { textMeasurement } : {} };
113
+ const readableLayout = (layout) => {
114
+ if (!isRecord(layout) || !Array.isArray(layout.placeholders) || !layout.placeholders.some((entry) => isRecord(entry) && entry.type === "group")) return layout;
115
+ const visit = (entries) => entries.map((entry) => {
116
+ if (!isRecord(entry) || entry.type !== "group") return entry;
117
+ const own = isRecord(entry.composition) ? entry.composition : {};
118
+ return { ...entry, composition: { ...own, ...typeof own.minFontSize === "number" ? { minFontSize: Math.max(minFontSize, own.minFontSize) } : {}, overflow: "warn" }, placeholders: Array.isArray(entry.placeholders) ? visit(entry.placeholders) : entry.placeholders };
119
+ });
120
+ return { ...layout, placeholders: visit(layout.placeholders) };
121
+ };
122
+ if (options.layout !== void 0) composeOptions.layout = readableLayout(options.layout);
123
+ const presentationSlides = options.presentation?.slides;
124
+ const slideCount = options.slideCount ?? (Array.isArray(presentationSlides) && presentationSlides.length ? presentationSlides.length : void 0);
108
125
  const geometry = (slide, pageIndex = 0) => {
109
126
  if (++evaluations > 2e4) throw new OPFPaginationError("Pagination exceeded its layout evaluation limit. Split the input into smaller sections.");
110
- return composeSlide(withReadability(slide, true), { ...composeOptions, slideNumber: (options.slideNumber ?? sourceIndex + 1) + pageIndex });
127
+ const slideNumber = (options.slideNumber ?? sourceIndex + 1) + pageIndex;
128
+ return composeSlide(resolveSlideVariables(withReadability(slide, true), { slideNumber, ...slideCount !== void 0 ? { slideCount } : {} }), { ...composeOptions, slideNumber });
111
129
  };
112
130
  const initial = geometry(source);
113
131
  const fit = (diagnostics) => diagnostics.filter((issue) => issue.code === "text-overflow" || issue.code === "small-cell" || !!initial.furniture?.diagnostics.includes(issue));
@@ -123,7 +141,14 @@ function paginateSlide(input, options = {}) {
123
141
  if (!fit(initial.diagnostics).length) return { slides: [source], pages: [{ slideIndex: sourceIndex, mappings: initial.items.map((item) => ({ sourcePath: item.path, outputPath: item.path })), ...initial.furniture ? { repeatedMappings: repeatedMappings(0) } : {} }] };
124
142
  const headerIssues = initial.diagnostics.filter((issue) => headingFields.has(issue.path.slice(sourceBase.length + 1)) || initial.furniture?.diagnostics.includes(issue));
125
143
  if (headerIssues.length) throw new OPFPaginationError("Repeated headings or header/footer content cannot fit or resolve. Change the repeated content or slide design before pagination.", headerIssues);
126
- const leaves = initial.items.filter((item) => !headingFields.has(item.field)).map((item) => leafFor(item.path, item.field, item.value));
144
+ const tokens = resolveSlideVariables(source, { slideNumber: 1, slideCount: 1 }) !== source;
145
+ const sourceValue = (path, fallback) => {
146
+ if (!tokens || !path.startsWith(`${sourceBase}.`)) return fallback;
147
+ let node = source;
148
+ for (const key of path.slice(sourceBase.length + 1).split(".")) node = isRecord(node) || Array.isArray(node) ? node[key] : void 0;
149
+ return node === void 0 ? fallback : node;
150
+ };
151
+ const leaves = initial.items.filter((item) => !headingFields.has(item.field)).map((item) => leafFor(item.path, item.field, sourceValue(item.path, item.value)));
127
152
  const leafPaths = new Set(leaves.map((leaf) => leaf.path));
128
153
  const slides = [], pages = [];
129
154
  let selected = /* @__PURE__ */ new Map();
@@ -246,10 +271,6 @@ function paginateSlide(input, options = {}) {
246
271
  if (!slides.length) throw new OPFPaginationError("No body content can be paginated.", initial.diagnostics);
247
272
  return { slides, pages };
248
273
  }
249
- var usesSlideTotal = (presentation) => [presentation, ...presentation.slides].some((owner) => ["header", "footer"].some((kind) => ["left", "center", "right"].some((zone) => {
250
- const content = owner?.design?.[kind]?.[zone];
251
- return content?.slideNumber === true && typeof content.slideNumberFormat === "string" && content.slideNumberFormat.includes("{total}");
252
- })));
253
274
  function paginate(input, options = {}) {
254
275
  checkCatalogsOption(options.catalogs, "paginate");
255
276
  assertValid(input, { only: ["format"] });
@@ -270,7 +291,7 @@ function paginate(input, options = {}) {
270
291
  }
271
292
  }
272
293
  if (output2.length >= maxSlides) throw new OPFPaginationError(`Pagination needs more than ${maxSlides} slides. No partial result was returned.`);
273
- const result2 = paginateSlide(slide, { ...context.options, textRasterPadding: options.textRasterPadding, fonts: options.fonts, presentation, slideIndex: index, slideNumber: output2.length + 1, slideCount: slideCount2, date: options.date, maxSlides: maxSlides - output2.length, minFontSize: options.minFontSize, reservedIds });
294
+ const result2 = paginateSlide(slide, { ...context.options, textRasterPadding: options.textRasterPadding, fonts: options.fonts, ...options.catalogs ? { catalogs: options.catalogs } : {}, presentation, slideIndex: index, slideNumber: output2.length + 1, slideCount: slideCount2, date: options.date, maxSlides: maxSlides - output2.length, minFontSize: options.minFontSize, reservedIds });
274
295
  const outputStart = output2.length;
275
296
  result2.pages.forEach((page, pageIndex) => {
276
297
  const remap = (mapping) => ({ ...mapping, outputPath: mapping.outputPath.replace(/^slides\.\d+/, `slides.${outputStart + pageIndex}`) });
@@ -281,9 +302,10 @@ function paginate(input, options = {}) {
281
302
  });
282
303
  return { output: output2, pages: pages2 };
283
304
  };
305
+ const usesSlideCount = usesSlideBuiltin(presentation, "deck.slideCount");
284
306
  let slideCount = presentation.slides.length, result = run(slideCount);
285
- for (let attempt = 0; usesSlideTotal(presentation) && result.output.length !== slideCount; attempt++) {
286
- if (attempt >= 3) throw new OPFPaginationError("Slide-number totals did not converge. Change the slide-number format or split the input.");
307
+ for (let attempt = 0; usesSlideCount && result.output.length !== slideCount; attempt++) {
308
+ if (attempt >= 3) throw new OPFPaginationError("The slide count did not converge. Change the text around {{deck.slideCount}} or split the input.");
287
309
  slideCount = result.output.length;
288
310
  result = run(slideCount);
289
311
  }
@@ -1,4 +1,4 @@
1
- import { validate } from './chunk-QDI75P2M.js';
1
+ import { validate } from './chunk-NLNYNBTS.js';
2
2
 
3
3
  // src/patch.ts
4
4
  var OPFPatchError = class extends Error {
@@ -1,4 +1,4 @@
1
- import { validate } from './chunk-QDI75P2M.js';
1
+ import { validate } from './chunk-NLNYNBTS.js';
2
2
 
3
3
  // src/convert/shared.ts
4
4
  var CONTENT_KEYS = ["text", "items", "bullets", "image", "video", "chart", "table", "code", "metric", "quote", "timeline"];