@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.d.ts +140 -2
- package/dist/index.js +525 -15
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
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
|
|
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(
|
|
1766
|
-
|
|
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
|
|
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
|
|
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
|
|
2487
|
-
const byTop =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|