@json-to-office/jto-ops 6.2.0 → 7.0.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 +261 -8
- package/dist/index.js +1002 -127
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -1061,6 +1061,7 @@ var DocxFormatAdapter = class {
|
|
|
1061
1061
|
options.services
|
|
1062
1062
|
);
|
|
1063
1063
|
const prepared = !hasPlugins && options.prepared?.format === "docx" ? options.prepared : void 0;
|
|
1064
|
+
const pluginPrepared = hasPlugins && options.prepared?.format === "docx" ? options.prepared : void 0;
|
|
1064
1065
|
const resolved = prepared ? void 0 : await this.resolveThemes(options);
|
|
1065
1066
|
const requestedTheme = resolved?.requested;
|
|
1066
1067
|
const customThemes = resolved?.customThemes;
|
|
@@ -1136,6 +1137,7 @@ var DocxFormatAdapter = class {
|
|
|
1136
1137
|
requestedTheme,
|
|
1137
1138
|
customThemes
|
|
1138
1139
|
);
|
|
1140
|
+
const usable = preparedFor(pluginPrepared, parsed);
|
|
1139
1141
|
const result = await generator.generateBuffer(docDefinition, {
|
|
1140
1142
|
validation: {
|
|
1141
1143
|
allowUnknownFields: options.validation?.allowUnknownFields
|
|
@@ -1144,10 +1146,12 @@ var DocxFormatAdapter = class {
|
|
|
1144
1146
|
generatedAt: options.generatedAt,
|
|
1145
1147
|
baseDir: options.baseDir,
|
|
1146
1148
|
renderer: options.renderer,
|
|
1147
|
-
svgRasterFallback: options.svgRasterFallback
|
|
1149
|
+
svgRasterFallback: options.svgRasterFallback,
|
|
1150
|
+
...usable && { prepared: usable }
|
|
1148
1151
|
});
|
|
1149
|
-
|
|
1150
|
-
|
|
1152
|
+
const emitted = withoutPreparedWarnings(result.warnings ?? [], usable);
|
|
1153
|
+
emitGenerationWarnings(emitted);
|
|
1154
|
+
options.warnings?.push(...toGenerationWarnings(emitted));
|
|
1151
1155
|
return result.buffer;
|
|
1152
1156
|
},
|
|
1153
1157
|
getStandardDefinition: generator.expandStandardDefinition ? async (config) => {
|
|
@@ -1230,15 +1234,35 @@ var DocxFormatAdapter = class {
|
|
|
1230
1234
|
resolved.requested,
|
|
1231
1235
|
resolved.customThemes
|
|
1232
1236
|
);
|
|
1233
|
-
const prepared =
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
customThemes:
|
|
1237
|
+
const prepared = options.plugins && options.plugins.length > 0 ? await (async () => {
|
|
1238
|
+
let generator = core.createDocumentGenerator({
|
|
1239
|
+
theme: resolved.requested,
|
|
1240
|
+
customThemes: resolved.requested ? {
|
|
1241
|
+
...resolved.customThemes,
|
|
1242
|
+
[CLI_THEME_KEY]: resolved.requested
|
|
1243
|
+
} : resolved.customThemes,
|
|
1244
|
+
services: withRequestedServices(
|
|
1245
|
+
buildDocxServices(),
|
|
1246
|
+
options.services
|
|
1247
|
+
),
|
|
1248
|
+
baseDir: options.baseDir,
|
|
1237
1249
|
fonts: options.fonts,
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1250
|
+
validation: {
|
|
1251
|
+
allowUnknownFields: options.validation?.allowUnknownFields
|
|
1252
|
+
},
|
|
1253
|
+
renderer: options.renderer
|
|
1254
|
+
});
|
|
1255
|
+
for (const plugin of options.plugins)
|
|
1256
|
+
generator = generator.addComponent(plugin);
|
|
1257
|
+
const result = await generator.prepareQuality(normalized.document);
|
|
1258
|
+
warnings.push(...result.warnings ?? []);
|
|
1259
|
+
return result.prepared;
|
|
1260
|
+
})() : core.prepareDocxQualityDocument(normalized.document, {
|
|
1261
|
+
customThemes: normalized.customThemes,
|
|
1262
|
+
fonts: options.fonts,
|
|
1263
|
+
renderer: options.renderer ?? documentRenderer(parsed),
|
|
1264
|
+
warnings
|
|
1265
|
+
});
|
|
1242
1266
|
return stampPrepared(
|
|
1243
1267
|
{
|
|
1244
1268
|
...prepared,
|
|
@@ -1269,7 +1293,7 @@ var DocxFormatAdapter = class {
|
|
|
1269
1293
|
async resolveTheme(options) {
|
|
1270
1294
|
const core = await import("@json-to-office/core-docx");
|
|
1271
1295
|
const { requested } = await this.resolveThemes(options);
|
|
1272
|
-
return requested ?? core.themes?.
|
|
1296
|
+
return requested ?? core.themes?.consulting ?? {};
|
|
1273
1297
|
}
|
|
1274
1298
|
/**
|
|
1275
1299
|
* Resolve `theme`/`themePath` once for a whole run: `themePath` is read a
|
|
@@ -1430,6 +1454,7 @@ var PptxFormatAdapter = class {
|
|
|
1430
1454
|
options.services
|
|
1431
1455
|
);
|
|
1432
1456
|
const prepared = !hasPlugins && options.prepared?.format === "pptx" ? options.prepared : void 0;
|
|
1457
|
+
const pluginPrepared = hasPlugins && options.prepared?.format === "pptx" ? options.prepared : void 0;
|
|
1433
1458
|
const resolved = prepared ? void 0 : await this.resolveThemes(options);
|
|
1434
1459
|
const requestedTheme = resolved?.requested;
|
|
1435
1460
|
const customThemes = resolved?.customThemes;
|
|
@@ -1504,6 +1529,7 @@ var PptxFormatAdapter = class {
|
|
|
1504
1529
|
requestedTheme,
|
|
1505
1530
|
customThemes
|
|
1506
1531
|
);
|
|
1532
|
+
const usable = preparedFor(pluginPrepared, parsed);
|
|
1507
1533
|
const result = await generator.generateBuffer(docDefinition, {
|
|
1508
1534
|
validation: {
|
|
1509
1535
|
allowUnknownFields: options.validation?.allowUnknownFields
|
|
@@ -1511,9 +1537,13 @@ var PptxFormatAdapter = class {
|
|
|
1511
1537
|
deterministic: options.deterministic,
|
|
1512
1538
|
generatedAt: options.generatedAt,
|
|
1513
1539
|
baseDir: options.baseDir,
|
|
1514
|
-
renderer: options.renderer
|
|
1540
|
+
renderer: options.renderer,
|
|
1541
|
+
...usable && { prepared: usable }
|
|
1515
1542
|
});
|
|
1516
|
-
const normalized =
|
|
1543
|
+
const normalized = withoutPreparedWarnings(
|
|
1544
|
+
toGenerationWarnings(result.warnings),
|
|
1545
|
+
usable
|
|
1546
|
+
);
|
|
1517
1547
|
emitGenerationWarnings(normalized);
|
|
1518
1548
|
options.warnings?.push(...normalized);
|
|
1519
1549
|
return result.buffer;
|
|
@@ -1580,19 +1610,37 @@ var PptxFormatAdapter = class {
|
|
|
1580
1610
|
resolved.requested,
|
|
1581
1611
|
resolved.customThemes
|
|
1582
1612
|
);
|
|
1583
|
-
const
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
customThemes: normalized.customThemes,
|
|
1587
|
-
fonts: options.fonts,
|
|
1588
|
-
services: withRequestedServices(
|
|
1589
|
-
buildServicesFromEnv(),
|
|
1590
|
-
options.services
|
|
1591
|
-
),
|
|
1592
|
-
renderer: options.renderer ?? documentRenderer(parsed),
|
|
1593
|
-
warnings
|
|
1594
|
-
}
|
|
1613
|
+
const services = withRequestedServices(
|
|
1614
|
+
buildServicesFromEnv(),
|
|
1615
|
+
options.services
|
|
1595
1616
|
);
|
|
1617
|
+
const prepared = options.plugins && options.plugins.length > 0 ? await (async () => {
|
|
1618
|
+
let generator = core.createPresentationGenerator({
|
|
1619
|
+
theme: resolved.requested,
|
|
1620
|
+
customThemes: resolved.requested ? {
|
|
1621
|
+
...resolved.customThemes,
|
|
1622
|
+
[CLI_THEME_KEY]: resolved.requested
|
|
1623
|
+
} : resolved.customThemes,
|
|
1624
|
+
services,
|
|
1625
|
+
baseDir: options.baseDir,
|
|
1626
|
+
fonts: options.fonts,
|
|
1627
|
+
validation: {
|
|
1628
|
+
allowUnknownFields: options.validation?.allowUnknownFields
|
|
1629
|
+
},
|
|
1630
|
+
renderer: options.renderer
|
|
1631
|
+
});
|
|
1632
|
+
for (const plugin of options.plugins)
|
|
1633
|
+
generator = generator.addComponent(plugin);
|
|
1634
|
+
const result = await generator.prepareQuality(normalized.document);
|
|
1635
|
+
warnings.push(...result.warnings ?? []);
|
|
1636
|
+
return result.prepared;
|
|
1637
|
+
})() : core.preparePptxQualityDocument(normalized.document, {
|
|
1638
|
+
customThemes: normalized.customThemes,
|
|
1639
|
+
fonts: options.fonts,
|
|
1640
|
+
services,
|
|
1641
|
+
renderer: options.renderer ?? documentRenderer(parsed),
|
|
1642
|
+
warnings
|
|
1643
|
+
});
|
|
1596
1644
|
return stampPrepared(
|
|
1597
1645
|
{
|
|
1598
1646
|
...prepared,
|
|
@@ -1624,7 +1672,7 @@ var PptxFormatAdapter = class {
|
|
|
1624
1672
|
const core = await import("@json-to-office/core-pptx");
|
|
1625
1673
|
const themes = core.pptxThemes || {};
|
|
1626
1674
|
const { requested } = await this.resolveThemes(options);
|
|
1627
|
-
return requested ?? themes.
|
|
1675
|
+
return requested ?? themes.consulting ?? {};
|
|
1628
1676
|
}
|
|
1629
1677
|
/**
|
|
1630
1678
|
* Resolve `theme`/`themePath` once for a whole run: `themePath` is read a
|
|
@@ -2179,48 +2227,92 @@ function authoredTextForMatch(text) {
|
|
|
2179
2227
|
function needleSegments(text) {
|
|
2180
2228
|
return authoredTextForMatch(text).split(FIELD).map(normalizeForMatch).filter((segment) => segment !== "");
|
|
2181
2229
|
}
|
|
2182
|
-
function indexDocument(pages, exclude = /* @__PURE__ */ new Set()) {
|
|
2230
|
+
function indexDocument(pages, exclude = /* @__PURE__ */ new Set(), order = pageOrder) {
|
|
2183
2231
|
const refs = [];
|
|
2184
2232
|
const fragments = [];
|
|
2185
2233
|
const positions = [];
|
|
2186
2234
|
let cursor = 0;
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2235
|
+
const push = (pageIndex, index) => {
|
|
2236
|
+
const word = pages[pageIndex].words[index];
|
|
2237
|
+
const key = `${pageIndex}:${index}`;
|
|
2238
|
+
const fragment = exclude.has(key) ? "" : normalizeForMatch(word.text);
|
|
2239
|
+
refs.push({ pageIndex, word: index });
|
|
2240
|
+
fragments.push(fragment);
|
|
2241
|
+
positions.push(cursor);
|
|
2242
|
+
cursor += fragment.length;
|
|
2243
|
+
};
|
|
2244
|
+
const orders = pages.map((page) => order(page.words, page.lines));
|
|
2245
|
+
orders.forEach((order2, pageIndex) => {
|
|
2246
|
+
for (const index of order2.upright) push(pageIndex, index);
|
|
2197
2247
|
});
|
|
2198
|
-
|
|
2248
|
+
const rotatedFrom = cursor;
|
|
2249
|
+
orders.forEach((order2, pageIndex) => {
|
|
2250
|
+
for (const index of order2.rotated) push(pageIndex, index);
|
|
2251
|
+
});
|
|
2252
|
+
return {
|
|
2253
|
+
pages,
|
|
2254
|
+
refs,
|
|
2255
|
+
fragments,
|
|
2256
|
+
positions,
|
|
2257
|
+
stream: fragments.join(""),
|
|
2258
|
+
rotatedFrom
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2261
|
+
function readingOrder(words2, lines = []) {
|
|
2262
|
+
const { upright, rotated } = pageOrder(words2, lines);
|
|
2263
|
+
return [...upright, ...rotated];
|
|
2264
|
+
}
|
|
2265
|
+
function pageOrder(words2, lines = []) {
|
|
2266
|
+
const rotated = rotatedWords(words2, lines);
|
|
2267
|
+
return {
|
|
2268
|
+
upright: uprightOrder(
|
|
2269
|
+
words2,
|
|
2270
|
+
words2.map((_, i) => i).filter((i) => !rotated.has(i))
|
|
2271
|
+
),
|
|
2272
|
+
rotated: [...rotated].sort((a, b) => a - b)
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
function linedOrder(words2, lines = []) {
|
|
2276
|
+
const rotated = rotatedWords(words2, lines);
|
|
2277
|
+
const upright = words2.map((_, i) => i).filter((i) => !rotated.has(i));
|
|
2278
|
+
return {
|
|
2279
|
+
upright: groupRows(words2, upright).flatMap(
|
|
2280
|
+
(row) => [...row.words].sort((a, b) => words2[a].xMin - words2[b].xMin)
|
|
2281
|
+
),
|
|
2282
|
+
rotated: [...rotated].sort((a, b) => a - b)
|
|
2283
|
+
};
|
|
2199
2284
|
}
|
|
2200
|
-
function
|
|
2285
|
+
function rotatedWords(words2, lines) {
|
|
2201
2286
|
const rotated = new Set(
|
|
2202
2287
|
words2.map((_, i) => i).filter((i) => {
|
|
2203
2288
|
const w = words2[i];
|
|
2204
2289
|
return w.text.length > 2 && w.yMax - w.yMin > 1.5 * (w.xMax - w.xMin);
|
|
2205
2290
|
})
|
|
2206
2291
|
);
|
|
2207
|
-
const
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2292
|
+
for (const line of lines) {
|
|
2293
|
+
if (line.words.length < 2) continue;
|
|
2294
|
+
const stacked = line.words.slice(1).every((index, n) => {
|
|
2295
|
+
const a = words2[line.words[n]];
|
|
2296
|
+
const b = words2[index];
|
|
2297
|
+
const across = Math.min(a.xMax, b.xMax) - Math.max(a.xMin, b.xMin);
|
|
2298
|
+
const along = Math.min(a.yMax, b.yMax) - Math.max(a.yMin, b.yMin);
|
|
2299
|
+
return across > 0 && along <= 0;
|
|
2300
|
+
});
|
|
2301
|
+
if (stacked) for (const index of line.words) rotated.add(index);
|
|
2216
2302
|
}
|
|
2217
|
-
return
|
|
2303
|
+
return rotated;
|
|
2218
2304
|
}
|
|
2219
2305
|
function median(values) {
|
|
2220
2306
|
const sorted = [...values].sort((a, b) => a - b);
|
|
2221
2307
|
const mid = sorted.length >> 1;
|
|
2222
2308
|
return sorted.length % 2 === 1 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
2223
2309
|
}
|
|
2310
|
+
var ROW_SIZE_RATIO = 2;
|
|
2311
|
+
function comparableSize(a, b) {
|
|
2312
|
+
const ha = a.yMax - a.yMin;
|
|
2313
|
+
const hb = b.yMax - b.yMin;
|
|
2314
|
+
return Math.max(ha, hb) <= ROW_SIZE_RATIO * Math.min(ha, hb);
|
|
2315
|
+
}
|
|
2224
2316
|
function groupRows(words2, indices) {
|
|
2225
2317
|
const rows = [];
|
|
2226
2318
|
const byY = [...indices].sort(
|
|
@@ -2229,7 +2321,8 @@ function groupRows(words2, indices) {
|
|
|
2229
2321
|
for (const i of byY) {
|
|
2230
2322
|
const word = words2[i];
|
|
2231
2323
|
const row = rows[rows.length - 1];
|
|
2232
|
-
|
|
2324
|
+
const peer = row?.words.find((w) => comparableSize(words2[w], word));
|
|
2325
|
+
if (row && peer !== void 0 && sameRow(words2[peer], word)) {
|
|
2233
2326
|
row.words.push(i);
|
|
2234
2327
|
row.yMin = Math.min(row.yMin, word.yMin);
|
|
2235
2328
|
row.yMax = Math.max(row.yMax, word.yMax);
|
|
@@ -2267,20 +2360,25 @@ function uprightOrder(words2, indices) {
|
|
|
2267
2360
|
const fragments = rows.map(fragmentsOf);
|
|
2268
2361
|
const boxes = grouped;
|
|
2269
2362
|
const order = [];
|
|
2363
|
+
const read = /* @__PURE__ */ new Set();
|
|
2364
|
+
const readColumn = (column) => {
|
|
2365
|
+
column.fragments.sort(
|
|
2366
|
+
(a, b) => words2[a.words[0]].yMin - words2[b.words[0]].yMin || a.xMin - b.xMin
|
|
2367
|
+
);
|
|
2368
|
+
for (const f of column.fragments) {
|
|
2369
|
+
order.push(...f.words);
|
|
2370
|
+
read.add(f);
|
|
2371
|
+
}
|
|
2372
|
+
};
|
|
2270
2373
|
let cluster = null;
|
|
2271
2374
|
const flush = () => {
|
|
2272
2375
|
if (!cluster) return;
|
|
2273
2376
|
if (cluster.columns.length < 2) {
|
|
2274
2377
|
for (const r of cluster.rows)
|
|
2275
|
-
for (const f of fragments[r]) order.push(...f.words);
|
|
2378
|
+
for (const f of fragments[r]) if (!read.has(f)) order.push(...f.words);
|
|
2276
2379
|
} else {
|
|
2277
2380
|
cluster.columns.sort((a, b) => a.xMin - b.xMin);
|
|
2278
|
-
for (const column of cluster.columns)
|
|
2279
|
-
column.fragments.sort(
|
|
2280
|
-
(a, b) => words2[a.words[0]].yMin - words2[b.words[0]].yMin || a.xMin - b.xMin
|
|
2281
|
-
);
|
|
2282
|
-
for (const f of column.fragments) order.push(...f.words);
|
|
2283
|
-
}
|
|
2381
|
+
for (const column of cluster.columns) readColumn(column);
|
|
2284
2382
|
}
|
|
2285
2383
|
cluster = null;
|
|
2286
2384
|
};
|
|
@@ -2294,6 +2392,16 @@ function uprightOrder(words2, indices) {
|
|
|
2294
2392
|
if (!adjacent || wide) flush();
|
|
2295
2393
|
if (!cluster) cluster = { rows: [], columns: [] };
|
|
2296
2394
|
cluster.rows.push(r);
|
|
2395
|
+
const landing = /* @__PURE__ */ new Map();
|
|
2396
|
+
for (const f of fragments[r]) {
|
|
2397
|
+
const hit = overlapping(cluster.columns, f)[0];
|
|
2398
|
+
if (hit) landing.set(hit, (landing.get(hit) ?? 0) + 1);
|
|
2399
|
+
}
|
|
2400
|
+
for (const [column, count] of landing) {
|
|
2401
|
+
if (count < 2) continue;
|
|
2402
|
+
readColumn(column);
|
|
2403
|
+
cluster.columns.splice(cluster.columns.indexOf(column), 1);
|
|
2404
|
+
}
|
|
2297
2405
|
for (const f of fragments[r]) {
|
|
2298
2406
|
const hit = overlapping(cluster.columns, f)[0];
|
|
2299
2407
|
if (hit) {
|
|
@@ -2361,6 +2469,7 @@ function occurrence(index, at, end) {
|
|
|
2361
2469
|
const parts = partsOf(index, at, end);
|
|
2362
2470
|
return {
|
|
2363
2471
|
at,
|
|
2472
|
+
end,
|
|
2364
2473
|
pageIndex: parts[0].pageIndex,
|
|
2365
2474
|
endPageIndex: parts[parts.length - 1].pageIndex,
|
|
2366
2475
|
parts
|
|
@@ -2386,7 +2495,11 @@ function findOccurrences(index, segments) {
|
|
|
2386
2495
|
end = next + segments[s].length;
|
|
2387
2496
|
}
|
|
2388
2497
|
if (!complete || !alignedToFragments(index, at, end)) continue;
|
|
2389
|
-
|
|
2498
|
+
const hit = occurrence(index, at, end);
|
|
2499
|
+
if (at < index.rotatedFrom ? end > index.rotatedFrom : hit.pageIndex !== hit.endPageIndex) {
|
|
2500
|
+
continue;
|
|
2501
|
+
}
|
|
2502
|
+
hits.push(hit);
|
|
2390
2503
|
searchFrom = end;
|
|
2391
2504
|
}
|
|
2392
2505
|
return hits;
|
|
@@ -2394,7 +2507,7 @@ function findOccurrences(index, segments) {
|
|
|
2394
2507
|
var MIN_NEEDLE_LENGTH = 3;
|
|
2395
2508
|
var MIN_PARTIAL_PREFIX = 16;
|
|
2396
2509
|
var CHROME_BAND = 0.2;
|
|
2397
|
-
function longestRenderedPrefix(index, needle) {
|
|
2510
|
+
function longestRenderedPrefix(index, needle, page, region) {
|
|
2398
2511
|
if (needle.length < MIN_PARTIAL_PREFIX) return void 0;
|
|
2399
2512
|
const anchor = needle.slice(0, MIN_PARTIAL_PREFIX);
|
|
2400
2513
|
let best;
|
|
@@ -2407,14 +2520,36 @@ function longestRenderedPrefix(index, needle) {
|
|
|
2407
2520
|
if (first >= index.fragments.length || index.positions[first] !== at) {
|
|
2408
2521
|
continue;
|
|
2409
2522
|
}
|
|
2523
|
+
if (page !== void 0 && index.refs[first].pageIndex !== page) continue;
|
|
2524
|
+
const rotated = at >= index.rotatedFrom;
|
|
2525
|
+
const limit = Math.min(
|
|
2526
|
+
page !== void 0 || rotated ? pageEnd(index, first) : index.stream.length,
|
|
2527
|
+
rotated ? index.stream.length : index.rotatedFrom
|
|
2528
|
+
);
|
|
2529
|
+
if (at + MIN_PARTIAL_PREFIX > limit) continue;
|
|
2410
2530
|
let length = MIN_PARTIAL_PREFIX;
|
|
2411
|
-
while (length < needle.length && index.stream.charCodeAt(at + length) === needle.charCodeAt(length)) {
|
|
2531
|
+
while (length < needle.length && at + length < limit && index.stream.charCodeAt(at + length) === needle.charCodeAt(length)) {
|
|
2412
2532
|
length++;
|
|
2413
2533
|
}
|
|
2534
|
+
if (region && !touchesRegion(partsOf(index, at, at + length), region)) {
|
|
2535
|
+
continue;
|
|
2536
|
+
}
|
|
2414
2537
|
if (!best || length > best.length) best = { length, at };
|
|
2415
2538
|
}
|
|
2416
2539
|
return best;
|
|
2417
2540
|
}
|
|
2541
|
+
function pageEnd(index, from) {
|
|
2542
|
+
const page = index.refs[from].pageIndex;
|
|
2543
|
+
let i = from;
|
|
2544
|
+
while (i < index.refs.length && index.refs[i].pageIndex === page) i++;
|
|
2545
|
+
return i < index.positions.length ? index.positions[i] : index.stream.length;
|
|
2546
|
+
}
|
|
2547
|
+
var REGION_TOLERANCE_PT = 2;
|
|
2548
|
+
function touchesRegion(parts, region) {
|
|
2549
|
+
return parts.some(
|
|
2550
|
+
(part) => part.xMax >= region.xMin - REGION_TOLERANCE_PT && part.xMin <= region.xMax + REGION_TOLERANCE_PT && part.yMax >= region.yMin - REGION_TOLERANCE_PT && part.yMin <= region.yMax + REGION_TOLERANCE_PT
|
|
2551
|
+
);
|
|
2552
|
+
}
|
|
2418
2553
|
function sameRow(a, b) {
|
|
2419
2554
|
const overlap = Math.min(a.yMax, b.yMax) - Math.max(a.yMin, b.yMin);
|
|
2420
2555
|
const shorter = Math.min(a.yMax - a.yMin, b.yMax - b.yMin);
|
|
@@ -2470,6 +2605,120 @@ function pageNumberRows(pages, claimed) {
|
|
|
2470
2605
|
)
|
|
2471
2606
|
);
|
|
2472
2607
|
}
|
|
2608
|
+
function insideRegion(occurrences, region) {
|
|
2609
|
+
let best;
|
|
2610
|
+
for (const o of occurrences) {
|
|
2611
|
+
let inside = 0;
|
|
2612
|
+
let total = 0;
|
|
2613
|
+
for (const part of o.parts) {
|
|
2614
|
+
const w = Math.min(part.xMax, region.xMax) - Math.max(part.xMin, region.xMin);
|
|
2615
|
+
const h = Math.min(part.yMax, region.yMax) - Math.max(part.yMin, region.yMin);
|
|
2616
|
+
if (w > 0 && h > 0) inside += w * h;
|
|
2617
|
+
total += (part.xMax - part.xMin) * (part.yMax - part.yMin);
|
|
2618
|
+
}
|
|
2619
|
+
const share = total > 0 ? inside / total : 0;
|
|
2620
|
+
if (share > 0 && (!best || share > best.share)) {
|
|
2621
|
+
best = { occurrence: o, share };
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
return best?.occurrence;
|
|
2625
|
+
}
|
|
2626
|
+
function overprintedOccurrence(index, page, region, needle, claimed) {
|
|
2627
|
+
const words2 = index.pages[page]?.words;
|
|
2628
|
+
if (!words2) return void 0;
|
|
2629
|
+
const fragmentOf = /* @__PURE__ */ new Map();
|
|
2630
|
+
index.refs.forEach((ref, i) => {
|
|
2631
|
+
if (ref.pageIndex !== page || index.fragments[i] === "") return;
|
|
2632
|
+
const w = words2[ref.word];
|
|
2633
|
+
const centre = (w.xMin + w.xMax) / 2;
|
|
2634
|
+
if (centre < region.xMin || centre > region.xMax) return;
|
|
2635
|
+
if (w.yMax < region.yMin) return;
|
|
2636
|
+
fragmentOf.set(ref.word, i);
|
|
2637
|
+
});
|
|
2638
|
+
const sequence = groupRows(words2, [...fragmentOf.keys()]).flatMap(
|
|
2639
|
+
(row) => [...row.words].sort((a, b) => words2[a].xMin - words2[b].xMin)
|
|
2640
|
+
);
|
|
2641
|
+
let reach = /* @__PURE__ */ new Map();
|
|
2642
|
+
let best;
|
|
2643
|
+
for (const word of sequence) {
|
|
2644
|
+
const i = fragmentOf.get(word);
|
|
2645
|
+
const text = index.fragments[i];
|
|
2646
|
+
const usable = !claimed(index.positions[i]);
|
|
2647
|
+
const next = /* @__PURE__ */ new Map();
|
|
2648
|
+
const offer = (consumed, path9) => {
|
|
2649
|
+
const held = next.get(consumed);
|
|
2650
|
+
if (!held || path9.skips < held.skips) next.set(consumed, path9);
|
|
2651
|
+
};
|
|
2652
|
+
const inside = words2[word].yMin + words2[word].yMax <= 2 * region.yMax && words2[word].yMin + words2[word].yMax >= 2 * region.yMin;
|
|
2653
|
+
if (usable && inside && needle.startsWith(text)) {
|
|
2654
|
+
offer(text.length, { skips: 0, words: [word] });
|
|
2655
|
+
}
|
|
2656
|
+
for (const [consumed, path9] of reach) {
|
|
2657
|
+
if (usable && needle.startsWith(text, consumed)) {
|
|
2658
|
+
offer(consumed + text.length, {
|
|
2659
|
+
skips: path9.skips,
|
|
2660
|
+
words: [...path9.words, word]
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
offer(consumed, { skips: path9.skips + 1, words: path9.words });
|
|
2664
|
+
}
|
|
2665
|
+
const done = next.get(needle.length);
|
|
2666
|
+
if (done && (!best || done.skips < best.skips)) best = done;
|
|
2667
|
+
next.delete(needle.length);
|
|
2668
|
+
reach = next;
|
|
2669
|
+
}
|
|
2670
|
+
if (!best || best.skips > best.words.length) return void 0;
|
|
2671
|
+
const first = fragmentOf.get(best.words[0]);
|
|
2672
|
+
const part = {
|
|
2673
|
+
pageIndex: page,
|
|
2674
|
+
words: best.words,
|
|
2675
|
+
xMin: Math.min(...best.words.map((w) => words2[w].xMin)),
|
|
2676
|
+
yMin: Math.min(...best.words.map((w) => words2[w].yMin)),
|
|
2677
|
+
xMax: Math.max(...best.words.map((w) => words2[w].xMax)),
|
|
2678
|
+
yMax: Math.max(...best.words.map((w) => words2[w].yMax))
|
|
2679
|
+
};
|
|
2680
|
+
const at = index.positions[first];
|
|
2681
|
+
return {
|
|
2682
|
+
occurrence: {
|
|
2683
|
+
at,
|
|
2684
|
+
end: at,
|
|
2685
|
+
pageIndex: page,
|
|
2686
|
+
endPageIndex: page,
|
|
2687
|
+
parts: [part]
|
|
2688
|
+
},
|
|
2689
|
+
claims: best.words.map((w) => {
|
|
2690
|
+
const i = fragmentOf.get(w);
|
|
2691
|
+
return {
|
|
2692
|
+
at: index.positions[i],
|
|
2693
|
+
end: index.positions[i] + index.fragments[i].length
|
|
2694
|
+
};
|
|
2695
|
+
})
|
|
2696
|
+
};
|
|
2697
|
+
}
|
|
2698
|
+
function linedOccurrence(lined, body, segments, claimed) {
|
|
2699
|
+
const bodyFragment = /* @__PURE__ */ new Map();
|
|
2700
|
+
body.refs.forEach((ref, i) => {
|
|
2701
|
+
if (body.fragments[i] !== "")
|
|
2702
|
+
bodyFragment.set(`${ref.pageIndex}:${ref.word}`, i);
|
|
2703
|
+
});
|
|
2704
|
+
for (const hit of findOccurrences(lined, segments)) {
|
|
2705
|
+
const claims = [];
|
|
2706
|
+
for (const part of hit.parts)
|
|
2707
|
+
for (const word of part.words) {
|
|
2708
|
+
const i = bodyFragment.get(`${part.pageIndex}:${word}`);
|
|
2709
|
+
if (i === void 0) continue;
|
|
2710
|
+
claims.push({
|
|
2711
|
+
at: body.positions[i],
|
|
2712
|
+
end: body.positions[i] + body.fragments[i].length
|
|
2713
|
+
});
|
|
2714
|
+
}
|
|
2715
|
+
if (claims.length === 0 || claims.some((claim) => claimed(claim.at)))
|
|
2716
|
+
continue;
|
|
2717
|
+
const at = Math.min(...claims.map((claim) => claim.at));
|
|
2718
|
+
return { occurrence: { ...hit, at, end: at }, claims };
|
|
2719
|
+
}
|
|
2720
|
+
return void 0;
|
|
2721
|
+
}
|
|
2473
2722
|
function assignInventory(pages, inventory) {
|
|
2474
2723
|
const results = /* @__PURE__ */ new Map();
|
|
2475
2724
|
const chromeWords = /* @__PURE__ */ new Set();
|
|
@@ -2509,7 +2758,9 @@ function assignInventory(pages, inventory) {
|
|
|
2509
2758
|
for (const row of pageNumberRows(pages, chromeWords))
|
|
2510
2759
|
claimRow(row.pageIndex, row.box);
|
|
2511
2760
|
const body = indexDocument(pages, chromeWords);
|
|
2512
|
-
|
|
2761
|
+
let lined;
|
|
2762
|
+
const claims = [];
|
|
2763
|
+
const claimsOver = (o) => claims.filter((claim) => o.at < claim.end && claim.at < o.end);
|
|
2513
2764
|
let cursor = 0;
|
|
2514
2765
|
for (const entry of inventory) {
|
|
2515
2766
|
if (entry.repeats) continue;
|
|
@@ -2519,10 +2770,44 @@ function assignInventory(pages, inventory) {
|
|
|
2519
2770
|
results.set(entry, { entry, needle, status: "skipped", occurrences: [] });
|
|
2520
2771
|
continue;
|
|
2521
2772
|
}
|
|
2522
|
-
let all = findOccurrences(body, segments)
|
|
2773
|
+
let all = findOccurrences(body, segments).filter(
|
|
2774
|
+
(o) => (entry.page === void 0 || o.pageIndex === entry.page && o.endPageIndex === entry.page) && (!entry.region || touchesRegion(o.parts, entry.region))
|
|
2775
|
+
);
|
|
2523
2776
|
let partial;
|
|
2777
|
+
let sparse;
|
|
2778
|
+
if (all.length === 0 && segments.length === 1 && entry.page !== void 0 && entry.region) {
|
|
2779
|
+
const overprinted = overprintedOccurrence(
|
|
2780
|
+
body,
|
|
2781
|
+
entry.page,
|
|
2782
|
+
entry.region,
|
|
2783
|
+
needle,
|
|
2784
|
+
(offset) => claims.some((claim) => claim.at <= offset && offset < claim.end)
|
|
2785
|
+
);
|
|
2786
|
+
if (overprinted) {
|
|
2787
|
+
all = [overprinted.occurrence];
|
|
2788
|
+
sparse = overprinted.claims;
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
if (all.length === 0 && entry.page === void 0 && !entry.optional) {
|
|
2792
|
+
lined ??= indexDocument(pages, chromeWords, linedOrder);
|
|
2793
|
+
const found = linedOccurrence(
|
|
2794
|
+
lined,
|
|
2795
|
+
body,
|
|
2796
|
+
segments,
|
|
2797
|
+
(offset) => claims.some((claim) => claim.at <= offset && offset < claim.end)
|
|
2798
|
+
);
|
|
2799
|
+
if (found) {
|
|
2800
|
+
all = [found.occurrence];
|
|
2801
|
+
sparse = found.claims;
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2524
2804
|
if (all.length === 0 && segments.length === 1 && !entry.optional) {
|
|
2525
|
-
const prefix = longestRenderedPrefix(
|
|
2805
|
+
const prefix = longestRenderedPrefix(
|
|
2806
|
+
body,
|
|
2807
|
+
needle,
|
|
2808
|
+
entry.page,
|
|
2809
|
+
entry.region
|
|
2810
|
+
);
|
|
2526
2811
|
if (prefix) {
|
|
2527
2812
|
all = [occurrence(body, prefix.at, prefix.at + prefix.length)];
|
|
2528
2813
|
partial = { matchedChars: prefix.length, totalChars: needle.length };
|
|
@@ -2537,18 +2822,22 @@ function assignInventory(pages, inventory) {
|
|
|
2537
2822
|
});
|
|
2538
2823
|
continue;
|
|
2539
2824
|
}
|
|
2540
|
-
let free = all.filter((o) =>
|
|
2825
|
+
let free = all.filter((o) => claimsOver(o).length === 0);
|
|
2541
2826
|
if (free.length === 0 && !entry.optional) {
|
|
2542
|
-
const held = all.find((o) =>
|
|
2827
|
+
const held = all.find((o) => {
|
|
2828
|
+
const over = claimsOver(o);
|
|
2829
|
+
return over.length > 0 && over.every((claim) => claim.entry.optional);
|
|
2830
|
+
});
|
|
2543
2831
|
if (held) {
|
|
2544
|
-
const
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2832
|
+
for (const claim of claimsOver(held)) {
|
|
2833
|
+
claims.splice(claims.indexOf(claim), 1);
|
|
2834
|
+
results.set(claim.entry, {
|
|
2835
|
+
entry: claim.entry,
|
|
2836
|
+
needle: results.get(claim.entry)?.needle ?? "",
|
|
2837
|
+
status: "skipped",
|
|
2838
|
+
occurrences: []
|
|
2839
|
+
});
|
|
2840
|
+
}
|
|
2552
2841
|
free = [held];
|
|
2553
2842
|
}
|
|
2554
2843
|
}
|
|
@@ -2561,9 +2850,10 @@ function assignInventory(pages, inventory) {
|
|
|
2561
2850
|
});
|
|
2562
2851
|
continue;
|
|
2563
2852
|
}
|
|
2564
|
-
const chosen = free.find((o) => o.at >= cursor) ?? free[0];
|
|
2565
|
-
|
|
2566
|
-
|
|
2853
|
+
const chosen = (entry.region && insideRegion(free, entry.region)) ?? free.find((o) => o.at >= cursor) ?? free[0];
|
|
2854
|
+
for (const range of sparse ?? [chosen])
|
|
2855
|
+
claims.push({ at: range.at, end: range.end, entry });
|
|
2856
|
+
if (chosen.at < body.rotatedFrom) cursor = chosen.at;
|
|
2567
2857
|
results.set(entry, {
|
|
2568
2858
|
entry,
|
|
2569
2859
|
needle,
|
|
@@ -2595,6 +2885,11 @@ function renderedDraftFor(fact) {
|
|
|
2595
2885
|
}
|
|
2596
2886
|
var VISIBLE_SPILL_PT = 2;
|
|
2597
2887
|
var OVERLAP_FRACTION = 0.3;
|
|
2888
|
+
var WORD_BOX_SLACK = 0.2;
|
|
2889
|
+
function inkBand(word) {
|
|
2890
|
+
const slack = WORD_BOX_SLACK * Math.max(0, word.yMax - word.yMin);
|
|
2891
|
+
return { yMin: word.yMin + slack, yMax: word.yMax - slack };
|
|
2892
|
+
}
|
|
2598
2893
|
function finding(draft) {
|
|
2599
2894
|
const { mapping, page, context, ...rest } = draft;
|
|
2600
2895
|
return {
|
|
@@ -2610,12 +2905,15 @@ function round(value) {
|
|
|
2610
2905
|
return Math.round(value * 10) / 10;
|
|
2611
2906
|
}
|
|
2612
2907
|
function intersects(a, b) {
|
|
2908
|
+
const inkA = inkBand(a);
|
|
2909
|
+
const inkB = inkBand(b);
|
|
2613
2910
|
const w = Math.min(a.xMax, b.xMax) - Math.max(a.xMin, b.xMin);
|
|
2614
|
-
const h = Math.min(
|
|
2911
|
+
const h = Math.min(inkA.yMax, inkB.yMax) - Math.max(inkA.yMin, inkB.yMin);
|
|
2615
2912
|
return w > 0 && h > 0 ? w * h : 0;
|
|
2616
2913
|
}
|
|
2617
2914
|
function area(w) {
|
|
2618
|
-
|
|
2915
|
+
const ink = inkBand(w);
|
|
2916
|
+
return Math.max(0, w.xMax - w.xMin) * Math.max(0, ink.yMax - ink.yMin);
|
|
2619
2917
|
}
|
|
2620
2918
|
function excerpt(text, max = 60) {
|
|
2621
2919
|
const flat = text.replace(/\s+/g, " ").trim();
|
|
@@ -2731,14 +3029,51 @@ function draftRenderedFindings(input) {
|
|
|
2731
3029
|
}
|
|
2732
3030
|
for (const match of matches) {
|
|
2733
3031
|
const box = match.entry.box;
|
|
3032
|
+
const region = match.entry.region;
|
|
2734
3033
|
if (!box || match.status !== "mapped") continue;
|
|
2735
3034
|
for (const o of match.occurrences) {
|
|
3035
|
+
const boxHeight = box.heightPt;
|
|
3036
|
+
const byEdges = region !== void 0 && boxHeight !== void 0;
|
|
3037
|
+
if (region && boxHeight !== void 0) {
|
|
3038
|
+
const words2 = o.parts.flatMap(
|
|
3039
|
+
(part) => part.words.map((w) => pages[part.pageIndex].words[w])
|
|
3040
|
+
);
|
|
3041
|
+
const inkTop = Math.min(...words2.map((w) => inkBand(w).yMin));
|
|
3042
|
+
const inkBottom = Math.max(...words2.map((w) => inkBand(w).yMax));
|
|
3043
|
+
const edges = [
|
|
3044
|
+
{ edge: "bottom", over: inkBottom - region.yMax, at: inkBottom },
|
|
3045
|
+
{ edge: "top", over: region.yMin - inkTop, at: inkTop }
|
|
3046
|
+
].sort((a, b) => b.over - a.over);
|
|
3047
|
+
const [worst2] = edges;
|
|
3048
|
+
if (worst2.over > VISIBLE_SPILL_PT) {
|
|
3049
|
+
findings.push(
|
|
3050
|
+
finding({
|
|
3051
|
+
ruleId: "rendered/spill",
|
|
3052
|
+
mapping: "mapped",
|
|
3053
|
+
page: o.pageIndex + 1,
|
|
3054
|
+
path: match.entry.path,
|
|
3055
|
+
message: `"${excerpt(match.entry.text)}" runs ${round(worst2.over)} pt past the ${worst2.edge} of its ${round(boxHeight)} pt box on page ${o.pageIndex + 1}.`,
|
|
3056
|
+
suggestion: "Shorten the text, reduce its size, or enlarge the box; the renderer let it spill past the edge the author drew.",
|
|
3057
|
+
evidence: {
|
|
3058
|
+
summary: "Rendered ink against the edge of the declared box",
|
|
3059
|
+
actual: round(worst2.at),
|
|
3060
|
+
expected: round(
|
|
3061
|
+
worst2.edge === "bottom" ? region.yMax : region.yMin
|
|
3062
|
+
),
|
|
3063
|
+
unit: "pt",
|
|
3064
|
+
values: { edge: worst2.edge, overPt: round(worst2.over) }
|
|
3065
|
+
}
|
|
3066
|
+
})
|
|
3067
|
+
);
|
|
3068
|
+
continue;
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
2736
3071
|
const extent = unionBox(o.parts);
|
|
2737
3072
|
const rendered = {
|
|
2738
3073
|
heightPt: extent.yMax - extent.yMin,
|
|
2739
3074
|
widthPt: extent.xMax - extent.xMin
|
|
2740
3075
|
};
|
|
2741
|
-
const worst = ["heightPt", "widthPt"].filter((axis) => box[axis] !== void 0).map((axis) => ({
|
|
3076
|
+
const worst = ["heightPt", "widthPt"].filter((axis) => box[axis] !== void 0).filter((axis) => !(byEdges && axis === "heightPt")).map((axis) => ({
|
|
2742
3077
|
axis,
|
|
2743
3078
|
declared: box[axis],
|
|
2744
3079
|
rendered: rendered[axis],
|
|
@@ -3152,11 +3487,295 @@ function analyzeRenderedDocument(input, options = {}) {
|
|
|
3152
3487
|
};
|
|
3153
3488
|
}
|
|
3154
3489
|
|
|
3490
|
+
// src/rendered-inventory.ts
|
|
3491
|
+
function renderedInventoryFromFacts(format, facts) {
|
|
3492
|
+
const entries = [];
|
|
3493
|
+
for (const fact of facts) {
|
|
3494
|
+
if (format === "docx" && fact.kind === "docx/text") {
|
|
3495
|
+
const frame = fact.frame;
|
|
3496
|
+
entries.push({
|
|
3497
|
+
path: fact.path,
|
|
3498
|
+
text: String(fact.text),
|
|
3499
|
+
role: fact.role,
|
|
3500
|
+
...typeof fact.level === "number" && { level: fact.level },
|
|
3501
|
+
...fact.repeats === true && { repeats: true },
|
|
3502
|
+
...fact.optional === true && { optional: true },
|
|
3503
|
+
...frame && {
|
|
3504
|
+
box: {
|
|
3505
|
+
...frame.widthPt !== void 0 && { widthPt: frame.widthPt },
|
|
3506
|
+
...frame.heightPt !== void 0 && { heightPt: frame.heightPt }
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
});
|
|
3510
|
+
} else if (format === "pptx" && fact.kind === "pptx/text") {
|
|
3511
|
+
if (fact.slideHidden === true) continue;
|
|
3512
|
+
const turned = typeof fact.rotationDeg === "number" && (fact.rotationDeg % 360 + 360) % 360 !== 0;
|
|
3513
|
+
if (turned) {
|
|
3514
|
+
entries.push({
|
|
3515
|
+
path: fact.path,
|
|
3516
|
+
text: String(fact.text),
|
|
3517
|
+
role: "slide-text",
|
|
3518
|
+
optional: true,
|
|
3519
|
+
...typeof fact.page === "number" && { page: fact.page }
|
|
3520
|
+
});
|
|
3521
|
+
continue;
|
|
3522
|
+
}
|
|
3523
|
+
const { boxXPt: x, boxYPt: y } = fact;
|
|
3524
|
+
const widthPt = fact.boxWidthPt;
|
|
3525
|
+
const heightPt = fact.boxHeightPt;
|
|
3526
|
+
entries.push({
|
|
3527
|
+
path: fact.path,
|
|
3528
|
+
text: String(fact.text),
|
|
3529
|
+
role: "slide-text",
|
|
3530
|
+
// A slide is its own page and its boxes are where the deck put
|
|
3531
|
+
// them, so the text is looked for there before anywhere else.
|
|
3532
|
+
...typeof fact.page === "number" && { page: fact.page },
|
|
3533
|
+
...typeof x === "number" && typeof y === "number" && typeof widthPt === "number" && typeof heightPt === "number" && {
|
|
3534
|
+
region: {
|
|
3535
|
+
xMin: x,
|
|
3536
|
+
yMin: y,
|
|
3537
|
+
xMax: x + widthPt,
|
|
3538
|
+
yMax: y + heightPt
|
|
3539
|
+
}
|
|
3540
|
+
},
|
|
3541
|
+
...(typeof widthPt === "number" || typeof heightPt === "number") && {
|
|
3542
|
+
box: {
|
|
3543
|
+
...typeof widthPt === "number" && { widthPt },
|
|
3544
|
+
...typeof heightPt === "number" && { heightPt }
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
});
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
return entries;
|
|
3551
|
+
}
|
|
3552
|
+
function requestedFontsFromFacts(format, facts, resolvedFonts) {
|
|
3553
|
+
const kind = format === "docx" ? "docx/font-family" : "pptx/font-family";
|
|
3554
|
+
const declared = new Set(
|
|
3555
|
+
resolvedFonts.filter((font) => font.declared).map((font) => font.family.trim().toLowerCase())
|
|
3556
|
+
);
|
|
3557
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3558
|
+
const requested = [];
|
|
3559
|
+
const add = (family, path9) => {
|
|
3560
|
+
if (typeof family !== "string" || family.trim() === "") return;
|
|
3561
|
+
const key = family.trim().toLowerCase();
|
|
3562
|
+
if (seen.has(key)) return;
|
|
3563
|
+
seen.add(key);
|
|
3564
|
+
requested.push({
|
|
3565
|
+
family: family.trim(),
|
|
3566
|
+
...path9 && { path: path9 },
|
|
3567
|
+
...declared.has(key) && { declared: true }
|
|
3568
|
+
});
|
|
3569
|
+
};
|
|
3570
|
+
for (const fact of facts) {
|
|
3571
|
+
if (fact.kind === kind) add(fact.family, fact.path);
|
|
3572
|
+
}
|
|
3573
|
+
for (const font of resolvedFonts) if (font.declared) add(font.family);
|
|
3574
|
+
return requested;
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3155
3577
|
// src/block-matrix.ts
|
|
3156
3578
|
import {
|
|
3157
3579
|
blockDependencies,
|
|
3580
|
+
blockReferencesFromDocument as blockReferencesFromDocument2,
|
|
3581
|
+
readBlockDefinitions as readBlockDefinitions2
|
|
3582
|
+
} from "@json-to-office/shared";
|
|
3583
|
+
|
|
3584
|
+
// src/matrix-inventory.ts
|
|
3585
|
+
import {
|
|
3586
|
+
blockReferencesFromDocument,
|
|
3158
3587
|
readBlockDefinitions
|
|
3159
3588
|
} from "@json-to-office/shared";
|
|
3589
|
+
function matrixUniverse(format, themes) {
|
|
3590
|
+
return {
|
|
3591
|
+
themes: [...themes],
|
|
3592
|
+
canvases: format === "docx" ? ["A4", "LETTER"] : ["wide169", "standard43"],
|
|
3593
|
+
fonts: ["design", "fallback"],
|
|
3594
|
+
edges: ["min", "max"]
|
|
3595
|
+
};
|
|
3596
|
+
}
|
|
3597
|
+
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3598
|
+
function templateFormat(name, document) {
|
|
3599
|
+
const root = isRecord(document) ? document.name : void 0;
|
|
3600
|
+
if (root === "docx" || root === "pptx") return root;
|
|
3601
|
+
if (name.endsWith(".docx.json")) return "docx";
|
|
3602
|
+
if (name.endsWith(".pptx.json")) return "pptx";
|
|
3603
|
+
return void 0;
|
|
3604
|
+
}
|
|
3605
|
+
function authoredDeckCanvas(props) {
|
|
3606
|
+
const width = typeof props.slideWidth === "number" ? props.slideWidth : 10;
|
|
3607
|
+
const height = typeof props.slideHeight === "number" ? props.slideHeight : 7.5;
|
|
3608
|
+
return Math.abs(width / height - 16 / 9) < Math.abs(width / height - 4 / 3) ? "wide169" : "standard43";
|
|
3609
|
+
}
|
|
3610
|
+
function requiredRoles(format, profile) {
|
|
3611
|
+
const required = profile?.rules?.[`${format}/required-chrome`]?.parameters?.required;
|
|
3612
|
+
return Array.isArray(required) ? required.filter((role) => typeof role === "string") : [];
|
|
3613
|
+
}
|
|
3614
|
+
function templateConditions(format, props, universe) {
|
|
3615
|
+
const named = typeof props.theme === "string" ? props.theme : void 0;
|
|
3616
|
+
const canvas = format === "pptx" ? authoredDeckCanvas(props) : void 0;
|
|
3617
|
+
const canvasExclusions = (reason) => canvas === void 0 ? [] : universe.canvases.filter((c) => c !== canvas).map((c) => ({ condition: `canvas ${c}`, reason }));
|
|
3618
|
+
if (named !== void 0 && universe.themes.includes(named)) {
|
|
3619
|
+
return {
|
|
3620
|
+
theme: named,
|
|
3621
|
+
conditions: {
|
|
3622
|
+
...structuredClone(universe),
|
|
3623
|
+
...canvas !== void 0 && { canvases: [canvas] }
|
|
3624
|
+
},
|
|
3625
|
+
exclusions: canvasExclusions(
|
|
3626
|
+
`slot budgets are measured on the ${canvas} slide the template is drawn on; another shape lays out, but holds fewer words`
|
|
3627
|
+
)
|
|
3628
|
+
};
|
|
3629
|
+
}
|
|
3630
|
+
const theme = named ?? "inline";
|
|
3631
|
+
const why = named === void 0 ? "authored against the template's own theme object" : `authored against "${named}", which is no bundled theme`;
|
|
3632
|
+
const exclusions = [
|
|
3633
|
+
...universe.themes.map((t) => ({ condition: `theme ${t}`, reason: why })),
|
|
3634
|
+
...canvasExclusions(
|
|
3635
|
+
"absolute geometry fitted to the template's own slide size"
|
|
3636
|
+
),
|
|
3637
|
+
...canvas === void 0 ? universe.canvases.filter((c) => c !== "A4").map((c) => ({
|
|
3638
|
+
condition: `canvas ${c}`,
|
|
3639
|
+
reason: "absolute geometry fitted to the template's own page"
|
|
3640
|
+
})) : [],
|
|
3641
|
+
{
|
|
3642
|
+
condition: "font fallback",
|
|
3643
|
+
reason: "lines fitted to the template's own faces; a wider face is another design, not a boundary"
|
|
3644
|
+
}
|
|
3645
|
+
];
|
|
3646
|
+
return {
|
|
3647
|
+
theme,
|
|
3648
|
+
conditions: {
|
|
3649
|
+
themes: [theme],
|
|
3650
|
+
canvases: [canvas ?? "A4"],
|
|
3651
|
+
fonts: ["design"],
|
|
3652
|
+
edges: [...universe.edges]
|
|
3653
|
+
},
|
|
3654
|
+
exclusions
|
|
3655
|
+
};
|
|
3656
|
+
}
|
|
3657
|
+
function buildMatrixInventory(templates, options) {
|
|
3658
|
+
const inventory = { templates: [], entries: [] };
|
|
3659
|
+
for (const source of templates) {
|
|
3660
|
+
const format = templateFormat(source.name, source.document);
|
|
3661
|
+
if (!format || !isRecord(source.document)) continue;
|
|
3662
|
+
const props = isRecord(source.document.props) ? source.document.props : {};
|
|
3663
|
+
const universe = matrixUniverse(format, options.themes[format]);
|
|
3664
|
+
const blueprint = options.blueprints?.find(
|
|
3665
|
+
(b) => b.format === format && b.definitions === source.name
|
|
3666
|
+
);
|
|
3667
|
+
const profile = blueprint ? options.profiles?.[blueprint.profile] : void 0;
|
|
3668
|
+
const roles = requiredRoles(format, profile);
|
|
3669
|
+
const { theme, conditions: conditions2, exclusions } = templateConditions(
|
|
3670
|
+
format,
|
|
3671
|
+
props,
|
|
3672
|
+
universe
|
|
3673
|
+
);
|
|
3674
|
+
const definitions = Object.keys(readBlockDefinitions(source.document));
|
|
3675
|
+
const references = blockReferencesFromDocument(source.document, {
|
|
3676
|
+
template: source.name,
|
|
3677
|
+
format
|
|
3678
|
+
});
|
|
3679
|
+
const templateExclusions = [...exclusions];
|
|
3680
|
+
if (definitions.length > 0 && references.length === 0) {
|
|
3681
|
+
templateExclusions.push({
|
|
3682
|
+
condition: "definitions",
|
|
3683
|
+
reason: "the definitions do not validate, so discovery publishes none"
|
|
3684
|
+
});
|
|
3685
|
+
}
|
|
3686
|
+
inventory.templates.push({
|
|
3687
|
+
template: source.name,
|
|
3688
|
+
format,
|
|
3689
|
+
theme,
|
|
3690
|
+
...blueprint && { profile: blueprint.profile },
|
|
3691
|
+
requiredRoles: roles,
|
|
3692
|
+
definitions,
|
|
3693
|
+
conditions: conditions2,
|
|
3694
|
+
exclusions: templateExclusions
|
|
3695
|
+
});
|
|
3696
|
+
for (const reference of references) {
|
|
3697
|
+
const slotless = Object.keys(reference.definition.slots ?? {}).length === 0;
|
|
3698
|
+
inventory.entries.push({
|
|
3699
|
+
id: `${source.name}#${reference.definitionPointer}`,
|
|
3700
|
+
template: source.name,
|
|
3701
|
+
format,
|
|
3702
|
+
name: reference.name,
|
|
3703
|
+
definitionPointer: reference.definitionPointer,
|
|
3704
|
+
dependencies: reference.dependencies,
|
|
3705
|
+
...blueprint && { profile: blueprint.profile },
|
|
3706
|
+
requiredRoles: roles,
|
|
3707
|
+
themes: [...conditions2.themes],
|
|
3708
|
+
canvases: [...conditions2.canvases],
|
|
3709
|
+
fonts: [...conditions2.fonts],
|
|
3710
|
+
edges: slotless ? ["max"] : [...conditions2.edges],
|
|
3711
|
+
exclusions: [
|
|
3712
|
+
...exclusions,
|
|
3713
|
+
...slotless ? [
|
|
3714
|
+
{
|
|
3715
|
+
condition: "edge min",
|
|
3716
|
+
reason: "no slots, so min and max are the same document"
|
|
3717
|
+
}
|
|
3718
|
+
] : []
|
|
3719
|
+
]
|
|
3720
|
+
});
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
return inventory;
|
|
3724
|
+
}
|
|
3725
|
+
function inventoryGaps(entry, universe) {
|
|
3726
|
+
const excluded = new Set(entry.exclusions.map((e) => e.condition));
|
|
3727
|
+
const gaps = [];
|
|
3728
|
+
const check = (kind, all, got) => {
|
|
3729
|
+
for (const value of all) {
|
|
3730
|
+
const condition = `${kind} ${value}`;
|
|
3731
|
+
const has = got.includes(value);
|
|
3732
|
+
if (has && excluded.has(condition)) gaps.push(`${condition}: both`);
|
|
3733
|
+
if (!has && !excluded.has(condition)) gaps.push(`${condition}: neither`);
|
|
3734
|
+
}
|
|
3735
|
+
};
|
|
3736
|
+
check("theme", universe.themes, entry.themes);
|
|
3737
|
+
check("canvas", universe.canvases, entry.canvases);
|
|
3738
|
+
check("font", universe.fonts, entry.fonts);
|
|
3739
|
+
check("edge", universe.edges, entry.edges);
|
|
3740
|
+
return gaps;
|
|
3741
|
+
}
|
|
3742
|
+
function describeInventory(inventory) {
|
|
3743
|
+
const lines = [];
|
|
3744
|
+
for (const template of inventory.templates) {
|
|
3745
|
+
lines.push(
|
|
3746
|
+
`${template.template} (${template.format}, theme ${template.theme}${template.profile ? `, profile ${template.profile}` : ""}): ${template.definitions.length} definition(s)`
|
|
3747
|
+
);
|
|
3748
|
+
for (const entry of inventory.entries.filter(
|
|
3749
|
+
(e) => e.template === template.template
|
|
3750
|
+
)) {
|
|
3751
|
+
lines.push(
|
|
3752
|
+
` ${entry.name}: themes ${entry.themes.join(" ")} \xB7 canvases ${entry.canvases.join(
|
|
3753
|
+
" "
|
|
3754
|
+
)} \xB7 fonts ${entry.fonts.join(" ")} \xB7 edges ${entry.edges.join(" ")}`
|
|
3755
|
+
);
|
|
3756
|
+
const reasons = /* @__PURE__ */ new Map();
|
|
3757
|
+
for (const exclusion of entry.exclusions) {
|
|
3758
|
+
reasons.set(exclusion.reason, [
|
|
3759
|
+
...reasons.get(exclusion.reason) ?? [],
|
|
3760
|
+
exclusion.condition
|
|
3761
|
+
]);
|
|
3762
|
+
}
|
|
3763
|
+
for (const [reason, conditions2] of reasons)
|
|
3764
|
+
lines.push(` not ${conditions2.join(", ")}: ${reason}`);
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
return lines.join("\n");
|
|
3768
|
+
}
|
|
3769
|
+
|
|
3770
|
+
// src/block-matrix.ts
|
|
3771
|
+
var PPTX_FALLBACK_FONTS = {
|
|
3772
|
+
heading: "DejaVu Sans",
|
|
3773
|
+
body: "DejaVu Sans"
|
|
3774
|
+
};
|
|
3775
|
+
var PPTX_CANVAS_SIZE = {
|
|
3776
|
+
wide169: { slideWidth: 13.333, slideHeight: 7.5 },
|
|
3777
|
+
standard43: { slideWidth: 10, slideHeight: 7.5 }
|
|
3778
|
+
};
|
|
3160
3779
|
var FALLBACK_FONTS = {
|
|
3161
3780
|
heading: { family: "DejaVu Sans" },
|
|
3162
3781
|
body: { family: "DejaVu Sans" }
|
|
@@ -3196,7 +3815,7 @@ function widestNumber(length, seed = 0) {
|
|
|
3196
3815
|
const integer = group(digits);
|
|
3197
3816
|
return `\u2212${integer}.${"0".repeat(length - integer.length - 2)}`;
|
|
3198
3817
|
}
|
|
3199
|
-
var
|
|
3818
|
+
var isRecord2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3200
3819
|
var NUMERIC_SLOTS = /* @__PURE__ */ new Set(["value", "delta", "cells"]);
|
|
3201
3820
|
function stringValue(slot, name, edge, seed) {
|
|
3202
3821
|
if (slot.enum && slot.enum.length > 0) {
|
|
@@ -3238,14 +3857,17 @@ function stringValue(slot, name, edge, seed) {
|
|
|
3238
3857
|
return edge === "max" ? words(count, seed, false) : "01";
|
|
3239
3858
|
return words(count, seed, sentence);
|
|
3240
3859
|
}
|
|
3241
|
-
function componentValue(name, edge, seed) {
|
|
3242
|
-
|
|
3860
|
+
function componentValue(name, edge, seed, format = "docx") {
|
|
3861
|
+
const chart = name === "chart" || format === "pptx" && name === "content";
|
|
3862
|
+
if (chart && (name === "chart" || edge === "max")) {
|
|
3243
3863
|
const categories = edge === "max" ? 8 : 2;
|
|
3244
3864
|
const series = edge === "max" ? 3 : 1;
|
|
3245
3865
|
return {
|
|
3246
3866
|
name: "chart",
|
|
3247
3867
|
props: {
|
|
3248
|
-
|
|
3868
|
+
// The native column chart: `column` in a report, `bar` standing
|
|
3869
|
+
// upright in a deck.
|
|
3870
|
+
type: format === "pptx" ? "bar" : "column",
|
|
3249
3871
|
valAxisTitle: edge === "max" ? `${words(3, seed, false)} (\u20ACm)` : "Revenue (\u20ACm)",
|
|
3250
3872
|
...edge === "max" && { catAxisTitle: words(3, seed + 3, false) },
|
|
3251
3873
|
chartColors: ["primary", "secondary", "accent"].slice(0, series),
|
|
@@ -3266,6 +3888,12 @@ function componentValue(name, edge, seed) {
|
|
|
3266
3888
|
}
|
|
3267
3889
|
};
|
|
3268
3890
|
}
|
|
3891
|
+
if (format === "pptx") {
|
|
3892
|
+
return {
|
|
3893
|
+
name: "image",
|
|
3894
|
+
props: { path: MATRIX_IMAGE, alt: words(6, seed, false) }
|
|
3895
|
+
};
|
|
3896
|
+
}
|
|
3269
3897
|
return {
|
|
3270
3898
|
name: "image",
|
|
3271
3899
|
props: {
|
|
@@ -3275,7 +3903,7 @@ function componentValue(name, edge, seed) {
|
|
|
3275
3903
|
}
|
|
3276
3904
|
};
|
|
3277
3905
|
}
|
|
3278
|
-
function boundarySlotValue(slot, name, edge, seed = 0) {
|
|
3906
|
+
function boundarySlotValue(slot, name, edge, seed = 0, format = "docx") {
|
|
3279
3907
|
switch (slot.type) {
|
|
3280
3908
|
case "string":
|
|
3281
3909
|
return stringValue(slot, name, edge, seed);
|
|
@@ -3285,12 +3913,18 @@ function boundarySlotValue(slot, name, edge, seed = 0) {
|
|
|
3285
3913
|
case "boolean":
|
|
3286
3914
|
return edge === "max" ? true : slot.default ?? true;
|
|
3287
3915
|
case "component":
|
|
3288
|
-
return componentValue(name, edge, seed);
|
|
3916
|
+
return componentValue(name, edge, seed, format);
|
|
3289
3917
|
case "object": {
|
|
3290
3918
|
const out = {};
|
|
3291
3919
|
for (const [key, property] of Object.entries(slot.properties ?? {})) {
|
|
3292
3920
|
if (edge === "min" && !property.required) continue;
|
|
3293
|
-
const value = boundarySlotValue(
|
|
3921
|
+
const value = boundarySlotValue(
|
|
3922
|
+
property,
|
|
3923
|
+
key,
|
|
3924
|
+
edge,
|
|
3925
|
+
seed + key.length,
|
|
3926
|
+
format
|
|
3927
|
+
);
|
|
3294
3928
|
if (value !== void 0) out[key] = value;
|
|
3295
3929
|
}
|
|
3296
3930
|
return out;
|
|
@@ -3300,21 +3934,21 @@ function boundarySlotValue(slot, name, edge, seed = 0) {
|
|
|
3300
3934
|
const item = slot.items ?? { type: "string" };
|
|
3301
3935
|
return Array.from(
|
|
3302
3936
|
{ length: count },
|
|
3303
|
-
(_, i) => boundarySlotValue(item, name, edge, seed + i)
|
|
3937
|
+
(_, i) => boundarySlotValue(item, name, edge, seed + i, format)
|
|
3304
3938
|
);
|
|
3305
3939
|
}
|
|
3306
3940
|
default:
|
|
3307
3941
|
return void 0;
|
|
3308
3942
|
}
|
|
3309
3943
|
}
|
|
3310
|
-
function boundaryInvocation(name, definition, edge, seed = 0,
|
|
3944
|
+
function boundaryInvocation(name, definition, edge, seed = 0, requiredRoles2 = [], format = "docx") {
|
|
3311
3945
|
const slots = {};
|
|
3312
3946
|
let offset = seed;
|
|
3313
3947
|
for (const [slotName, slot] of Object.entries(definition.slots)) {
|
|
3314
3948
|
offset += slotName.length;
|
|
3315
|
-
const required = slot.required === true || slot.role !== void 0 &&
|
|
3949
|
+
const required = slot.required === true || slot.role !== void 0 && requiredRoles2.includes(slot.role);
|
|
3316
3950
|
if (edge === "min" && !required) continue;
|
|
3317
|
-
const value = boundarySlotValue(slot, slotName, edge, offset);
|
|
3951
|
+
const value = boundarySlotValue(slot, slotName, edge, offset, format);
|
|
3318
3952
|
if (value !== void 0) slots[slotName] = value;
|
|
3319
3953
|
}
|
|
3320
3954
|
const labels = slots.labels;
|
|
@@ -3322,17 +3956,30 @@ function boundaryInvocation(name, definition, edge, seed = 0, requiredRoles = []
|
|
|
3322
3956
|
if (Array.isArray(labels) && Array.isArray(columns)) {
|
|
3323
3957
|
const cells = definition.slots.columns?.items?.properties?.cells;
|
|
3324
3958
|
for (const column of columns) {
|
|
3325
|
-
if (
|
|
3959
|
+
if (isRecord2(column) && Array.isArray(column.cells) && cells?.items) {
|
|
3326
3960
|
column.cells = labels.map(
|
|
3327
|
-
(_, i) => boundarySlotValue(
|
|
3961
|
+
(_, i) => boundarySlotValue(
|
|
3962
|
+
cells.items,
|
|
3963
|
+
"cells",
|
|
3964
|
+
edge,
|
|
3965
|
+
offset + i,
|
|
3966
|
+
format
|
|
3967
|
+
)
|
|
3328
3968
|
);
|
|
3329
3969
|
}
|
|
3330
3970
|
}
|
|
3331
3971
|
}
|
|
3332
3972
|
return { name: "block", props: { ref: name, slots } };
|
|
3333
3973
|
}
|
|
3334
|
-
function overBudgetInvocation(name, definition, seed = 0) {
|
|
3335
|
-
const invocation = boundaryInvocation(
|
|
3974
|
+
function overBudgetInvocation(name, definition, seed = 0, format = "docx") {
|
|
3975
|
+
const invocation = boundaryInvocation(
|
|
3976
|
+
name,
|
|
3977
|
+
definition,
|
|
3978
|
+
"max",
|
|
3979
|
+
seed,
|
|
3980
|
+
[],
|
|
3981
|
+
format
|
|
3982
|
+
);
|
|
3336
3983
|
const slots = invocation.props.slots;
|
|
3337
3984
|
for (const [slotName, slot] of Object.entries(definition.slots)) {
|
|
3338
3985
|
if (slot.type === "string" && slot.maxWords !== void 0) {
|
|
@@ -3350,27 +3997,41 @@ function overBudgetInvocation(name, definition, seed = 0) {
|
|
|
3350
3997
|
return void 0;
|
|
3351
3998
|
}
|
|
3352
3999
|
function enumerateBlockDefinitions(template, templateName) {
|
|
3353
|
-
const
|
|
4000
|
+
const format = templateFormat(templateName, template);
|
|
4001
|
+
if (!format)
|
|
4002
|
+
throw new Error(`${templateName} is neither a report nor a deck.`);
|
|
4003
|
+
const references = blockReferencesFromDocument2(template, {
|
|
4004
|
+
template: templateName,
|
|
4005
|
+
format
|
|
4006
|
+
});
|
|
4007
|
+
const declared = Object.keys(readBlockDefinitions2(template));
|
|
4008
|
+
if (references.length !== declared.length)
|
|
4009
|
+
throw new Error(
|
|
4010
|
+
`${templateName}: discovery publishes ${references.length} of ${declared.length} definitions; the rest do not validate.`
|
|
4011
|
+
);
|
|
3354
4012
|
const examples = /* @__PURE__ */ new Map();
|
|
3355
4013
|
const walk = (node) => {
|
|
3356
4014
|
if (Array.isArray(node)) {
|
|
3357
4015
|
node.forEach(walk);
|
|
3358
4016
|
return;
|
|
3359
4017
|
}
|
|
3360
|
-
if (!
|
|
4018
|
+
if (!isRecord2(node)) return;
|
|
3361
4019
|
const props = node.props;
|
|
3362
|
-
if (node.name === "block" &&
|
|
3363
|
-
examples.set(props.ref,
|
|
4020
|
+
if (node.name === "block" && isRecord2(props) && typeof props.ref === "string" && !examples.has(props.ref)) {
|
|
4021
|
+
examples.set(props.ref, isRecord2(props.slots) ? props.slots : {});
|
|
3364
4022
|
}
|
|
3365
4023
|
walk(node.children);
|
|
3366
4024
|
};
|
|
3367
|
-
walk(
|
|
3368
|
-
return
|
|
3369
|
-
name,
|
|
4025
|
+
walk(isRecord2(template) ? template.children : void 0);
|
|
4026
|
+
return references.map((reference) => ({
|
|
4027
|
+
name: reference.name,
|
|
4028
|
+
format,
|
|
3370
4029
|
template: templateName,
|
|
3371
|
-
pointer:
|
|
3372
|
-
definition,
|
|
3373
|
-
...examples.has(name) && {
|
|
4030
|
+
pointer: reference.definitionPointer,
|
|
4031
|
+
definition: reference.definition,
|
|
4032
|
+
...examples.has(reference.name) && {
|
|
4033
|
+
example: examples.get(reference.name)
|
|
4034
|
+
}
|
|
3374
4035
|
}));
|
|
3375
4036
|
}
|
|
3376
4037
|
var CHROME = ["cover", "running-head", "section-opener"];
|
|
@@ -3382,19 +4043,27 @@ function nominal(entry, name, definitions) {
|
|
|
3382
4043
|
props: { ref: name, slots: structuredClone(entry.example) }
|
|
3383
4044
|
};
|
|
3384
4045
|
}
|
|
3385
|
-
return boundaryInvocation(
|
|
4046
|
+
return boundaryInvocation(
|
|
4047
|
+
name,
|
|
4048
|
+
definitions[name],
|
|
4049
|
+
"min",
|
|
4050
|
+
0,
|
|
4051
|
+
[],
|
|
4052
|
+
entry.format
|
|
4053
|
+
);
|
|
3386
4054
|
}
|
|
3387
|
-
function inlineImages(value) {
|
|
3388
|
-
if (Array.isArray(value)) return value.map(inlineImages);
|
|
3389
|
-
if (!
|
|
3390
|
-
if (value.name === "image" &&
|
|
3391
|
-
const props = { ...value.props, base64: MATRIX_IMAGE };
|
|
3392
|
-
delete props.path;
|
|
4055
|
+
function inlineImages(value, format = "docx") {
|
|
4056
|
+
if (Array.isArray(value)) return value.map((v) => inlineImages(v, format));
|
|
4057
|
+
if (!isRecord2(value)) return value;
|
|
4058
|
+
if (value.name === "image" && isRecord2(value.props) && typeof value.props.path === "string" && !value.props.path.startsWith("data:")) {
|
|
4059
|
+
const props = format === "pptx" ? { ...value.props, path: MATRIX_IMAGE } : { ...value.props, base64: MATRIX_IMAGE };
|
|
4060
|
+
if (format === "docx") delete props.path;
|
|
3393
4061
|
return { ...value, props };
|
|
3394
4062
|
}
|
|
3395
|
-
if (value.name === "highcharts")
|
|
4063
|
+
if (value.name === "highcharts")
|
|
4064
|
+
return componentValue("chart", "min", 0, format);
|
|
3396
4065
|
return Object.fromEntries(
|
|
3397
|
-
Object.entries(value).map(([k, v]) => [k, inlineImages(v)])
|
|
4066
|
+
Object.entries(value).map(([k, v]) => [k, inlineImages(v, format)])
|
|
3398
4067
|
);
|
|
3399
4068
|
}
|
|
3400
4069
|
function withDefinitions(definitions, names) {
|
|
@@ -3410,9 +4079,9 @@ function withDefinitions(definitions, names) {
|
|
|
3410
4079
|
}
|
|
3411
4080
|
function conditions(props, children, theme, font, canvas) {
|
|
3412
4081
|
const sections = children.map((child) => {
|
|
3413
|
-
if (!
|
|
3414
|
-
const props2 =
|
|
3415
|
-
const page =
|
|
4082
|
+
if (!isRecord2(child) || child.name !== "section") return child;
|
|
4083
|
+
const props2 = isRecord2(child.props) ? child.props : {};
|
|
4084
|
+
const page = isRecord2(props2.page) ? props2.page : {};
|
|
3416
4085
|
return { ...child, props: { ...props2, page: { ...page, size: canvas } } };
|
|
3417
4086
|
});
|
|
3418
4087
|
const chart = JSON.stringify(sections).includes('"name":"chart"');
|
|
@@ -3428,12 +4097,12 @@ function conditions(props, children, theme, font, canvas) {
|
|
|
3428
4097
|
};
|
|
3429
4098
|
}
|
|
3430
4099
|
var BODY_COPY = words(40, 1e3, true);
|
|
3431
|
-
function blockCaseDocument(entries, name, edge, theme, { font = "design", canvas = "A4", requiredRoles = [] } = {}) {
|
|
4100
|
+
function blockCaseDocument(entries, name, edge, theme, { font = "design", canvas = "A4", requiredRoles: requiredRoles2 = [] } = {}) {
|
|
3432
4101
|
const definitions = Object.fromEntries(
|
|
3433
4102
|
entries.map((e) => [e.name, e.definition])
|
|
3434
4103
|
);
|
|
3435
4104
|
const byName = new Map(entries.map((e) => [e.name, e]));
|
|
3436
|
-
const under = (chrome) => chrome === name ? boundaryInvocation(name, definitions[name], edge, 0,
|
|
4105
|
+
const under = (chrome) => chrome === name ? boundaryInvocation(name, definitions[name], edge, 0, requiredRoles2) : inlineImages(nominal(byName.get(chrome), chrome, definitions));
|
|
3437
4106
|
const cover = under("cover");
|
|
3438
4107
|
const runningHead = under("running-head");
|
|
3439
4108
|
const opener = under("section-opener");
|
|
@@ -3445,7 +4114,7 @@ function blockCaseDocument(entries, name, edge, theme, { font = "design", canvas
|
|
|
3445
4114
|
);
|
|
3446
4115
|
}
|
|
3447
4116
|
body.push(
|
|
3448
|
-
boundaryInvocation(name, definitions[name], edge, 0,
|
|
4117
|
+
boundaryInvocation(name, definitions[name], edge, 0, requiredRoles2)
|
|
3449
4118
|
);
|
|
3450
4119
|
}
|
|
3451
4120
|
body.push({ name: "paragraph", props: { text: BODY_COPY } });
|
|
@@ -3476,26 +4145,26 @@ function blockCaseDocument(entries, name, edge, theme, { font = "design", canvas
|
|
|
3476
4145
|
canvas
|
|
3477
4146
|
);
|
|
3478
4147
|
}
|
|
3479
|
-
function reportCaseDocument(template, edge, theme, { font = "design", canvas = "A4", requiredRoles = [] } = {}) {
|
|
3480
|
-
if (!
|
|
4148
|
+
function reportCaseDocument(template, edge, theme, { font = "design", canvas = "A4", requiredRoles: requiredRoles2 = [] } = {}) {
|
|
4149
|
+
if (!isRecord2(template) || !isRecord2(template.props))
|
|
3481
4150
|
throw new Error("A template is a document with props.");
|
|
3482
|
-
const definitions =
|
|
4151
|
+
const definitions = readBlockDefinitions2(template);
|
|
3483
4152
|
let seed = 0;
|
|
3484
4153
|
const rewrite = (node) => {
|
|
3485
4154
|
if (Array.isArray(node)) return node.map(rewrite);
|
|
3486
|
-
if (!
|
|
4155
|
+
if (!isRecord2(node)) return node;
|
|
3487
4156
|
const props2 = node.props;
|
|
3488
|
-
if (node.name === "block" &&
|
|
4157
|
+
if (node.name === "block" && isRecord2(props2) && typeof props2.ref === "string" && definitions[props2.ref]) {
|
|
3489
4158
|
seed += 7;
|
|
3490
4159
|
return boundaryInvocation(
|
|
3491
4160
|
props2.ref,
|
|
3492
4161
|
definitions[props2.ref],
|
|
3493
4162
|
edge,
|
|
3494
4163
|
seed,
|
|
3495
|
-
|
|
4164
|
+
requiredRoles2
|
|
3496
4165
|
);
|
|
3497
4166
|
}
|
|
3498
|
-
if (node.name === "paragraph" &&
|
|
4167
|
+
if (node.name === "paragraph" && isRecord2(props2) && typeof props2.text === "string") {
|
|
3499
4168
|
return {
|
|
3500
4169
|
...node,
|
|
3501
4170
|
props: { ...props2, text: edge === "max" ? BODY_COPY : props2.text }
|
|
@@ -3556,6 +4225,200 @@ function generateBlockMatrix(template, templateName, options) {
|
|
|
3556
4225
|
}
|
|
3557
4226
|
return cases;
|
|
3558
4227
|
}
|
|
4228
|
+
function deckConditions(props, children, theme, font, canvas, themeObject) {
|
|
4229
|
+
const out = structuredClone(props);
|
|
4230
|
+
delete out.qualityProfile;
|
|
4231
|
+
const inline = typeof props.theme !== "string";
|
|
4232
|
+
if (!inline || theme !== "inline") {
|
|
4233
|
+
if (font === "fallback") {
|
|
4234
|
+
const base = themeObject?.(theme);
|
|
4235
|
+
if (!base)
|
|
4236
|
+
throw new Error(
|
|
4237
|
+
`The fallback case on "${theme}" needs the theme's object.`
|
|
4238
|
+
);
|
|
4239
|
+
const fonts = isRecord2(base.fonts) ? base.fonts : {};
|
|
4240
|
+
out.theme = {
|
|
4241
|
+
...structuredClone(base),
|
|
4242
|
+
fonts: { ...fonts, ...PPTX_FALLBACK_FONTS }
|
|
4243
|
+
};
|
|
4244
|
+
} else {
|
|
4245
|
+
out.theme = theme;
|
|
4246
|
+
}
|
|
4247
|
+
}
|
|
4248
|
+
if ((canvas === "wide169" || canvas === "standard43") && authoredDeckCanvas(props) !== canvas)
|
|
4249
|
+
Object.assign(out, PPTX_CANVAS_SIZE[canvas]);
|
|
4250
|
+
return { name: "pptx", props: out, children };
|
|
4251
|
+
}
|
|
4252
|
+
function templateProps(template) {
|
|
4253
|
+
if (!isRecord2(template) || !isRecord2(template.props))
|
|
4254
|
+
throw new Error("A template is a document with props.");
|
|
4255
|
+
return template.props;
|
|
4256
|
+
}
|
|
4257
|
+
function deckBlockCaseDocument(template, entries, name, edge, theme, {
|
|
4258
|
+
font = "design",
|
|
4259
|
+
canvas = "wide169",
|
|
4260
|
+
requiredRoles: requiredRoles2 = [],
|
|
4261
|
+
themeObject
|
|
4262
|
+
} = {}) {
|
|
4263
|
+
const props = templateProps(template);
|
|
4264
|
+
const definitions = Object.fromEntries(
|
|
4265
|
+
entries.map((e) => [e.name, e.definition])
|
|
4266
|
+
);
|
|
4267
|
+
const byName = new Map(entries.map((e) => [e.name, e]));
|
|
4268
|
+
const used = /* @__PURE__ */ new Set([name]);
|
|
4269
|
+
const slides = [];
|
|
4270
|
+
if (name !== "cover" && byName.has("cover")) {
|
|
4271
|
+
used.add("cover");
|
|
4272
|
+
slides.push({
|
|
4273
|
+
name: "slide",
|
|
4274
|
+
children: [
|
|
4275
|
+
inlineImages(
|
|
4276
|
+
nominal(byName.get("cover"), "cover", definitions),
|
|
4277
|
+
"pptx"
|
|
4278
|
+
)
|
|
4279
|
+
]
|
|
4280
|
+
});
|
|
4281
|
+
}
|
|
4282
|
+
slides.push({
|
|
4283
|
+
name: "slide",
|
|
4284
|
+
children: [
|
|
4285
|
+
boundaryInvocation(
|
|
4286
|
+
name,
|
|
4287
|
+
definitions[name],
|
|
4288
|
+
edge,
|
|
4289
|
+
0,
|
|
4290
|
+
requiredRoles2,
|
|
4291
|
+
"pptx"
|
|
4292
|
+
)
|
|
4293
|
+
]
|
|
4294
|
+
});
|
|
4295
|
+
return deckConditions(
|
|
4296
|
+
{ ...props, blocks: withDefinitions(definitions, used) },
|
|
4297
|
+
slides,
|
|
4298
|
+
theme,
|
|
4299
|
+
font,
|
|
4300
|
+
canvas,
|
|
4301
|
+
themeObject
|
|
4302
|
+
);
|
|
4303
|
+
}
|
|
4304
|
+
function deckReportCaseDocument(template, edge, theme, {
|
|
4305
|
+
font = "design",
|
|
4306
|
+
canvas = "wide169",
|
|
4307
|
+
requiredRoles: requiredRoles2 = [],
|
|
4308
|
+
themeObject
|
|
4309
|
+
} = {}) {
|
|
4310
|
+
const props = templateProps(template);
|
|
4311
|
+
const definitions = readBlockDefinitions2(template);
|
|
4312
|
+
let seed = 0;
|
|
4313
|
+
const rewrite = (node) => {
|
|
4314
|
+
if (Array.isArray(node)) return node.map(rewrite);
|
|
4315
|
+
if (!isRecord2(node)) return node;
|
|
4316
|
+
const nodeProps = node.props;
|
|
4317
|
+
if (node.name === "block" && isRecord2(nodeProps) && typeof nodeProps.ref === "string" && definitions[nodeProps.ref]) {
|
|
4318
|
+
seed += 7;
|
|
4319
|
+
return boundaryInvocation(
|
|
4320
|
+
nodeProps.ref,
|
|
4321
|
+
definitions[nodeProps.ref],
|
|
4322
|
+
edge,
|
|
4323
|
+
seed,
|
|
4324
|
+
requiredRoles2,
|
|
4325
|
+
"pptx"
|
|
4326
|
+
);
|
|
4327
|
+
}
|
|
4328
|
+
return Array.isArray(node.children) ? { ...node, children: rewrite(node.children) } : node;
|
|
4329
|
+
};
|
|
4330
|
+
return deckConditions(
|
|
4331
|
+
props,
|
|
4332
|
+
rewrite(template.children),
|
|
4333
|
+
theme,
|
|
4334
|
+
font,
|
|
4335
|
+
canvas,
|
|
4336
|
+
themeObject
|
|
4337
|
+
);
|
|
4338
|
+
}
|
|
4339
|
+
var stem = (template) => template.replace(/\.(docx|pptx)\.json$/, "");
|
|
4340
|
+
function generateMatrixCases(inventory, templates, selection = {}, { themeObject } = {}) {
|
|
4341
|
+
const pick = (supported, wanted) => supported.filter((value) => !wanted || wanted.includes(value));
|
|
4342
|
+
const cases = [];
|
|
4343
|
+
for (const summary of inventory.templates) {
|
|
4344
|
+
if (selection.templates && !selection.templates.includes(summary.template))
|
|
4345
|
+
continue;
|
|
4346
|
+
const source = templates.find((t) => t.name === summary.template);
|
|
4347
|
+
const entries = inventory.entries.filter(
|
|
4348
|
+
(e) => e.template === summary.template
|
|
4349
|
+
);
|
|
4350
|
+
if (!source || entries.length === 0) continue;
|
|
4351
|
+
const definitions = enumerateBlockDefinitions(
|
|
4352
|
+
source.document,
|
|
4353
|
+
summary.template
|
|
4354
|
+
);
|
|
4355
|
+
const withSlots = entries.some((e) => e.edges.includes("min"));
|
|
4356
|
+
for (const theme of pick(summary.conditions.themes, selection.themes))
|
|
4357
|
+
for (const font of pick(summary.conditions.fonts, selection.fonts))
|
|
4358
|
+
for (const canvas of pick(
|
|
4359
|
+
summary.conditions.canvases,
|
|
4360
|
+
selection.canvases
|
|
4361
|
+
))
|
|
4362
|
+
for (const edge of pick(summary.conditions.edges, selection.edges)) {
|
|
4363
|
+
const shared = {
|
|
4364
|
+
format: summary.format,
|
|
4365
|
+
template: summary.template,
|
|
4366
|
+
...summary.profile && { profile: summary.profile },
|
|
4367
|
+
edge,
|
|
4368
|
+
theme,
|
|
4369
|
+
font,
|
|
4370
|
+
canvas
|
|
4371
|
+
};
|
|
4372
|
+
const conditions2 = {
|
|
4373
|
+
font,
|
|
4374
|
+
canvas,
|
|
4375
|
+
requiredRoles: summary.requiredRoles,
|
|
4376
|
+
themeObject
|
|
4377
|
+
};
|
|
4378
|
+
if (selection.blocks !== false)
|
|
4379
|
+
for (const entry of entries) {
|
|
4380
|
+
if (!entry.edges.includes(edge)) continue;
|
|
4381
|
+
cases.push({
|
|
4382
|
+
...shared,
|
|
4383
|
+
id: `${stem(summary.template)}/${entry.name}@${edge}/${theme}/${font}/${canvas}`,
|
|
4384
|
+
block: entry.name,
|
|
4385
|
+
document: summary.format === "docx" ? blockCaseDocument(
|
|
4386
|
+
definitions,
|
|
4387
|
+
entry.name,
|
|
4388
|
+
edge,
|
|
4389
|
+
theme,
|
|
4390
|
+
conditions2
|
|
4391
|
+
) : deckBlockCaseDocument(
|
|
4392
|
+
source.document,
|
|
4393
|
+
definitions,
|
|
4394
|
+
entry.name,
|
|
4395
|
+
edge,
|
|
4396
|
+
theme,
|
|
4397
|
+
conditions2
|
|
4398
|
+
)
|
|
4399
|
+
});
|
|
4400
|
+
}
|
|
4401
|
+
if (selection.report !== false && withSlots)
|
|
4402
|
+
cases.push({
|
|
4403
|
+
...shared,
|
|
4404
|
+
id: `${stem(summary.template)}/report@${edge}/${theme}/${font}/${canvas}`,
|
|
4405
|
+
block: "report",
|
|
4406
|
+
document: summary.format === "docx" ? reportCaseDocument(
|
|
4407
|
+
source.document,
|
|
4408
|
+
edge,
|
|
4409
|
+
theme,
|
|
4410
|
+
conditions2
|
|
4411
|
+
) : deckReportCaseDocument(
|
|
4412
|
+
source.document,
|
|
4413
|
+
edge,
|
|
4414
|
+
theme,
|
|
4415
|
+
conditions2
|
|
4416
|
+
)
|
|
4417
|
+
});
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
return cases;
|
|
4421
|
+
}
|
|
3559
4422
|
export {
|
|
3560
4423
|
DocxFormatAdapter,
|
|
3561
4424
|
FALLBACK_FONTS,
|
|
@@ -3565,6 +4428,8 @@ export {
|
|
|
3565
4428
|
MATRIX_IMAGE,
|
|
3566
4429
|
MacOSCoreTextStager,
|
|
3567
4430
|
NoopFontStager,
|
|
4431
|
+
PPTX_CANVAS_SIZE,
|
|
4432
|
+
PPTX_FALLBACK_FONTS,
|
|
3568
4433
|
PptxFormatAdapter,
|
|
3569
4434
|
RENDERED_QUALITY_RULES,
|
|
3570
4435
|
VISIBLE_SPILL_PT,
|
|
@@ -3575,10 +4440,14 @@ export {
|
|
|
3575
4440
|
blockCaseDocument,
|
|
3576
4441
|
boundaryInvocation,
|
|
3577
4442
|
boundarySlotValue,
|
|
4443
|
+
buildMatrixInventory,
|
|
3578
4444
|
clearRasterizerCache,
|
|
3579
4445
|
createAdapter,
|
|
3580
4446
|
createLibreOfficePptxBatchRasterizer,
|
|
3581
4447
|
createLibreOfficePptxRasterizer,
|
|
4448
|
+
deckBlockCaseDocument,
|
|
4449
|
+
deckReportCaseDocument,
|
|
4450
|
+
describeInventory,
|
|
3582
4451
|
emitDiagnostic,
|
|
3583
4452
|
enumerateBlockDefinitions,
|
|
3584
4453
|
extractPdfFonts,
|
|
@@ -3586,9 +4455,12 @@ export {
|
|
|
3586
4455
|
extractPdfTextGeometry,
|
|
3587
4456
|
familyRendered,
|
|
3588
4457
|
generateBlockMatrix,
|
|
4458
|
+
generateMatrixCases,
|
|
3589
4459
|
getFontStager,
|
|
3590
4460
|
getRasterizerCacheStats,
|
|
3591
4461
|
inkedRows,
|
|
4462
|
+
inventoryGaps,
|
|
4463
|
+
matrixUniverse,
|
|
3592
4464
|
normalizeForMatch,
|
|
3593
4465
|
overBudgetInvocation,
|
|
3594
4466
|
pageInkFromPgm,
|
|
@@ -3598,9 +4470,12 @@ export {
|
|
|
3598
4470
|
pdffontsAvailable,
|
|
3599
4471
|
pdftotextAvailable,
|
|
3600
4472
|
readingOrder,
|
|
4473
|
+
renderedInventoryFromFacts,
|
|
3601
4474
|
reportCaseDocument,
|
|
4475
|
+
requestedFontsFromFacts,
|
|
3602
4476
|
runWithDiagnosticSink,
|
|
3603
4477
|
stderrDiagnosticSink,
|
|
4478
|
+
templateFormat,
|
|
3604
4479
|
widestNumber
|
|
3605
4480
|
};
|
|
3606
4481
|
//# sourceMappingURL=index.js.map
|