@json-to-office/core-docx 3.3.0 → 4.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.js +353 -4
- package/dist/index.js.map +1 -1
- package/dist/plugin/example/index.js +166 -1
- package/dist/plugin/example/index.js.map +1 -1
- package/dist/quality/facts.d.ts +26 -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/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
|
|
@@ -12817,6 +12823,93 @@ function walkActive(node, path4, page, visit) {
|
|
|
12817
12823
|
(child, index) => walkActive(child, `${path4}/children/${index}`, page, visit)
|
|
12818
12824
|
);
|
|
12819
12825
|
}
|
|
12826
|
+
function textSizeFact(node, props, path4, typography, role) {
|
|
12827
|
+
const size = effectiveFontSize(node, props, typography);
|
|
12828
|
+
if (size === void 0) return void 0;
|
|
12829
|
+
return {
|
|
12830
|
+
id: `docx:text-size:${path4}`,
|
|
12831
|
+
kind: "docx/text-size",
|
|
12832
|
+
path: path4,
|
|
12833
|
+
role: role ?? styleKey(node, props),
|
|
12834
|
+
fontSizePt: size.fontSizePt,
|
|
12835
|
+
authored: size.authored,
|
|
12836
|
+
...size.authored && { sizePath: `${path4}/props/font/size` }
|
|
12837
|
+
};
|
|
12838
|
+
}
|
|
12839
|
+
function tableTextSizeFacts(props, authored, path4, typography, page) {
|
|
12840
|
+
const facts = [];
|
|
12841
|
+
const sizeOf = (holder) => finiteNumber(asRecord(asRecord(holder)?.font)?.size);
|
|
12842
|
+
const authoredSize = (holder, pointer, role) => {
|
|
12843
|
+
const size = sizeOf(holder);
|
|
12844
|
+
if (size === void 0) return;
|
|
12845
|
+
facts.push({
|
|
12846
|
+
id: `docx:text-size:${pointer}`,
|
|
12847
|
+
kind: "docx/text-size",
|
|
12848
|
+
path: pointer,
|
|
12849
|
+
role,
|
|
12850
|
+
fontSizePt: size,
|
|
12851
|
+
authored: true,
|
|
12852
|
+
sizePath: `${pointer}/font/size`
|
|
12853
|
+
});
|
|
12854
|
+
};
|
|
12855
|
+
const cellContent = (holder, pointer, role) => {
|
|
12856
|
+
const content = asRecord(holder)?.content;
|
|
12857
|
+
if (asRecord(content) === void 0) return;
|
|
12858
|
+
walkActive(content, `${pointer}/content`, page, (node, nodePath) => {
|
|
12859
|
+
if (node.name !== "paragraph" && node.name !== "heading") return;
|
|
12860
|
+
const nodeProps = asRecord(node.props) ?? {};
|
|
12861
|
+
if (typeof nodeProps.text !== "string" || nodeProps.text.trim() === "")
|
|
12862
|
+
return;
|
|
12863
|
+
const fact = textSizeFact(node, nodeProps, nodePath, typography, role);
|
|
12864
|
+
if (fact) facts.push(fact);
|
|
12865
|
+
});
|
|
12866
|
+
};
|
|
12867
|
+
if (authored) {
|
|
12868
|
+
authoredSize(
|
|
12869
|
+
authored.cellDefaults,
|
|
12870
|
+
`${path4}/props/cellDefaults`,
|
|
12871
|
+
"tableCell"
|
|
12872
|
+
);
|
|
12873
|
+
authoredSize(
|
|
12874
|
+
authored.headerCellDefaults,
|
|
12875
|
+
`${path4}/props/headerCellDefaults`,
|
|
12876
|
+
"tableHeader"
|
|
12877
|
+
);
|
|
12878
|
+
const columns = Array.isArray(authored.columns) ? authored.columns : [];
|
|
12879
|
+
columns.forEach((column, index) => {
|
|
12880
|
+
const record = asRecord(column) ?? {};
|
|
12881
|
+
const columnPath = `${path4}/props/columns/${index}`;
|
|
12882
|
+
authoredSize(
|
|
12883
|
+
record.cellDefaults,
|
|
12884
|
+
`${columnPath}/cellDefaults`,
|
|
12885
|
+
"tableCell"
|
|
12886
|
+
);
|
|
12887
|
+
authoredSize(record.header, `${columnPath}/header`, "tableHeader");
|
|
12888
|
+
cellContent(record.header, `${columnPath}/header`, "tableHeader");
|
|
12889
|
+
const cells = Array.isArray(record.cells) ? record.cells : [];
|
|
12890
|
+
cells.forEach((cell, cellIndex) => {
|
|
12891
|
+
authoredSize(cell, `${columnPath}/cells/${cellIndex}`, "tableCell");
|
|
12892
|
+
cellContent(cell, `${columnPath}/cells/${cellIndex}`, "tableCell");
|
|
12893
|
+
});
|
|
12894
|
+
});
|
|
12895
|
+
}
|
|
12896
|
+
for (const [key, role] of [
|
|
12897
|
+
["cellDefaults", "tableCell"],
|
|
12898
|
+
["headerCellDefaults", "tableHeader"]
|
|
12899
|
+
]) {
|
|
12900
|
+
const size = sizeOf(props[key]);
|
|
12901
|
+
if (size === void 0) continue;
|
|
12902
|
+
facts.push({
|
|
12903
|
+
id: `docx:text-size:${path4}:${role}`,
|
|
12904
|
+
kind: "docx/text-size",
|
|
12905
|
+
path: path4,
|
|
12906
|
+
role,
|
|
12907
|
+
fontSizePt: size,
|
|
12908
|
+
authored: false
|
|
12909
|
+
});
|
|
12910
|
+
}
|
|
12911
|
+
return facts;
|
|
12912
|
+
}
|
|
12820
12913
|
function prepareDocxQualityDocument(document, options = {}) {
|
|
12821
12914
|
const themed = options.context ?? resolveThemeContext(normalizeDocument(document)[0], {
|
|
12822
12915
|
customThemes: options.customThemes,
|
|
@@ -12886,6 +12979,43 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
12886
12979
|
const header = part("header");
|
|
12887
12980
|
const footer = part("footer");
|
|
12888
12981
|
inherited = { header, footer };
|
|
12982
|
+
for (const kind of ["header", "footer"]) {
|
|
12983
|
+
if (!Array.isArray(props[kind])) continue;
|
|
12984
|
+
const drawnByBlock = !Array.isArray(
|
|
12985
|
+
asRecord(
|
|
12986
|
+
asRecord(
|
|
12987
|
+
(Array.isArray(themed.document.children) ? themed.document.children : [])[index]
|
|
12988
|
+
)?.props
|
|
12989
|
+
)?.[kind]
|
|
12990
|
+
);
|
|
12991
|
+
const part2 = props[kind];
|
|
12992
|
+
const visitChrome = (child, childPath) => {
|
|
12993
|
+
if (child.name !== "paragraph" && child.name !== "heading") return;
|
|
12994
|
+
const childProps = asRecord(child.props) ?? {};
|
|
12995
|
+
if (typeof childProps.text !== "string" || childProps.text.trim() === "")
|
|
12996
|
+
return;
|
|
12997
|
+
const fact = textSizeFact(
|
|
12998
|
+
child,
|
|
12999
|
+
childProps,
|
|
13000
|
+
childPath,
|
|
13001
|
+
typography,
|
|
13002
|
+
kind
|
|
13003
|
+
);
|
|
13004
|
+
if (fact)
|
|
13005
|
+
addFact({
|
|
13006
|
+
...fact,
|
|
13007
|
+
generated: drawnByBlock || authoredPath(childPath) !== childPath
|
|
13008
|
+
});
|
|
13009
|
+
};
|
|
13010
|
+
part2.forEach(
|
|
13011
|
+
(child, childIndex) => walkActive(
|
|
13012
|
+
child,
|
|
13013
|
+
`/children/${index}/props/${kind}/${childIndex}`,
|
|
13014
|
+
basePage,
|
|
13015
|
+
visitChrome
|
|
13016
|
+
)
|
|
13017
|
+
);
|
|
13018
|
+
}
|
|
12889
13019
|
addFact({
|
|
12890
13020
|
id: `docx:section-chrome:${index}`,
|
|
12891
13021
|
kind: "docx/section-chrome",
|
|
@@ -12919,12 +13049,34 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
12919
13049
|
(value) => `#${resolveDesignColor2(value, visualColors)}`
|
|
12920
13050
|
) ?? SERIES_COLOR_TOKENS.filter((token) => paletteHexes[token] !== void 0);
|
|
12921
13051
|
const authoredPropsAt = (pointer) => asRecord(asRecord(nodeAtPointer(context.document, pointer))?.props);
|
|
13052
|
+
const roleSizesPt = {};
|
|
13053
|
+
for (const key of Object.keys(typography.styles)) {
|
|
13054
|
+
const size = effectiveFontSize(
|
|
13055
|
+
{ name: "paragraph" },
|
|
13056
|
+
{ themeStyle: key },
|
|
13057
|
+
typography
|
|
13058
|
+
);
|
|
13059
|
+
if (size) roleSizesPt[key] = size.fontSizePt;
|
|
13060
|
+
}
|
|
13061
|
+
const scale = resolved.theme.typography?.scale?.[designCanvas2("docx", resolved.theme.page?.size)];
|
|
13062
|
+
const typeScalePt = [
|
|
13063
|
+
.../* @__PURE__ */ new Set([
|
|
13064
|
+
...Object.values(roleSizesPt),
|
|
13065
|
+
...["heading", "body", "mono", "light"].flatMap((role) => {
|
|
13066
|
+
const size = resolveFontSize(resolved.theme, role);
|
|
13067
|
+
return size === void 0 ? [] : [size];
|
|
13068
|
+
}),
|
|
13069
|
+
...scale ? typeScaleSizes(scale) : []
|
|
13070
|
+
])
|
|
13071
|
+
].sort((a, b) => a - b);
|
|
12922
13072
|
addFact({
|
|
12923
13073
|
id: "docx:theme",
|
|
12924
13074
|
kind: "docx/theme",
|
|
12925
13075
|
path: "/props",
|
|
12926
13076
|
themeName: context.themeName,
|
|
12927
13077
|
paletteHexes,
|
|
13078
|
+
typeScalePt,
|
|
13079
|
+
roleSizesPt,
|
|
12928
13080
|
// `heading` and `body` only. A theme also names `mono` and `light`, but
|
|
12929
13081
|
// those paint nothing until a component asks for them — counting an
|
|
12930
13082
|
// unused `Courier New` against a document's family budget would flag a
|
|
@@ -12977,6 +13129,15 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
12977
13129
|
if (node.name === "table") {
|
|
12978
13130
|
const fact = tableFact(props, path4, page.availableWidthTwips);
|
|
12979
13131
|
if (fact) addFact(fact);
|
|
13132
|
+
for (const size of tableTextSizeFacts(
|
|
13133
|
+
props,
|
|
13134
|
+
authoredPropsAt(path4),
|
|
13135
|
+
path4,
|
|
13136
|
+
typography,
|
|
13137
|
+
page
|
|
13138
|
+
)) {
|
|
13139
|
+
addFact({ ...size, generated: authoredPath(size.path) !== size.path });
|
|
13140
|
+
}
|
|
12980
13141
|
const design = tableDesignFact(
|
|
12981
13142
|
props,
|
|
12982
13143
|
path4,
|
|
@@ -13035,6 +13196,10 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
13035
13196
|
if (node.name === "paragraph" || node.name === "heading") {
|
|
13036
13197
|
const fact = lineBoxFact(node, props, path4, typography, context.document);
|
|
13037
13198
|
if (fact) addFact(fact);
|
|
13199
|
+
if (typeof props.text === "string" && props.text.trim() !== "") {
|
|
13200
|
+
const fact2 = textSizeFact(node, props, path4, typography);
|
|
13201
|
+
if (fact2) addFact({ ...fact2, generated: authoredPath(path4) !== path4 });
|
|
13202
|
+
}
|
|
13038
13203
|
}
|
|
13039
13204
|
if (node.name === "image" || node.name === "visual") {
|
|
13040
13205
|
for (const fact of svgTextFacts(props, path4)) addFact(fact);
|
|
@@ -13607,6 +13772,7 @@ function numberParameter(parameters, name, fallback) {
|
|
|
13607
13772
|
}
|
|
13608
13773
|
var docxTableWidthRule = {
|
|
13609
13774
|
id: "docx/table-width",
|
|
13775
|
+
description: "Explicit column widths that sum past the usable width of their section.",
|
|
13610
13776
|
code: QUALITY_CODES.TABLE_WIDTH_OVERFLOW,
|
|
13611
13777
|
category: "integrity",
|
|
13612
13778
|
defaultSeverity: "warning",
|
|
@@ -13654,6 +13820,7 @@ var docxTableWidthRule = {
|
|
|
13654
13820
|
};
|
|
13655
13821
|
var docxHeadingHierarchyRule = {
|
|
13656
13822
|
id: "docx/heading-hierarchy",
|
|
13823
|
+
description: "A heading that skips a level and breaks the outline.",
|
|
13657
13824
|
code: QUALITY_CODES.HEADING_SKIP,
|
|
13658
13825
|
category: "hierarchy",
|
|
13659
13826
|
defaultSeverity: "info",
|
|
@@ -13697,6 +13864,7 @@ function frameTextFacts(facts) {
|
|
|
13697
13864
|
}
|
|
13698
13865
|
var docxTextFitRule = {
|
|
13699
13866
|
id: "docx/text-fit",
|
|
13867
|
+
description: "A word too wide for its floating frame, or a frame whose wrapped text runs off the sheet.",
|
|
13700
13868
|
code: QUALITY_CODES.TEXT_OVERFLOW,
|
|
13701
13869
|
category: "integrity",
|
|
13702
13870
|
defaultSeverity: "warning",
|
|
@@ -13770,6 +13938,7 @@ var docxTextFitRule = {
|
|
|
13770
13938
|
var FRAME_COLLISION_MIN_WIDTH_TWIPS = 240;
|
|
13771
13939
|
var docxFrameCollisionRule = {
|
|
13772
13940
|
id: "docx/frame-collision",
|
|
13941
|
+
description: "Two page-anchored frames whose estimated text lands on the same region of a page.",
|
|
13773
13942
|
code: QUALITY_CODES.FRAME_COLLISION,
|
|
13774
13943
|
category: "integrity",
|
|
13775
13944
|
defaultSeverity: "warning",
|
|
@@ -13866,6 +14035,7 @@ var docxFrameCollisionRule = {
|
|
|
13866
14035
|
};
|
|
13867
14036
|
var docxSvgTextBoundsRule = {
|
|
13868
14037
|
id: "docx/svg-text-bounds",
|
|
14038
|
+
description: "A text baseline outside an inline SVG\u2019s viewBox, so the words are never painted.",
|
|
13869
14039
|
code: QUALITY_CODES.SVG_TEXT_CLIPPED,
|
|
13870
14040
|
category: "integrity",
|
|
13871
14041
|
defaultSeverity: "warning",
|
|
@@ -13901,6 +14071,7 @@ function tenths(value) {
|
|
|
13901
14071
|
}
|
|
13902
14072
|
var docxLineBoxRule = {
|
|
13903
14073
|
id: "docx/line-box",
|
|
14074
|
+
description: "An `exactly` line box shorter than the capitals it has to hold.",
|
|
13904
14075
|
code: QUALITY_CODES.LINE_BOX_COLLAPSE,
|
|
13905
14076
|
category: "legibility",
|
|
13906
14077
|
defaultSeverity: "warning",
|
|
@@ -13954,6 +14125,7 @@ var docxLineBoxRule = {
|
|
|
13954
14125
|
};
|
|
13955
14126
|
var docxPlaceholderRule = {
|
|
13956
14127
|
id: "docx/placeholder-text",
|
|
14128
|
+
description: "An unfilled scaffold slot, or leftover filler copy.",
|
|
13957
14129
|
code: QUALITY_CODES.PLACEHOLDER_TEXT,
|
|
13958
14130
|
category: "integrity",
|
|
13959
14131
|
defaultSeverity: "warning",
|
|
@@ -13972,6 +14144,7 @@ var docxPlaceholderRule = {
|
|
|
13972
14144
|
};
|
|
13973
14145
|
var docxSlotBudgetRule = {
|
|
13974
14146
|
id: "docx/slot-budget",
|
|
14147
|
+
description: "A block slot holding more words than its budget allows \u2014 a takeaway past the word count the block sets.",
|
|
13975
14148
|
code: QUALITY_CODES.SLOT_BUDGET,
|
|
13976
14149
|
category: "composition",
|
|
13977
14150
|
defaultSeverity: "warning",
|
|
@@ -13995,6 +14168,7 @@ var docxSlotBudgetRule = {
|
|
|
13995
14168
|
var DEFAULT_MAX_FONT_FAMILIES = 3;
|
|
13996
14169
|
var docxFontCountRule = {
|
|
13997
14170
|
id: "docx/font-count",
|
|
14171
|
+
description: "Distinct font families the document can paint.",
|
|
13998
14172
|
code: QUALITY_CODES.FONT_COUNT,
|
|
13999
14173
|
category: "brand",
|
|
14000
14174
|
defaultSeverity: "warning",
|
|
@@ -14031,6 +14205,7 @@ var docxFontCountRule = {
|
|
|
14031
14205
|
};
|
|
14032
14206
|
var docxPaletteRule = {
|
|
14033
14207
|
id: "docx/palette-adherence",
|
|
14208
|
+
description: "A literal colour the resolved theme does not define.",
|
|
14034
14209
|
code: QUALITY_CODES.OFF_PALETTE,
|
|
14035
14210
|
category: "brand",
|
|
14036
14211
|
defaultSeverity: "info",
|
|
@@ -14056,6 +14231,7 @@ var docxPaletteRule = {
|
|
|
14056
14231
|
var DEFAULT_MAX_TABLE_ROWS_PER_PAGE = 25;
|
|
14057
14232
|
var docxChartRule = {
|
|
14058
14233
|
id: "docx/chart-design",
|
|
14234
|
+
description: "What a chart claims about its numbers: the comparison, the palette, the unit and the caption.",
|
|
14059
14235
|
code: QUALITY_CODES.CHART_OVERLOADED,
|
|
14060
14236
|
category: "information-design",
|
|
14061
14237
|
defaultSeverity: "warning",
|
|
@@ -14097,6 +14273,7 @@ function seriesColorFix(fact) {
|
|
|
14097
14273
|
}
|
|
14098
14274
|
var docxTableDesignRule = {
|
|
14099
14275
|
id: "docx/table-design",
|
|
14276
|
+
description: "How a table lays its numbers out: alignment, rounding, rules and length.",
|
|
14100
14277
|
code: QUALITY_CODES.TABLE_NUMERIC_ALIGN,
|
|
14101
14278
|
category: "information-design",
|
|
14102
14279
|
defaultSeverity: "warning",
|
|
@@ -14154,6 +14331,7 @@ function stringListParameter(parameters, name) {
|
|
|
14154
14331
|
}
|
|
14155
14332
|
var docxRequiredChromeRule = {
|
|
14156
14333
|
id: "docx/required-chrome",
|
|
14334
|
+
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
14335
|
code: QUALITY_CODES.CHROME_MISSING,
|
|
14158
14336
|
category: "consistency",
|
|
14159
14337
|
defaultSeverity: "warning",
|
|
@@ -14177,6 +14355,7 @@ var docxRequiredChromeRule = {
|
|
|
14177
14355
|
var SECTION_CHROME_PARTS = ["header", "footer", "pageNumber"];
|
|
14178
14356
|
var docxRunningHeadRule = {
|
|
14179
14357
|
id: "docx/running-head",
|
|
14358
|
+
description: "A body section without the running head a profile or policy expects. Off until one names parts.",
|
|
14180
14359
|
code: QUALITY_CODES.CHROME_MISSING,
|
|
14181
14360
|
category: "consistency",
|
|
14182
14361
|
defaultSeverity: "warning",
|
|
@@ -14209,6 +14388,170 @@ var docxRunningHeadRule = {
|
|
|
14209
14388
|
});
|
|
14210
14389
|
}
|
|
14211
14390
|
};
|
|
14391
|
+
var SIZE_TOLERANCE_PT = 0.25;
|
|
14392
|
+
function themeFact(facts) {
|
|
14393
|
+
return facts.find(
|
|
14394
|
+
(fact) => fact.kind === "docx/theme"
|
|
14395
|
+
);
|
|
14396
|
+
}
|
|
14397
|
+
function textSizeFacts(facts) {
|
|
14398
|
+
return facts.filter(
|
|
14399
|
+
(fact) => fact.kind === "docx/text-size"
|
|
14400
|
+
);
|
|
14401
|
+
}
|
|
14402
|
+
function roleDriftFacts(facts, theme) {
|
|
14403
|
+
const byRole = /* @__PURE__ */ new Map();
|
|
14404
|
+
for (const fact of textSizeFacts(facts)) {
|
|
14405
|
+
byRole.set(fact.role, [...byRole.get(fact.role) ?? [], fact]);
|
|
14406
|
+
}
|
|
14407
|
+
const drifting = /* @__PURE__ */ new Map();
|
|
14408
|
+
for (const [role, members] of byRole) {
|
|
14409
|
+
const sizes = [...new Set(members.map((fact) => fact.fontSizePt))].sort(
|
|
14410
|
+
(a, b) => a - b
|
|
14411
|
+
);
|
|
14412
|
+
if (sizes.length < 2) continue;
|
|
14413
|
+
const expected = theme?.roleSizesPt[role];
|
|
14414
|
+
if (expected === void 0) continue;
|
|
14415
|
+
for (const fact of members) {
|
|
14416
|
+
if (fact.sizePath !== void 0 && !fact.generated && Math.abs(fact.fontSizePt - expected) > SIZE_TOLERANCE_PT)
|
|
14417
|
+
drifting.set(fact, { expected, sizes });
|
|
14418
|
+
}
|
|
14419
|
+
}
|
|
14420
|
+
return drifting;
|
|
14421
|
+
}
|
|
14422
|
+
function nearestSize(size, scale) {
|
|
14423
|
+
let best;
|
|
14424
|
+
for (const candidate of scale) {
|
|
14425
|
+
if (best === void 0 || Math.abs(candidate - size) < Math.abs(best - size))
|
|
14426
|
+
best = candidate;
|
|
14427
|
+
}
|
|
14428
|
+
return best;
|
|
14429
|
+
}
|
|
14430
|
+
var docxTypeScaleRule = {
|
|
14431
|
+
id: "docx/type-scale",
|
|
14432
|
+
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.",
|
|
14433
|
+
code: QUALITY_CODES.TYPE_OFF_SCALE,
|
|
14434
|
+
category: "consistency",
|
|
14435
|
+
defaultSeverity: "warning",
|
|
14436
|
+
defaultCertainty: "deterministic",
|
|
14437
|
+
formats: ["docx"],
|
|
14438
|
+
defaultEnabled: false,
|
|
14439
|
+
evaluate: ({ facts }) => {
|
|
14440
|
+
const theme = themeFact(facts);
|
|
14441
|
+
const scale = theme?.typeScalePt ?? [];
|
|
14442
|
+
if (scale.length === 0) return [];
|
|
14443
|
+
const drifting = roleDriftFacts(facts, theme);
|
|
14444
|
+
const offScale = textSizeFacts(facts).filter(
|
|
14445
|
+
(fact) => fact.sizePath !== void 0 && !fact.generated && !drifting.has(fact) && !scale.some(
|
|
14446
|
+
(size) => Math.abs(size - fact.fontSizePt) <= SIZE_TOLERANCE_PT
|
|
14447
|
+
)
|
|
14448
|
+
);
|
|
14449
|
+
const groups = /* @__PURE__ */ new Map();
|
|
14450
|
+
for (const fact of offScale) {
|
|
14451
|
+
const key = `${fact.role}@${fact.fontSizePt}`;
|
|
14452
|
+
groups.set(key, [...groups.get(key) ?? [], fact]);
|
|
14453
|
+
}
|
|
14454
|
+
return [...groups.values()].map((members) => {
|
|
14455
|
+
const [first] = members;
|
|
14456
|
+
const nearest = nearestSize(first.fontSizePt, scale);
|
|
14457
|
+
const sizePaths = members.map((fact) => fact.sizePath);
|
|
14458
|
+
const count = members.length === 1 ? "" : ` (${members.length} places, patched together)`;
|
|
14459
|
+
return {
|
|
14460
|
+
path: sizePaths[0],
|
|
14461
|
+
...sizePaths.length > 1 && { relatedPaths: sizePaths.slice(1) },
|
|
14462
|
+
message: `${first.fontSizePt}pt is not a size the ${theme.themeName} theme paints; the nearest on its scale is ${nearest}pt${count}.`,
|
|
14463
|
+
suggestion: `Use ${nearest}pt, or drop the size and let the "${first.role}" style set it.`,
|
|
14464
|
+
context: { role: first.role, scale, paths: sizePaths },
|
|
14465
|
+
evidence: {
|
|
14466
|
+
actual: first.fontSizePt,
|
|
14467
|
+
expected: nearest,
|
|
14468
|
+
unit: "pt",
|
|
14469
|
+
values: { source: "theme" }
|
|
14470
|
+
},
|
|
14471
|
+
fixes: sizePaths.map((path4) => ({
|
|
14472
|
+
op: "replace",
|
|
14473
|
+
path: path4,
|
|
14474
|
+
value: nearest
|
|
14475
|
+
}))
|
|
14476
|
+
};
|
|
14477
|
+
});
|
|
14478
|
+
}
|
|
14479
|
+
};
|
|
14480
|
+
var docxSizeCountRule = {
|
|
14481
|
+
id: "docx/size-count",
|
|
14482
|
+
description: "More distinct text sizes than maximumSizes allows, blocks included. Off until a profile or policy enables it.",
|
|
14483
|
+
code: QUALITY_CODES.TYPE_SIZE_COUNT,
|
|
14484
|
+
category: "consistency",
|
|
14485
|
+
defaultSeverity: "warning",
|
|
14486
|
+
defaultCertainty: "deterministic",
|
|
14487
|
+
formats: ["docx"],
|
|
14488
|
+
defaultEnabled: false,
|
|
14489
|
+
defaultParameters: { maximumSizes: 8 },
|
|
14490
|
+
evaluate: ({ facts, configuration, profile }) => {
|
|
14491
|
+
const maximum = numberParameter(
|
|
14492
|
+
configuration.parameters,
|
|
14493
|
+
"maximumSizes",
|
|
14494
|
+
8
|
|
14495
|
+
);
|
|
14496
|
+
const firstPathBySize = /* @__PURE__ */ new Map();
|
|
14497
|
+
for (const fact of textSizeFacts(facts)) {
|
|
14498
|
+
const size = Math.round(fact.fontSizePt * 4) / 4;
|
|
14499
|
+
if (!firstPathBySize.has(size)) firstPathBySize.set(size, fact.path);
|
|
14500
|
+
}
|
|
14501
|
+
if (firstPathBySize.size <= maximum) return [];
|
|
14502
|
+
const sizes = [...firstPathBySize.keys()].sort((a, b) => a - b);
|
|
14503
|
+
return [
|
|
14504
|
+
{
|
|
14505
|
+
path: themeFact(facts)?.path ?? "/props",
|
|
14506
|
+
relatedPaths: sizes.map((size) => firstPathBySize.get(size)),
|
|
14507
|
+
message: `The document paints ${sizes.length} distinct text sizes (${sizes.join(", ")}pt); the ${profile?.id ?? "selected"} profile allows ${maximum}.`,
|
|
14508
|
+
suggestion: "Keep to the theme styles \u2014 title, headings, body, label, source \u2014 and drop the ad-hoc sizes.",
|
|
14509
|
+
context: { sizes, maximum },
|
|
14510
|
+
evidence: {
|
|
14511
|
+
actual: sizes.length,
|
|
14512
|
+
expected: maximum,
|
|
14513
|
+
values: { source: "profile" }
|
|
14514
|
+
}
|
|
14515
|
+
}
|
|
14516
|
+
];
|
|
14517
|
+
}
|
|
14518
|
+
};
|
|
14519
|
+
var docxRoleDriftRule = {
|
|
14520
|
+
id: "docx/role-drift",
|
|
14521
|
+
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.",
|
|
14522
|
+
code: QUALITY_CODES.TYPE_ROLE_DRIFT,
|
|
14523
|
+
category: "consistency",
|
|
14524
|
+
defaultSeverity: "warning",
|
|
14525
|
+
defaultCertainty: "deterministic",
|
|
14526
|
+
formats: ["docx"],
|
|
14527
|
+
defaultEnabled: false,
|
|
14528
|
+
evaluate: ({ facts }) => {
|
|
14529
|
+
const theme = themeFact(facts);
|
|
14530
|
+
const findings = [];
|
|
14531
|
+
for (const [fact, { expected, sizes }] of roleDriftFacts(facts, theme)) {
|
|
14532
|
+
const keeper = textSizeFacts(facts).find(
|
|
14533
|
+
(member) => member.role === fact.role && Math.abs(member.fontSizePt - expected) <= SIZE_TOLERANCE_PT
|
|
14534
|
+
);
|
|
14535
|
+
const others = sizes.filter((size) => size !== fact.fontSizePt);
|
|
14536
|
+
const sizePath = fact.sizePath;
|
|
14537
|
+
findings.push({
|
|
14538
|
+
path: sizePath,
|
|
14539
|
+
...keeper && { relatedPaths: [keeper.path] },
|
|
14540
|
+
message: `"${fact.role}" is painted at ${fact.fontSizePt}pt here and at ${others.join("pt, ")}pt elsewhere; the theme sets it at ${expected}pt.`,
|
|
14541
|
+
suggestion: `Drop the size so "${fact.role}" paints at the theme's ${expected}pt.`,
|
|
14542
|
+
context: { role: fact.role, sizes },
|
|
14543
|
+
evidence: {
|
|
14544
|
+
actual: fact.fontSizePt,
|
|
14545
|
+
expected,
|
|
14546
|
+
unit: "pt",
|
|
14547
|
+
values: { role: fact.role, source: "theme" }
|
|
14548
|
+
},
|
|
14549
|
+
fixes: [{ op: "replace", path: sizePath, value: expected }]
|
|
14550
|
+
});
|
|
14551
|
+
}
|
|
14552
|
+
return findings;
|
|
14553
|
+
}
|
|
14554
|
+
};
|
|
14212
14555
|
var DOCX_QUALITY_RULES = {
|
|
14213
14556
|
id: "docx/default",
|
|
14214
14557
|
rules: [
|
|
@@ -14225,14 +14568,17 @@ var DOCX_QUALITY_RULES = {
|
|
|
14225
14568
|
docxFontCountRule,
|
|
14226
14569
|
docxPaletteRule,
|
|
14227
14570
|
docxRequiredChromeRule,
|
|
14228
|
-
docxRunningHeadRule
|
|
14571
|
+
docxRunningHeadRule,
|
|
14572
|
+
docxTypeScaleRule,
|
|
14573
|
+
docxSizeCountRule,
|
|
14574
|
+
docxRoleDriftRule
|
|
14229
14575
|
]
|
|
14230
14576
|
};
|
|
14231
14577
|
var DOCX_QUALITY_PROFILES = {
|
|
14232
14578
|
"client-report": {
|
|
14233
14579
|
id: "client-report",
|
|
14234
14580
|
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.",
|
|
14581
|
+
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
14582
|
rules: {
|
|
14237
14583
|
"docx/required-chrome": {
|
|
14238
14584
|
parameters: { required: ["takeaway", "source"] }
|
|
@@ -14243,7 +14589,10 @@ var DOCX_QUALITY_PROFILES = {
|
|
|
14243
14589
|
fromSection: 1
|
|
14244
14590
|
}
|
|
14245
14591
|
},
|
|
14246
|
-
"docx/heading-hierarchy": { severity: "warning" }
|
|
14592
|
+
"docx/heading-hierarchy": { severity: "warning" },
|
|
14593
|
+
"docx/type-scale": { enabled: true },
|
|
14594
|
+
"docx/size-count": { enabled: true, parameters: { maximumSizes: 8 } },
|
|
14595
|
+
"docx/role-drift": { enabled: true }
|
|
14247
14596
|
}
|
|
14248
14597
|
},
|
|
14249
14598
|
"executive-report": {
|