@json-to-office/jto-ops 4.2.0 → 4.4.2

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
@@ -1746,7 +1746,7 @@ var LINE_OR_WORD_PATTERN = /<line\s+xMin="(-?[\d.]+)"\s+yMin="(-?[\d.]+)"\s+xMax
1746
1746
  function parsePdfTextBbox(bboxXml) {
1747
1747
  const pages = [];
1748
1748
  for (const pageMatch of bboxXml.matchAll(PAGE_PATTERN)) {
1749
- const words = [];
1749
+ const words2 = [];
1750
1750
  const lines = [];
1751
1751
  let open;
1752
1752
  for (const token of pageMatch[3].matchAll(LINE_OR_WORD_PATTERN)) {
@@ -1762,8 +1762,8 @@ function parsePdfTextBbox(bboxXml) {
1762
1762
  words: []
1763
1763
  };
1764
1764
  } else {
1765
- open?.words.push(words.length);
1766
- words.push({
1765
+ open?.words.push(words2.length);
1766
+ words2.push({
1767
1767
  xMin: Number(token[5]),
1768
1768
  yMin: Number(token[6]),
1769
1769
  xMax: Number(token[7]),
@@ -1775,7 +1775,7 @@ function parsePdfTextBbox(bboxXml) {
1775
1775
  pages.push({
1776
1776
  widthPt: Number(pageMatch[1]),
1777
1777
  heightPt: Number(pageMatch[2]),
1778
- words,
1778
+ words: words2,
1779
1779
  lines
1780
1780
  });
1781
1781
  }
@@ -2012,7 +2012,7 @@ var RENDERED_QUALITY_RULES = {
2012
2012
  renderedRule({
2013
2013
  id: "rendered/empty-page",
2014
2014
  code: QUALITY_CODES.RENDERED_EMPTY_PAGE,
2015
- description: "A page with no text at all: a full-page figure is fine, a blank page from a stray break is not.",
2015
+ description: "A wordless page (`kind: blank`), or in docx one whose only words are its running head or footer (`kind: chrome-only`): a full-page figure is fine, a blank page from a stray break or an empty section is not.",
2016
2016
  category: "integrity",
2017
2017
  defaultSeverity: "info"
2018
2018
  }),
@@ -2052,17 +2052,108 @@ function indexDocument(pages, exclude = /* @__PURE__ */ new Set()) {
2052
2052
  const positions = [];
2053
2053
  let cursor = 0;
2054
2054
  pages.forEach((page, pageIndex) => {
2055
- page.words.forEach((word, index) => {
2055
+ for (const index of readingOrder(page.words)) {
2056
+ const word = page.words[index];
2056
2057
  const key = `${pageIndex}:${index}`;
2057
2058
  const fragment = exclude.has(key) ? "" : normalizeForMatch(word.text);
2058
2059
  refs.push({ pageIndex, word: index });
2059
2060
  fragments.push(fragment);
2060
2061
  positions.push(cursor);
2061
2062
  cursor += fragment.length;
2062
- });
2063
+ }
2063
2064
  });
2064
2065
  return { pages, refs, fragments, positions, stream: fragments.join("") };
2065
2066
  }
2067
+ function readingOrder(words2) {
2068
+ const rotated = new Set(
2069
+ words2.map((_, i) => i).filter((i) => {
2070
+ const w = words2[i];
2071
+ return w.text.length > 2 && w.yMax - w.yMin > 1.5 * (w.xMax - w.xMin);
2072
+ })
2073
+ );
2074
+ const upright = uprightOrder(
2075
+ words2,
2076
+ words2.map((_, i) => i).filter((i) => !rotated.has(i))
2077
+ );
2078
+ const order = [];
2079
+ let next = 0;
2080
+ for (let i = 0; i < words2.length; i++) {
2081
+ if (rotated.has(i)) order.push(i);
2082
+ else order.push(upright[next++]);
2083
+ }
2084
+ return order;
2085
+ }
2086
+ function uprightOrder(words2, indices) {
2087
+ const rows = [];
2088
+ const byY = [...indices].sort(
2089
+ (a, b) => words2[a].yMin - words2[b].yMin || words2[a].xMin - words2[b].xMin
2090
+ );
2091
+ for (const i of byY) {
2092
+ const row = rows[rows.length - 1];
2093
+ if (row && sameRow(words2[row[0]], words2[i])) row.push(i);
2094
+ else rows.push([i]);
2095
+ }
2096
+ const fragmentsOf = (row) => {
2097
+ const sorted = [...row].sort((a, b) => words2[a].xMin - words2[b].xMin);
2098
+ const out = [];
2099
+ for (const i of sorted) {
2100
+ const last = out[out.length - 1];
2101
+ const w = words2[i];
2102
+ const height = w.yMax - w.yMin;
2103
+ if (last && w.xMin - last.xMax <= height / 2) {
2104
+ last.words.push(i);
2105
+ last.xMax = Math.max(last.xMax, w.xMax);
2106
+ } else out.push({ xMin: w.xMin, xMax: w.xMax, words: [i] });
2107
+ }
2108
+ return out;
2109
+ };
2110
+ const fragments = rows.map(fragmentsOf);
2111
+ const boxes = rows.map((row) => ({
2112
+ yMin: Math.min(...row.map((i) => words2[i].yMin)),
2113
+ yMax: Math.max(...row.map((i) => words2[i].yMax))
2114
+ }));
2115
+ const order = [];
2116
+ let cluster = null;
2117
+ const flush = () => {
2118
+ if (!cluster) return;
2119
+ if (!cluster.multi) {
2120
+ for (const r of cluster.rows)
2121
+ for (const f of fragments[r]) order.push(...f.words);
2122
+ } else {
2123
+ cluster.columns.sort((a, b) => a.xMin - b.xMin);
2124
+ for (const column of cluster.columns) {
2125
+ column.fragments.sort(
2126
+ (a, b) => words2[a.words[0]].yMin - words2[b.words[0]].yMin || a.xMin - b.xMin
2127
+ );
2128
+ for (const f of column.fragments) order.push(...f.words);
2129
+ }
2130
+ }
2131
+ cluster = null;
2132
+ };
2133
+ const overlapping = (columns, f) => columns.filter(
2134
+ (c) => Math.min(c.xMax, f.xMax) - Math.max(c.xMin, f.xMin) > 0
2135
+ );
2136
+ for (let r = 0; r < rows.length; r++) {
2137
+ const lineHeight = boxes[r].yMax - boxes[r].yMin;
2138
+ const adjacent = cluster !== null && boxes[r].yMin - boxes[r - 1].yMax <= lineHeight * 0.9;
2139
+ const wide = cluster !== null && fragments[r].some((f) => overlapping(cluster.columns, f).length > 1);
2140
+ if (!adjacent || wide) flush();
2141
+ if (!cluster) cluster = { rows: [], columns: [], multi: false };
2142
+ cluster.rows.push(r);
2143
+ if (fragments[r].length > 1) cluster.multi = true;
2144
+ for (const f of fragments[r]) {
2145
+ const hit = overlapping(cluster.columns, f)[0];
2146
+ if (hit) {
2147
+ hit.xMin = Math.min(hit.xMin, f.xMin);
2148
+ hit.xMax = Math.max(hit.xMax, f.xMax);
2149
+ hit.fragments.push(f);
2150
+ } else
2151
+ cluster.columns.push({ xMin: f.xMin, xMax: f.xMax, fragments: [f] });
2152
+ }
2153
+ }
2154
+ flush();
2155
+ return order;
2156
+ }
2066
2157
  function fragmentAt(index, offset) {
2067
2158
  let low = 0;
2068
2159
  let high = index.positions.length - 1;
@@ -2483,14 +2574,14 @@ function draftRenderedFindings(input) {
2483
2574
  pages.forEach((page, pageIndex) => {
2484
2575
  const lines = lineOfWord(page);
2485
2576
  const seen = /* @__PURE__ */ new Set();
2486
- const words = page.words;
2487
- const byTop = words.map((_, i) => i).sort((a, b) => words[a].yMin - words[b].yMin);
2577
+ const words2 = page.words;
2578
+ const byTop = words2.map((_, i) => i).sort((a, b) => words2[a].yMin - words2[b].yMin);
2488
2579
  for (let p = 0; p < byTop.length; p++) {
2489
2580
  const i = byTop[p];
2490
- const a = words[i];
2581
+ const a = words2[i];
2491
2582
  for (let q = p + 1; q < byTop.length; q++) {
2492
2583
  const j = byTop[q];
2493
- const b = words[j];
2584
+ const b = words2[j];
2494
2585
  if (b.yMin >= a.yMax) break;
2495
2586
  const shared = intersects(a, b);
2496
2587
  if (shared <= 0) continue;
@@ -2568,15 +2659,18 @@ function draftRenderedFindings(input) {
2568
2659
  }
2569
2660
  }
2570
2661
  pages.forEach((page, pageIndex) => {
2571
- if (page.words.length > 0) return;
2662
+ const hasBody = format === "docx" ? page.words.some((_, i) => !chromeWords.has(`${pageIndex}:${i}`)) : page.words.length > 0;
2663
+ if (hasBody) return;
2664
+ const kind = page.words.length > 0 ? "chrome-only" : "blank";
2572
2665
  findings.push(
2573
2666
  finding({
2574
2667
  ruleId: "rendered/empty-page",
2575
2668
  mapping: "unmapped",
2576
2669
  page: pageIndex + 1,
2577
2670
  path: "",
2578
- message: `Page ${pageIndex + 1} carries no text. A full-page figure is fine; a blank page from a stray break is not.`,
2579
- suggestion: "Preview the page; remove the page break or the empty section if nothing was meant to be there."
2671
+ message: `Page ${pageIndex + 1} carries ${kind === "chrome-only" ? "only its running head or footer" : "no text"}. A full-page figure is fine; a blank page from a stray break or an empty section is not.`,
2672
+ suggestion: "Preview the page; remove the page break or the empty section if nothing was meant to be there.",
2673
+ context: { kind }
2580
2674
  })
2581
2675
  );
2582
2676
  });
@@ -2724,9 +2818,416 @@ function analyzeRenderedDocument(input, options = {}) {
2724
2818
  analysis
2725
2819
  };
2726
2820
  }
2821
+
2822
+ // src/block-matrix.ts
2823
+ import {
2824
+ blockDependencies,
2825
+ readBlockDefinitions
2826
+ } from "@json-to-office/shared";
2827
+ var FALLBACK_FONTS = {
2828
+ heading: { family: "DejaVu Sans" },
2829
+ body: { family: "DejaVu Sans" }
2830
+ };
2831
+ var MATRIX_IMAGE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAABytg0kAAAAFElEQVR42mNk+M9QzwAFjDAGACPuA/8fMSCgAAAAAElFTkSuQmCC";
2832
+ var WORDS = [
2833
+ "performance",
2834
+ "delivery",
2835
+ "retention",
2836
+ "operating",
2837
+ "programme",
2838
+ "contracted",
2839
+ "recommendation",
2840
+ "quarter",
2841
+ "measurement",
2842
+ "ownership",
2843
+ "renewal",
2844
+ "segment",
2845
+ "margin",
2846
+ "baseline",
2847
+ "procurement",
2848
+ "integration"
2849
+ ];
2850
+ function words(count, seed, sentence) {
2851
+ if (count <= 0) return "";
2852
+ const out = [`Item${seed}`];
2853
+ for (let i = 1; i < count; i += 1) out.push(WORDS[(seed + i) % WORDS.length]);
2854
+ return sentence ? `${out.join(" ")}.` : out.join(" ");
2855
+ }
2856
+ function widestNumber(length, seed = 0) {
2857
+ if (length <= 0) return "";
2858
+ if (length === 1) return "9";
2859
+ if (length < 4) return `\u2212${"9".repeat(length - 1)}`;
2860
+ const group = (digits2) => Array.from({ length: digits2 }, (_, i) => String((i + seed) % 9 + 1)).join("").replace(/\B(?=(\d{3})+(?!\d))/g, ",");
2861
+ let digits = 1;
2862
+ while (group(digits + 1).length + 3 <= length) digits += 1;
2863
+ const integer = group(digits);
2864
+ return `\u2212${integer}.${"0".repeat(length - integer.length - 2)}`;
2865
+ }
2866
+ var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
2867
+ var NUMERIC_SLOTS = /* @__PURE__ */ new Set(["value", "delta", "cells"]);
2868
+ function stringValue(slot, name, edge, seed) {
2869
+ if (slot.enum && slot.enum.length > 0) {
2870
+ const values = slot.enum.map(String);
2871
+ return edge === "max" ? values.reduce((a, b) => b.length > a.length ? b : a) : values[0];
2872
+ }
2873
+ if (slot.maxLength !== void 0 && NUMERIC_SLOTS.has(name)) {
2874
+ return widestNumber(
2875
+ edge === "max" ? slot.maxLength : Math.min(3, slot.maxLength),
2876
+ seed
2877
+ );
2878
+ }
2879
+ if (slot.maxLength !== void 0 && name === "unit") {
2880
+ return edge === "max" ? ` \u20AC${"m".repeat(Math.max(0, slot.maxLength - 2))}`.slice(
2881
+ 0,
2882
+ slot.maxLength
2883
+ ) : "%";
2884
+ }
2885
+ if (slot.maxLength !== void 0) {
2886
+ const target = edge === "max" ? slot.maxLength : Math.max(1, slot.minLength ?? 1);
2887
+ let text = `Item${seed}`.slice(0, target);
2888
+ let i = 0;
2889
+ for (; ; ) {
2890
+ const next = WORDS[(seed + i) % WORDS.length];
2891
+ i += 1;
2892
+ if (text.length + 1 + next.length > target) {
2893
+ if (i > WORDS.length) break;
2894
+ continue;
2895
+ }
2896
+ text += ` ${next}`;
2897
+ }
2898
+ return text;
2899
+ }
2900
+ const budget = slot.maxWords ?? 12;
2901
+ const count = edge === "max" ? budget : Math.max(1, Math.min(2, budget));
2902
+ const sentence = !slot.oneLine && slot.role !== "source";
2903
+ if (slot.role === "source") return `Source: ${words(count - 1, seed, false)}`;
2904
+ if (name === "number")
2905
+ return edge === "max" ? words(count, seed, false) : "01";
2906
+ return words(count, seed, sentence);
2907
+ }
2908
+ function componentValue(name, edge, seed) {
2909
+ if (name === "chart") {
2910
+ const categories = edge === "max" ? 8 : 2;
2911
+ const series = edge === "max" ? 3 : 1;
2912
+ return {
2913
+ name: "chart",
2914
+ props: {
2915
+ type: "column",
2916
+ valAxisTitle: edge === "max" ? `${words(3, seed, false)} (\u20ACm)` : "Revenue (\u20ACm)",
2917
+ ...edge === "max" && { catAxisTitle: words(3, seed + 3, false) },
2918
+ chartColors: ["primary", "secondary", "accent"].slice(0, series),
2919
+ data: Array.from({ length: series }, (_, s) => ({
2920
+ name: words(edge === "max" ? 3 : 1, seed + s, false),
2921
+ // Two words per category is where LibreOffice's native chart
2922
+ // still keeps rotated labels clear of the axis title; at three
2923
+ // the title is drawn over them, at any category count.
2924
+ labels: Array.from(
2925
+ { length: categories },
2926
+ (_2, c) => edge === "max" ? words(2, seed + c, false) : `Q${c + 1}`
2927
+ ),
2928
+ values: Array.from(
2929
+ { length: categories },
2930
+ (_2, c) => 1.2 * (c + 1) + s
2931
+ )
2932
+ }))
2933
+ }
2934
+ };
2935
+ }
2936
+ return {
2937
+ name: "image",
2938
+ props: {
2939
+ base64: MATRIX_IMAGE,
2940
+ width: edge === "max" ? name === "logo" ? "40%" : "100%" : "25%",
2941
+ alt: words(6, seed, false)
2942
+ }
2943
+ };
2944
+ }
2945
+ function boundarySlotValue(slot, name, edge, seed = 0) {
2946
+ switch (slot.type) {
2947
+ case "string":
2948
+ return stringValue(slot, name, edge, seed);
2949
+ case "number":
2950
+ case "integer":
2951
+ return edge === "max" ? slot.maximum ?? 9999 : slot.minimum ?? 0;
2952
+ case "boolean":
2953
+ return edge === "max" ? true : slot.default ?? true;
2954
+ case "component":
2955
+ return componentValue(name, edge, seed);
2956
+ case "object": {
2957
+ const out = {};
2958
+ for (const [key, property] of Object.entries(slot.properties ?? {})) {
2959
+ if (edge === "min" && !property.required) continue;
2960
+ const value = boundarySlotValue(property, key, edge, seed + key.length);
2961
+ if (value !== void 0) out[key] = value;
2962
+ }
2963
+ return out;
2964
+ }
2965
+ case "array": {
2966
+ const count = edge === "max" ? slot.maxItems ?? 3 : slot.minItems ?? (slot.required ? 1 : 0);
2967
+ const item = slot.items ?? { type: "string" };
2968
+ return Array.from(
2969
+ { length: count },
2970
+ (_, i) => boundarySlotValue(item, name, edge, seed + i)
2971
+ );
2972
+ }
2973
+ default:
2974
+ return void 0;
2975
+ }
2976
+ }
2977
+ function boundaryInvocation(name, definition, edge, seed = 0, requiredRoles = []) {
2978
+ const slots = {};
2979
+ let offset = seed;
2980
+ for (const [slotName, slot] of Object.entries(definition.slots)) {
2981
+ offset += slotName.length;
2982
+ const required = slot.required === true || slot.role !== void 0 && requiredRoles.includes(slot.role);
2983
+ if (edge === "min" && !required) continue;
2984
+ const value = boundarySlotValue(slot, slotName, edge, offset);
2985
+ if (value !== void 0) slots[slotName] = value;
2986
+ }
2987
+ const labels = slots.labels;
2988
+ const columns = slots.columns;
2989
+ if (Array.isArray(labels) && Array.isArray(columns)) {
2990
+ const cells = definition.slots.columns?.items?.properties?.cells;
2991
+ for (const column of columns) {
2992
+ if (isRecord(column) && Array.isArray(column.cells) && cells?.items) {
2993
+ column.cells = labels.map(
2994
+ (_, i) => boundarySlotValue(cells.items, "cells", edge, offset + i)
2995
+ );
2996
+ }
2997
+ }
2998
+ }
2999
+ return { name: "block", props: { ref: name, slots } };
3000
+ }
3001
+ function overBudgetInvocation(name, definition, seed = 0) {
3002
+ const invocation = boundaryInvocation(name, definition, "max", seed);
3003
+ const slots = invocation.props.slots;
3004
+ for (const [slotName, slot] of Object.entries(definition.slots)) {
3005
+ if (slot.type === "string" && slot.maxWords !== void 0) {
3006
+ slots[slotName] = `${words(slot.maxWords + 1, seed, !slot.oneLine)}`;
3007
+ return { invocation, slot: slotName, kind: "words" };
3008
+ }
3009
+ }
3010
+ for (const [slotName, slot] of Object.entries(definition.slots)) {
3011
+ if (slot.type === "array" && slot.maxItems !== void 0 && Array.isArray(slots[slotName])) {
3012
+ const items = slots[slotName];
3013
+ slots[slotName] = [...items, items[0]];
3014
+ return { invocation, slot: slotName, kind: "items" };
3015
+ }
3016
+ }
3017
+ return void 0;
3018
+ }
3019
+ function enumerateBlockDefinitions(template, templateName) {
3020
+ const definitions = readBlockDefinitions(template);
3021
+ const examples = /* @__PURE__ */ new Map();
3022
+ const walk = (node) => {
3023
+ if (Array.isArray(node)) {
3024
+ node.forEach(walk);
3025
+ return;
3026
+ }
3027
+ if (!isRecord(node)) return;
3028
+ const props = node.props;
3029
+ if (node.name === "block" && isRecord(props) && typeof props.ref === "string" && !examples.has(props.ref)) {
3030
+ examples.set(props.ref, isRecord(props.slots) ? props.slots : {});
3031
+ }
3032
+ walk(node.children);
3033
+ };
3034
+ walk(isRecord(template) ? template.children : void 0);
3035
+ return Object.entries(definitions).map(([name, definition]) => ({
3036
+ name,
3037
+ template: templateName,
3038
+ pointer: `/props/blocks/${name.replace(/~/g, "~0").replace(/\//g, "~1")}`,
3039
+ definition,
3040
+ ...examples.has(name) && { example: examples.get(name) }
3041
+ }));
3042
+ }
3043
+ var CHROME = ["cover", "running-head", "section-opener"];
3044
+ function nominal(entry, name, definitions) {
3045
+ if (!entry) return void 0;
3046
+ if (entry.example) {
3047
+ return {
3048
+ name: "block",
3049
+ props: { ref: name, slots: structuredClone(entry.example) }
3050
+ };
3051
+ }
3052
+ return boundaryInvocation(name, definitions[name], "min");
3053
+ }
3054
+ function inlineImages(value) {
3055
+ if (Array.isArray(value)) return value.map(inlineImages);
3056
+ if (!isRecord(value)) return value;
3057
+ if (value.name === "image" && isRecord(value.props) && typeof value.props.path === "string") {
3058
+ const props = { ...value.props, base64: MATRIX_IMAGE };
3059
+ delete props.path;
3060
+ return { ...value, props };
3061
+ }
3062
+ if (value.name === "highcharts") return componentValue("chart", "min", 0);
3063
+ return Object.fromEntries(
3064
+ Object.entries(value).map(([k, v]) => [k, inlineImages(v)])
3065
+ );
3066
+ }
3067
+ function withDefinitions(definitions, names) {
3068
+ const out = {};
3069
+ for (const name of names) {
3070
+ if (!(name in definitions)) continue;
3071
+ out[name] = definitions[name];
3072
+ for (const dependency of blockDependencies(definitions, name)) {
3073
+ if (dependency in definitions) out[dependency] = definitions[dependency];
3074
+ }
3075
+ }
3076
+ return out;
3077
+ }
3078
+ function conditions(props, children, theme, font, canvas) {
3079
+ const sections = children.map((child) => {
3080
+ if (!isRecord(child) || child.name !== "section") return child;
3081
+ const props2 = isRecord(child.props) ? child.props : {};
3082
+ const page = isRecord(props2.page) ? props2.page : {};
3083
+ return { ...child, props: { ...props2, page: { ...page, size: canvas } } };
3084
+ });
3085
+ const chart = JSON.stringify(sections).includes('"name":"chart"');
3086
+ return {
3087
+ name: "docx",
3088
+ ...chart && { renderer: "office-open" },
3089
+ props: {
3090
+ ...props,
3091
+ theme,
3092
+ ...font === "fallback" && { themeOverrides: { fonts: FALLBACK_FONTS } }
3093
+ },
3094
+ children: sections
3095
+ };
3096
+ }
3097
+ var BODY_COPY = words(40, 1e3, true);
3098
+ function blockCaseDocument(entries, name, edge, theme, { font = "design", canvas = "A4", requiredRoles = [] } = {}) {
3099
+ const definitions = Object.fromEntries(
3100
+ entries.map((e) => [e.name, e.definition])
3101
+ );
3102
+ const byName = new Map(entries.map((e) => [e.name, e]));
3103
+ const under = (chrome) => chrome === name ? boundaryInvocation(name, definitions[name], edge, 0, requiredRoles) : inlineImages(nominal(byName.get(chrome), chrome, definitions));
3104
+ const cover = under("cover");
3105
+ const runningHead = under("running-head");
3106
+ const opener = under("section-opener");
3107
+ const body = [];
3108
+ if (!CHROME.includes(name)) {
3109
+ if (name === "footnotes" && byName.has("figure")) {
3110
+ body.push(
3111
+ inlineImages(nominal(byName.get("figure"), "figure", definitions))
3112
+ );
3113
+ }
3114
+ body.push(
3115
+ boundaryInvocation(name, definitions[name], edge, 0, requiredRoles)
3116
+ );
3117
+ }
3118
+ body.push({ name: "paragraph", props: { text: BODY_COPY } });
3119
+ const used = /* @__PURE__ */ new Set([
3120
+ name,
3121
+ ...CHROME,
3122
+ ...name === "footnotes" ? ["figure"] : []
3123
+ ]);
3124
+ const children = [];
3125
+ if (cover) children.push({ name: "section", children: [cover] });
3126
+ children.push({
3127
+ name: "section",
3128
+ children: [runningHead, opener, ...body].filter((c) => c !== void 0)
3129
+ });
3130
+ return conditions(
3131
+ {
3132
+ metadata: {
3133
+ title: "Boundary report",
3134
+ author: "jto-ops block matrix",
3135
+ company: "Example client",
3136
+ date: "September 2026"
3137
+ },
3138
+ blocks: withDefinitions(definitions, used)
3139
+ },
3140
+ children,
3141
+ theme,
3142
+ font,
3143
+ canvas
3144
+ );
3145
+ }
3146
+ function reportCaseDocument(template, edge, theme, { font = "design", canvas = "A4", requiredRoles = [] } = {}) {
3147
+ if (!isRecord(template) || !isRecord(template.props))
3148
+ throw new Error("A template is a document with props.");
3149
+ const definitions = readBlockDefinitions(template);
3150
+ let seed = 0;
3151
+ const rewrite = (node) => {
3152
+ if (Array.isArray(node)) return node.map(rewrite);
3153
+ if (!isRecord(node)) return node;
3154
+ const props2 = node.props;
3155
+ if (node.name === "block" && isRecord(props2) && typeof props2.ref === "string" && definitions[props2.ref]) {
3156
+ seed += 7;
3157
+ return boundaryInvocation(
3158
+ props2.ref,
3159
+ definitions[props2.ref],
3160
+ edge,
3161
+ seed,
3162
+ requiredRoles
3163
+ );
3164
+ }
3165
+ if (node.name === "paragraph" && isRecord(props2) && typeof props2.text === "string") {
3166
+ return {
3167
+ ...node,
3168
+ props: { ...props2, text: edge === "max" ? BODY_COPY : props2.text }
3169
+ };
3170
+ }
3171
+ return Array.isArray(node.children) ? { ...node, children: rewrite(node.children) } : node;
3172
+ };
3173
+ const props = structuredClone(template.props);
3174
+ delete props.theme;
3175
+ delete props.themeOverrides;
3176
+ return conditions(
3177
+ props,
3178
+ rewrite(template.children),
3179
+ theme,
3180
+ font,
3181
+ canvas
3182
+ );
3183
+ }
3184
+ function generateBlockMatrix(template, templateName, options) {
3185
+ const entries = enumerateBlockDefinitions(template, templateName);
3186
+ const fonts = options.fonts ?? ["design", "fallback"];
3187
+ const edges = options.edges ?? ["min", "max"];
3188
+ const canvases = options.canvases ?? ["A4"];
3189
+ const cases = [];
3190
+ for (const theme of options.themes)
3191
+ for (const font of fonts)
3192
+ for (const canvas of canvases)
3193
+ for (const edge of edges) {
3194
+ if (options.blocks !== false)
3195
+ for (const entry of entries)
3196
+ cases.push({
3197
+ id: `${entry.name}@${edge}/${theme}/${font}/${canvas}`,
3198
+ block: entry.name,
3199
+ edge,
3200
+ theme,
3201
+ font,
3202
+ canvas,
3203
+ document: blockCaseDocument(entries, entry.name, edge, theme, {
3204
+ font,
3205
+ canvas,
3206
+ requiredRoles: options.requiredRoles
3207
+ })
3208
+ });
3209
+ if (options.report !== false)
3210
+ cases.push({
3211
+ id: `report@${edge}/${theme}/${font}/${canvas}`,
3212
+ block: "report",
3213
+ edge,
3214
+ theme,
3215
+ font,
3216
+ canvas,
3217
+ document: reportCaseDocument(template, edge, theme, {
3218
+ font,
3219
+ canvas,
3220
+ requiredRoles: options.requiredRoles
3221
+ })
3222
+ });
3223
+ }
3224
+ return cases;
3225
+ }
2727
3226
  export {
2728
3227
  DocxFormatAdapter,
3228
+ FALLBACK_FONTS,
2729
3229
  FontconfigStager,
3230
+ MATRIX_IMAGE,
2730
3231
  MacOSCoreTextStager,
2731
3232
  NoopFontStager,
2732
3233
  PptxFormatAdapter,
@@ -2736,22 +3237,31 @@ export {
2736
3237
  analyzeRenderedDocument,
2737
3238
  assignInventory,
2738
3239
  authoredTextForMatch,
3240
+ blockCaseDocument,
3241
+ boundaryInvocation,
3242
+ boundarySlotValue,
2739
3243
  clearRasterizerCache,
2740
3244
  createAdapter,
2741
3245
  createLibreOfficePptxBatchRasterizer,
2742
3246
  createLibreOfficePptxRasterizer,
2743
3247
  emitDiagnostic,
3248
+ enumerateBlockDefinitions,
2744
3249
  extractPdfFonts,
2745
3250
  extractPdfTextGeometry,
2746
3251
  familyRendered,
3252
+ generateBlockMatrix,
2747
3253
  getFontStager,
2748
3254
  getRasterizerCacheStats,
2749
3255
  normalizeForMatch,
3256
+ overBudgetInvocation,
2750
3257
  parsePdfFonts,
2751
3258
  parsePdfTextBbox,
2752
3259
  pdffontsAvailable,
2753
3260
  pdftotextAvailable,
3261
+ readingOrder,
3262
+ reportCaseDocument,
2754
3263
  runWithDiagnosticSink,
2755
- stderrDiagnosticSink
3264
+ stderrDiagnosticSink,
3265
+ widestNumber
2756
3266
  };
2757
3267
  //# sourceMappingURL=index.js.map