@json-to-office/jto-ops 6.8.0 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1061,6 +1061,7 @@ var DocxFormatAdapter = class {
1061
1061
  options.services
1062
1062
  );
1063
1063
  const prepared = !hasPlugins && options.prepared?.format === "docx" ? options.prepared : void 0;
1064
+ const pluginPrepared = hasPlugins && options.prepared?.format === "docx" ? options.prepared : void 0;
1064
1065
  const resolved = prepared ? void 0 : await this.resolveThemes(options);
1065
1066
  const requestedTheme = resolved?.requested;
1066
1067
  const customThemes = resolved?.customThemes;
@@ -1136,6 +1137,7 @@ var DocxFormatAdapter = class {
1136
1137
  requestedTheme,
1137
1138
  customThemes
1138
1139
  );
1140
+ const usable = preparedFor(pluginPrepared, parsed);
1139
1141
  const result = await generator.generateBuffer(docDefinition, {
1140
1142
  validation: {
1141
1143
  allowUnknownFields: options.validation?.allowUnknownFields
@@ -1144,10 +1146,12 @@ var DocxFormatAdapter = class {
1144
1146
  generatedAt: options.generatedAt,
1145
1147
  baseDir: options.baseDir,
1146
1148
  renderer: options.renderer,
1147
- svgRasterFallback: options.svgRasterFallback
1149
+ svgRasterFallback: options.svgRasterFallback,
1150
+ ...usable && { prepared: usable }
1148
1151
  });
1149
- emitGenerationWarnings(result.warnings ?? []);
1150
- options.warnings?.push(...toGenerationWarnings(result.warnings));
1152
+ const emitted = withoutPreparedWarnings(result.warnings ?? [], usable);
1153
+ emitGenerationWarnings(emitted);
1154
+ options.warnings?.push(...toGenerationWarnings(emitted));
1151
1155
  return result.buffer;
1152
1156
  },
1153
1157
  getStandardDefinition: generator.expandStandardDefinition ? async (config) => {
@@ -1230,15 +1234,35 @@ var DocxFormatAdapter = class {
1230
1234
  resolved.requested,
1231
1235
  resolved.customThemes
1232
1236
  );
1233
- const prepared = core.prepareDocxQualityDocument(
1234
- normalized.document,
1235
- {
1236
- customThemes: normalized.customThemes,
1237
+ const prepared = options.plugins && options.plugins.length > 0 ? await (async () => {
1238
+ let generator = core.createDocumentGenerator({
1239
+ theme: resolved.requested,
1240
+ customThemes: resolved.requested ? {
1241
+ ...resolved.customThemes,
1242
+ [CLI_THEME_KEY]: resolved.requested
1243
+ } : resolved.customThemes,
1244
+ services: withRequestedServices(
1245
+ buildDocxServices(),
1246
+ options.services
1247
+ ),
1248
+ baseDir: options.baseDir,
1237
1249
  fonts: options.fonts,
1238
- renderer: options.renderer ?? documentRenderer(parsed),
1239
- warnings
1240
- }
1241
- );
1250
+ validation: {
1251
+ allowUnknownFields: options.validation?.allowUnknownFields
1252
+ },
1253
+ renderer: options.renderer
1254
+ });
1255
+ for (const plugin of options.plugins)
1256
+ generator = generator.addComponent(plugin);
1257
+ const result = await generator.prepareQuality(normalized.document);
1258
+ warnings.push(...result.warnings ?? []);
1259
+ return result.prepared;
1260
+ })() : core.prepareDocxQualityDocument(normalized.document, {
1261
+ customThemes: normalized.customThemes,
1262
+ fonts: options.fonts,
1263
+ renderer: options.renderer ?? documentRenderer(parsed),
1264
+ warnings
1265
+ });
1242
1266
  return stampPrepared(
1243
1267
  {
1244
1268
  ...prepared,
@@ -1269,7 +1293,7 @@ var DocxFormatAdapter = class {
1269
1293
  async resolveTheme(options) {
1270
1294
  const core = await import("@json-to-office/core-docx");
1271
1295
  const { requested } = await this.resolveThemes(options);
1272
- return requested ?? core.themes?.minimal ?? {};
1296
+ return requested ?? core.themes?.consulting ?? {};
1273
1297
  }
1274
1298
  /**
1275
1299
  * Resolve `theme`/`themePath` once for a whole run: `themePath` is read a
@@ -1430,6 +1454,7 @@ var PptxFormatAdapter = class {
1430
1454
  options.services
1431
1455
  );
1432
1456
  const prepared = !hasPlugins && options.prepared?.format === "pptx" ? options.prepared : void 0;
1457
+ const pluginPrepared = hasPlugins && options.prepared?.format === "pptx" ? options.prepared : void 0;
1433
1458
  const resolved = prepared ? void 0 : await this.resolveThemes(options);
1434
1459
  const requestedTheme = resolved?.requested;
1435
1460
  const customThemes = resolved?.customThemes;
@@ -1504,6 +1529,7 @@ var PptxFormatAdapter = class {
1504
1529
  requestedTheme,
1505
1530
  customThemes
1506
1531
  );
1532
+ const usable = preparedFor(pluginPrepared, parsed);
1507
1533
  const result = await generator.generateBuffer(docDefinition, {
1508
1534
  validation: {
1509
1535
  allowUnknownFields: options.validation?.allowUnknownFields
@@ -1511,9 +1537,13 @@ var PptxFormatAdapter = class {
1511
1537
  deterministic: options.deterministic,
1512
1538
  generatedAt: options.generatedAt,
1513
1539
  baseDir: options.baseDir,
1514
- renderer: options.renderer
1540
+ renderer: options.renderer,
1541
+ ...usable && { prepared: usable }
1515
1542
  });
1516
- const normalized = toGenerationWarnings(result.warnings);
1543
+ const normalized = withoutPreparedWarnings(
1544
+ toGenerationWarnings(result.warnings),
1545
+ usable
1546
+ );
1517
1547
  emitGenerationWarnings(normalized);
1518
1548
  options.warnings?.push(...normalized);
1519
1549
  return result.buffer;
@@ -1580,19 +1610,37 @@ var PptxFormatAdapter = class {
1580
1610
  resolved.requested,
1581
1611
  resolved.customThemes
1582
1612
  );
1583
- const prepared = core.preparePptxQualityDocument(
1584
- normalized.document,
1585
- {
1586
- customThemes: normalized.customThemes,
1587
- fonts: options.fonts,
1588
- services: withRequestedServices(
1589
- buildServicesFromEnv(),
1590
- options.services
1591
- ),
1592
- renderer: options.renderer ?? documentRenderer(parsed),
1593
- warnings
1594
- }
1613
+ const services = withRequestedServices(
1614
+ buildServicesFromEnv(),
1615
+ options.services
1595
1616
  );
1617
+ const prepared = options.plugins && options.plugins.length > 0 ? await (async () => {
1618
+ let generator = core.createPresentationGenerator({
1619
+ theme: resolved.requested,
1620
+ customThemes: resolved.requested ? {
1621
+ ...resolved.customThemes,
1622
+ [CLI_THEME_KEY]: resolved.requested
1623
+ } : resolved.customThemes,
1624
+ services,
1625
+ baseDir: options.baseDir,
1626
+ fonts: options.fonts,
1627
+ validation: {
1628
+ allowUnknownFields: options.validation?.allowUnknownFields
1629
+ },
1630
+ renderer: options.renderer
1631
+ });
1632
+ for (const plugin of options.plugins)
1633
+ generator = generator.addComponent(plugin);
1634
+ const result = await generator.prepareQuality(normalized.document);
1635
+ warnings.push(...result.warnings ?? []);
1636
+ return result.prepared;
1637
+ })() : core.preparePptxQualityDocument(normalized.document, {
1638
+ customThemes: normalized.customThemes,
1639
+ fonts: options.fonts,
1640
+ services,
1641
+ renderer: options.renderer ?? documentRenderer(parsed),
1642
+ warnings
1643
+ });
1596
1644
  return stampPrepared(
1597
1645
  {
1598
1646
  ...prepared,
@@ -1624,7 +1672,7 @@ var PptxFormatAdapter = class {
1624
1672
  const core = await import("@json-to-office/core-pptx");
1625
1673
  const themes = core.pptxThemes || {};
1626
1674
  const { requested } = await this.resolveThemes(options);
1627
- return requested ?? themes.minimal ?? {};
1675
+ return requested ?? themes.consulting ?? {};
1628
1676
  }
1629
1677
  /**
1630
1678
  * Resolve `theme`/`themePath` once for a whole run: `themePath` is read a
@@ -2179,7 +2227,7 @@ function authoredTextForMatch(text) {
2179
2227
  function needleSegments(text) {
2180
2228
  return authoredTextForMatch(text).split(FIELD).map(normalizeForMatch).filter((segment) => segment !== "");
2181
2229
  }
2182
- function indexDocument(pages, exclude = /* @__PURE__ */ new Set()) {
2230
+ function indexDocument(pages, exclude = /* @__PURE__ */ new Set(), order = pageOrder) {
2183
2231
  const refs = [];
2184
2232
  const fragments = [];
2185
2233
  const positions = [];
@@ -2193,13 +2241,13 @@ function indexDocument(pages, exclude = /* @__PURE__ */ new Set()) {
2193
2241
  positions.push(cursor);
2194
2242
  cursor += fragment.length;
2195
2243
  };
2196
- const orders = pages.map((page) => pageOrder(page.words, page.lines));
2197
- orders.forEach((order, pageIndex) => {
2198
- for (const index of order.upright) push(pageIndex, index);
2244
+ const orders = pages.map((page) => order(page.words, page.lines));
2245
+ orders.forEach((order2, pageIndex) => {
2246
+ for (const index of order2.upright) push(pageIndex, index);
2199
2247
  });
2200
2248
  const rotatedFrom = cursor;
2201
- orders.forEach((order, pageIndex) => {
2202
- for (const index of order.rotated) push(pageIndex, index);
2249
+ orders.forEach((order2, pageIndex) => {
2250
+ for (const index of order2.rotated) push(pageIndex, index);
2203
2251
  });
2204
2252
  return {
2205
2253
  pages,
@@ -2224,6 +2272,16 @@ function pageOrder(words2, lines = []) {
2224
2272
  rotated: [...rotated].sort((a, b) => a - b)
2225
2273
  };
2226
2274
  }
2275
+ function linedOrder(words2, lines = []) {
2276
+ const rotated = rotatedWords(words2, lines);
2277
+ const upright = words2.map((_, i) => i).filter((i) => !rotated.has(i));
2278
+ return {
2279
+ upright: groupRows(words2, upright).flatMap(
2280
+ (row) => [...row.words].sort((a, b) => words2[a].xMin - words2[b].xMin)
2281
+ ),
2282
+ rotated: [...rotated].sort((a, b) => a - b)
2283
+ };
2284
+ }
2227
2285
  function rotatedWords(words2, lines) {
2228
2286
  const rotated = new Set(
2229
2287
  words2.map((_, i) => i).filter((i) => {
@@ -2637,6 +2695,30 @@ function overprintedOccurrence(index, page, region, needle, claimed) {
2637
2695
  })
2638
2696
  };
2639
2697
  }
2698
+ function linedOccurrence(lined, body, segments, claimed) {
2699
+ const bodyFragment = /* @__PURE__ */ new Map();
2700
+ body.refs.forEach((ref, i) => {
2701
+ if (body.fragments[i] !== "")
2702
+ bodyFragment.set(`${ref.pageIndex}:${ref.word}`, i);
2703
+ });
2704
+ for (const hit of findOccurrences(lined, segments)) {
2705
+ const claims = [];
2706
+ for (const part of hit.parts)
2707
+ for (const word of part.words) {
2708
+ const i = bodyFragment.get(`${part.pageIndex}:${word}`);
2709
+ if (i === void 0) continue;
2710
+ claims.push({
2711
+ at: body.positions[i],
2712
+ end: body.positions[i] + body.fragments[i].length
2713
+ });
2714
+ }
2715
+ if (claims.length === 0 || claims.some((claim) => claimed(claim.at)))
2716
+ continue;
2717
+ const at = Math.min(...claims.map((claim) => claim.at));
2718
+ return { occurrence: { ...hit, at, end: at }, claims };
2719
+ }
2720
+ return void 0;
2721
+ }
2640
2722
  function assignInventory(pages, inventory) {
2641
2723
  const results = /* @__PURE__ */ new Map();
2642
2724
  const chromeWords = /* @__PURE__ */ new Set();
@@ -2676,6 +2758,7 @@ function assignInventory(pages, inventory) {
2676
2758
  for (const row of pageNumberRows(pages, chromeWords))
2677
2759
  claimRow(row.pageIndex, row.box);
2678
2760
  const body = indexDocument(pages, chromeWords);
2761
+ let lined;
2679
2762
  const claims = [];
2680
2763
  const claimsOver = (o) => claims.filter((claim) => o.at < claim.end && claim.at < o.end);
2681
2764
  let cursor = 0;
@@ -2705,6 +2788,19 @@ function assignInventory(pages, inventory) {
2705
2788
  sparse = overprinted.claims;
2706
2789
  }
2707
2790
  }
2791
+ if (all.length === 0 && entry.page === void 0 && !entry.optional) {
2792
+ lined ??= indexDocument(pages, chromeWords, linedOrder);
2793
+ const found = linedOccurrence(
2794
+ lined,
2795
+ body,
2796
+ segments,
2797
+ (offset) => claims.some((claim) => claim.at <= offset && offset < claim.end)
2798
+ );
2799
+ if (found) {
2800
+ all = [found.occurrence];
2801
+ sparse = found.claims;
2802
+ }
2803
+ }
2708
2804
  if (all.length === 0 && segments.length === 1 && !entry.optional) {
2709
2805
  const prefix = longestRenderedPrefix(
2710
2806
  body,
@@ -3481,13 +3577,210 @@ function requestedFontsFromFacts(format, facts, resolvedFonts) {
3481
3577
  // src/block-matrix.ts
3482
3578
  import {
3483
3579
  blockDependencies,
3580
+ blockReferencesFromDocument as blockReferencesFromDocument2,
3581
+ readBlockDefinitions as readBlockDefinitions2
3582
+ } from "@json-to-office/shared";
3583
+
3584
+ // src/matrix-inventory.ts
3585
+ import {
3586
+ blockReferencesFromDocument,
3484
3587
  readBlockDefinitions
3485
3588
  } from "@json-to-office/shared";
3589
+ function matrixUniverse(format, themes) {
3590
+ return {
3591
+ themes: [...themes],
3592
+ canvases: format === "docx" ? ["A4", "LETTER"] : ["wide169", "standard43"],
3593
+ fonts: ["design", "fallback"],
3594
+ edges: ["min", "max"]
3595
+ };
3596
+ }
3597
+ var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
3598
+ function templateFormat(name, document) {
3599
+ const root = isRecord(document) ? document.name : void 0;
3600
+ if (root === "docx" || root === "pptx") return root;
3601
+ if (name.endsWith(".docx.json")) return "docx";
3602
+ if (name.endsWith(".pptx.json")) return "pptx";
3603
+ return void 0;
3604
+ }
3605
+ function authoredDeckCanvas(props) {
3606
+ const width = typeof props.slideWidth === "number" ? props.slideWidth : 10;
3607
+ const height = typeof props.slideHeight === "number" ? props.slideHeight : 7.5;
3608
+ return Math.abs(width / height - 16 / 9) < Math.abs(width / height - 4 / 3) ? "wide169" : "standard43";
3609
+ }
3610
+ function requiredRoles(format, profile) {
3611
+ const required = profile?.rules?.[`${format}/required-chrome`]?.parameters?.required;
3612
+ return Array.isArray(required) ? required.filter((role) => typeof role === "string") : [];
3613
+ }
3614
+ function templateConditions(format, props, universe) {
3615
+ const named = typeof props.theme === "string" ? props.theme : void 0;
3616
+ const canvas = format === "pptx" ? authoredDeckCanvas(props) : void 0;
3617
+ const canvasExclusions = (reason) => canvas === void 0 ? [] : universe.canvases.filter((c) => c !== canvas).map((c) => ({ condition: `canvas ${c}`, reason }));
3618
+ if (named !== void 0 && universe.themes.includes(named)) {
3619
+ return {
3620
+ theme: named,
3621
+ conditions: {
3622
+ ...structuredClone(universe),
3623
+ ...canvas !== void 0 && { canvases: [canvas] }
3624
+ },
3625
+ exclusions: canvasExclusions(
3626
+ `slot budgets are measured on the ${canvas} slide the template is drawn on; another shape lays out, but holds fewer words`
3627
+ )
3628
+ };
3629
+ }
3630
+ const theme = named ?? "inline";
3631
+ const why = named === void 0 ? "authored against the template's own theme object" : `authored against "${named}", which is no bundled theme`;
3632
+ const exclusions = [
3633
+ ...universe.themes.map((t) => ({ condition: `theme ${t}`, reason: why })),
3634
+ ...canvasExclusions(
3635
+ "absolute geometry fitted to the template's own slide size"
3636
+ ),
3637
+ ...canvas === void 0 ? universe.canvases.filter((c) => c !== "A4").map((c) => ({
3638
+ condition: `canvas ${c}`,
3639
+ reason: "absolute geometry fitted to the template's own page"
3640
+ })) : [],
3641
+ {
3642
+ condition: "font fallback",
3643
+ reason: "lines fitted to the template's own faces; a wider face is another design, not a boundary"
3644
+ }
3645
+ ];
3646
+ return {
3647
+ theme,
3648
+ conditions: {
3649
+ themes: [theme],
3650
+ canvases: [canvas ?? "A4"],
3651
+ fonts: ["design"],
3652
+ edges: [...universe.edges]
3653
+ },
3654
+ exclusions
3655
+ };
3656
+ }
3657
+ function buildMatrixInventory(templates, options) {
3658
+ const inventory = { templates: [], entries: [] };
3659
+ for (const source of templates) {
3660
+ const format = templateFormat(source.name, source.document);
3661
+ if (!format || !isRecord(source.document)) continue;
3662
+ const props = isRecord(source.document.props) ? source.document.props : {};
3663
+ const universe = matrixUniverse(format, options.themes[format]);
3664
+ const blueprint = options.blueprints?.find(
3665
+ (b) => b.format === format && b.definitions === source.name
3666
+ );
3667
+ const profile = blueprint ? options.profiles?.[blueprint.profile] : void 0;
3668
+ const roles = requiredRoles(format, profile);
3669
+ const { theme, conditions: conditions2, exclusions } = templateConditions(
3670
+ format,
3671
+ props,
3672
+ universe
3673
+ );
3674
+ const definitions = Object.keys(readBlockDefinitions(source.document));
3675
+ const references = blockReferencesFromDocument(source.document, {
3676
+ template: source.name,
3677
+ format
3678
+ });
3679
+ const templateExclusions = [...exclusions];
3680
+ if (definitions.length > 0 && references.length === 0) {
3681
+ templateExclusions.push({
3682
+ condition: "definitions",
3683
+ reason: "the definitions do not validate, so discovery publishes none"
3684
+ });
3685
+ }
3686
+ inventory.templates.push({
3687
+ template: source.name,
3688
+ format,
3689
+ theme,
3690
+ ...blueprint && { profile: blueprint.profile },
3691
+ requiredRoles: roles,
3692
+ definitions,
3693
+ conditions: conditions2,
3694
+ exclusions: templateExclusions
3695
+ });
3696
+ for (const reference of references) {
3697
+ const slotless = Object.keys(reference.definition.slots ?? {}).length === 0;
3698
+ inventory.entries.push({
3699
+ id: `${source.name}#${reference.definitionPointer}`,
3700
+ template: source.name,
3701
+ format,
3702
+ name: reference.name,
3703
+ definitionPointer: reference.definitionPointer,
3704
+ dependencies: reference.dependencies,
3705
+ ...blueprint && { profile: blueprint.profile },
3706
+ requiredRoles: roles,
3707
+ themes: [...conditions2.themes],
3708
+ canvases: [...conditions2.canvases],
3709
+ fonts: [...conditions2.fonts],
3710
+ edges: slotless ? ["max"] : [...conditions2.edges],
3711
+ exclusions: [
3712
+ ...exclusions,
3713
+ ...slotless ? [
3714
+ {
3715
+ condition: "edge min",
3716
+ reason: "no slots, so min and max are the same document"
3717
+ }
3718
+ ] : []
3719
+ ]
3720
+ });
3721
+ }
3722
+ }
3723
+ return inventory;
3724
+ }
3725
+ function inventoryGaps(entry, universe) {
3726
+ const excluded = new Set(entry.exclusions.map((e) => e.condition));
3727
+ const gaps = [];
3728
+ const check = (kind, all, got) => {
3729
+ for (const value of all) {
3730
+ const condition = `${kind} ${value}`;
3731
+ const has = got.includes(value);
3732
+ if (has && excluded.has(condition)) gaps.push(`${condition}: both`);
3733
+ if (!has && !excluded.has(condition)) gaps.push(`${condition}: neither`);
3734
+ }
3735
+ };
3736
+ check("theme", universe.themes, entry.themes);
3737
+ check("canvas", universe.canvases, entry.canvases);
3738
+ check("font", universe.fonts, entry.fonts);
3739
+ check("edge", universe.edges, entry.edges);
3740
+ return gaps;
3741
+ }
3742
+ function describeInventory(inventory) {
3743
+ const lines = [];
3744
+ for (const template of inventory.templates) {
3745
+ lines.push(
3746
+ `${template.template} (${template.format}, theme ${template.theme}${template.profile ? `, profile ${template.profile}` : ""}): ${template.definitions.length} definition(s)`
3747
+ );
3748
+ for (const entry of inventory.entries.filter(
3749
+ (e) => e.template === template.template
3750
+ )) {
3751
+ lines.push(
3752
+ ` ${entry.name}: themes ${entry.themes.join(" ")} \xB7 canvases ${entry.canvases.join(
3753
+ " "
3754
+ )} \xB7 fonts ${entry.fonts.join(" ")} \xB7 edges ${entry.edges.join(" ")}`
3755
+ );
3756
+ const reasons = /* @__PURE__ */ new Map();
3757
+ for (const exclusion of entry.exclusions) {
3758
+ reasons.set(exclusion.reason, [
3759
+ ...reasons.get(exclusion.reason) ?? [],
3760
+ exclusion.condition
3761
+ ]);
3762
+ }
3763
+ for (const [reason, conditions2] of reasons)
3764
+ lines.push(` not ${conditions2.join(", ")}: ${reason}`);
3765
+ }
3766
+ }
3767
+ return lines.join("\n");
3768
+ }
3769
+
3770
+ // src/block-matrix.ts
3771
+ var PPTX_FALLBACK_FONTS = {
3772
+ heading: "DejaVu Sans",
3773
+ body: "DejaVu Sans"
3774
+ };
3775
+ var PPTX_CANVAS_SIZE = {
3776
+ wide169: { slideWidth: 13.333, slideHeight: 7.5 },
3777
+ standard43: { slideWidth: 10, slideHeight: 7.5 }
3778
+ };
3486
3779
  var FALLBACK_FONTS = {
3487
3780
  heading: { family: "DejaVu Sans" },
3488
3781
  body: { family: "DejaVu Sans" }
3489
3782
  };
3490
- var MATRIX_IMAGE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAABytg0kAAAAFElEQVR42mNk+M9QzwAFjDAGACPuA/8fMSCgAAAAAElFTkSuQmCC";
3783
+ var MATRIX_IMAGE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAAEklEQVR42mOIrt34HxkzoAsAAE/xFEFoJgXRAAAAAElFTkSuQmCC";
3491
3784
  var WORDS = [
3492
3785
  "performance",
3493
3786
  "delivery",
@@ -3522,7 +3815,7 @@ function widestNumber(length, seed = 0) {
3522
3815
  const integer = group(digits);
3523
3816
  return `\u2212${integer}.${"0".repeat(length - integer.length - 2)}`;
3524
3817
  }
3525
- var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
3818
+ var isRecord2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
3526
3819
  var NUMERIC_SLOTS = /* @__PURE__ */ new Set(["value", "delta", "cells"]);
3527
3820
  function stringValue(slot, name, edge, seed) {
3528
3821
  if (slot.enum && slot.enum.length > 0) {
@@ -3564,14 +3857,17 @@ function stringValue(slot, name, edge, seed) {
3564
3857
  return edge === "max" ? words(count, seed, false) : "01";
3565
3858
  return words(count, seed, sentence);
3566
3859
  }
3567
- function componentValue(name, edge, seed) {
3568
- if (name === "chart") {
3860
+ function componentValue(name, edge, seed, format = "docx") {
3861
+ const chart = name === "chart" || format === "pptx" && name === "content";
3862
+ if (chart && (name === "chart" || edge === "max")) {
3569
3863
  const categories = edge === "max" ? 8 : 2;
3570
3864
  const series = edge === "max" ? 3 : 1;
3571
3865
  return {
3572
3866
  name: "chart",
3573
3867
  props: {
3574
- type: "column",
3868
+ // The native column chart: `column` in a report, `bar` standing
3869
+ // upright in a deck.
3870
+ type: format === "pptx" ? "bar" : "column",
3575
3871
  valAxisTitle: edge === "max" ? `${words(3, seed, false)} (\u20ACm)` : "Revenue (\u20ACm)",
3576
3872
  ...edge === "max" && { catAxisTitle: words(3, seed + 3, false) },
3577
3873
  chartColors: ["primary", "secondary", "accent"].slice(0, series),
@@ -3592,6 +3888,12 @@ function componentValue(name, edge, seed) {
3592
3888
  }
3593
3889
  };
3594
3890
  }
3891
+ if (format === "pptx") {
3892
+ return {
3893
+ name: "image",
3894
+ props: { path: MATRIX_IMAGE, alt: words(6, seed, false) }
3895
+ };
3896
+ }
3595
3897
  return {
3596
3898
  name: "image",
3597
3899
  props: {
@@ -3601,7 +3903,7 @@ function componentValue(name, edge, seed) {
3601
3903
  }
3602
3904
  };
3603
3905
  }
3604
- function boundarySlotValue(slot, name, edge, seed = 0) {
3906
+ function boundarySlotValue(slot, name, edge, seed = 0, format = "docx") {
3605
3907
  switch (slot.type) {
3606
3908
  case "string":
3607
3909
  return stringValue(slot, name, edge, seed);
@@ -3611,12 +3913,18 @@ function boundarySlotValue(slot, name, edge, seed = 0) {
3611
3913
  case "boolean":
3612
3914
  return edge === "max" ? true : slot.default ?? true;
3613
3915
  case "component":
3614
- return componentValue(name, edge, seed);
3916
+ return componentValue(name, edge, seed, format);
3615
3917
  case "object": {
3616
3918
  const out = {};
3617
3919
  for (const [key, property] of Object.entries(slot.properties ?? {})) {
3618
3920
  if (edge === "min" && !property.required) continue;
3619
- const value = boundarySlotValue(property, key, edge, seed + key.length);
3921
+ const value = boundarySlotValue(
3922
+ property,
3923
+ key,
3924
+ edge,
3925
+ seed + key.length,
3926
+ format
3927
+ );
3620
3928
  if (value !== void 0) out[key] = value;
3621
3929
  }
3622
3930
  return out;
@@ -3626,21 +3934,21 @@ function boundarySlotValue(slot, name, edge, seed = 0) {
3626
3934
  const item = slot.items ?? { type: "string" };
3627
3935
  return Array.from(
3628
3936
  { length: count },
3629
- (_, i) => boundarySlotValue(item, name, edge, seed + i)
3937
+ (_, i) => boundarySlotValue(item, name, edge, seed + i, format)
3630
3938
  );
3631
3939
  }
3632
3940
  default:
3633
3941
  return void 0;
3634
3942
  }
3635
3943
  }
3636
- function boundaryInvocation(name, definition, edge, seed = 0, requiredRoles = []) {
3944
+ function boundaryInvocation(name, definition, edge, seed = 0, requiredRoles2 = [], format = "docx") {
3637
3945
  const slots = {};
3638
3946
  let offset = seed;
3639
3947
  for (const [slotName, slot] of Object.entries(definition.slots)) {
3640
3948
  offset += slotName.length;
3641
- const required = slot.required === true || slot.role !== void 0 && requiredRoles.includes(slot.role);
3949
+ const required = slot.required === true || slot.role !== void 0 && requiredRoles2.includes(slot.role);
3642
3950
  if (edge === "min" && !required) continue;
3643
- const value = boundarySlotValue(slot, slotName, edge, offset);
3951
+ const value = boundarySlotValue(slot, slotName, edge, offset, format);
3644
3952
  if (value !== void 0) slots[slotName] = value;
3645
3953
  }
3646
3954
  const labels = slots.labels;
@@ -3648,17 +3956,30 @@ function boundaryInvocation(name, definition, edge, seed = 0, requiredRoles = []
3648
3956
  if (Array.isArray(labels) && Array.isArray(columns)) {
3649
3957
  const cells = definition.slots.columns?.items?.properties?.cells;
3650
3958
  for (const column of columns) {
3651
- if (isRecord(column) && Array.isArray(column.cells) && cells?.items) {
3959
+ if (isRecord2(column) && Array.isArray(column.cells) && cells?.items) {
3652
3960
  column.cells = labels.map(
3653
- (_, i) => boundarySlotValue(cells.items, "cells", edge, offset + i)
3961
+ (_, i) => boundarySlotValue(
3962
+ cells.items,
3963
+ "cells",
3964
+ edge,
3965
+ offset + i,
3966
+ format
3967
+ )
3654
3968
  );
3655
3969
  }
3656
3970
  }
3657
3971
  }
3658
3972
  return { name: "block", props: { ref: name, slots } };
3659
3973
  }
3660
- function overBudgetInvocation(name, definition, seed = 0) {
3661
- const invocation = boundaryInvocation(name, definition, "max", seed);
3974
+ function overBudgetInvocation(name, definition, seed = 0, format = "docx") {
3975
+ const invocation = boundaryInvocation(
3976
+ name,
3977
+ definition,
3978
+ "max",
3979
+ seed,
3980
+ [],
3981
+ format
3982
+ );
3662
3983
  const slots = invocation.props.slots;
3663
3984
  for (const [slotName, slot] of Object.entries(definition.slots)) {
3664
3985
  if (slot.type === "string" && slot.maxWords !== void 0) {
@@ -3676,27 +3997,41 @@ function overBudgetInvocation(name, definition, seed = 0) {
3676
3997
  return void 0;
3677
3998
  }
3678
3999
  function enumerateBlockDefinitions(template, templateName) {
3679
- const definitions = readBlockDefinitions(template);
4000
+ const format = templateFormat(templateName, template);
4001
+ if (!format)
4002
+ throw new Error(`${templateName} is neither a report nor a deck.`);
4003
+ const references = blockReferencesFromDocument2(template, {
4004
+ template: templateName,
4005
+ format
4006
+ });
4007
+ const declared = Object.keys(readBlockDefinitions2(template));
4008
+ if (references.length !== declared.length)
4009
+ throw new Error(
4010
+ `${templateName}: discovery publishes ${references.length} of ${declared.length} definitions; the rest do not validate.`
4011
+ );
3680
4012
  const examples = /* @__PURE__ */ new Map();
3681
4013
  const walk = (node) => {
3682
4014
  if (Array.isArray(node)) {
3683
4015
  node.forEach(walk);
3684
4016
  return;
3685
4017
  }
3686
- if (!isRecord(node)) return;
4018
+ if (!isRecord2(node)) return;
3687
4019
  const props = node.props;
3688
- if (node.name === "block" && isRecord(props) && typeof props.ref === "string" && !examples.has(props.ref)) {
3689
- examples.set(props.ref, isRecord(props.slots) ? props.slots : {});
4020
+ if (node.name === "block" && isRecord2(props) && typeof props.ref === "string" && !examples.has(props.ref)) {
4021
+ examples.set(props.ref, isRecord2(props.slots) ? props.slots : {});
3690
4022
  }
3691
4023
  walk(node.children);
3692
4024
  };
3693
- walk(isRecord(template) ? template.children : void 0);
3694
- return Object.entries(definitions).map(([name, definition]) => ({
3695
- name,
4025
+ walk(isRecord2(template) ? template.children : void 0);
4026
+ return references.map((reference) => ({
4027
+ name: reference.name,
4028
+ format,
3696
4029
  template: templateName,
3697
- pointer: `/props/blocks/${name.replace(/~/g, "~0").replace(/\//g, "~1")}`,
3698
- definition,
3699
- ...examples.has(name) && { example: examples.get(name) }
4030
+ pointer: reference.definitionPointer,
4031
+ definition: reference.definition,
4032
+ ...examples.has(reference.name) && {
4033
+ example: examples.get(reference.name)
4034
+ }
3700
4035
  }));
3701
4036
  }
3702
4037
  var CHROME = ["cover", "running-head", "section-opener"];
@@ -3708,19 +4043,27 @@ function nominal(entry, name, definitions) {
3708
4043
  props: { ref: name, slots: structuredClone(entry.example) }
3709
4044
  };
3710
4045
  }
3711
- return boundaryInvocation(name, definitions[name], "min");
4046
+ return boundaryInvocation(
4047
+ name,
4048
+ definitions[name],
4049
+ "min",
4050
+ 0,
4051
+ [],
4052
+ entry.format
4053
+ );
3712
4054
  }
3713
- function inlineImages(value) {
3714
- if (Array.isArray(value)) return value.map(inlineImages);
3715
- if (!isRecord(value)) return value;
3716
- if (value.name === "image" && isRecord(value.props) && typeof value.props.path === "string") {
3717
- const props = { ...value.props, base64: MATRIX_IMAGE };
3718
- delete props.path;
4055
+ function inlineImages(value, format = "docx") {
4056
+ if (Array.isArray(value)) return value.map((v) => inlineImages(v, format));
4057
+ if (!isRecord2(value)) return value;
4058
+ if (value.name === "image" && isRecord2(value.props) && typeof value.props.path === "string" && !value.props.path.startsWith("data:")) {
4059
+ const props = format === "pptx" ? { ...value.props, path: MATRIX_IMAGE } : { ...value.props, base64: MATRIX_IMAGE };
4060
+ if (format === "docx") delete props.path;
3719
4061
  return { ...value, props };
3720
4062
  }
3721
- if (value.name === "highcharts") return componentValue("chart", "min", 0);
4063
+ if (value.name === "highcharts")
4064
+ return componentValue("chart", "min", 0, format);
3722
4065
  return Object.fromEntries(
3723
- Object.entries(value).map(([k, v]) => [k, inlineImages(v)])
4066
+ Object.entries(value).map(([k, v]) => [k, inlineImages(v, format)])
3724
4067
  );
3725
4068
  }
3726
4069
  function withDefinitions(definitions, names) {
@@ -3736,9 +4079,9 @@ function withDefinitions(definitions, names) {
3736
4079
  }
3737
4080
  function conditions(props, children, theme, font, canvas) {
3738
4081
  const sections = children.map((child) => {
3739
- if (!isRecord(child) || child.name !== "section") return child;
3740
- const props2 = isRecord(child.props) ? child.props : {};
3741
- const page = isRecord(props2.page) ? props2.page : {};
4082
+ if (!isRecord2(child) || child.name !== "section") return child;
4083
+ const props2 = isRecord2(child.props) ? child.props : {};
4084
+ const page = isRecord2(props2.page) ? props2.page : {};
3742
4085
  return { ...child, props: { ...props2, page: { ...page, size: canvas } } };
3743
4086
  });
3744
4087
  const chart = JSON.stringify(sections).includes('"name":"chart"');
@@ -3754,12 +4097,12 @@ function conditions(props, children, theme, font, canvas) {
3754
4097
  };
3755
4098
  }
3756
4099
  var BODY_COPY = words(40, 1e3, true);
3757
- function blockCaseDocument(entries, name, edge, theme, { font = "design", canvas = "A4", requiredRoles = [] } = {}) {
4100
+ function blockCaseDocument(entries, name, edge, theme, { font = "design", canvas = "A4", requiredRoles: requiredRoles2 = [] } = {}) {
3758
4101
  const definitions = Object.fromEntries(
3759
4102
  entries.map((e) => [e.name, e.definition])
3760
4103
  );
3761
4104
  const byName = new Map(entries.map((e) => [e.name, e]));
3762
- const under = (chrome) => chrome === name ? boundaryInvocation(name, definitions[name], edge, 0, requiredRoles) : inlineImages(nominal(byName.get(chrome), chrome, definitions));
4105
+ const under = (chrome) => chrome === name ? boundaryInvocation(name, definitions[name], edge, 0, requiredRoles2) : inlineImages(nominal(byName.get(chrome), chrome, definitions));
3763
4106
  const cover = under("cover");
3764
4107
  const runningHead = under("running-head");
3765
4108
  const opener = under("section-opener");
@@ -3771,7 +4114,7 @@ function blockCaseDocument(entries, name, edge, theme, { font = "design", canvas
3771
4114
  );
3772
4115
  }
3773
4116
  body.push(
3774
- boundaryInvocation(name, definitions[name], edge, 0, requiredRoles)
4117
+ boundaryInvocation(name, definitions[name], edge, 0, requiredRoles2)
3775
4118
  );
3776
4119
  }
3777
4120
  body.push({ name: "paragraph", props: { text: BODY_COPY } });
@@ -3802,26 +4145,26 @@ function blockCaseDocument(entries, name, edge, theme, { font = "design", canvas
3802
4145
  canvas
3803
4146
  );
3804
4147
  }
3805
- function reportCaseDocument(template, edge, theme, { font = "design", canvas = "A4", requiredRoles = [] } = {}) {
3806
- if (!isRecord(template) || !isRecord(template.props))
4148
+ function reportCaseDocument(template, edge, theme, { font = "design", canvas = "A4", requiredRoles: requiredRoles2 = [] } = {}) {
4149
+ if (!isRecord2(template) || !isRecord2(template.props))
3807
4150
  throw new Error("A template is a document with props.");
3808
- const definitions = readBlockDefinitions(template);
4151
+ const definitions = readBlockDefinitions2(template);
3809
4152
  let seed = 0;
3810
4153
  const rewrite = (node) => {
3811
4154
  if (Array.isArray(node)) return node.map(rewrite);
3812
- if (!isRecord(node)) return node;
4155
+ if (!isRecord2(node)) return node;
3813
4156
  const props2 = node.props;
3814
- if (node.name === "block" && isRecord(props2) && typeof props2.ref === "string" && definitions[props2.ref]) {
4157
+ if (node.name === "block" && isRecord2(props2) && typeof props2.ref === "string" && definitions[props2.ref]) {
3815
4158
  seed += 7;
3816
4159
  return boundaryInvocation(
3817
4160
  props2.ref,
3818
4161
  definitions[props2.ref],
3819
4162
  edge,
3820
4163
  seed,
3821
- requiredRoles
4164
+ requiredRoles2
3822
4165
  );
3823
4166
  }
3824
- if (node.name === "paragraph" && isRecord(props2) && typeof props2.text === "string") {
4167
+ if (node.name === "paragraph" && isRecord2(props2) && typeof props2.text === "string") {
3825
4168
  return {
3826
4169
  ...node,
3827
4170
  props: { ...props2, text: edge === "max" ? BODY_COPY : props2.text }
@@ -3882,6 +4225,200 @@ function generateBlockMatrix(template, templateName, options) {
3882
4225
  }
3883
4226
  return cases;
3884
4227
  }
4228
+ function deckConditions(props, children, theme, font, canvas, themeObject) {
4229
+ const out = structuredClone(props);
4230
+ delete out.qualityProfile;
4231
+ const inline = typeof props.theme !== "string";
4232
+ if (!inline || theme !== "inline") {
4233
+ if (font === "fallback") {
4234
+ const base = themeObject?.(theme);
4235
+ if (!base)
4236
+ throw new Error(
4237
+ `The fallback case on "${theme}" needs the theme's object.`
4238
+ );
4239
+ const fonts = isRecord2(base.fonts) ? base.fonts : {};
4240
+ out.theme = {
4241
+ ...structuredClone(base),
4242
+ fonts: { ...fonts, ...PPTX_FALLBACK_FONTS }
4243
+ };
4244
+ } else {
4245
+ out.theme = theme;
4246
+ }
4247
+ }
4248
+ if ((canvas === "wide169" || canvas === "standard43") && authoredDeckCanvas(props) !== canvas)
4249
+ Object.assign(out, PPTX_CANVAS_SIZE[canvas]);
4250
+ return { name: "pptx", props: out, children };
4251
+ }
4252
+ function templateProps(template) {
4253
+ if (!isRecord2(template) || !isRecord2(template.props))
4254
+ throw new Error("A template is a document with props.");
4255
+ return template.props;
4256
+ }
4257
+ function deckBlockCaseDocument(template, entries, name, edge, theme, {
4258
+ font = "design",
4259
+ canvas = "wide169",
4260
+ requiredRoles: requiredRoles2 = [],
4261
+ themeObject
4262
+ } = {}) {
4263
+ const props = templateProps(template);
4264
+ const definitions = Object.fromEntries(
4265
+ entries.map((e) => [e.name, e.definition])
4266
+ );
4267
+ const byName = new Map(entries.map((e) => [e.name, e]));
4268
+ const used = /* @__PURE__ */ new Set([name]);
4269
+ const slides = [];
4270
+ if (name !== "cover" && byName.has("cover")) {
4271
+ used.add("cover");
4272
+ slides.push({
4273
+ name: "slide",
4274
+ children: [
4275
+ inlineImages(
4276
+ nominal(byName.get("cover"), "cover", definitions),
4277
+ "pptx"
4278
+ )
4279
+ ]
4280
+ });
4281
+ }
4282
+ slides.push({
4283
+ name: "slide",
4284
+ children: [
4285
+ boundaryInvocation(
4286
+ name,
4287
+ definitions[name],
4288
+ edge,
4289
+ 0,
4290
+ requiredRoles2,
4291
+ "pptx"
4292
+ )
4293
+ ]
4294
+ });
4295
+ return deckConditions(
4296
+ { ...props, blocks: withDefinitions(definitions, used) },
4297
+ slides,
4298
+ theme,
4299
+ font,
4300
+ canvas,
4301
+ themeObject
4302
+ );
4303
+ }
4304
+ function deckReportCaseDocument(template, edge, theme, {
4305
+ font = "design",
4306
+ canvas = "wide169",
4307
+ requiredRoles: requiredRoles2 = [],
4308
+ themeObject
4309
+ } = {}) {
4310
+ const props = templateProps(template);
4311
+ const definitions = readBlockDefinitions2(template);
4312
+ let seed = 0;
4313
+ const rewrite = (node) => {
4314
+ if (Array.isArray(node)) return node.map(rewrite);
4315
+ if (!isRecord2(node)) return node;
4316
+ const nodeProps = node.props;
4317
+ if (node.name === "block" && isRecord2(nodeProps) && typeof nodeProps.ref === "string" && definitions[nodeProps.ref]) {
4318
+ seed += 7;
4319
+ return boundaryInvocation(
4320
+ nodeProps.ref,
4321
+ definitions[nodeProps.ref],
4322
+ edge,
4323
+ seed,
4324
+ requiredRoles2,
4325
+ "pptx"
4326
+ );
4327
+ }
4328
+ return Array.isArray(node.children) ? { ...node, children: rewrite(node.children) } : node;
4329
+ };
4330
+ return deckConditions(
4331
+ props,
4332
+ rewrite(template.children),
4333
+ theme,
4334
+ font,
4335
+ canvas,
4336
+ themeObject
4337
+ );
4338
+ }
4339
+ var stem = (template) => template.replace(/\.(docx|pptx)\.json$/, "");
4340
+ function generateMatrixCases(inventory, templates, selection = {}, { themeObject } = {}) {
4341
+ const pick = (supported, wanted) => supported.filter((value) => !wanted || wanted.includes(value));
4342
+ const cases = [];
4343
+ for (const summary of inventory.templates) {
4344
+ if (selection.templates && !selection.templates.includes(summary.template))
4345
+ continue;
4346
+ const source = templates.find((t) => t.name === summary.template);
4347
+ const entries = inventory.entries.filter(
4348
+ (e) => e.template === summary.template
4349
+ );
4350
+ if (!source || entries.length === 0) continue;
4351
+ const definitions = enumerateBlockDefinitions(
4352
+ source.document,
4353
+ summary.template
4354
+ );
4355
+ const withSlots = entries.some((e) => e.edges.includes("min"));
4356
+ for (const theme of pick(summary.conditions.themes, selection.themes))
4357
+ for (const font of pick(summary.conditions.fonts, selection.fonts))
4358
+ for (const canvas of pick(
4359
+ summary.conditions.canvases,
4360
+ selection.canvases
4361
+ ))
4362
+ for (const edge of pick(summary.conditions.edges, selection.edges)) {
4363
+ const shared = {
4364
+ format: summary.format,
4365
+ template: summary.template,
4366
+ ...summary.profile && { profile: summary.profile },
4367
+ edge,
4368
+ theme,
4369
+ font,
4370
+ canvas
4371
+ };
4372
+ const conditions2 = {
4373
+ font,
4374
+ canvas,
4375
+ requiredRoles: summary.requiredRoles,
4376
+ themeObject
4377
+ };
4378
+ if (selection.blocks !== false)
4379
+ for (const entry of entries) {
4380
+ if (!entry.edges.includes(edge)) continue;
4381
+ cases.push({
4382
+ ...shared,
4383
+ id: `${stem(summary.template)}/${entry.name}@${edge}/${theme}/${font}/${canvas}`,
4384
+ block: entry.name,
4385
+ document: summary.format === "docx" ? blockCaseDocument(
4386
+ definitions,
4387
+ entry.name,
4388
+ edge,
4389
+ theme,
4390
+ conditions2
4391
+ ) : deckBlockCaseDocument(
4392
+ source.document,
4393
+ definitions,
4394
+ entry.name,
4395
+ edge,
4396
+ theme,
4397
+ conditions2
4398
+ )
4399
+ });
4400
+ }
4401
+ if (selection.report !== false && withSlots)
4402
+ cases.push({
4403
+ ...shared,
4404
+ id: `${stem(summary.template)}/report@${edge}/${theme}/${font}/${canvas}`,
4405
+ block: "report",
4406
+ document: summary.format === "docx" ? reportCaseDocument(
4407
+ source.document,
4408
+ edge,
4409
+ theme,
4410
+ conditions2
4411
+ ) : deckReportCaseDocument(
4412
+ source.document,
4413
+ edge,
4414
+ theme,
4415
+ conditions2
4416
+ )
4417
+ });
4418
+ }
4419
+ }
4420
+ return cases;
4421
+ }
3885
4422
  export {
3886
4423
  DocxFormatAdapter,
3887
4424
  FALLBACK_FONTS,
@@ -3891,6 +4428,8 @@ export {
3891
4428
  MATRIX_IMAGE,
3892
4429
  MacOSCoreTextStager,
3893
4430
  NoopFontStager,
4431
+ PPTX_CANVAS_SIZE,
4432
+ PPTX_FALLBACK_FONTS,
3894
4433
  PptxFormatAdapter,
3895
4434
  RENDERED_QUALITY_RULES,
3896
4435
  VISIBLE_SPILL_PT,
@@ -3901,10 +4440,14 @@ export {
3901
4440
  blockCaseDocument,
3902
4441
  boundaryInvocation,
3903
4442
  boundarySlotValue,
4443
+ buildMatrixInventory,
3904
4444
  clearRasterizerCache,
3905
4445
  createAdapter,
3906
4446
  createLibreOfficePptxBatchRasterizer,
3907
4447
  createLibreOfficePptxRasterizer,
4448
+ deckBlockCaseDocument,
4449
+ deckReportCaseDocument,
4450
+ describeInventory,
3908
4451
  emitDiagnostic,
3909
4452
  enumerateBlockDefinitions,
3910
4453
  extractPdfFonts,
@@ -3912,9 +4455,12 @@ export {
3912
4455
  extractPdfTextGeometry,
3913
4456
  familyRendered,
3914
4457
  generateBlockMatrix,
4458
+ generateMatrixCases,
3915
4459
  getFontStager,
3916
4460
  getRasterizerCacheStats,
3917
4461
  inkedRows,
4462
+ inventoryGaps,
4463
+ matrixUniverse,
3918
4464
  normalizeForMatch,
3919
4465
  overBudgetInvocation,
3920
4466
  pageInkFromPgm,
@@ -3929,6 +4475,7 @@ export {
3929
4475
  requestedFontsFromFacts,
3930
4476
  runWithDiagnosticSink,
3931
4477
  stderrDiagnosticSink,
4478
+ templateFormat,
3932
4479
  widestNumber
3933
4480
  };
3934
4481
  //# sourceMappingURL=index.js.map