@json-to-office/core-docx 3.3.0 → 4.1.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 +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +556 -36
- package/dist/index.js.map +1 -1
- package/dist/plugin/example/index.js +368 -33
- package/dist/plugin/example/index.js.map +1 -1
- package/dist/quality/facts.d.ts +27 -1
- package/dist/quality/facts.d.ts.map +1 -1
- package/dist/quality/rules.d.ts +38 -1
- package/dist/quality/rules.d.ts.map +1 -1
- package/dist/quality/text-inventory.d.ts +45 -0
- package/dist/quality/text-inventory.d.ts.map +1 -0
- package/dist/templates/themes/consulting.docx.theme.json +1 -0
- package/dist/templates/themes/devportal.docx.theme.json +1 -0
- package/dist/templates/themes/index.d.ts +5 -0
- package/dist/templates/themes/index.d.ts.map +1 -1
- package/dist/templates/themes/minimal.docx.theme.json +1 -0
- package/dist/templates/themes/vermilion.docx.theme.json +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -246,6 +246,7 @@ var init_minimal_docx_theme = __esm({
|
|
|
246
246
|
name: "minimal",
|
|
247
247
|
displayName: "Minimal Clean",
|
|
248
248
|
description: "A quiet, warm minimal theme \u2014 Calibri throughout, sage-green ink on ivory neutrals, a large tracked-tight bold title, wide margins and hairline rules",
|
|
249
|
+
whenToUse: "Quiet documents where the type carries the tone: notes, briefs, drafts and internal papers that want warmth without decoration.",
|
|
249
250
|
version: "4.0.0",
|
|
250
251
|
colors: {
|
|
251
252
|
primary: "#2B302B",
|
|
@@ -435,6 +436,7 @@ var init_devportal_docx_theme = __esm({
|
|
|
435
436
|
name: "devportal",
|
|
436
437
|
displayName: "Field Editorial",
|
|
437
438
|
description: "A compact editorial theme \u2014 Helvetica in near-black ink with a burnt-orange accent, tight condensed titles, letterspaced labels, warm tinted fills and hairline rules",
|
|
439
|
+
whenToUse: "Developer and product documentation, API guides and technical handbooks: dense text, code and tables that need compact, letterspaced structure.",
|
|
438
440
|
version: "4.0.0",
|
|
439
441
|
colors: {
|
|
440
442
|
primary: "#12191F",
|
|
@@ -633,6 +635,7 @@ var init_vermilion_docx_theme = __esm({
|
|
|
633
635
|
name: "vermilion",
|
|
634
636
|
displayName: "Vermilion Editorial",
|
|
635
637
|
description: "Poster-red editorial system \u2014 vermilion display headings, ink text, warm creams and hairline rules",
|
|
638
|
+
whenToUse: "Editorial and annual-report work that wants a strong red display voice: covers, feature-style sections, reports built around a few large headings.",
|
|
636
639
|
version: "1.0.0",
|
|
637
640
|
colors: {
|
|
638
641
|
primary: "#282829",
|
|
@@ -851,6 +854,7 @@ var init_consulting_docx_theme = __esm({
|
|
|
851
854
|
name: "consulting",
|
|
852
855
|
displayName: "Consulting House",
|
|
853
856
|
description: "The house style for client and technical reports \u2014 near-black ink, three greys and one deep-blue accent, Calibri body under Arial headings, hairline rules, no fills behind body text, safe fonts only",
|
|
857
|
+
whenToUse: "Client and technical reports, memos and anything a client will read as the house's own work; the default choice for a report unless the brief names a brand.",
|
|
854
858
|
version: "1.0.0",
|
|
855
859
|
colors: {
|
|
856
860
|
primary: "#1A1F26",
|
|
@@ -11876,8 +11880,10 @@ import {
|
|
|
11876
11880
|
} from "@json-to-office/quality";
|
|
11877
11881
|
import { DEFAULT_DOCX_RENDERER_ID as DEFAULT_DOCX_RENDERER_ID2 } from "@json-to-office/shared-docx";
|
|
11878
11882
|
import {
|
|
11883
|
+
designCanvas as designCanvas2,
|
|
11879
11884
|
designColors as designColors2,
|
|
11880
|
-
resolveDesignColor as resolveDesignColor2
|
|
11885
|
+
resolveDesignColor as resolveDesignColor2,
|
|
11886
|
+
typeScaleSizes
|
|
11881
11887
|
} from "@json-to-office/shared";
|
|
11882
11888
|
|
|
11883
11889
|
// src/core/generationContext.ts
|
|
@@ -12390,9 +12396,176 @@ async function expandBlocksWithPlugins(document, theme, plugins, render, preserv
|
|
|
12390
12396
|
init_styleHelpers();
|
|
12391
12397
|
init_defaults();
|
|
12392
12398
|
init_widthUtils();
|
|
12399
|
+
|
|
12400
|
+
// src/quality/text-inventory.ts
|
|
12393
12401
|
function asRecord(value) {
|
|
12394
12402
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
12395
12403
|
}
|
|
12404
|
+
function twipsToPt(value) {
|
|
12405
|
+
return typeof value === "number" && Number.isFinite(value) ? value / 20 : void 0;
|
|
12406
|
+
}
|
|
12407
|
+
function frameOf(props) {
|
|
12408
|
+
const widthPt = twipsToPt(asRecord(props.floating)?.width);
|
|
12409
|
+
return widthPt === void 0 ? void 0 : { widthPt };
|
|
12410
|
+
}
|
|
12411
|
+
function collectDocxTextInventory(children, basePath = "/children") {
|
|
12412
|
+
const entries = [];
|
|
12413
|
+
const add = (path4, text, role, extra = {}) => {
|
|
12414
|
+
if (typeof text !== "string" || text.trim() === "") return;
|
|
12415
|
+
entries.push({
|
|
12416
|
+
id: `docx:text:${path4}`,
|
|
12417
|
+
kind: "docx/text",
|
|
12418
|
+
path: path4,
|
|
12419
|
+
text,
|
|
12420
|
+
role,
|
|
12421
|
+
order: entries.length,
|
|
12422
|
+
...extra
|
|
12423
|
+
});
|
|
12424
|
+
};
|
|
12425
|
+
const visitCell = (cell, path4, role, inherited) => {
|
|
12426
|
+
const cellRole = inherited.repeats ? "chrome" : role;
|
|
12427
|
+
const extra = inherited.repeats ? { repeats: true } : {};
|
|
12428
|
+
if (typeof cell === "string") {
|
|
12429
|
+
add(path4, cell, cellRole, extra);
|
|
12430
|
+
return;
|
|
12431
|
+
}
|
|
12432
|
+
const rec = asRecord(cell);
|
|
12433
|
+
if (!rec) return;
|
|
12434
|
+
if ("content" in rec) {
|
|
12435
|
+
if (typeof rec.content === "string")
|
|
12436
|
+
add(`${path4}/content`, rec.content, cellRole, extra);
|
|
12437
|
+
else {
|
|
12438
|
+
const inner = asRecord(rec.content);
|
|
12439
|
+
if (inner) visitNode(inner, `${path4}/content`, inherited);
|
|
12440
|
+
}
|
|
12441
|
+
return;
|
|
12442
|
+
}
|
|
12443
|
+
if (typeof rec.name === "string") visitNode(rec, path4, inherited);
|
|
12444
|
+
};
|
|
12445
|
+
const visitNode = (node, path4, inherited) => {
|
|
12446
|
+
if (node.enabled === false) return;
|
|
12447
|
+
const props = asRecord(node.props) ?? {};
|
|
12448
|
+
const name = typeof node.name === "string" ? node.name : "";
|
|
12449
|
+
const extra = {
|
|
12450
|
+
...inherited.repeats && { repeats: true }
|
|
12451
|
+
};
|
|
12452
|
+
switch (name) {
|
|
12453
|
+
case "heading": {
|
|
12454
|
+
if (inherited.repeats) {
|
|
12455
|
+
add(`${path4}/props/text`, props.text, "chrome", extra);
|
|
12456
|
+
break;
|
|
12457
|
+
}
|
|
12458
|
+
const level = typeof props.level === "number" && Number.isFinite(props.level) ? props.level : 1;
|
|
12459
|
+
add(`${path4}/props/text`, props.text, "heading", { ...extra, level });
|
|
12460
|
+
break;
|
|
12461
|
+
}
|
|
12462
|
+
case "paragraph": {
|
|
12463
|
+
const frame = frameOf(props);
|
|
12464
|
+
add(
|
|
12465
|
+
`${path4}/props/text`,
|
|
12466
|
+
props.text,
|
|
12467
|
+
inherited.repeats ? "chrome" : "body",
|
|
12468
|
+
{
|
|
12469
|
+
...extra,
|
|
12470
|
+
...frame && { frame }
|
|
12471
|
+
}
|
|
12472
|
+
);
|
|
12473
|
+
break;
|
|
12474
|
+
}
|
|
12475
|
+
case "list": {
|
|
12476
|
+
if (Array.isArray(props.items)) {
|
|
12477
|
+
props.items.forEach((item, index) => {
|
|
12478
|
+
const itemPath = `${path4}/props/items/${index}`;
|
|
12479
|
+
if (typeof item === "string")
|
|
12480
|
+
add(itemPath, item, "list-item", extra);
|
|
12481
|
+
else {
|
|
12482
|
+
const rec = asRecord(item);
|
|
12483
|
+
if (rec) add(`${itemPath}/text`, rec.text, "list-item", extra);
|
|
12484
|
+
}
|
|
12485
|
+
});
|
|
12486
|
+
}
|
|
12487
|
+
break;
|
|
12488
|
+
}
|
|
12489
|
+
case "table": {
|
|
12490
|
+
const columns = Array.isArray(props.columns) ? props.columns.map(asRecord) : [];
|
|
12491
|
+
columns.forEach((column, columnIndex) => {
|
|
12492
|
+
if (!column) return;
|
|
12493
|
+
visitCell(
|
|
12494
|
+
column.header,
|
|
12495
|
+
`${path4}/props/columns/${columnIndex}/header`,
|
|
12496
|
+
"table-header",
|
|
12497
|
+
inherited
|
|
12498
|
+
);
|
|
12499
|
+
});
|
|
12500
|
+
const rows = Math.max(
|
|
12501
|
+
0,
|
|
12502
|
+
...columns.map(
|
|
12503
|
+
(column) => Array.isArray(column?.cells) ? column.cells.length : 0
|
|
12504
|
+
)
|
|
12505
|
+
);
|
|
12506
|
+
for (let row = 0; row < rows; row++) {
|
|
12507
|
+
columns.forEach((column, columnIndex) => {
|
|
12508
|
+
if (!column || !Array.isArray(column.cells)) return;
|
|
12509
|
+
if (row >= column.cells.length) return;
|
|
12510
|
+
visitCell(
|
|
12511
|
+
column.cells[row],
|
|
12512
|
+
`${path4}/props/columns/${columnIndex}/cells/${row}`,
|
|
12513
|
+
"table-cell",
|
|
12514
|
+
inherited
|
|
12515
|
+
);
|
|
12516
|
+
});
|
|
12517
|
+
}
|
|
12518
|
+
break;
|
|
12519
|
+
}
|
|
12520
|
+
case "statistic": {
|
|
12521
|
+
add(`${path4}/props/number`, props.number, "statistic", extra);
|
|
12522
|
+
add(`${path4}/props/description`, props.description, "statistic", extra);
|
|
12523
|
+
break;
|
|
12524
|
+
}
|
|
12525
|
+
case "image":
|
|
12526
|
+
case "chart":
|
|
12527
|
+
case "highcharts":
|
|
12528
|
+
case "visual":
|
|
12529
|
+
case "visual-native": {
|
|
12530
|
+
add(`${path4}/props/caption`, props.caption, "caption", extra);
|
|
12531
|
+
break;
|
|
12532
|
+
}
|
|
12533
|
+
case "section": {
|
|
12534
|
+
for (const part of ["header", "footer"]) {
|
|
12535
|
+
const components = props[part];
|
|
12536
|
+
if (!Array.isArray(components)) continue;
|
|
12537
|
+
components.forEach((component, index) => {
|
|
12538
|
+
const rec = asRecord(component);
|
|
12539
|
+
if (rec) {
|
|
12540
|
+
visitNode(rec, `${path4}/props/${part}/${index}`, {
|
|
12541
|
+
repeats: true
|
|
12542
|
+
});
|
|
12543
|
+
}
|
|
12544
|
+
});
|
|
12545
|
+
}
|
|
12546
|
+
break;
|
|
12547
|
+
}
|
|
12548
|
+
default:
|
|
12549
|
+
break;
|
|
12550
|
+
}
|
|
12551
|
+
if (Array.isArray(node.children)) {
|
|
12552
|
+
node.children.forEach((child, index) => {
|
|
12553
|
+
const rec = asRecord(child);
|
|
12554
|
+
if (rec) visitNode(rec, `${path4}/children/${index}`, inherited);
|
|
12555
|
+
});
|
|
12556
|
+
}
|
|
12557
|
+
};
|
|
12558
|
+
children.forEach((child, index) => {
|
|
12559
|
+
const rec = asRecord(child);
|
|
12560
|
+
if (rec) visitNode(rec, `${basePath}/${index}`, {});
|
|
12561
|
+
});
|
|
12562
|
+
return entries;
|
|
12563
|
+
}
|
|
12564
|
+
|
|
12565
|
+
// src/quality/facts.ts
|
|
12566
|
+
function asRecord2(value) {
|
|
12567
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
12568
|
+
}
|
|
12396
12569
|
function pageBox(theme, themeName, pageOverride) {
|
|
12397
12570
|
const page = createSectionProperties(
|
|
12398
12571
|
getColumnSettings("single"),
|
|
@@ -12418,7 +12591,7 @@ function tableFact(props, path4, availableWidthTwips) {
|
|
|
12418
12591
|
let percentSum = 0;
|
|
12419
12592
|
const explicitWidths = [];
|
|
12420
12593
|
columns.forEach((column, index) => {
|
|
12421
|
-
const width =
|
|
12594
|
+
const width = asRecord2(column)?.width;
|
|
12422
12595
|
if (typeof width === "number" && Number.isFinite(width)) {
|
|
12423
12596
|
hasExplicitWidth = true;
|
|
12424
12597
|
explicitWidths.push({ index, width });
|
|
@@ -12499,7 +12672,7 @@ function chartFact(node, props, path4, paletteTokens) {
|
|
|
12499
12672
|
const categoryCount = Math.max(
|
|
12500
12673
|
0,
|
|
12501
12674
|
...series.map((entry) => {
|
|
12502
|
-
const record =
|
|
12675
|
+
const record = asRecord2(entry);
|
|
12503
12676
|
const labels = Array.isArray(record?.labels) ? record.labels.length : 0;
|
|
12504
12677
|
const values = Array.isArray(record?.values) ? record.values.length : 0;
|
|
12505
12678
|
return Math.max(labels, values);
|
|
@@ -12541,8 +12714,8 @@ function statesOwnBorders(authored) {
|
|
|
12541
12714
|
return true;
|
|
12542
12715
|
}
|
|
12543
12716
|
const layers = [
|
|
12544
|
-
|
|
12545
|
-
|
|
12717
|
+
asRecord2(authored.cellDefaults),
|
|
12718
|
+
asRecord2(authored.headerCellDefaults)
|
|
12546
12719
|
];
|
|
12547
12720
|
if (layers.some(
|
|
12548
12721
|
(layer) => layer?.borderSize !== void 0 || layer?.borderColor !== void 0
|
|
@@ -12551,8 +12724,8 @@ function statesOwnBorders(authored) {
|
|
|
12551
12724
|
}
|
|
12552
12725
|
const columns = Array.isArray(authored.columns) ? authored.columns : [];
|
|
12553
12726
|
return columns.some((column) => {
|
|
12554
|
-
const record =
|
|
12555
|
-
const defaults =
|
|
12727
|
+
const record = asRecord2(column);
|
|
12728
|
+
const defaults = asRecord2(record?.cellDefaults);
|
|
12556
12729
|
return defaults?.borderSize !== void 0 || defaults?.borderColor !== void 0;
|
|
12557
12730
|
});
|
|
12558
12731
|
}
|
|
@@ -12576,7 +12749,7 @@ function tableDesignFact(props, path4, theme, themeName, authored) {
|
|
|
12576
12749
|
);
|
|
12577
12750
|
const columns = authoredColumns.map(
|
|
12578
12751
|
(column, index) => {
|
|
12579
|
-
const authoredColumn =
|
|
12752
|
+
const authoredColumn = asRecord2(column) ?? {};
|
|
12580
12753
|
const cells = Array.isArray(authoredColumn.cells) ? authoredColumn.cells : [];
|
|
12581
12754
|
const alignments = /* @__PURE__ */ new Set();
|
|
12582
12755
|
const values = [];
|
|
@@ -12595,11 +12768,11 @@ function tableDesignFact(props, path4, theme, themeName, authored) {
|
|
|
12595
12768
|
},
|
|
12596
12769
|
values,
|
|
12597
12770
|
alignment: alignments.size === 1 ? [...alignments][0] : alignments.size === 0 ? "left" : "mixed",
|
|
12598
|
-
hasCellDefaults:
|
|
12599
|
-
hasHeader:
|
|
12771
|
+
hasCellDefaults: asRecord2(authoredColumn.cellDefaults) !== void 0,
|
|
12772
|
+
hasHeader: asRecord2(authoredColumn.header) !== void 0,
|
|
12600
12773
|
generated: false,
|
|
12601
12774
|
cellsWithOwnAlignment: cells.flatMap((cell, cellIndex) => {
|
|
12602
|
-
const alignment2 =
|
|
12775
|
+
const alignment2 = asRecord2(cell)?.horizontalAlignment;
|
|
12603
12776
|
return typeof alignment2 === "string" && DOCX_ALIGNMENTS.has(alignment2) && alignment2 !== "right" ? [cellIndex] : [];
|
|
12604
12777
|
})
|
|
12605
12778
|
};
|
|
@@ -12620,7 +12793,7 @@ function finiteNumber(value) {
|
|
|
12620
12793
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
12621
12794
|
}
|
|
12622
12795
|
function lineHeightPt(fontSizePt, spacing) {
|
|
12623
|
-
const rule =
|
|
12796
|
+
const rule = asRecord2(spacing);
|
|
12624
12797
|
const value = finiteNumber(rule?.value);
|
|
12625
12798
|
switch (rule?.type) {
|
|
12626
12799
|
// `exactly` is an absolute line box, and display type routinely sets it
|
|
@@ -12637,7 +12810,7 @@ function lineHeightPt(fontSizePt, spacing) {
|
|
|
12637
12810
|
}
|
|
12638
12811
|
}
|
|
12639
12812
|
function characterSpacingPt(spacing) {
|
|
12640
|
-
const rule =
|
|
12813
|
+
const rule = asRecord2(spacing);
|
|
12641
12814
|
const value = finiteNumber(rule?.value);
|
|
12642
12815
|
if (rule === void 0 || value === void 0) return 0;
|
|
12643
12816
|
return rule.type === "condensed" ? -value / TWIPS_PER_POINT3 : value / TWIPS_PER_POINT3;
|
|
@@ -12651,7 +12824,7 @@ function nodeAtPointer(root, pointer) {
|
|
|
12651
12824
|
current = current[index];
|
|
12652
12825
|
continue;
|
|
12653
12826
|
}
|
|
12654
|
-
const record =
|
|
12827
|
+
const record = asRecord2(current);
|
|
12655
12828
|
if (!record) return void 0;
|
|
12656
12829
|
current = record[token];
|
|
12657
12830
|
}
|
|
@@ -12667,10 +12840,10 @@ function styleKey(node, props) {
|
|
|
12667
12840
|
return typeof props.themeStyle === "string" && props.themeStyle !== "" ? props.themeStyle : "normal";
|
|
12668
12841
|
}
|
|
12669
12842
|
function effectiveFontSize(node, props, typography) {
|
|
12670
|
-
const authored = finiteNumber(
|
|
12843
|
+
const authored = finiteNumber(asRecord2(props.font)?.size);
|
|
12671
12844
|
if (authored !== void 0) return { fontSizePt: authored, authored: true };
|
|
12672
12845
|
const { styles, theme } = typography;
|
|
12673
|
-
const style =
|
|
12846
|
+
const style = asRecord2(styles[styleKey(node, props)]) ?? asRecord2(styles.normal);
|
|
12674
12847
|
const stated = finiteNumber(style?.size);
|
|
12675
12848
|
if (stated !== void 0) return { fontSizePt: stated, authored: false };
|
|
12676
12849
|
const reference = style?.font === "heading" || style?.font === "body" || style?.font === "mono" || style?.font === "light" ? style.font : void 0;
|
|
@@ -12680,11 +12853,11 @@ function effectiveFontSize(node, props, typography) {
|
|
|
12680
12853
|
function pinnedLineBox(props, path4) {
|
|
12681
12854
|
const candidates = [
|
|
12682
12855
|
[props.lineSpacing, `${path4}/props/lineSpacing`],
|
|
12683
|
-
[
|
|
12856
|
+
[asRecord2(props.font)?.lineSpacing, `${path4}/props/font/lineSpacing`]
|
|
12684
12857
|
];
|
|
12685
12858
|
for (const [spacing, pointer] of candidates) {
|
|
12686
12859
|
if (spacing === void 0) continue;
|
|
12687
|
-
const rule =
|
|
12860
|
+
const rule = asRecord2(spacing);
|
|
12688
12861
|
if (rule?.type !== "exactly") return void 0;
|
|
12689
12862
|
const lineBoxPt = finiteNumber(rule.value);
|
|
12690
12863
|
return lineBoxPt === void 0 ? void 0 : { lineBoxPt, pointer };
|
|
@@ -12710,8 +12883,8 @@ function lineBoxFact(node, props, path4, typography, authored) {
|
|
|
12710
12883
|
};
|
|
12711
12884
|
}
|
|
12712
12885
|
function frameSignature(floating) {
|
|
12713
|
-
const horizontal =
|
|
12714
|
-
const vertical =
|
|
12886
|
+
const horizontal = asRecord2(floating.horizontalPosition);
|
|
12887
|
+
const vertical = asRecord2(floating.verticalPosition);
|
|
12715
12888
|
return JSON.stringify([
|
|
12716
12889
|
floating.width ?? null,
|
|
12717
12890
|
floating.height ?? null,
|
|
@@ -12721,24 +12894,24 @@ function frameSignature(floating) {
|
|
|
12721
12894
|
vertical?.offset ?? null,
|
|
12722
12895
|
vertical?.relative ?? null,
|
|
12723
12896
|
vertical?.align ?? null,
|
|
12724
|
-
|
|
12897
|
+
asRecord2(floating.wrap)?.type ?? null
|
|
12725
12898
|
]);
|
|
12726
12899
|
}
|
|
12727
12900
|
function frameTextFact(props, path4, page, frameChainId, flowIndex) {
|
|
12728
|
-
const floating =
|
|
12901
|
+
const floating = asRecord2(props.floating);
|
|
12729
12902
|
const frameWidthTwips = finiteNumber(floating?.width);
|
|
12730
12903
|
if (frameWidthTwips === void 0) return void 0;
|
|
12731
12904
|
const text = typeof props.text === "string" ? props.text : "";
|
|
12732
12905
|
if (text.trim() === "") return void 0;
|
|
12733
|
-
const font =
|
|
12906
|
+
const font = asRecord2(props.font);
|
|
12734
12907
|
const fontSizePt = finiteNumber(font?.size);
|
|
12735
12908
|
if (fontSizePt === void 0) return void 0;
|
|
12736
12909
|
const longestWord = text.split(/\s+/).reduce(
|
|
12737
12910
|
(longest, word) => word.length > longest.length ? word : longest,
|
|
12738
12911
|
""
|
|
12739
12912
|
);
|
|
12740
|
-
const horizontal =
|
|
12741
|
-
const vertical =
|
|
12913
|
+
const horizontal = asRecord2(floating?.horizontalPosition);
|
|
12914
|
+
const vertical = asRecord2(floating?.verticalPosition);
|
|
12742
12915
|
const offsetX = finiteNumber(horizontal?.offset);
|
|
12743
12916
|
const offsetY = finiteNumber(vertical?.offset);
|
|
12744
12917
|
const absolutelyPinned = horizontal?.offset !== void 0 || vertical?.offset !== void 0;
|
|
@@ -12809,7 +12982,7 @@ function svgTextFacts(props, path4) {
|
|
|
12809
12982
|
return facts;
|
|
12810
12983
|
}
|
|
12811
12984
|
function walkActive(node, path4, page, visit) {
|
|
12812
|
-
const rec =
|
|
12985
|
+
const rec = asRecord2(node);
|
|
12813
12986
|
if (!rec || rec.enabled === false) return;
|
|
12814
12987
|
visit(rec, path4, page);
|
|
12815
12988
|
const children = Array.isArray(rec.children) ? rec.children : [];
|
|
@@ -12817,6 +12990,93 @@ function walkActive(node, path4, page, visit) {
|
|
|
12817
12990
|
(child, index) => walkActive(child, `${path4}/children/${index}`, page, visit)
|
|
12818
12991
|
);
|
|
12819
12992
|
}
|
|
12993
|
+
function textSizeFact(node, props, path4, typography, role) {
|
|
12994
|
+
const size = effectiveFontSize(node, props, typography);
|
|
12995
|
+
if (size === void 0) return void 0;
|
|
12996
|
+
return {
|
|
12997
|
+
id: `docx:text-size:${path4}`,
|
|
12998
|
+
kind: "docx/text-size",
|
|
12999
|
+
path: path4,
|
|
13000
|
+
role: role ?? styleKey(node, props),
|
|
13001
|
+
fontSizePt: size.fontSizePt,
|
|
13002
|
+
authored: size.authored,
|
|
13003
|
+
...size.authored && { sizePath: `${path4}/props/font/size` }
|
|
13004
|
+
};
|
|
13005
|
+
}
|
|
13006
|
+
function tableTextSizeFacts(props, authored, path4, typography, page) {
|
|
13007
|
+
const facts = [];
|
|
13008
|
+
const sizeOf = (holder) => finiteNumber(asRecord2(asRecord2(holder)?.font)?.size);
|
|
13009
|
+
const authoredSize = (holder, pointer, role) => {
|
|
13010
|
+
const size = sizeOf(holder);
|
|
13011
|
+
if (size === void 0) return;
|
|
13012
|
+
facts.push({
|
|
13013
|
+
id: `docx:text-size:${pointer}`,
|
|
13014
|
+
kind: "docx/text-size",
|
|
13015
|
+
path: pointer,
|
|
13016
|
+
role,
|
|
13017
|
+
fontSizePt: size,
|
|
13018
|
+
authored: true,
|
|
13019
|
+
sizePath: `${pointer}/font/size`
|
|
13020
|
+
});
|
|
13021
|
+
};
|
|
13022
|
+
const cellContent = (holder, pointer, role) => {
|
|
13023
|
+
const content = asRecord2(holder)?.content;
|
|
13024
|
+
if (asRecord2(content) === void 0) return;
|
|
13025
|
+
walkActive(content, `${pointer}/content`, page, (node, nodePath) => {
|
|
13026
|
+
if (node.name !== "paragraph" && node.name !== "heading") return;
|
|
13027
|
+
const nodeProps = asRecord2(node.props) ?? {};
|
|
13028
|
+
if (typeof nodeProps.text !== "string" || nodeProps.text.trim() === "")
|
|
13029
|
+
return;
|
|
13030
|
+
const fact = textSizeFact(node, nodeProps, nodePath, typography, role);
|
|
13031
|
+
if (fact) facts.push(fact);
|
|
13032
|
+
});
|
|
13033
|
+
};
|
|
13034
|
+
if (authored) {
|
|
13035
|
+
authoredSize(
|
|
13036
|
+
authored.cellDefaults,
|
|
13037
|
+
`${path4}/props/cellDefaults`,
|
|
13038
|
+
"tableCell"
|
|
13039
|
+
);
|
|
13040
|
+
authoredSize(
|
|
13041
|
+
authored.headerCellDefaults,
|
|
13042
|
+
`${path4}/props/headerCellDefaults`,
|
|
13043
|
+
"tableHeader"
|
|
13044
|
+
);
|
|
13045
|
+
const columns = Array.isArray(authored.columns) ? authored.columns : [];
|
|
13046
|
+
columns.forEach((column, index) => {
|
|
13047
|
+
const record = asRecord2(column) ?? {};
|
|
13048
|
+
const columnPath = `${path4}/props/columns/${index}`;
|
|
13049
|
+
authoredSize(
|
|
13050
|
+
record.cellDefaults,
|
|
13051
|
+
`${columnPath}/cellDefaults`,
|
|
13052
|
+
"tableCell"
|
|
13053
|
+
);
|
|
13054
|
+
authoredSize(record.header, `${columnPath}/header`, "tableHeader");
|
|
13055
|
+
cellContent(record.header, `${columnPath}/header`, "tableHeader");
|
|
13056
|
+
const cells = Array.isArray(record.cells) ? record.cells : [];
|
|
13057
|
+
cells.forEach((cell, cellIndex) => {
|
|
13058
|
+
authoredSize(cell, `${columnPath}/cells/${cellIndex}`, "tableCell");
|
|
13059
|
+
cellContent(cell, `${columnPath}/cells/${cellIndex}`, "tableCell");
|
|
13060
|
+
});
|
|
13061
|
+
});
|
|
13062
|
+
}
|
|
13063
|
+
for (const [key, role] of [
|
|
13064
|
+
["cellDefaults", "tableCell"],
|
|
13065
|
+
["headerCellDefaults", "tableHeader"]
|
|
13066
|
+
]) {
|
|
13067
|
+
const size = sizeOf(props[key]);
|
|
13068
|
+
if (size === void 0) continue;
|
|
13069
|
+
facts.push({
|
|
13070
|
+
id: `docx:text-size:${path4}:${role}`,
|
|
13071
|
+
kind: "docx/text-size",
|
|
13072
|
+
path: path4,
|
|
13073
|
+
role,
|
|
13074
|
+
fontSizePt: size,
|
|
13075
|
+
authored: false
|
|
13076
|
+
});
|
|
13077
|
+
}
|
|
13078
|
+
return facts;
|
|
13079
|
+
}
|
|
12820
13080
|
function prepareDocxQualityDocument(document, options = {}) {
|
|
12821
13081
|
const themed = options.context ?? resolveThemeContext(normalizeDocument(document)[0], {
|
|
12822
13082
|
customThemes: options.customThemes,
|
|
@@ -12881,11 +13141,48 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
12881
13141
|
let inherited = {};
|
|
12882
13142
|
topLevel.forEach((node, index) => {
|
|
12883
13143
|
if (node?.name !== "section") return;
|
|
12884
|
-
const props =
|
|
13144
|
+
const props = asRecord2(node.props) ?? {};
|
|
12885
13145
|
const part = (kind) => props[kind] === "linkToPrevious" ? inherited[kind] : props[kind];
|
|
12886
13146
|
const header = part("header");
|
|
12887
13147
|
const footer = part("footer");
|
|
12888
13148
|
inherited = { header, footer };
|
|
13149
|
+
for (const kind of ["header", "footer"]) {
|
|
13150
|
+
if (!Array.isArray(props[kind])) continue;
|
|
13151
|
+
const drawnByBlock = !Array.isArray(
|
|
13152
|
+
asRecord2(
|
|
13153
|
+
asRecord2(
|
|
13154
|
+
(Array.isArray(themed.document.children) ? themed.document.children : [])[index]
|
|
13155
|
+
)?.props
|
|
13156
|
+
)?.[kind]
|
|
13157
|
+
);
|
|
13158
|
+
const part2 = props[kind];
|
|
13159
|
+
const visitChrome = (child, childPath) => {
|
|
13160
|
+
if (child.name !== "paragraph" && child.name !== "heading") return;
|
|
13161
|
+
const childProps = asRecord2(child.props) ?? {};
|
|
13162
|
+
if (typeof childProps.text !== "string" || childProps.text.trim() === "")
|
|
13163
|
+
return;
|
|
13164
|
+
const fact = textSizeFact(
|
|
13165
|
+
child,
|
|
13166
|
+
childProps,
|
|
13167
|
+
childPath,
|
|
13168
|
+
typography,
|
|
13169
|
+
kind
|
|
13170
|
+
);
|
|
13171
|
+
if (fact)
|
|
13172
|
+
addFact({
|
|
13173
|
+
...fact,
|
|
13174
|
+
generated: drawnByBlock || authoredPath(childPath) !== childPath
|
|
13175
|
+
});
|
|
13176
|
+
};
|
|
13177
|
+
part2.forEach(
|
|
13178
|
+
(child, childIndex) => walkActive(
|
|
13179
|
+
child,
|
|
13180
|
+
`/children/${index}/props/${kind}/${childIndex}`,
|
|
13181
|
+
basePage,
|
|
13182
|
+
visitChrome
|
|
13183
|
+
)
|
|
13184
|
+
);
|
|
13185
|
+
}
|
|
12889
13186
|
addFact({
|
|
12890
13187
|
id: `docx:section-chrome:${index}`,
|
|
12891
13188
|
kind: "docx/section-chrome",
|
|
@@ -12918,13 +13215,35 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
12918
13215
|
const paletteTokens = resolved.theme.palette?.chart?.map(
|
|
12919
13216
|
(value) => `#${resolveDesignColor2(value, visualColors)}`
|
|
12920
13217
|
) ?? SERIES_COLOR_TOKENS.filter((token) => paletteHexes[token] !== void 0);
|
|
12921
|
-
const authoredPropsAt = (pointer) =>
|
|
13218
|
+
const authoredPropsAt = (pointer) => asRecord2(asRecord2(nodeAtPointer(context.document, pointer))?.props);
|
|
13219
|
+
const roleSizesPt = {};
|
|
13220
|
+
for (const key of Object.keys(typography.styles)) {
|
|
13221
|
+
const size = effectiveFontSize(
|
|
13222
|
+
{ name: "paragraph" },
|
|
13223
|
+
{ themeStyle: key },
|
|
13224
|
+
typography
|
|
13225
|
+
);
|
|
13226
|
+
if (size) roleSizesPt[key] = size.fontSizePt;
|
|
13227
|
+
}
|
|
13228
|
+
const scale = resolved.theme.typography?.scale?.[designCanvas2("docx", resolved.theme.page?.size)];
|
|
13229
|
+
const typeScalePt = [
|
|
13230
|
+
.../* @__PURE__ */ new Set([
|
|
13231
|
+
...Object.values(roleSizesPt),
|
|
13232
|
+
...["heading", "body", "mono", "light"].flatMap((role) => {
|
|
13233
|
+
const size = resolveFontSize(resolved.theme, role);
|
|
13234
|
+
return size === void 0 ? [] : [size];
|
|
13235
|
+
}),
|
|
13236
|
+
...scale ? typeScaleSizes(scale) : []
|
|
13237
|
+
])
|
|
13238
|
+
].sort((a, b) => a - b);
|
|
12922
13239
|
addFact({
|
|
12923
13240
|
id: "docx:theme",
|
|
12924
13241
|
kind: "docx/theme",
|
|
12925
13242
|
path: "/props",
|
|
12926
13243
|
themeName: context.themeName,
|
|
12927
13244
|
paletteHexes,
|
|
13245
|
+
typeScalePt,
|
|
13246
|
+
roleSizesPt,
|
|
12928
13247
|
// `heading` and `body` only. A theme also names `mono` and `light`, but
|
|
12929
13248
|
// those paint nothing until a component asks for them — counting an
|
|
12930
13249
|
// unused `Courier New` against a document's family budget would flag a
|
|
@@ -12932,7 +13251,7 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
12932
13251
|
fontFamilies: [
|
|
12933
13252
|
...new Set(
|
|
12934
13253
|
["heading", "body"].flatMap((role) => {
|
|
12935
|
-
const family =
|
|
13254
|
+
const family = asRecord2(
|
|
12936
13255
|
resolved.theme.fonts?.[role]
|
|
12937
13256
|
)?.family;
|
|
12938
13257
|
return typeof family === "string" && family.trim() !== "" ? [family] : [];
|
|
@@ -12968,15 +13287,27 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
12968
13287
|
excerpt: occurrence.match.excerpt
|
|
12969
13288
|
});
|
|
12970
13289
|
});
|
|
13290
|
+
for (const entry of collectDocxTextInventory(resolved.children)) {
|
|
13291
|
+
addFact({ ...entry, generated: authoredPath(entry.path) !== entry.path });
|
|
13292
|
+
}
|
|
12971
13293
|
let previousVisitPath;
|
|
12972
13294
|
let lastFrame;
|
|
12973
13295
|
let flowIndex = 0;
|
|
12974
13296
|
const parentOf = (path4) => path4.slice(0, path4.lastIndexOf("/"));
|
|
12975
13297
|
const visit = (node, path4, page) => {
|
|
12976
|
-
const props =
|
|
13298
|
+
const props = asRecord2(node.props) ?? {};
|
|
12977
13299
|
if (node.name === "table") {
|
|
12978
13300
|
const fact = tableFact(props, path4, page.availableWidthTwips);
|
|
12979
13301
|
if (fact) addFact(fact);
|
|
13302
|
+
for (const size of tableTextSizeFacts(
|
|
13303
|
+
props,
|
|
13304
|
+
authoredPropsAt(path4),
|
|
13305
|
+
path4,
|
|
13306
|
+
typography,
|
|
13307
|
+
page
|
|
13308
|
+
)) {
|
|
13309
|
+
addFact({ ...size, generated: authoredPath(size.path) !== size.path });
|
|
13310
|
+
}
|
|
12980
13311
|
const design = tableDesignFact(
|
|
12981
13312
|
props,
|
|
12982
13313
|
path4,
|
|
@@ -12993,7 +13324,7 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
12993
13324
|
return {
|
|
12994
13325
|
...column,
|
|
12995
13326
|
path: authored,
|
|
12996
|
-
generated: table2 === void 0 ||
|
|
13327
|
+
generated: table2 === void 0 || asRecord2(nodeAtPointer(themed.document, table2))?.name !== "table"
|
|
12997
13328
|
};
|
|
12998
13329
|
})
|
|
12999
13330
|
});
|
|
@@ -13009,7 +13340,7 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
13009
13340
|
addFact({
|
|
13010
13341
|
...fact,
|
|
13011
13342
|
generated: !["chart", "highcharts"].includes(
|
|
13012
|
-
String(
|
|
13343
|
+
String(asRecord2(nodeAtPointer(themed.document, authored))?.name)
|
|
13013
13344
|
),
|
|
13014
13345
|
seriesColorsPath: authoredPath(fact.seriesColorsPath),
|
|
13015
13346
|
...fact.annotation && {
|
|
@@ -13023,7 +13354,7 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
13023
13354
|
}
|
|
13024
13355
|
}
|
|
13025
13356
|
if (node.name === "paragraph" || node.name === "text-box") {
|
|
13026
|
-
const floating =
|
|
13357
|
+
const floating = asRecord2(props.floating);
|
|
13027
13358
|
if (floating) {
|
|
13028
13359
|
const signature = frameSignature(floating);
|
|
13029
13360
|
const chainId = lastFrame !== void 0 && previousVisitPath === lastFrame.path && parentOf(lastFrame.path) === parentOf(path4) && lastFrame.signature === signature ? lastFrame.chainId : `docx:frame-chain:${path4}`;
|
|
@@ -13035,6 +13366,10 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
13035
13366
|
if (node.name === "paragraph" || node.name === "heading") {
|
|
13036
13367
|
const fact = lineBoxFact(node, props, path4, typography, context.document);
|
|
13037
13368
|
if (fact) addFact(fact);
|
|
13369
|
+
if (typeof props.text === "string" && props.text.trim() !== "") {
|
|
13370
|
+
const fact2 = textSizeFact(node, props, path4, typography);
|
|
13371
|
+
if (fact2) addFact({ ...fact2, generated: authoredPath(path4) !== path4 });
|
|
13372
|
+
}
|
|
13038
13373
|
}
|
|
13039
13374
|
if (node.name === "image" || node.name === "visual") {
|
|
13040
13375
|
for (const fact of svgTextFacts(props, path4)) addFact(fact);
|
|
@@ -13607,6 +13942,7 @@ function numberParameter(parameters, name, fallback) {
|
|
|
13607
13942
|
}
|
|
13608
13943
|
var docxTableWidthRule = {
|
|
13609
13944
|
id: "docx/table-width",
|
|
13945
|
+
description: "Explicit column widths that sum past the usable width of their section.",
|
|
13610
13946
|
code: QUALITY_CODES.TABLE_WIDTH_OVERFLOW,
|
|
13611
13947
|
category: "integrity",
|
|
13612
13948
|
defaultSeverity: "warning",
|
|
@@ -13654,6 +13990,7 @@ var docxTableWidthRule = {
|
|
|
13654
13990
|
};
|
|
13655
13991
|
var docxHeadingHierarchyRule = {
|
|
13656
13992
|
id: "docx/heading-hierarchy",
|
|
13993
|
+
description: "A heading that skips a level and breaks the outline.",
|
|
13657
13994
|
code: QUALITY_CODES.HEADING_SKIP,
|
|
13658
13995
|
category: "hierarchy",
|
|
13659
13996
|
defaultSeverity: "info",
|
|
@@ -13697,6 +14034,7 @@ function frameTextFacts(facts) {
|
|
|
13697
14034
|
}
|
|
13698
14035
|
var docxTextFitRule = {
|
|
13699
14036
|
id: "docx/text-fit",
|
|
14037
|
+
description: "A word too wide for its floating frame, or a frame whose wrapped text runs off the sheet.",
|
|
13700
14038
|
code: QUALITY_CODES.TEXT_OVERFLOW,
|
|
13701
14039
|
category: "integrity",
|
|
13702
14040
|
defaultSeverity: "warning",
|
|
@@ -13770,6 +14108,7 @@ var docxTextFitRule = {
|
|
|
13770
14108
|
var FRAME_COLLISION_MIN_WIDTH_TWIPS = 240;
|
|
13771
14109
|
var docxFrameCollisionRule = {
|
|
13772
14110
|
id: "docx/frame-collision",
|
|
14111
|
+
description: "Two page-anchored frames whose estimated text lands on the same region of a page.",
|
|
13773
14112
|
code: QUALITY_CODES.FRAME_COLLISION,
|
|
13774
14113
|
category: "integrity",
|
|
13775
14114
|
defaultSeverity: "warning",
|
|
@@ -13866,6 +14205,7 @@ var docxFrameCollisionRule = {
|
|
|
13866
14205
|
};
|
|
13867
14206
|
var docxSvgTextBoundsRule = {
|
|
13868
14207
|
id: "docx/svg-text-bounds",
|
|
14208
|
+
description: "A text baseline outside an inline SVG\u2019s viewBox, so the words are never painted.",
|
|
13869
14209
|
code: QUALITY_CODES.SVG_TEXT_CLIPPED,
|
|
13870
14210
|
category: "integrity",
|
|
13871
14211
|
defaultSeverity: "warning",
|
|
@@ -13901,6 +14241,7 @@ function tenths(value) {
|
|
|
13901
14241
|
}
|
|
13902
14242
|
var docxLineBoxRule = {
|
|
13903
14243
|
id: "docx/line-box",
|
|
14244
|
+
description: "An `exactly` line box shorter than the capitals it has to hold.",
|
|
13904
14245
|
code: QUALITY_CODES.LINE_BOX_COLLAPSE,
|
|
13905
14246
|
category: "legibility",
|
|
13906
14247
|
defaultSeverity: "warning",
|
|
@@ -13954,6 +14295,7 @@ var docxLineBoxRule = {
|
|
|
13954
14295
|
};
|
|
13955
14296
|
var docxPlaceholderRule = {
|
|
13956
14297
|
id: "docx/placeholder-text",
|
|
14298
|
+
description: "An unfilled scaffold slot, or leftover filler copy.",
|
|
13957
14299
|
code: QUALITY_CODES.PLACEHOLDER_TEXT,
|
|
13958
14300
|
category: "integrity",
|
|
13959
14301
|
defaultSeverity: "warning",
|
|
@@ -13972,6 +14314,7 @@ var docxPlaceholderRule = {
|
|
|
13972
14314
|
};
|
|
13973
14315
|
var docxSlotBudgetRule = {
|
|
13974
14316
|
id: "docx/slot-budget",
|
|
14317
|
+
description: "A block slot holding more words than its budget allows \u2014 a takeaway past the word count the block sets.",
|
|
13975
14318
|
code: QUALITY_CODES.SLOT_BUDGET,
|
|
13976
14319
|
category: "composition",
|
|
13977
14320
|
defaultSeverity: "warning",
|
|
@@ -13995,6 +14338,7 @@ var docxSlotBudgetRule = {
|
|
|
13995
14338
|
var DEFAULT_MAX_FONT_FAMILIES = 3;
|
|
13996
14339
|
var docxFontCountRule = {
|
|
13997
14340
|
id: "docx/font-count",
|
|
14341
|
+
description: "Distinct font families the document can paint.",
|
|
13998
14342
|
code: QUALITY_CODES.FONT_COUNT,
|
|
13999
14343
|
category: "brand",
|
|
14000
14344
|
defaultSeverity: "warning",
|
|
@@ -14031,6 +14375,7 @@ var docxFontCountRule = {
|
|
|
14031
14375
|
};
|
|
14032
14376
|
var docxPaletteRule = {
|
|
14033
14377
|
id: "docx/palette-adherence",
|
|
14378
|
+
description: "A literal colour the resolved theme does not define.",
|
|
14034
14379
|
code: QUALITY_CODES.OFF_PALETTE,
|
|
14035
14380
|
category: "brand",
|
|
14036
14381
|
defaultSeverity: "info",
|
|
@@ -14056,6 +14401,7 @@ var docxPaletteRule = {
|
|
|
14056
14401
|
var DEFAULT_MAX_TABLE_ROWS_PER_PAGE = 25;
|
|
14057
14402
|
var docxChartRule = {
|
|
14058
14403
|
id: "docx/chart-design",
|
|
14404
|
+
description: "What a chart claims about its numbers: the comparison, the palette, the unit and the caption.",
|
|
14059
14405
|
code: QUALITY_CODES.CHART_OVERLOADED,
|
|
14060
14406
|
category: "information-design",
|
|
14061
14407
|
defaultSeverity: "warning",
|
|
@@ -14097,6 +14443,7 @@ function seriesColorFix(fact) {
|
|
|
14097
14443
|
}
|
|
14098
14444
|
var docxTableDesignRule = {
|
|
14099
14445
|
id: "docx/table-design",
|
|
14446
|
+
description: "How a table lays its numbers out: alignment, rounding, rules and length.",
|
|
14100
14447
|
code: QUALITY_CODES.TABLE_NUMERIC_ALIGN,
|
|
14101
14448
|
category: "information-design",
|
|
14102
14449
|
defaultSeverity: "warning",
|
|
@@ -14154,6 +14501,7 @@ function stringListParameter(parameters, name) {
|
|
|
14154
14501
|
}
|
|
14155
14502
|
var docxRequiredChromeRule = {
|
|
14156
14503
|
id: "docx/required-chrome",
|
|
14504
|
+
description: "A block slot with a role a profile or policy requires \u2014 a takeaway, a source \u2014 left empty. Off until one names roles.",
|
|
14157
14505
|
code: QUALITY_CODES.CHROME_MISSING,
|
|
14158
14506
|
category: "consistency",
|
|
14159
14507
|
defaultSeverity: "warning",
|
|
@@ -14177,6 +14525,7 @@ var docxRequiredChromeRule = {
|
|
|
14177
14525
|
var SECTION_CHROME_PARTS = ["header", "footer", "pageNumber"];
|
|
14178
14526
|
var docxRunningHeadRule = {
|
|
14179
14527
|
id: "docx/running-head",
|
|
14528
|
+
description: "A body section without the running head a profile or policy expects. Off until one names parts.",
|
|
14180
14529
|
code: QUALITY_CODES.CHROME_MISSING,
|
|
14181
14530
|
category: "consistency",
|
|
14182
14531
|
defaultSeverity: "warning",
|
|
@@ -14209,6 +14558,170 @@ var docxRunningHeadRule = {
|
|
|
14209
14558
|
});
|
|
14210
14559
|
}
|
|
14211
14560
|
};
|
|
14561
|
+
var SIZE_TOLERANCE_PT = 0.25;
|
|
14562
|
+
function themeFact(facts) {
|
|
14563
|
+
return facts.find(
|
|
14564
|
+
(fact) => fact.kind === "docx/theme"
|
|
14565
|
+
);
|
|
14566
|
+
}
|
|
14567
|
+
function textSizeFacts(facts) {
|
|
14568
|
+
return facts.filter(
|
|
14569
|
+
(fact) => fact.kind === "docx/text-size"
|
|
14570
|
+
);
|
|
14571
|
+
}
|
|
14572
|
+
function roleDriftFacts(facts, theme) {
|
|
14573
|
+
const byRole = /* @__PURE__ */ new Map();
|
|
14574
|
+
for (const fact of textSizeFacts(facts)) {
|
|
14575
|
+
byRole.set(fact.role, [...byRole.get(fact.role) ?? [], fact]);
|
|
14576
|
+
}
|
|
14577
|
+
const drifting = /* @__PURE__ */ new Map();
|
|
14578
|
+
for (const [role, members] of byRole) {
|
|
14579
|
+
const sizes = [...new Set(members.map((fact) => fact.fontSizePt))].sort(
|
|
14580
|
+
(a, b) => a - b
|
|
14581
|
+
);
|
|
14582
|
+
if (sizes.length < 2) continue;
|
|
14583
|
+
const expected = theme?.roleSizesPt[role];
|
|
14584
|
+
if (expected === void 0) continue;
|
|
14585
|
+
for (const fact of members) {
|
|
14586
|
+
if (fact.sizePath !== void 0 && !fact.generated && Math.abs(fact.fontSizePt - expected) > SIZE_TOLERANCE_PT)
|
|
14587
|
+
drifting.set(fact, { expected, sizes });
|
|
14588
|
+
}
|
|
14589
|
+
}
|
|
14590
|
+
return drifting;
|
|
14591
|
+
}
|
|
14592
|
+
function nearestSize(size, scale) {
|
|
14593
|
+
let best;
|
|
14594
|
+
for (const candidate of scale) {
|
|
14595
|
+
if (best === void 0 || Math.abs(candidate - size) < Math.abs(best - size))
|
|
14596
|
+
best = candidate;
|
|
14597
|
+
}
|
|
14598
|
+
return best;
|
|
14599
|
+
}
|
|
14600
|
+
var docxTypeScaleRule = {
|
|
14601
|
+
id: "docx/type-scale",
|
|
14602
|
+
description: "An authored size the theme never paints: not a style, not a font role, not a step of its scale. Off until a profile or policy enables it.",
|
|
14603
|
+
code: QUALITY_CODES.TYPE_OFF_SCALE,
|
|
14604
|
+
category: "consistency",
|
|
14605
|
+
defaultSeverity: "warning",
|
|
14606
|
+
defaultCertainty: "deterministic",
|
|
14607
|
+
formats: ["docx"],
|
|
14608
|
+
defaultEnabled: false,
|
|
14609
|
+
evaluate: ({ facts }) => {
|
|
14610
|
+
const theme = themeFact(facts);
|
|
14611
|
+
const scale = theme?.typeScalePt ?? [];
|
|
14612
|
+
if (scale.length === 0) return [];
|
|
14613
|
+
const drifting = roleDriftFacts(facts, theme);
|
|
14614
|
+
const offScale = textSizeFacts(facts).filter(
|
|
14615
|
+
(fact) => fact.sizePath !== void 0 && !fact.generated && !drifting.has(fact) && !scale.some(
|
|
14616
|
+
(size) => Math.abs(size - fact.fontSizePt) <= SIZE_TOLERANCE_PT
|
|
14617
|
+
)
|
|
14618
|
+
);
|
|
14619
|
+
const groups = /* @__PURE__ */ new Map();
|
|
14620
|
+
for (const fact of offScale) {
|
|
14621
|
+
const key = `${fact.role}@${fact.fontSizePt}`;
|
|
14622
|
+
groups.set(key, [...groups.get(key) ?? [], fact]);
|
|
14623
|
+
}
|
|
14624
|
+
return [...groups.values()].map((members) => {
|
|
14625
|
+
const [first] = members;
|
|
14626
|
+
const nearest = nearestSize(first.fontSizePt, scale);
|
|
14627
|
+
const sizePaths = members.map((fact) => fact.sizePath);
|
|
14628
|
+
const count = members.length === 1 ? "" : ` (${members.length} places, patched together)`;
|
|
14629
|
+
return {
|
|
14630
|
+
path: sizePaths[0],
|
|
14631
|
+
...sizePaths.length > 1 && { relatedPaths: sizePaths.slice(1) },
|
|
14632
|
+
message: `${first.fontSizePt}pt is not a size the ${theme.themeName} theme paints; the nearest on its scale is ${nearest}pt${count}.`,
|
|
14633
|
+
suggestion: `Use ${nearest}pt, or drop the size and let the "${first.role}" style set it.`,
|
|
14634
|
+
context: { role: first.role, scale, paths: sizePaths },
|
|
14635
|
+
evidence: {
|
|
14636
|
+
actual: first.fontSizePt,
|
|
14637
|
+
expected: nearest,
|
|
14638
|
+
unit: "pt",
|
|
14639
|
+
values: { source: "theme" }
|
|
14640
|
+
},
|
|
14641
|
+
fixes: sizePaths.map((path4) => ({
|
|
14642
|
+
op: "replace",
|
|
14643
|
+
path: path4,
|
|
14644
|
+
value: nearest
|
|
14645
|
+
}))
|
|
14646
|
+
};
|
|
14647
|
+
});
|
|
14648
|
+
}
|
|
14649
|
+
};
|
|
14650
|
+
var docxSizeCountRule = {
|
|
14651
|
+
id: "docx/size-count",
|
|
14652
|
+
description: "More distinct text sizes than maximumSizes allows, blocks included. Off until a profile or policy enables it.",
|
|
14653
|
+
code: QUALITY_CODES.TYPE_SIZE_COUNT,
|
|
14654
|
+
category: "consistency",
|
|
14655
|
+
defaultSeverity: "warning",
|
|
14656
|
+
defaultCertainty: "deterministic",
|
|
14657
|
+
formats: ["docx"],
|
|
14658
|
+
defaultEnabled: false,
|
|
14659
|
+
defaultParameters: { maximumSizes: 8 },
|
|
14660
|
+
evaluate: ({ facts, configuration, profile }) => {
|
|
14661
|
+
const maximum = numberParameter(
|
|
14662
|
+
configuration.parameters,
|
|
14663
|
+
"maximumSizes",
|
|
14664
|
+
8
|
|
14665
|
+
);
|
|
14666
|
+
const firstPathBySize = /* @__PURE__ */ new Map();
|
|
14667
|
+
for (const fact of textSizeFacts(facts)) {
|
|
14668
|
+
const size = Math.round(fact.fontSizePt * 4) / 4;
|
|
14669
|
+
if (!firstPathBySize.has(size)) firstPathBySize.set(size, fact.path);
|
|
14670
|
+
}
|
|
14671
|
+
if (firstPathBySize.size <= maximum) return [];
|
|
14672
|
+
const sizes = [...firstPathBySize.keys()].sort((a, b) => a - b);
|
|
14673
|
+
return [
|
|
14674
|
+
{
|
|
14675
|
+
path: themeFact(facts)?.path ?? "/props",
|
|
14676
|
+
relatedPaths: sizes.map((size) => firstPathBySize.get(size)),
|
|
14677
|
+
message: `The document paints ${sizes.length} distinct text sizes (${sizes.join(", ")}pt); the ${profile?.id ?? "selected"} profile allows ${maximum}.`,
|
|
14678
|
+
suggestion: "Keep to the theme styles \u2014 title, headings, body, label, source \u2014 and drop the ad-hoc sizes.",
|
|
14679
|
+
context: { sizes, maximum },
|
|
14680
|
+
evidence: {
|
|
14681
|
+
actual: sizes.length,
|
|
14682
|
+
expected: maximum,
|
|
14683
|
+
values: { source: "profile" }
|
|
14684
|
+
}
|
|
14685
|
+
}
|
|
14686
|
+
];
|
|
14687
|
+
}
|
|
14688
|
+
};
|
|
14689
|
+
var docxRoleDriftRule = {
|
|
14690
|
+
id: "docx/role-drift",
|
|
14691
|
+
description: "One heading level, table role or paragraph style painted at two sizes; the theme size is the fix. Off until a profile or policy enables it.",
|
|
14692
|
+
code: QUALITY_CODES.TYPE_ROLE_DRIFT,
|
|
14693
|
+
category: "consistency",
|
|
14694
|
+
defaultSeverity: "warning",
|
|
14695
|
+
defaultCertainty: "deterministic",
|
|
14696
|
+
formats: ["docx"],
|
|
14697
|
+
defaultEnabled: false,
|
|
14698
|
+
evaluate: ({ facts }) => {
|
|
14699
|
+
const theme = themeFact(facts);
|
|
14700
|
+
const findings = [];
|
|
14701
|
+
for (const [fact, { expected, sizes }] of roleDriftFacts(facts, theme)) {
|
|
14702
|
+
const keeper = textSizeFacts(facts).find(
|
|
14703
|
+
(member) => member.role === fact.role && Math.abs(member.fontSizePt - expected) <= SIZE_TOLERANCE_PT
|
|
14704
|
+
);
|
|
14705
|
+
const others = sizes.filter((size) => size !== fact.fontSizePt);
|
|
14706
|
+
const sizePath = fact.sizePath;
|
|
14707
|
+
findings.push({
|
|
14708
|
+
path: sizePath,
|
|
14709
|
+
...keeper && { relatedPaths: [keeper.path] },
|
|
14710
|
+
message: `"${fact.role}" is painted at ${fact.fontSizePt}pt here and at ${others.join("pt, ")}pt elsewhere; the theme sets it at ${expected}pt.`,
|
|
14711
|
+
suggestion: `Drop the size so "${fact.role}" paints at the theme's ${expected}pt.`,
|
|
14712
|
+
context: { role: fact.role, sizes },
|
|
14713
|
+
evidence: {
|
|
14714
|
+
actual: fact.fontSizePt,
|
|
14715
|
+
expected,
|
|
14716
|
+
unit: "pt",
|
|
14717
|
+
values: { role: fact.role, source: "theme" }
|
|
14718
|
+
},
|
|
14719
|
+
fixes: [{ op: "replace", path: sizePath, value: expected }]
|
|
14720
|
+
});
|
|
14721
|
+
}
|
|
14722
|
+
return findings;
|
|
14723
|
+
}
|
|
14724
|
+
};
|
|
14212
14725
|
var DOCX_QUALITY_RULES = {
|
|
14213
14726
|
id: "docx/default",
|
|
14214
14727
|
rules: [
|
|
@@ -14225,14 +14738,17 @@ var DOCX_QUALITY_RULES = {
|
|
|
14225
14738
|
docxFontCountRule,
|
|
14226
14739
|
docxPaletteRule,
|
|
14227
14740
|
docxRequiredChromeRule,
|
|
14228
|
-
docxRunningHeadRule
|
|
14741
|
+
docxRunningHeadRule,
|
|
14742
|
+
docxTypeScaleRule,
|
|
14743
|
+
docxSizeCountRule,
|
|
14744
|
+
docxRoleDriftRule
|
|
14229
14745
|
]
|
|
14230
14746
|
};
|
|
14231
14747
|
var DOCX_QUALITY_PROFILES = {
|
|
14232
14748
|
"client-report": {
|
|
14233
14749
|
id: "client-report",
|
|
14234
14750
|
formats: ["docx"],
|
|
14235
|
-
description: "Client or public-administration report: a running head with page numbers on every section after the cover, a takeaway and a source wherever a block declares them, no heading skipped.",
|
|
14751
|
+
description: "Client or public-administration report: a running head with page numbers on every section after the cover, a takeaway and a source wherever a block declares them, no heading skipped, and every size on the theme scale with at most eight in play.",
|
|
14236
14752
|
rules: {
|
|
14237
14753
|
"docx/required-chrome": {
|
|
14238
14754
|
parameters: { required: ["takeaway", "source"] }
|
|
@@ -14243,7 +14759,10 @@ var DOCX_QUALITY_PROFILES = {
|
|
|
14243
14759
|
fromSection: 1
|
|
14244
14760
|
}
|
|
14245
14761
|
},
|
|
14246
|
-
"docx/heading-hierarchy": { severity: "warning" }
|
|
14762
|
+
"docx/heading-hierarchy": { severity: "warning" },
|
|
14763
|
+
"docx/type-scale": { enabled: true },
|
|
14764
|
+
"docx/size-count": { enabled: true, parameters: { maximumSizes: 8 } },
|
|
14765
|
+
"docx/role-drift": { enabled: true }
|
|
14247
14766
|
}
|
|
14248
14767
|
},
|
|
14249
14768
|
"executive-report": {
|
|
@@ -15975,6 +16494,7 @@ export {
|
|
|
15975
16494
|
analyzeDocxQuality,
|
|
15976
16495
|
blockSlotBudgets,
|
|
15977
16496
|
cleanComponentProps,
|
|
16497
|
+
collectDocxTextInventory,
|
|
15978
16498
|
consultingTheme,
|
|
15979
16499
|
createComponent,
|
|
15980
16500
|
createDocumentGenerator,
|