@json-to-office/jto-ops 4.1.0 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +192 -10
- package/dist/index.js +703 -57
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
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
|
}
|
|
@@ -1947,9 +1947,93 @@ async function extractPdfFonts(pdfPath, options = {}) {
|
|
|
1947
1947
|
}
|
|
1948
1948
|
|
|
1949
1949
|
// src/rendered-analysis.ts
|
|
1950
|
+
import {
|
|
1951
|
+
QualityEngine
|
|
1952
|
+
} from "@json-to-office/quality";
|
|
1953
|
+
|
|
1954
|
+
// src/rendered-rules.ts
|
|
1950
1955
|
import {
|
|
1951
1956
|
QUALITY_CODES
|
|
1952
1957
|
} from "@json-to-office/quality";
|
|
1958
|
+
function renderedRule(spec) {
|
|
1959
|
+
return {
|
|
1960
|
+
id: spec.id,
|
|
1961
|
+
code: spec.code,
|
|
1962
|
+
description: spec.description,
|
|
1963
|
+
category: spec.category,
|
|
1964
|
+
defaultSeverity: spec.defaultSeverity,
|
|
1965
|
+
defaultCertainty: "rendered",
|
|
1966
|
+
...spec.formats && { formats: spec.formats },
|
|
1967
|
+
evaluate({ facts }) {
|
|
1968
|
+
const fact = facts.find(isRenderedGeometryFact);
|
|
1969
|
+
if (!fact) return [];
|
|
1970
|
+
return renderedDraftFor(fact).byRule.get(spec.id) ?? [];
|
|
1971
|
+
}
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
var RENDERED_QUALITY_RULES = {
|
|
1975
|
+
id: "rendered",
|
|
1976
|
+
rules: [
|
|
1977
|
+
renderedRule({
|
|
1978
|
+
id: "rendered/clip",
|
|
1979
|
+
code: QUALITY_CODES.RENDERED_CLIP,
|
|
1980
|
+
description: "Words rendered past the page edge by more than 2 pt, or an authored string of which only a leading part rendered (`kind: truncated`, with the share found).",
|
|
1981
|
+
category: "integrity",
|
|
1982
|
+
defaultSeverity: "warning"
|
|
1983
|
+
}),
|
|
1984
|
+
renderedRule({
|
|
1985
|
+
id: "rendered/spill",
|
|
1986
|
+
code: QUALITY_CODES.RENDERED_SPILL,
|
|
1987
|
+
description: "A framed paragraph or slide text box drawn wider (DOCX) or larger (PPTX) than the box it declared.",
|
|
1988
|
+
category: "integrity",
|
|
1989
|
+
defaultSeverity: "warning"
|
|
1990
|
+
}),
|
|
1991
|
+
renderedRule({
|
|
1992
|
+
id: "rendered/overlap",
|
|
1993
|
+
code: QUALITY_CODES.RENDERED_OVERLAP,
|
|
1994
|
+
description: "Two words from different lines whose boxes intersect by more than 30% of the smaller.",
|
|
1995
|
+
category: "integrity",
|
|
1996
|
+
defaultSeverity: "warning"
|
|
1997
|
+
}),
|
|
1998
|
+
renderedRule({
|
|
1999
|
+
id: "rendered/text-missing",
|
|
2000
|
+
code: QUALITY_CODES.RENDERED_TEXT_MISSING,
|
|
2001
|
+
description: "An authored string that appears nowhere in the PDF: fully clipped, hidden, or dropped by the renderer.",
|
|
2002
|
+
category: "integrity",
|
|
2003
|
+
defaultSeverity: "warning"
|
|
2004
|
+
}),
|
|
2005
|
+
renderedRule({
|
|
2006
|
+
id: "rendered/font-substituted",
|
|
2007
|
+
code: QUALITY_CODES.RENDERED_FONT_SUBSTITUTED,
|
|
2008
|
+
description: "A requested family the PDF embeds no face of; a warning when the document declared a source for it, information when the host simply lacks it.",
|
|
2009
|
+
category: "brand",
|
|
2010
|
+
defaultSeverity: "info"
|
|
2011
|
+
}),
|
|
2012
|
+
renderedRule({
|
|
2013
|
+
id: "rendered/empty-page",
|
|
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.",
|
|
2016
|
+
category: "integrity",
|
|
2017
|
+
defaultSeverity: "info"
|
|
2018
|
+
}),
|
|
2019
|
+
renderedRule({
|
|
2020
|
+
id: "rendered/heading-stranded",
|
|
2021
|
+
code: QUALITY_CODES.RENDERED_HEADING_STRANDED,
|
|
2022
|
+
description: "A heading that is the last body line on its page.",
|
|
2023
|
+
category: "composition",
|
|
2024
|
+
defaultSeverity: "warning",
|
|
2025
|
+
formats: ["docx"]
|
|
2026
|
+
}),
|
|
2027
|
+
renderedRule({
|
|
2028
|
+
id: "rendered/paragraph-split",
|
|
2029
|
+
code: QUALITY_CODES.RENDERED_PARAGRAPH_SPLIT,
|
|
2030
|
+
description: "A paragraph leaving one line alone on either side of a page break (`kind: orphan` or `widow`).",
|
|
2031
|
+
category: "composition",
|
|
2032
|
+
defaultSeverity: "info",
|
|
2033
|
+
formats: ["docx"]
|
|
2034
|
+
})
|
|
2035
|
+
]
|
|
2036
|
+
};
|
|
1953
2037
|
|
|
1954
2038
|
// src/rendered-text-match.ts
|
|
1955
2039
|
function normalizeForMatch(value) {
|
|
@@ -1968,17 +2052,108 @@ function indexDocument(pages, exclude = /* @__PURE__ */ new Set()) {
|
|
|
1968
2052
|
const positions = [];
|
|
1969
2053
|
let cursor = 0;
|
|
1970
2054
|
pages.forEach((page, pageIndex) => {
|
|
1971
|
-
page.words
|
|
2055
|
+
for (const index of readingOrder(page.words)) {
|
|
2056
|
+
const word = page.words[index];
|
|
1972
2057
|
const key = `${pageIndex}:${index}`;
|
|
1973
2058
|
const fragment = exclude.has(key) ? "" : normalizeForMatch(word.text);
|
|
1974
2059
|
refs.push({ pageIndex, word: index });
|
|
1975
2060
|
fragments.push(fragment);
|
|
1976
2061
|
positions.push(cursor);
|
|
1977
2062
|
cursor += fragment.length;
|
|
1978
|
-
}
|
|
2063
|
+
}
|
|
1979
2064
|
});
|
|
1980
2065
|
return { pages, refs, fragments, positions, stream: fragments.join("") };
|
|
1981
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
|
+
}
|
|
1982
2157
|
function fragmentAt(index, offset) {
|
|
1983
2158
|
let low = 0;
|
|
1984
2159
|
let high = index.positions.length - 1;
|
|
@@ -2142,7 +2317,7 @@ function assignInventory(pages, inventory) {
|
|
|
2142
2317
|
});
|
|
2143
2318
|
});
|
|
2144
2319
|
const body = indexDocument(pages, chromeWords);
|
|
2145
|
-
const claimed = /* @__PURE__ */ new
|
|
2320
|
+
const claimed = /* @__PURE__ */ new Map();
|
|
2146
2321
|
let cursor = 0;
|
|
2147
2322
|
for (const entry of inventory) {
|
|
2148
2323
|
if (entry.repeats) continue;
|
|
@@ -2154,7 +2329,7 @@ function assignInventory(pages, inventory) {
|
|
|
2154
2329
|
}
|
|
2155
2330
|
let all = findOccurrences(body, segments);
|
|
2156
2331
|
let partial;
|
|
2157
|
-
if (all.length === 0 && segments.length === 1) {
|
|
2332
|
+
if (all.length === 0 && segments.length === 1 && !entry.optional) {
|
|
2158
2333
|
const prefix = longestRenderedPrefix(body, needle);
|
|
2159
2334
|
if (prefix) {
|
|
2160
2335
|
all = [occurrence(body, prefix.at, prefix.at + prefix.length)];
|
|
@@ -2162,10 +2337,29 @@ function assignInventory(pages, inventory) {
|
|
|
2162
2337
|
}
|
|
2163
2338
|
}
|
|
2164
2339
|
if (all.length === 0) {
|
|
2165
|
-
results.set(entry, {
|
|
2340
|
+
results.set(entry, {
|
|
2341
|
+
entry,
|
|
2342
|
+
needle,
|
|
2343
|
+
status: entry.optional ? "skipped" : "missing",
|
|
2344
|
+
occurrences: []
|
|
2345
|
+
});
|
|
2166
2346
|
continue;
|
|
2167
2347
|
}
|
|
2168
|
-
|
|
2348
|
+
let free = all.filter((o) => !claimed.has(o.at));
|
|
2349
|
+
if (free.length === 0 && !entry.optional) {
|
|
2350
|
+
const held = all.find((o) => claimed.get(o.at)?.optional);
|
|
2351
|
+
if (held) {
|
|
2352
|
+
const holder = claimed.get(held.at);
|
|
2353
|
+
claimed.delete(held.at);
|
|
2354
|
+
results.set(holder, {
|
|
2355
|
+
entry: holder,
|
|
2356
|
+
needle: results.get(holder)?.needle ?? "",
|
|
2357
|
+
status: "skipped",
|
|
2358
|
+
occurrences: []
|
|
2359
|
+
});
|
|
2360
|
+
free = [held];
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2169
2363
|
if (free.length === 0) {
|
|
2170
2364
|
results.set(entry, {
|
|
2171
2365
|
entry,
|
|
@@ -2176,7 +2370,7 @@ function assignInventory(pages, inventory) {
|
|
|
2176
2370
|
continue;
|
|
2177
2371
|
}
|
|
2178
2372
|
const chosen = free.find((o) => o.at >= cursor) ?? free[0];
|
|
2179
|
-
claimed.
|
|
2373
|
+
claimed.set(chosen.at, entry);
|
|
2180
2374
|
cursor = chosen.at;
|
|
2181
2375
|
results.set(entry, {
|
|
2182
2376
|
entry,
|
|
@@ -2193,15 +2387,23 @@ function assignInventory(pages, inventory) {
|
|
|
2193
2387
|
}
|
|
2194
2388
|
|
|
2195
2389
|
// src/rendered-analysis.ts
|
|
2390
|
+
function isRenderedGeometryFact(fact) {
|
|
2391
|
+
return fact.kind === "rendered/geometry";
|
|
2392
|
+
}
|
|
2393
|
+
var drafts = /* @__PURE__ */ new WeakMap();
|
|
2394
|
+
function renderedDraftFor(fact) {
|
|
2395
|
+
let draft = drafts.get(fact);
|
|
2396
|
+
if (!draft) {
|
|
2397
|
+
draft = draftRenderedFindings(fact.input);
|
|
2398
|
+
drafts.set(fact, draft);
|
|
2399
|
+
}
|
|
2400
|
+
return draft;
|
|
2401
|
+
}
|
|
2196
2402
|
var VISIBLE_SPILL_PT = 2;
|
|
2197
2403
|
var OVERLAP_FRACTION = 0.3;
|
|
2198
2404
|
function finding(draft) {
|
|
2199
|
-
const {
|
|
2405
|
+
const { mapping, page, context, ...rest } = draft;
|
|
2200
2406
|
return {
|
|
2201
|
-
source: "quality",
|
|
2202
|
-
ruleId,
|
|
2203
|
-
certainty: "rendered",
|
|
2204
|
-
blocking: false,
|
|
2205
2407
|
...rest,
|
|
2206
2408
|
context: {
|
|
2207
2409
|
...context,
|
|
@@ -2259,7 +2461,7 @@ function unionBox(parts) {
|
|
|
2259
2461
|
{ xMin: Infinity, yMin: Infinity, xMax: -Infinity, yMax: -Infinity }
|
|
2260
2462
|
);
|
|
2261
2463
|
}
|
|
2262
|
-
function
|
|
2464
|
+
function draftRenderedFindings(input) {
|
|
2263
2465
|
const { pages, format } = input;
|
|
2264
2466
|
const { matches, chromeWords } = assignInventory(pages, input.inventory);
|
|
2265
2467
|
const owners = wordOwners(matches);
|
|
@@ -2293,9 +2495,6 @@ function analyzeRenderedDocument(input) {
|
|
|
2293
2495
|
findings.push(
|
|
2294
2496
|
finding({
|
|
2295
2497
|
ruleId: "rendered/clip",
|
|
2296
|
-
code: QUALITY_CODES.RENDERED_CLIP,
|
|
2297
|
-
category: "integrity",
|
|
2298
|
-
severity: "warning",
|
|
2299
2498
|
mapping: bucket.match ? "mapped" : "unmapped",
|
|
2300
2499
|
page: bucket.page,
|
|
2301
2500
|
path: bucket.match?.entry.path ?? "",
|
|
@@ -2320,9 +2519,6 @@ function analyzeRenderedDocument(input) {
|
|
|
2320
2519
|
findings.push(
|
|
2321
2520
|
finding({
|
|
2322
2521
|
ruleId: "rendered/clip",
|
|
2323
|
-
code: QUALITY_CODES.RENDERED_CLIP,
|
|
2324
|
-
category: "integrity",
|
|
2325
|
-
severity: "warning",
|
|
2326
2522
|
mapping: "mapped",
|
|
2327
2523
|
page: o.endPageIndex + 1,
|
|
2328
2524
|
path: match.entry.path,
|
|
@@ -2359,9 +2555,6 @@ function analyzeRenderedDocument(input) {
|
|
|
2359
2555
|
findings.push(
|
|
2360
2556
|
finding({
|
|
2361
2557
|
ruleId: "rendered/spill",
|
|
2362
|
-
code: QUALITY_CODES.RENDERED_SPILL,
|
|
2363
|
-
category: "integrity",
|
|
2364
|
-
severity: "warning",
|
|
2365
2558
|
mapping: "mapped",
|
|
2366
2559
|
page: o.pageIndex + 1,
|
|
2367
2560
|
path: match.entry.path,
|
|
@@ -2381,14 +2574,14 @@ function analyzeRenderedDocument(input) {
|
|
|
2381
2574
|
pages.forEach((page, pageIndex) => {
|
|
2382
2575
|
const lines = lineOfWord(page);
|
|
2383
2576
|
const seen = /* @__PURE__ */ new Set();
|
|
2384
|
-
const
|
|
2385
|
-
const byTop =
|
|
2577
|
+
const words2 = page.words;
|
|
2578
|
+
const byTop = words2.map((_, i) => i).sort((a, b) => words2[a].yMin - words2[b].yMin);
|
|
2386
2579
|
for (let p = 0; p < byTop.length; p++) {
|
|
2387
2580
|
const i = byTop[p];
|
|
2388
|
-
const a =
|
|
2581
|
+
const a = words2[i];
|
|
2389
2582
|
for (let q = p + 1; q < byTop.length; q++) {
|
|
2390
2583
|
const j = byTop[q];
|
|
2391
|
-
const b =
|
|
2584
|
+
const b = words2[j];
|
|
2392
2585
|
if (b.yMin >= a.yMax) break;
|
|
2393
2586
|
const shared = intersects(a, b);
|
|
2394
2587
|
if (shared <= 0) continue;
|
|
@@ -2407,9 +2600,6 @@ function analyzeRenderedDocument(input) {
|
|
|
2407
2600
|
findings.push(
|
|
2408
2601
|
finding({
|
|
2409
2602
|
ruleId: "rendered/overlap",
|
|
2410
|
-
code: QUALITY_CODES.RENDERED_OVERLAP,
|
|
2411
|
-
category: "integrity",
|
|
2412
|
-
severity: "warning",
|
|
2413
2603
|
mapping,
|
|
2414
2604
|
page: pageIndex + 1,
|
|
2415
2605
|
path: pathA || pathB,
|
|
@@ -2432,9 +2622,6 @@ function analyzeRenderedDocument(input) {
|
|
|
2432
2622
|
findings.push(
|
|
2433
2623
|
finding({
|
|
2434
2624
|
ruleId: "rendered/text-missing",
|
|
2435
|
-
code: QUALITY_CODES.RENDERED_TEXT_MISSING,
|
|
2436
|
-
category: "integrity",
|
|
2437
|
-
severity: "warning",
|
|
2438
2625
|
mapping: "mapped",
|
|
2439
2626
|
path: match.entry.path,
|
|
2440
2627
|
message: `"${excerpt(match.entry.text)}" does not appear anywhere in the rendered pages.`,
|
|
@@ -2456,8 +2643,6 @@ function analyzeRenderedDocument(input) {
|
|
|
2456
2643
|
findings.push(
|
|
2457
2644
|
finding({
|
|
2458
2645
|
ruleId: "rendered/font-substituted",
|
|
2459
|
-
code: QUALITY_CODES.RENDERED_FONT_SUBSTITUTED,
|
|
2460
|
-
category: "brand",
|
|
2461
2646
|
severity: requested.declared ? "warning" : "info",
|
|
2462
2647
|
mapping: requested.path ? "mapped" : "unmapped",
|
|
2463
2648
|
path: requested.path ?? "",
|
|
@@ -2478,9 +2663,6 @@ function analyzeRenderedDocument(input) {
|
|
|
2478
2663
|
findings.push(
|
|
2479
2664
|
finding({
|
|
2480
2665
|
ruleId: "rendered/empty-page",
|
|
2481
|
-
code: QUALITY_CODES.RENDERED_EMPTY_PAGE,
|
|
2482
|
-
category: "integrity",
|
|
2483
|
-
severity: "info",
|
|
2484
2666
|
mapping: "unmapped",
|
|
2485
2667
|
page: pageIndex + 1,
|
|
2486
2668
|
path: "",
|
|
@@ -2503,9 +2685,6 @@ function analyzeRenderedDocument(input) {
|
|
|
2503
2685
|
findings.push(
|
|
2504
2686
|
finding({
|
|
2505
2687
|
ruleId: "rendered/heading-stranded",
|
|
2506
|
-
code: QUALITY_CODES.RENDERED_HEADING_STRANDED,
|
|
2507
|
-
category: "composition",
|
|
2508
|
-
severity: "warning",
|
|
2509
2688
|
mapping: "mapped",
|
|
2510
2689
|
page: part.pageIndex + 1,
|
|
2511
2690
|
path: match.entry.path,
|
|
@@ -2539,9 +2718,6 @@ function analyzeRenderedDocument(input) {
|
|
|
2539
2718
|
findings.push(
|
|
2540
2719
|
finding({
|
|
2541
2720
|
ruleId: "rendered/paragraph-split",
|
|
2542
|
-
code: QUALITY_CODES.RENDERED_PARAGRAPH_SPLIT,
|
|
2543
|
-
category: "composition",
|
|
2544
|
-
severity: "info",
|
|
2545
2721
|
mapping: "mapped",
|
|
2546
2722
|
page: (which === "orphan" ? first.pageIndex : last.pageIndex) + 1,
|
|
2547
2723
|
path: match.entry.path,
|
|
@@ -2563,56 +2739,526 @@ function analyzeRenderedDocument(input) {
|
|
|
2563
2739
|
skipped: 0
|
|
2564
2740
|
};
|
|
2565
2741
|
for (const match of matches) inventory[match.status] += 1;
|
|
2742
|
+
const byRule = /* @__PURE__ */ new Map();
|
|
2743
|
+
for (const draft of findings) {
|
|
2744
|
+
const bucket = byRule.get(draft.ruleId);
|
|
2745
|
+
if (bucket) bucket.push(draft);
|
|
2746
|
+
else byRule.set(draft.ruleId, [draft]);
|
|
2747
|
+
}
|
|
2748
|
+
return {
|
|
2749
|
+
drafts: findings,
|
|
2750
|
+
byRule,
|
|
2751
|
+
inventory,
|
|
2752
|
+
// The same gate the check itself runs under: an empty font list
|
|
2753
|
+
// verified nothing, and must not read as "nothing was substituted".
|
|
2754
|
+
...input.fonts && input.fonts.length > 0 && input.requestedFonts && {
|
|
2755
|
+
fonts: { requested: input.requestedFonts.length, substituted }
|
|
2756
|
+
}
|
|
2757
|
+
};
|
|
2758
|
+
}
|
|
2759
|
+
var engine;
|
|
2760
|
+
function renderedEngine() {
|
|
2761
|
+
engine ??= new QualityEngine(RENDERED_QUALITY_RULES.rules);
|
|
2762
|
+
return engine;
|
|
2763
|
+
}
|
|
2764
|
+
function analyzeRenderedDocument(input, options = {}) {
|
|
2765
|
+
const fact = {
|
|
2766
|
+
id: "rendered:geometry",
|
|
2767
|
+
kind: "rendered/geometry",
|
|
2768
|
+
path: "",
|
|
2769
|
+
input
|
|
2770
|
+
};
|
|
2771
|
+
const analysis = renderedEngine().analyzeSync(
|
|
2772
|
+
{
|
|
2773
|
+
format: input.format,
|
|
2774
|
+
model: void 0,
|
|
2775
|
+
facts: [fact],
|
|
2776
|
+
provenance: {},
|
|
2777
|
+
...input.renderer !== void 0 && { renderer: input.renderer }
|
|
2778
|
+
},
|
|
2779
|
+
options
|
|
2780
|
+
);
|
|
2781
|
+
let draft;
|
|
2782
|
+
try {
|
|
2783
|
+
draft = renderedDraftFor(fact);
|
|
2784
|
+
} catch {
|
|
2785
|
+
draft = void 0;
|
|
2786
|
+
}
|
|
2566
2787
|
const byMapping = {
|
|
2567
2788
|
mapped: 0,
|
|
2568
2789
|
ambiguous: 0,
|
|
2569
2790
|
unmapped: 0
|
|
2570
2791
|
};
|
|
2571
|
-
for (const f of
|
|
2792
|
+
for (const f of analysis.diagnostics) {
|
|
2572
2793
|
byMapping[f.context?.mapping ?? "unmapped"] += 1;
|
|
2573
2794
|
}
|
|
2574
2795
|
return {
|
|
2575
|
-
findings,
|
|
2796
|
+
findings: analysis.diagnostics,
|
|
2576
2797
|
summary: {
|
|
2577
|
-
pages: pages.length,
|
|
2578
|
-
words: pages.reduce((n, p) => n + p.words.length, 0),
|
|
2579
|
-
inventory
|
|
2798
|
+
pages: input.pages.length,
|
|
2799
|
+
words: input.pages.reduce((n, p) => n + p.words.length, 0),
|
|
2800
|
+
inventory: draft?.inventory ?? {
|
|
2801
|
+
mapped: 0,
|
|
2802
|
+
ambiguous: 0,
|
|
2803
|
+
missing: 0,
|
|
2804
|
+
skipped: 0
|
|
2805
|
+
},
|
|
2580
2806
|
findings: byMapping,
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2807
|
+
...draft?.fonts && { fonts: draft.fonts },
|
|
2808
|
+
suppressed: analysis.suppressedCount,
|
|
2809
|
+
blocked: analysis.blocked,
|
|
2810
|
+
truncated: analysis.truncated,
|
|
2811
|
+
...analysis.profileId !== void 0 && {
|
|
2812
|
+
profileId: analysis.profileId
|
|
2585
2813
|
}
|
|
2814
|
+
},
|
|
2815
|
+
analysis
|
|
2816
|
+
};
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
// src/block-matrix.ts
|
|
2820
|
+
import {
|
|
2821
|
+
blockDependencies,
|
|
2822
|
+
readBlockDefinitions
|
|
2823
|
+
} from "@json-to-office/shared";
|
|
2824
|
+
var FALLBACK_FONTS = {
|
|
2825
|
+
heading: { family: "DejaVu Sans" },
|
|
2826
|
+
body: { family: "DejaVu Sans" }
|
|
2827
|
+
};
|
|
2828
|
+
var MATRIX_IMAGE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAABytg0kAAAAFElEQVR42mNk+M9QzwAFjDAGACPuA/8fMSCgAAAAAElFTkSuQmCC";
|
|
2829
|
+
var WORDS = [
|
|
2830
|
+
"performance",
|
|
2831
|
+
"delivery",
|
|
2832
|
+
"retention",
|
|
2833
|
+
"operating",
|
|
2834
|
+
"programme",
|
|
2835
|
+
"contracted",
|
|
2836
|
+
"recommendation",
|
|
2837
|
+
"quarter",
|
|
2838
|
+
"measurement",
|
|
2839
|
+
"ownership",
|
|
2840
|
+
"renewal",
|
|
2841
|
+
"segment",
|
|
2842
|
+
"margin",
|
|
2843
|
+
"baseline",
|
|
2844
|
+
"procurement",
|
|
2845
|
+
"integration"
|
|
2846
|
+
];
|
|
2847
|
+
function words(count, seed, sentence) {
|
|
2848
|
+
if (count <= 0) return "";
|
|
2849
|
+
const out = [`Item${seed}`];
|
|
2850
|
+
for (let i = 1; i < count; i += 1) out.push(WORDS[(seed + i) % WORDS.length]);
|
|
2851
|
+
return sentence ? `${out.join(" ")}.` : out.join(" ");
|
|
2852
|
+
}
|
|
2853
|
+
function widestNumber(length, seed = 0) {
|
|
2854
|
+
if (length <= 0) return "";
|
|
2855
|
+
if (length === 1) return "9";
|
|
2856
|
+
if (length < 4) return `\u2212${"9".repeat(length - 1)}`;
|
|
2857
|
+
const group = (digits2) => Array.from({ length: digits2 }, (_, i) => String((i + seed) % 9 + 1)).join("").replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
2858
|
+
let digits = 1;
|
|
2859
|
+
while (group(digits + 1).length + 3 <= length) digits += 1;
|
|
2860
|
+
const integer = group(digits);
|
|
2861
|
+
return `\u2212${integer}.${"0".repeat(length - integer.length - 2)}`;
|
|
2862
|
+
}
|
|
2863
|
+
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2864
|
+
var NUMERIC_SLOTS = /* @__PURE__ */ new Set(["value", "delta", "cells"]);
|
|
2865
|
+
function stringValue(slot, name, edge, seed) {
|
|
2866
|
+
if (slot.enum && slot.enum.length > 0) {
|
|
2867
|
+
const values = slot.enum.map(String);
|
|
2868
|
+
return edge === "max" ? values.reduce((a, b) => b.length > a.length ? b : a) : values[0];
|
|
2869
|
+
}
|
|
2870
|
+
if (slot.maxLength !== void 0 && NUMERIC_SLOTS.has(name)) {
|
|
2871
|
+
return widestNumber(
|
|
2872
|
+
edge === "max" ? slot.maxLength : Math.min(3, slot.maxLength),
|
|
2873
|
+
seed
|
|
2874
|
+
);
|
|
2875
|
+
}
|
|
2876
|
+
if (slot.maxLength !== void 0 && name === "unit") {
|
|
2877
|
+
return edge === "max" ? ` \u20AC${"m".repeat(Math.max(0, slot.maxLength - 2))}`.slice(
|
|
2878
|
+
0,
|
|
2879
|
+
slot.maxLength
|
|
2880
|
+
) : "%";
|
|
2881
|
+
}
|
|
2882
|
+
if (slot.maxLength !== void 0) {
|
|
2883
|
+
const target = edge === "max" ? slot.maxLength : Math.max(1, slot.minLength ?? 1);
|
|
2884
|
+
let text = `Item${seed}`.slice(0, target);
|
|
2885
|
+
let i = 0;
|
|
2886
|
+
for (; ; ) {
|
|
2887
|
+
const next = WORDS[(seed + i) % WORDS.length];
|
|
2888
|
+
i += 1;
|
|
2889
|
+
if (text.length + 1 + next.length > target) {
|
|
2890
|
+
if (i > WORDS.length) break;
|
|
2891
|
+
continue;
|
|
2892
|
+
}
|
|
2893
|
+
text += ` ${next}`;
|
|
2894
|
+
}
|
|
2895
|
+
return text;
|
|
2896
|
+
}
|
|
2897
|
+
const budget = slot.maxWords ?? 12;
|
|
2898
|
+
const count = edge === "max" ? budget : Math.max(1, Math.min(2, budget));
|
|
2899
|
+
const sentence = !slot.oneLine && slot.role !== "source";
|
|
2900
|
+
if (slot.role === "source") return `Source: ${words(count - 1, seed, false)}`;
|
|
2901
|
+
if (name === "number")
|
|
2902
|
+
return edge === "max" ? words(count, seed, false) : "01";
|
|
2903
|
+
return words(count, seed, sentence);
|
|
2904
|
+
}
|
|
2905
|
+
function componentValue(name, edge, seed) {
|
|
2906
|
+
if (name === "chart") {
|
|
2907
|
+
const categories = edge === "max" ? 8 : 2;
|
|
2908
|
+
const series = edge === "max" ? 3 : 1;
|
|
2909
|
+
return {
|
|
2910
|
+
name: "chart",
|
|
2911
|
+
props: {
|
|
2912
|
+
type: "column",
|
|
2913
|
+
valAxisTitle: edge === "max" ? `${words(3, seed, false)} (\u20ACm)` : "Revenue (\u20ACm)",
|
|
2914
|
+
...edge === "max" && { catAxisTitle: words(3, seed + 3, false) },
|
|
2915
|
+
chartColors: ["primary", "secondary", "accent"].slice(0, series),
|
|
2916
|
+
data: Array.from({ length: series }, (_, s) => ({
|
|
2917
|
+
name: words(edge === "max" ? 3 : 1, seed + s, false),
|
|
2918
|
+
// Two words per category is where LibreOffice's native chart
|
|
2919
|
+
// still keeps rotated labels clear of the axis title; at three
|
|
2920
|
+
// the title is drawn over them, at any category count.
|
|
2921
|
+
labels: Array.from(
|
|
2922
|
+
{ length: categories },
|
|
2923
|
+
(_2, c) => edge === "max" ? words(2, seed + c, false) : `Q${c + 1}`
|
|
2924
|
+
),
|
|
2925
|
+
values: Array.from(
|
|
2926
|
+
{ length: categories },
|
|
2927
|
+
(_2, c) => 1.2 * (c + 1) + s
|
|
2928
|
+
)
|
|
2929
|
+
}))
|
|
2930
|
+
}
|
|
2931
|
+
};
|
|
2932
|
+
}
|
|
2933
|
+
return {
|
|
2934
|
+
name: "image",
|
|
2935
|
+
props: {
|
|
2936
|
+
base64: MATRIX_IMAGE,
|
|
2937
|
+
width: edge === "max" ? name === "logo" ? "40%" : "100%" : "25%",
|
|
2938
|
+
alt: words(6, seed, false)
|
|
2939
|
+
}
|
|
2940
|
+
};
|
|
2941
|
+
}
|
|
2942
|
+
function boundarySlotValue(slot, name, edge, seed = 0) {
|
|
2943
|
+
switch (slot.type) {
|
|
2944
|
+
case "string":
|
|
2945
|
+
return stringValue(slot, name, edge, seed);
|
|
2946
|
+
case "number":
|
|
2947
|
+
case "integer":
|
|
2948
|
+
return edge === "max" ? slot.maximum ?? 9999 : slot.minimum ?? 0;
|
|
2949
|
+
case "boolean":
|
|
2950
|
+
return edge === "max" ? true : slot.default ?? true;
|
|
2951
|
+
case "component":
|
|
2952
|
+
return componentValue(name, edge, seed);
|
|
2953
|
+
case "object": {
|
|
2954
|
+
const out = {};
|
|
2955
|
+
for (const [key, property] of Object.entries(slot.properties ?? {})) {
|
|
2956
|
+
if (edge === "min" && !property.required) continue;
|
|
2957
|
+
const value = boundarySlotValue(property, key, edge, seed + key.length);
|
|
2958
|
+
if (value !== void 0) out[key] = value;
|
|
2959
|
+
}
|
|
2960
|
+
return out;
|
|
2961
|
+
}
|
|
2962
|
+
case "array": {
|
|
2963
|
+
const count = edge === "max" ? slot.maxItems ?? 3 : slot.minItems ?? (slot.required ? 1 : 0);
|
|
2964
|
+
const item = slot.items ?? { type: "string" };
|
|
2965
|
+
return Array.from(
|
|
2966
|
+
{ length: count },
|
|
2967
|
+
(_, i) => boundarySlotValue(item, name, edge, seed + i)
|
|
2968
|
+
);
|
|
2969
|
+
}
|
|
2970
|
+
default:
|
|
2971
|
+
return void 0;
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
function boundaryInvocation(name, definition, edge, seed = 0, requiredRoles = []) {
|
|
2975
|
+
const slots = {};
|
|
2976
|
+
let offset = seed;
|
|
2977
|
+
for (const [slotName, slot] of Object.entries(definition.slots)) {
|
|
2978
|
+
offset += slotName.length;
|
|
2979
|
+
const required = slot.required === true || slot.role !== void 0 && requiredRoles.includes(slot.role);
|
|
2980
|
+
if (edge === "min" && !required) continue;
|
|
2981
|
+
const value = boundarySlotValue(slot, slotName, edge, offset);
|
|
2982
|
+
if (value !== void 0) slots[slotName] = value;
|
|
2983
|
+
}
|
|
2984
|
+
const labels = slots.labels;
|
|
2985
|
+
const columns = slots.columns;
|
|
2986
|
+
if (Array.isArray(labels) && Array.isArray(columns)) {
|
|
2987
|
+
const cells = definition.slots.columns?.items?.properties?.cells;
|
|
2988
|
+
for (const column of columns) {
|
|
2989
|
+
if (isRecord(column) && Array.isArray(column.cells) && cells?.items) {
|
|
2990
|
+
column.cells = labels.map(
|
|
2991
|
+
(_, i) => boundarySlotValue(cells.items, "cells", edge, offset + i)
|
|
2992
|
+
);
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
return { name: "block", props: { ref: name, slots } };
|
|
2997
|
+
}
|
|
2998
|
+
function overBudgetInvocation(name, definition, seed = 0) {
|
|
2999
|
+
const invocation = boundaryInvocation(name, definition, "max", seed);
|
|
3000
|
+
const slots = invocation.props.slots;
|
|
3001
|
+
for (const [slotName, slot] of Object.entries(definition.slots)) {
|
|
3002
|
+
if (slot.type === "string" && slot.maxWords !== void 0) {
|
|
3003
|
+
slots[slotName] = `${words(slot.maxWords + 1, seed, !slot.oneLine)}`;
|
|
3004
|
+
return { invocation, slot: slotName, kind: "words" };
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
for (const [slotName, slot] of Object.entries(definition.slots)) {
|
|
3008
|
+
if (slot.type === "array" && slot.maxItems !== void 0 && Array.isArray(slots[slotName])) {
|
|
3009
|
+
const items = slots[slotName];
|
|
3010
|
+
slots[slotName] = [...items, items[0]];
|
|
3011
|
+
return { invocation, slot: slotName, kind: "items" };
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
return void 0;
|
|
3015
|
+
}
|
|
3016
|
+
function enumerateBlockDefinitions(template, templateName) {
|
|
3017
|
+
const definitions = readBlockDefinitions(template);
|
|
3018
|
+
const examples = /* @__PURE__ */ new Map();
|
|
3019
|
+
const walk = (node) => {
|
|
3020
|
+
if (Array.isArray(node)) {
|
|
3021
|
+
node.forEach(walk);
|
|
3022
|
+
return;
|
|
2586
3023
|
}
|
|
3024
|
+
if (!isRecord(node)) return;
|
|
3025
|
+
const props = node.props;
|
|
3026
|
+
if (node.name === "block" && isRecord(props) && typeof props.ref === "string" && !examples.has(props.ref)) {
|
|
3027
|
+
examples.set(props.ref, isRecord(props.slots) ? props.slots : {});
|
|
3028
|
+
}
|
|
3029
|
+
walk(node.children);
|
|
2587
3030
|
};
|
|
3031
|
+
walk(isRecord(template) ? template.children : void 0);
|
|
3032
|
+
return Object.entries(definitions).map(([name, definition]) => ({
|
|
3033
|
+
name,
|
|
3034
|
+
template: templateName,
|
|
3035
|
+
pointer: `/props/blocks/${name.replace(/~/g, "~0").replace(/\//g, "~1")}`,
|
|
3036
|
+
definition,
|
|
3037
|
+
...examples.has(name) && { example: examples.get(name) }
|
|
3038
|
+
}));
|
|
3039
|
+
}
|
|
3040
|
+
var CHROME = ["cover", "running-head", "section-opener"];
|
|
3041
|
+
function nominal(entry, name, definitions) {
|
|
3042
|
+
if (!entry) return void 0;
|
|
3043
|
+
if (entry.example) {
|
|
3044
|
+
return {
|
|
3045
|
+
name: "block",
|
|
3046
|
+
props: { ref: name, slots: structuredClone(entry.example) }
|
|
3047
|
+
};
|
|
3048
|
+
}
|
|
3049
|
+
return boundaryInvocation(name, definitions[name], "min");
|
|
3050
|
+
}
|
|
3051
|
+
function inlineImages(value) {
|
|
3052
|
+
if (Array.isArray(value)) return value.map(inlineImages);
|
|
3053
|
+
if (!isRecord(value)) return value;
|
|
3054
|
+
if (value.name === "image" && isRecord(value.props) && typeof value.props.path === "string") {
|
|
3055
|
+
const props = { ...value.props, base64: MATRIX_IMAGE };
|
|
3056
|
+
delete props.path;
|
|
3057
|
+
return { ...value, props };
|
|
3058
|
+
}
|
|
3059
|
+
if (value.name === "highcharts") return componentValue("chart", "min", 0);
|
|
3060
|
+
return Object.fromEntries(
|
|
3061
|
+
Object.entries(value).map(([k, v]) => [k, inlineImages(v)])
|
|
3062
|
+
);
|
|
3063
|
+
}
|
|
3064
|
+
function withDefinitions(definitions, names) {
|
|
3065
|
+
const out = {};
|
|
3066
|
+
for (const name of names) {
|
|
3067
|
+
if (!(name in definitions)) continue;
|
|
3068
|
+
out[name] = definitions[name];
|
|
3069
|
+
for (const dependency of blockDependencies(definitions, name)) {
|
|
3070
|
+
if (dependency in definitions) out[dependency] = definitions[dependency];
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
return out;
|
|
3074
|
+
}
|
|
3075
|
+
function conditions(props, children, theme, font, canvas) {
|
|
3076
|
+
const sections = children.map((child) => {
|
|
3077
|
+
if (!isRecord(child) || child.name !== "section") return child;
|
|
3078
|
+
const props2 = isRecord(child.props) ? child.props : {};
|
|
3079
|
+
const page = isRecord(props2.page) ? props2.page : {};
|
|
3080
|
+
return { ...child, props: { ...props2, page: { ...page, size: canvas } } };
|
|
3081
|
+
});
|
|
3082
|
+
const chart = JSON.stringify(sections).includes('"name":"chart"');
|
|
3083
|
+
return {
|
|
3084
|
+
name: "docx",
|
|
3085
|
+
...chart && { renderer: "office-open" },
|
|
3086
|
+
props: {
|
|
3087
|
+
...props,
|
|
3088
|
+
theme,
|
|
3089
|
+
...font === "fallback" && { themeOverrides: { fonts: FALLBACK_FONTS } }
|
|
3090
|
+
},
|
|
3091
|
+
children: sections
|
|
3092
|
+
};
|
|
3093
|
+
}
|
|
3094
|
+
var BODY_COPY = words(40, 1e3, true);
|
|
3095
|
+
function blockCaseDocument(entries, name, edge, theme, { font = "design", canvas = "A4", requiredRoles = [] } = {}) {
|
|
3096
|
+
const definitions = Object.fromEntries(
|
|
3097
|
+
entries.map((e) => [e.name, e.definition])
|
|
3098
|
+
);
|
|
3099
|
+
const byName = new Map(entries.map((e) => [e.name, e]));
|
|
3100
|
+
const under = (chrome) => chrome === name ? boundaryInvocation(name, definitions[name], edge, 0, requiredRoles) : inlineImages(nominal(byName.get(chrome), chrome, definitions));
|
|
3101
|
+
const cover = under("cover");
|
|
3102
|
+
const runningHead = under("running-head");
|
|
3103
|
+
const opener = under("section-opener");
|
|
3104
|
+
const body = [];
|
|
3105
|
+
if (!CHROME.includes(name)) {
|
|
3106
|
+
if (name === "footnotes" && byName.has("figure")) {
|
|
3107
|
+
body.push(
|
|
3108
|
+
inlineImages(nominal(byName.get("figure"), "figure", definitions))
|
|
3109
|
+
);
|
|
3110
|
+
}
|
|
3111
|
+
body.push(
|
|
3112
|
+
boundaryInvocation(name, definitions[name], edge, 0, requiredRoles)
|
|
3113
|
+
);
|
|
3114
|
+
}
|
|
3115
|
+
body.push({ name: "paragraph", props: { text: BODY_COPY } });
|
|
3116
|
+
const used = /* @__PURE__ */ new Set([
|
|
3117
|
+
name,
|
|
3118
|
+
...CHROME,
|
|
3119
|
+
...name === "footnotes" ? ["figure"] : []
|
|
3120
|
+
]);
|
|
3121
|
+
const children = [];
|
|
3122
|
+
if (cover) children.push({ name: "section", children: [cover] });
|
|
3123
|
+
children.push({
|
|
3124
|
+
name: "section",
|
|
3125
|
+
children: [runningHead, opener, ...body].filter((c) => c !== void 0)
|
|
3126
|
+
});
|
|
3127
|
+
return conditions(
|
|
3128
|
+
{
|
|
3129
|
+
metadata: {
|
|
3130
|
+
title: "Boundary report",
|
|
3131
|
+
author: "jto-ops block matrix",
|
|
3132
|
+
company: "Example client",
|
|
3133
|
+
date: "September 2026"
|
|
3134
|
+
},
|
|
3135
|
+
blocks: withDefinitions(definitions, used)
|
|
3136
|
+
},
|
|
3137
|
+
children,
|
|
3138
|
+
theme,
|
|
3139
|
+
font,
|
|
3140
|
+
canvas
|
|
3141
|
+
);
|
|
3142
|
+
}
|
|
3143
|
+
function reportCaseDocument(template, edge, theme, { font = "design", canvas = "A4", requiredRoles = [] } = {}) {
|
|
3144
|
+
if (!isRecord(template) || !isRecord(template.props))
|
|
3145
|
+
throw new Error("A template is a document with props.");
|
|
3146
|
+
const definitions = readBlockDefinitions(template);
|
|
3147
|
+
let seed = 0;
|
|
3148
|
+
const rewrite = (node) => {
|
|
3149
|
+
if (Array.isArray(node)) return node.map(rewrite);
|
|
3150
|
+
if (!isRecord(node)) return node;
|
|
3151
|
+
const props2 = node.props;
|
|
3152
|
+
if (node.name === "block" && isRecord(props2) && typeof props2.ref === "string" && definitions[props2.ref]) {
|
|
3153
|
+
seed += 7;
|
|
3154
|
+
return boundaryInvocation(
|
|
3155
|
+
props2.ref,
|
|
3156
|
+
definitions[props2.ref],
|
|
3157
|
+
edge,
|
|
3158
|
+
seed,
|
|
3159
|
+
requiredRoles
|
|
3160
|
+
);
|
|
3161
|
+
}
|
|
3162
|
+
if (node.name === "paragraph" && isRecord(props2) && typeof props2.text === "string") {
|
|
3163
|
+
return {
|
|
3164
|
+
...node,
|
|
3165
|
+
props: { ...props2, text: edge === "max" ? BODY_COPY : props2.text }
|
|
3166
|
+
};
|
|
3167
|
+
}
|
|
3168
|
+
return Array.isArray(node.children) ? { ...node, children: rewrite(node.children) } : node;
|
|
3169
|
+
};
|
|
3170
|
+
const props = structuredClone(template.props);
|
|
3171
|
+
delete props.theme;
|
|
3172
|
+
delete props.themeOverrides;
|
|
3173
|
+
return conditions(
|
|
3174
|
+
props,
|
|
3175
|
+
rewrite(template.children),
|
|
3176
|
+
theme,
|
|
3177
|
+
font,
|
|
3178
|
+
canvas
|
|
3179
|
+
);
|
|
3180
|
+
}
|
|
3181
|
+
function generateBlockMatrix(template, templateName, options) {
|
|
3182
|
+
const entries = enumerateBlockDefinitions(template, templateName);
|
|
3183
|
+
const fonts = options.fonts ?? ["design", "fallback"];
|
|
3184
|
+
const edges = options.edges ?? ["min", "max"];
|
|
3185
|
+
const canvases = options.canvases ?? ["A4"];
|
|
3186
|
+
const cases = [];
|
|
3187
|
+
for (const theme of options.themes)
|
|
3188
|
+
for (const font of fonts)
|
|
3189
|
+
for (const canvas of canvases)
|
|
3190
|
+
for (const edge of edges) {
|
|
3191
|
+
if (options.blocks !== false)
|
|
3192
|
+
for (const entry of entries)
|
|
3193
|
+
cases.push({
|
|
3194
|
+
id: `${entry.name}@${edge}/${theme}/${font}/${canvas}`,
|
|
3195
|
+
block: entry.name,
|
|
3196
|
+
edge,
|
|
3197
|
+
theme,
|
|
3198
|
+
font,
|
|
3199
|
+
canvas,
|
|
3200
|
+
document: blockCaseDocument(entries, entry.name, edge, theme, {
|
|
3201
|
+
font,
|
|
3202
|
+
canvas,
|
|
3203
|
+
requiredRoles: options.requiredRoles
|
|
3204
|
+
})
|
|
3205
|
+
});
|
|
3206
|
+
if (options.report !== false)
|
|
3207
|
+
cases.push({
|
|
3208
|
+
id: `report@${edge}/${theme}/${font}/${canvas}`,
|
|
3209
|
+
block: "report",
|
|
3210
|
+
edge,
|
|
3211
|
+
theme,
|
|
3212
|
+
font,
|
|
3213
|
+
canvas,
|
|
3214
|
+
document: reportCaseDocument(template, edge, theme, {
|
|
3215
|
+
font,
|
|
3216
|
+
canvas,
|
|
3217
|
+
requiredRoles: options.requiredRoles
|
|
3218
|
+
})
|
|
3219
|
+
});
|
|
3220
|
+
}
|
|
3221
|
+
return cases;
|
|
2588
3222
|
}
|
|
2589
3223
|
export {
|
|
2590
3224
|
DocxFormatAdapter,
|
|
3225
|
+
FALLBACK_FONTS,
|
|
2591
3226
|
FontconfigStager,
|
|
3227
|
+
MATRIX_IMAGE,
|
|
2592
3228
|
MacOSCoreTextStager,
|
|
2593
3229
|
NoopFontStager,
|
|
2594
3230
|
PptxFormatAdapter,
|
|
3231
|
+
RENDERED_QUALITY_RULES,
|
|
2595
3232
|
VISIBLE_SPILL_PT,
|
|
2596
3233
|
WindowsFontStager,
|
|
2597
3234
|
analyzeRenderedDocument,
|
|
2598
3235
|
assignInventory,
|
|
2599
3236
|
authoredTextForMatch,
|
|
3237
|
+
blockCaseDocument,
|
|
3238
|
+
boundaryInvocation,
|
|
3239
|
+
boundarySlotValue,
|
|
2600
3240
|
clearRasterizerCache,
|
|
2601
3241
|
createAdapter,
|
|
2602
3242
|
createLibreOfficePptxBatchRasterizer,
|
|
2603
3243
|
createLibreOfficePptxRasterizer,
|
|
2604
3244
|
emitDiagnostic,
|
|
3245
|
+
enumerateBlockDefinitions,
|
|
2605
3246
|
extractPdfFonts,
|
|
2606
3247
|
extractPdfTextGeometry,
|
|
2607
3248
|
familyRendered,
|
|
3249
|
+
generateBlockMatrix,
|
|
2608
3250
|
getFontStager,
|
|
2609
3251
|
getRasterizerCacheStats,
|
|
2610
3252
|
normalizeForMatch,
|
|
3253
|
+
overBudgetInvocation,
|
|
2611
3254
|
parsePdfFonts,
|
|
2612
3255
|
parsePdfTextBbox,
|
|
2613
3256
|
pdffontsAvailable,
|
|
2614
3257
|
pdftotextAvailable,
|
|
3258
|
+
readingOrder,
|
|
3259
|
+
reportCaseDocument,
|
|
2615
3260
|
runWithDiagnosticSink,
|
|
2616
|
-
stderrDiagnosticSink
|
|
3261
|
+
stderrDiagnosticSink,
|
|
3262
|
+
widestNumber
|
|
2617
3263
|
};
|
|
2618
3264
|
//# sourceMappingURL=index.js.map
|