@json-to-office/core-docx 6.1.0 → 6.4.1
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 +514 -238
- package/dist/index.js.map +1 -1
- package/dist/plugin/example/index.js +211 -2
- package/dist/plugin/example/index.js.map +1 -1
- package/dist/quality/facts.d.ts +55 -1
- package/dist/quality/facts.d.ts.map +1 -1
- package/dist/quality/rules.d.ts +73 -12
- package/dist/quality/rules.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -12884,6 +12884,7 @@ async function expandBlocksWithPlugins(document, theme, plugins, render, preserv
|
|
|
12884
12884
|
init_styleHelpers();
|
|
12885
12885
|
init_defaults();
|
|
12886
12886
|
init_widthUtils();
|
|
12887
|
+
import probe2 from "probe-image-size";
|
|
12887
12888
|
|
|
12888
12889
|
// src/quality/text-inventory.ts
|
|
12889
12890
|
function tocDepth(depth) {
|
|
@@ -13134,6 +13135,116 @@ function collectDocxTextInventory(children, basePath = "/children") {
|
|
|
13134
13135
|
return entries;
|
|
13135
13136
|
}
|
|
13136
13137
|
|
|
13138
|
+
// src/quality/text-metrics.ts
|
|
13139
|
+
var DEFAULT_ADVANCE = 556;
|
|
13140
|
+
var ADVANCE = {
|
|
13141
|
+
" ": 278,
|
|
13142
|
+
A: 667,
|
|
13143
|
+
B: 667,
|
|
13144
|
+
C: 722,
|
|
13145
|
+
D: 722,
|
|
13146
|
+
E: 667,
|
|
13147
|
+
F: 611,
|
|
13148
|
+
G: 778,
|
|
13149
|
+
H: 722,
|
|
13150
|
+
I: 278,
|
|
13151
|
+
J: 500,
|
|
13152
|
+
K: 667,
|
|
13153
|
+
L: 556,
|
|
13154
|
+
M: 833,
|
|
13155
|
+
N: 722,
|
|
13156
|
+
O: 778,
|
|
13157
|
+
P: 667,
|
|
13158
|
+
Q: 778,
|
|
13159
|
+
R: 722,
|
|
13160
|
+
S: 667,
|
|
13161
|
+
T: 611,
|
|
13162
|
+
U: 722,
|
|
13163
|
+
V: 667,
|
|
13164
|
+
W: 944,
|
|
13165
|
+
X: 667,
|
|
13166
|
+
Y: 667,
|
|
13167
|
+
Z: 611,
|
|
13168
|
+
a: 556,
|
|
13169
|
+
b: 556,
|
|
13170
|
+
c: 500,
|
|
13171
|
+
d: 556,
|
|
13172
|
+
e: 556,
|
|
13173
|
+
f: 278,
|
|
13174
|
+
g: 556,
|
|
13175
|
+
h: 556,
|
|
13176
|
+
i: 222,
|
|
13177
|
+
j: 222,
|
|
13178
|
+
k: 500,
|
|
13179
|
+
l: 222,
|
|
13180
|
+
m: 833,
|
|
13181
|
+
n: 556,
|
|
13182
|
+
o: 556,
|
|
13183
|
+
p: 556,
|
|
13184
|
+
q: 556,
|
|
13185
|
+
r: 333,
|
|
13186
|
+
s: 500,
|
|
13187
|
+
t: 278,
|
|
13188
|
+
u: 556,
|
|
13189
|
+
v: 500,
|
|
13190
|
+
w: 722,
|
|
13191
|
+
x: 500,
|
|
13192
|
+
y: 500,
|
|
13193
|
+
z: 500,
|
|
13194
|
+
".": 278,
|
|
13195
|
+
",": 278,
|
|
13196
|
+
":": 278,
|
|
13197
|
+
";": 278,
|
|
13198
|
+
"!": 278,
|
|
13199
|
+
"?": 556,
|
|
13200
|
+
"'": 191,
|
|
13201
|
+
'"': 355,
|
|
13202
|
+
"-": 333,
|
|
13203
|
+
"\u2013": 556,
|
|
13204
|
+
"\u2014": 1e3,
|
|
13205
|
+
"&": 667,
|
|
13206
|
+
"%": 889,
|
|
13207
|
+
"(": 333,
|
|
13208
|
+
")": 333,
|
|
13209
|
+
"/": 278,
|
|
13210
|
+
"@": 1015,
|
|
13211
|
+
"#": 556,
|
|
13212
|
+
"+": 584,
|
|
13213
|
+
"=": 584
|
|
13214
|
+
};
|
|
13215
|
+
function estimateTextWidthPt(text, fontSizePt, trackingPt = 0) {
|
|
13216
|
+
let units = 0;
|
|
13217
|
+
for (const character of text) {
|
|
13218
|
+
units += ADVANCE[character] ?? DEFAULT_ADVANCE;
|
|
13219
|
+
}
|
|
13220
|
+
const width = units / 1e3 * fontSizePt + text.length * trackingPt;
|
|
13221
|
+
return Math.max(0, width);
|
|
13222
|
+
}
|
|
13223
|
+
function estimateWrappedLines(text, widthPt, fontSizePt, trackingPt = 0) {
|
|
13224
|
+
if (widthPt <= 0) return 1;
|
|
13225
|
+
let lines = 0;
|
|
13226
|
+
for (const paragraph2 of text.split("\n")) {
|
|
13227
|
+
const words = paragraph2.split(/\s+/).filter(Boolean);
|
|
13228
|
+
if (words.length === 0) {
|
|
13229
|
+
lines += 1;
|
|
13230
|
+
continue;
|
|
13231
|
+
}
|
|
13232
|
+
let current = "";
|
|
13233
|
+
let used = 0;
|
|
13234
|
+
for (const word of words) {
|
|
13235
|
+
const candidate = current === "" ? word : `${current} ${word}`;
|
|
13236
|
+
if (estimateTextWidthPt(candidate, fontSizePt, trackingPt) <= widthPt) {
|
|
13237
|
+
current = candidate;
|
|
13238
|
+
} else {
|
|
13239
|
+
if (current !== "") used += 1;
|
|
13240
|
+
current = word;
|
|
13241
|
+
}
|
|
13242
|
+
}
|
|
13243
|
+
lines += used + (current === "" ? 0 : 1);
|
|
13244
|
+
}
|
|
13245
|
+
return Math.max(1, lines);
|
|
13246
|
+
}
|
|
13247
|
+
|
|
13137
13248
|
// src/quality/facts.ts
|
|
13138
13249
|
function asRecord2(value) {
|
|
13139
13250
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
@@ -13831,7 +13942,9 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
13831
13942
|
)
|
|
13832
13943
|
]
|
|
13833
13944
|
});
|
|
13834
|
-
|
|
13945
|
+
const content = { ...document, props: { ...asRecord2(document.props) } };
|
|
13946
|
+
delete content.props.themeOverrides;
|
|
13947
|
+
collectColorLiterals(content).forEach((literal, index) => {
|
|
13835
13948
|
addFact({
|
|
13836
13949
|
id: `docx:color:${index}:${literal.path}`,
|
|
13837
13950
|
kind: "docx/color",
|
|
@@ -13840,7 +13953,7 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
13840
13953
|
hex: literal.hex
|
|
13841
13954
|
});
|
|
13842
13955
|
});
|
|
13843
|
-
collectFontFamilies(
|
|
13956
|
+
collectFontFamilies(content).forEach((use, index) => {
|
|
13844
13957
|
addFact({
|
|
13845
13958
|
id: `docx:font:${index}:${use.path}`,
|
|
13846
13959
|
kind: "docx/font-family",
|
|
@@ -13945,14 +14058,29 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
13945
14058
|
}
|
|
13946
14059
|
if (node.name === "image" || node.name === "visual") {
|
|
13947
14060
|
for (const fact of svgTextFacts(props, path4)) addFact(fact);
|
|
14061
|
+
const drawn = drawnRatio(props);
|
|
14062
|
+
const natural = readableRatio(props);
|
|
14063
|
+
addFact({
|
|
14064
|
+
id: `docx:image:${path4}`,
|
|
14065
|
+
kind: "docx/image",
|
|
14066
|
+
path: path4,
|
|
14067
|
+
...typeof props.alt === "string" && props.alt.trim() !== "" && { alt: props.alt },
|
|
14068
|
+
captioned: isCaptioned(facts, authoredPath(path4)),
|
|
14069
|
+
...drawn !== void 0 && { drawnRatio: drawn },
|
|
14070
|
+
...natural !== void 0 && { naturalRatio: natural }
|
|
14071
|
+
});
|
|
13948
14072
|
}
|
|
13949
14073
|
if (node.name === "heading") {
|
|
13950
14074
|
const level = typeof props.level === "number" && Number.isFinite(props.level) ? props.level : 1;
|
|
14075
|
+
const styleKeepNext = asRecord2(
|
|
14076
|
+
typography.styles[`heading${level}`]
|
|
14077
|
+
)?.keepNext;
|
|
13951
14078
|
addFact({
|
|
13952
14079
|
id: `docx:heading:${path4}`,
|
|
13953
14080
|
kind: "docx/heading",
|
|
13954
14081
|
path: `${path4}/props/level`,
|
|
13955
14082
|
level,
|
|
14083
|
+
keepNext: props.keepNext === true || props.keepNext === void 0 && styleKeepNext === true,
|
|
13956
14084
|
...previousHeadingLevel !== void 0 && {
|
|
13957
14085
|
previousLevel: previousHeadingLevel
|
|
13958
14086
|
}
|
|
@@ -13967,6 +14095,65 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
13967
14095
|
const page = rec.name === "section" ? pageBox(resolved.theme, context.themeName, rec.props?.page) : basePage;
|
|
13968
14096
|
walkActive(component, `/children/${index}`, page, visit);
|
|
13969
14097
|
});
|
|
14098
|
+
const wordsOf = (text) => text.split(/\s+/).filter(Boolean).length;
|
|
14099
|
+
const bodyRoles = /* @__PURE__ */ new Set([
|
|
14100
|
+
"body",
|
|
14101
|
+
"list-item",
|
|
14102
|
+
"table-header",
|
|
14103
|
+
"table-cell",
|
|
14104
|
+
"statistic",
|
|
14105
|
+
"caption"
|
|
14106
|
+
]);
|
|
14107
|
+
const exhibitKinds = /* @__PURE__ */ new Set(["docx/table", "docx/chart", "docx/image"]);
|
|
14108
|
+
resolved.children.forEach((component, index) => {
|
|
14109
|
+
const rec = component;
|
|
14110
|
+
if (rec.name !== "section") return;
|
|
14111
|
+
const prefix = `/children/${index}/`;
|
|
14112
|
+
const own = facts.filter((fact) => fact.path.startsWith(prefix));
|
|
14113
|
+
const texts = own.filter(
|
|
14114
|
+
(fact) => fact.kind === "docx/text"
|
|
14115
|
+
);
|
|
14116
|
+
addFact({
|
|
14117
|
+
id: `docx:section:${index}`,
|
|
14118
|
+
kind: "docx/section",
|
|
14119
|
+
path: `/children/${index}`,
|
|
14120
|
+
index,
|
|
14121
|
+
words: texts.filter((fact) => bodyRoles.has(fact.role)).reduce((total, fact) => total + wordsOf(fact.text), 0),
|
|
14122
|
+
exhibits: own.filter((fact) => exhibitKinds.has(fact.kind)).length,
|
|
14123
|
+
headings: texts.filter((fact) => fact.role === "heading").length
|
|
14124
|
+
});
|
|
14125
|
+
if (hasColumnsComponent(rec)) return;
|
|
14126
|
+
const page = pageBox(
|
|
14127
|
+
resolved.theme,
|
|
14128
|
+
context.themeName,
|
|
14129
|
+
rec.props?.page
|
|
14130
|
+
);
|
|
14131
|
+
const fontSizePt = effectiveFontSize({ name: "paragraph" }, {}, typography)?.fontSizePt ?? 11;
|
|
14132
|
+
const widthPt = page.availableWidthTwips / 20;
|
|
14133
|
+
const averageAdvancePt = estimateTextWidthPt(MEASURE_SAMPLE, fontSizePt) / MEASURE_SAMPLE.length;
|
|
14134
|
+
if (averageAdvancePt > 0)
|
|
14135
|
+
addFact({
|
|
14136
|
+
id: `docx:measure:${index}`,
|
|
14137
|
+
kind: "docx/measure",
|
|
14138
|
+
path: `/children/${index}`,
|
|
14139
|
+
index,
|
|
14140
|
+
charactersPerLine: Math.round(widthPt / averageAdvancePt),
|
|
14141
|
+
widthTwips: page.availableWidthTwips,
|
|
14142
|
+
fontSizePt
|
|
14143
|
+
});
|
|
14144
|
+
});
|
|
14145
|
+
const headingCount = facts.filter(
|
|
14146
|
+
(fact) => fact.kind === "docx/text" && fact.role === "heading"
|
|
14147
|
+
).length;
|
|
14148
|
+
addFact({
|
|
14149
|
+
id: "docx:outline",
|
|
14150
|
+
kind: "docx/outline",
|
|
14151
|
+
path: "/props",
|
|
14152
|
+
headings: headingCount,
|
|
14153
|
+
contents: facts.some(
|
|
14154
|
+
(fact) => fact.kind === "docx/text" && fact.role === "toc-entry"
|
|
14155
|
+
)
|
|
14156
|
+
});
|
|
13970
14157
|
return {
|
|
13971
14158
|
format: "docx",
|
|
13972
14159
|
model: {
|
|
@@ -13997,6 +14184,52 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
13997
14184
|
}
|
|
13998
14185
|
};
|
|
13999
14186
|
}
|
|
14187
|
+
function hasColumnsComponent(node) {
|
|
14188
|
+
if (Array.isArray(node)) return node.some(hasColumnsComponent);
|
|
14189
|
+
const rec = asRecord2(node);
|
|
14190
|
+
if (!rec) return false;
|
|
14191
|
+
if (rec.name === "columns") return true;
|
|
14192
|
+
return Object.values(rec).some(hasColumnsComponent);
|
|
14193
|
+
}
|
|
14194
|
+
var CAPTION_LABEL = /^\s*(?:\*\*)?\s*(figure|exhibit|table|chart|image)\b/i;
|
|
14195
|
+
function isCaptioned(facts, image) {
|
|
14196
|
+
return facts.some((fact) => {
|
|
14197
|
+
if (fact.kind !== "docx/text") return false;
|
|
14198
|
+
const text = fact;
|
|
14199
|
+
const near = siblingOf(text.path) === siblingOf(image) || text.path.startsWith(`${image}/`) || image.startsWith(`${text.path}/`);
|
|
14200
|
+
return near && (text.role === "caption" || CAPTION_LABEL.test(text.text));
|
|
14201
|
+
});
|
|
14202
|
+
}
|
|
14203
|
+
function siblingOf(pointer) {
|
|
14204
|
+
return pointer.slice(0, pointer.lastIndexOf("/"));
|
|
14205
|
+
}
|
|
14206
|
+
var MEASURE_SAMPLE = "the quick brown fox jumps over the lazy dog and then settles under it ";
|
|
14207
|
+
function drawnRatio(props) {
|
|
14208
|
+
const width = finiteNumber(props.width);
|
|
14209
|
+
const height = finiteNumber(props.height);
|
|
14210
|
+
return width !== void 0 && height !== void 0 && height > 0 ? width / height : void 0;
|
|
14211
|
+
}
|
|
14212
|
+
function readableRatio(props) {
|
|
14213
|
+
if (typeof props.svg === "string") {
|
|
14214
|
+
const box = /viewBox\s*=\s*["']\s*[-\d.]+[ ,]+[-\d.]+[ ,]+([\d.]+)[ ,]+([\d.]+)/.exec(
|
|
14215
|
+
props.svg
|
|
14216
|
+
);
|
|
14217
|
+
const width = box ? Number(box[1]) : NaN;
|
|
14218
|
+
const height = box ? Number(box[2]) : NaN;
|
|
14219
|
+
return Number.isFinite(width) && Number.isFinite(height) && height > 0 ? width / height : void 0;
|
|
14220
|
+
}
|
|
14221
|
+
if (typeof props.base64 !== "string") return void 0;
|
|
14222
|
+
const comma = props.base64.indexOf(",");
|
|
14223
|
+
if (comma < 0) return void 0;
|
|
14224
|
+
try {
|
|
14225
|
+
const size = probe2.sync(
|
|
14226
|
+
Buffer.from(props.base64.slice(comma + 1), "base64")
|
|
14227
|
+
);
|
|
14228
|
+
return size && size.height > 0 ? size.width / size.height : void 0;
|
|
14229
|
+
} catch {
|
|
14230
|
+
return void 0;
|
|
14231
|
+
}
|
|
14232
|
+
}
|
|
14000
14233
|
function slotIsFilled(value) {
|
|
14001
14234
|
if (typeof value === "string") return value.trim() !== "";
|
|
14002
14235
|
return value !== void 0 && value !== null && value !== false && (!Array.isArray(value) || value.length > 0);
|
|
@@ -14398,122 +14631,16 @@ import {
|
|
|
14398
14631
|
nearestPaletteToken,
|
|
14399
14632
|
offPaletteFinding,
|
|
14400
14633
|
placeholderFinding,
|
|
14634
|
+
DEFAULT_IMAGE_ASPECT_TOLERANCE,
|
|
14635
|
+
driftingSizes,
|
|
14636
|
+
imageAspectFinding,
|
|
14637
|
+
offScaleFindings,
|
|
14401
14638
|
QUALITY_CODES,
|
|
14639
|
+
roleDriftFindings,
|
|
14640
|
+
sizeCountFinding,
|
|
14402
14641
|
QualityEngine,
|
|
14403
14642
|
tableInfoDesignFindings
|
|
14404
14643
|
} from "@json-to-office/quality";
|
|
14405
|
-
|
|
14406
|
-
// src/quality/text-metrics.ts
|
|
14407
|
-
var DEFAULT_ADVANCE = 556;
|
|
14408
|
-
var ADVANCE = {
|
|
14409
|
-
" ": 278,
|
|
14410
|
-
A: 667,
|
|
14411
|
-
B: 667,
|
|
14412
|
-
C: 722,
|
|
14413
|
-
D: 722,
|
|
14414
|
-
E: 667,
|
|
14415
|
-
F: 611,
|
|
14416
|
-
G: 778,
|
|
14417
|
-
H: 722,
|
|
14418
|
-
I: 278,
|
|
14419
|
-
J: 500,
|
|
14420
|
-
K: 667,
|
|
14421
|
-
L: 556,
|
|
14422
|
-
M: 833,
|
|
14423
|
-
N: 722,
|
|
14424
|
-
O: 778,
|
|
14425
|
-
P: 667,
|
|
14426
|
-
Q: 778,
|
|
14427
|
-
R: 722,
|
|
14428
|
-
S: 667,
|
|
14429
|
-
T: 611,
|
|
14430
|
-
U: 722,
|
|
14431
|
-
V: 667,
|
|
14432
|
-
W: 944,
|
|
14433
|
-
X: 667,
|
|
14434
|
-
Y: 667,
|
|
14435
|
-
Z: 611,
|
|
14436
|
-
a: 556,
|
|
14437
|
-
b: 556,
|
|
14438
|
-
c: 500,
|
|
14439
|
-
d: 556,
|
|
14440
|
-
e: 556,
|
|
14441
|
-
f: 278,
|
|
14442
|
-
g: 556,
|
|
14443
|
-
h: 556,
|
|
14444
|
-
i: 222,
|
|
14445
|
-
j: 222,
|
|
14446
|
-
k: 500,
|
|
14447
|
-
l: 222,
|
|
14448
|
-
m: 833,
|
|
14449
|
-
n: 556,
|
|
14450
|
-
o: 556,
|
|
14451
|
-
p: 556,
|
|
14452
|
-
q: 556,
|
|
14453
|
-
r: 333,
|
|
14454
|
-
s: 500,
|
|
14455
|
-
t: 278,
|
|
14456
|
-
u: 556,
|
|
14457
|
-
v: 500,
|
|
14458
|
-
w: 722,
|
|
14459
|
-
x: 500,
|
|
14460
|
-
y: 500,
|
|
14461
|
-
z: 500,
|
|
14462
|
-
".": 278,
|
|
14463
|
-
",": 278,
|
|
14464
|
-
":": 278,
|
|
14465
|
-
";": 278,
|
|
14466
|
-
"!": 278,
|
|
14467
|
-
"?": 556,
|
|
14468
|
-
"'": 191,
|
|
14469
|
-
'"': 355,
|
|
14470
|
-
"-": 333,
|
|
14471
|
-
"\u2013": 556,
|
|
14472
|
-
"\u2014": 1e3,
|
|
14473
|
-
"&": 667,
|
|
14474
|
-
"%": 889,
|
|
14475
|
-
"(": 333,
|
|
14476
|
-
")": 333,
|
|
14477
|
-
"/": 278,
|
|
14478
|
-
"@": 1015,
|
|
14479
|
-
"#": 556,
|
|
14480
|
-
"+": 584,
|
|
14481
|
-
"=": 584
|
|
14482
|
-
};
|
|
14483
|
-
function estimateTextWidthPt(text, fontSizePt, trackingPt = 0) {
|
|
14484
|
-
let units = 0;
|
|
14485
|
-
for (const character of text) {
|
|
14486
|
-
units += ADVANCE[character] ?? DEFAULT_ADVANCE;
|
|
14487
|
-
}
|
|
14488
|
-
const width = units / 1e3 * fontSizePt + text.length * trackingPt;
|
|
14489
|
-
return Math.max(0, width);
|
|
14490
|
-
}
|
|
14491
|
-
function estimateWrappedLines(text, widthPt, fontSizePt, trackingPt = 0) {
|
|
14492
|
-
if (widthPt <= 0) return 1;
|
|
14493
|
-
let lines = 0;
|
|
14494
|
-
for (const paragraph2 of text.split("\n")) {
|
|
14495
|
-
const words = paragraph2.split(/\s+/).filter(Boolean);
|
|
14496
|
-
if (words.length === 0) {
|
|
14497
|
-
lines += 1;
|
|
14498
|
-
continue;
|
|
14499
|
-
}
|
|
14500
|
-
let current = "";
|
|
14501
|
-
let used = 0;
|
|
14502
|
-
for (const word of words) {
|
|
14503
|
-
const candidate = current === "" ? word : `${current} ${word}`;
|
|
14504
|
-
if (estimateTextWidthPt(candidate, fontSizePt, trackingPt) <= widthPt) {
|
|
14505
|
-
current = candidate;
|
|
14506
|
-
} else {
|
|
14507
|
-
if (current !== "") used += 1;
|
|
14508
|
-
current = word;
|
|
14509
|
-
}
|
|
14510
|
-
}
|
|
14511
|
-
lines += used + (current === "" ? 0 : 1);
|
|
14512
|
-
}
|
|
14513
|
-
return Math.max(1, lines);
|
|
14514
|
-
}
|
|
14515
|
-
|
|
14516
|
-
// src/quality/rules.ts
|
|
14517
14644
|
var WIDTH_TOLERANCE_TWIPS = 10;
|
|
14518
14645
|
function numberParameter(parameters, name, fallback) {
|
|
14519
14646
|
const value = parameters[name];
|
|
@@ -15170,44 +15297,23 @@ var docxRunningHeadRule = {
|
|
|
15170
15297
|
});
|
|
15171
15298
|
}
|
|
15172
15299
|
};
|
|
15173
|
-
var SIZE_TOLERANCE_PT = 0.25;
|
|
15174
15300
|
function themeFact(facts) {
|
|
15175
15301
|
return facts.find(
|
|
15176
15302
|
(fact) => fact.kind === "docx/theme"
|
|
15177
15303
|
);
|
|
15178
15304
|
}
|
|
15179
|
-
|
|
15180
|
-
|
|
15181
|
-
|
|
15182
|
-
|
|
15183
|
-
|
|
15184
|
-
|
|
15185
|
-
|
|
15186
|
-
|
|
15187
|
-
|
|
15188
|
-
|
|
15189
|
-
|
|
15190
|
-
|
|
15191
|
-
const sizes = [...new Set(members.map((fact) => fact.fontSizePt))].sort(
|
|
15192
|
-
(a, b) => a - b
|
|
15193
|
-
);
|
|
15194
|
-
if (sizes.length < 2) continue;
|
|
15195
|
-
const expected = theme?.roleSizesPt[role];
|
|
15196
|
-
if (expected === void 0) continue;
|
|
15197
|
-
for (const fact of members) {
|
|
15198
|
-
if (fact.sizePath !== void 0 && !fact.generated && Math.abs(fact.fontSizePt - expected) > SIZE_TOLERANCE_PT)
|
|
15199
|
-
drifting.set(fact, { expected, sizes });
|
|
15200
|
-
}
|
|
15201
|
-
}
|
|
15202
|
-
return drifting;
|
|
15203
|
-
}
|
|
15204
|
-
function nearestSize(size, scale) {
|
|
15205
|
-
let best;
|
|
15206
|
-
for (const candidate of scale) {
|
|
15207
|
-
if (best === void 0 || Math.abs(candidate - size) < Math.abs(best - size))
|
|
15208
|
-
best = candidate;
|
|
15209
|
-
}
|
|
15210
|
-
return best;
|
|
15305
|
+
var DOCX_TYPE_VOCABULARY = {
|
|
15306
|
+
subject: "document",
|
|
15307
|
+
keepTo: "Keep to the theme styles \u2014 title, headings, body, label, source \u2014 and drop the ad-hoc sizes."
|
|
15308
|
+
};
|
|
15309
|
+
function paintedSizes(facts) {
|
|
15310
|
+
return facts.filter((fact) => fact.kind === "docx/text-size").map((fact) => ({
|
|
15311
|
+
path: fact.path,
|
|
15312
|
+
role: fact.role,
|
|
15313
|
+
fontSizePt: fact.fontSizePt,
|
|
15314
|
+
...fact.sizePath !== void 0 && { sizePath: fact.sizePath },
|
|
15315
|
+
generated: fact.generated
|
|
15316
|
+
}));
|
|
15211
15317
|
}
|
|
15212
15318
|
var docxTypeScaleRule = {
|
|
15213
15319
|
id: "docx/type-scale",
|
|
@@ -15220,43 +15326,15 @@ var docxTypeScaleRule = {
|
|
|
15220
15326
|
defaultEnabled: false,
|
|
15221
15327
|
evaluate: ({ facts }) => {
|
|
15222
15328
|
const theme = themeFact(facts);
|
|
15223
|
-
|
|
15224
|
-
|
|
15225
|
-
|
|
15226
|
-
|
|
15227
|
-
|
|
15228
|
-
|
|
15229
|
-
|
|
15329
|
+
if (!theme) return [];
|
|
15330
|
+
const sizes = paintedSizes(facts);
|
|
15331
|
+
return offScaleFindings(
|
|
15332
|
+
sizes,
|
|
15333
|
+
theme.typeScalePt,
|
|
15334
|
+
theme.themeName,
|
|
15335
|
+
DOCX_TYPE_VOCABULARY,
|
|
15336
|
+
driftingSizes(sizes, theme.roleSizesPt)
|
|
15230
15337
|
);
|
|
15231
|
-
const groups = /* @__PURE__ */ new Map();
|
|
15232
|
-
for (const fact of offScale) {
|
|
15233
|
-
const key = `${fact.role}@${fact.fontSizePt}`;
|
|
15234
|
-
groups.set(key, [...groups.get(key) ?? [], fact]);
|
|
15235
|
-
}
|
|
15236
|
-
return [...groups.values()].map((members) => {
|
|
15237
|
-
const [first] = members;
|
|
15238
|
-
const nearest = nearestSize(first.fontSizePt, scale);
|
|
15239
|
-
const sizePaths = members.map((fact) => fact.sizePath);
|
|
15240
|
-
const count = members.length === 1 ? "" : ` (${members.length} places, patched together)`;
|
|
15241
|
-
return {
|
|
15242
|
-
path: sizePaths[0],
|
|
15243
|
-
...sizePaths.length > 1 && { relatedPaths: sizePaths.slice(1) },
|
|
15244
|
-
message: `${first.fontSizePt}pt is not a size the ${theme.themeName} theme paints; the nearest on its scale is ${nearest}pt${count}.`,
|
|
15245
|
-
suggestion: `Use ${nearest}pt, or drop the size and let the "${first.role}" style set it.`,
|
|
15246
|
-
context: { role: first.role, scale, paths: sizePaths },
|
|
15247
|
-
evidence: {
|
|
15248
|
-
actual: first.fontSizePt,
|
|
15249
|
-
expected: nearest,
|
|
15250
|
-
unit: "pt",
|
|
15251
|
-
values: { source: "theme" }
|
|
15252
|
-
},
|
|
15253
|
-
fixes: sizePaths.map((path4) => ({
|
|
15254
|
-
op: "replace",
|
|
15255
|
-
path: path4,
|
|
15256
|
-
value: nearest
|
|
15257
|
-
}))
|
|
15258
|
-
};
|
|
15259
|
-
});
|
|
15260
15338
|
}
|
|
15261
15339
|
};
|
|
15262
15340
|
var docxSizeCountRule = {
|
|
@@ -15269,34 +15347,13 @@ var docxSizeCountRule = {
|
|
|
15269
15347
|
formats: ["docx"],
|
|
15270
15348
|
defaultEnabled: false,
|
|
15271
15349
|
defaultParameters: { maximumSizes: 8 },
|
|
15272
|
-
evaluate: ({ facts, configuration, profile }) =>
|
|
15273
|
-
|
|
15274
|
-
|
|
15275
|
-
|
|
15276
|
-
|
|
15277
|
-
|
|
15278
|
-
|
|
15279
|
-
for (const fact of textSizeFacts(facts)) {
|
|
15280
|
-
const size = Math.round(fact.fontSizePt * 4) / 4;
|
|
15281
|
-
if (!firstPathBySize.has(size)) firstPathBySize.set(size, fact.path);
|
|
15282
|
-
}
|
|
15283
|
-
if (firstPathBySize.size <= maximum) return [];
|
|
15284
|
-
const sizes = [...firstPathBySize.keys()].sort((a, b) => a - b);
|
|
15285
|
-
return [
|
|
15286
|
-
{
|
|
15287
|
-
path: themeFact(facts)?.path ?? "/props",
|
|
15288
|
-
relatedPaths: sizes.map((size) => firstPathBySize.get(size)),
|
|
15289
|
-
message: `The document paints ${sizes.length} distinct text sizes (${sizes.join(", ")}pt); the ${profile?.id ?? "selected"} profile allows ${maximum}.`,
|
|
15290
|
-
suggestion: "Keep to the theme styles \u2014 title, headings, body, label, source \u2014 and drop the ad-hoc sizes.",
|
|
15291
|
-
context: { sizes, maximum },
|
|
15292
|
-
evidence: {
|
|
15293
|
-
actual: sizes.length,
|
|
15294
|
-
expected: maximum,
|
|
15295
|
-
values: { source: "profile" }
|
|
15296
|
-
}
|
|
15297
|
-
}
|
|
15298
|
-
];
|
|
15299
|
-
}
|
|
15350
|
+
evaluate: ({ facts, configuration, profile }) => sizeCountFinding(
|
|
15351
|
+
paintedSizes(facts),
|
|
15352
|
+
numberParameter(configuration.parameters, "maximumSizes", 8),
|
|
15353
|
+
themeFact(facts)?.path ?? "/props",
|
|
15354
|
+
profile?.id,
|
|
15355
|
+
DOCX_TYPE_VOCABULARY
|
|
15356
|
+
)
|
|
15300
15357
|
};
|
|
15301
15358
|
var docxRoleDriftRule = {
|
|
15302
15359
|
id: "docx/role-drift",
|
|
@@ -15309,29 +15366,220 @@ var docxRoleDriftRule = {
|
|
|
15309
15366
|
defaultEnabled: false,
|
|
15310
15367
|
evaluate: ({ facts }) => {
|
|
15311
15368
|
const theme = themeFact(facts);
|
|
15312
|
-
|
|
15313
|
-
|
|
15314
|
-
|
|
15315
|
-
|
|
15316
|
-
|
|
15317
|
-
|
|
15318
|
-
|
|
15319
|
-
|
|
15320
|
-
|
|
15321
|
-
|
|
15322
|
-
|
|
15323
|
-
|
|
15324
|
-
|
|
15369
|
+
return theme ? roleDriftFindings(paintedSizes(facts), theme.roleSizesPt) : [];
|
|
15370
|
+
}
|
|
15371
|
+
};
|
|
15372
|
+
var docxBodyMeasureRule = {
|
|
15373
|
+
id: "docx/body-measure",
|
|
15374
|
+
description: "A section whose body copy runs outside the readable measure. Off until a profile or policy enables it.",
|
|
15375
|
+
code: QUALITY_CODES.BODY_MEASURE,
|
|
15376
|
+
category: "legibility",
|
|
15377
|
+
defaultSeverity: "warning",
|
|
15378
|
+
defaultCertainty: "estimated",
|
|
15379
|
+
formats: ["docx"],
|
|
15380
|
+
defaultEnabled: false,
|
|
15381
|
+
defaultParameters: { minimumCharacters: 45, maximumCharacters: 90 },
|
|
15382
|
+
evaluate: ({ facts, configuration }) => {
|
|
15383
|
+
const minimum = numberParameter(
|
|
15384
|
+
configuration.parameters,
|
|
15385
|
+
"minimumCharacters",
|
|
15386
|
+
45
|
|
15387
|
+
);
|
|
15388
|
+
const maximum = numberParameter(
|
|
15389
|
+
configuration.parameters,
|
|
15390
|
+
"maximumCharacters",
|
|
15391
|
+
90
|
|
15392
|
+
);
|
|
15393
|
+
const words = new Map(
|
|
15394
|
+
facts.filter((fact) => fact.kind === "docx/section").map((fact) => [fact.index, fact.words])
|
|
15395
|
+
);
|
|
15396
|
+
return facts.filter((fact) => fact.kind === "docx/measure").filter((fact) => (words.get(fact.index) ?? 0) >= 40).filter(
|
|
15397
|
+
(fact) => fact.charactersPerLine < minimum || fact.charactersPerLine > maximum
|
|
15398
|
+
).map((fact) => {
|
|
15399
|
+
const wide = fact.charactersPerLine > maximum;
|
|
15400
|
+
return {
|
|
15401
|
+
path: fact.path,
|
|
15402
|
+
message: `Body copy runs about ${fact.charactersPerLine} characters a line at ${fact.fontSizePt}pt; a readable measure is ${minimum}\u2013${maximum}.`,
|
|
15403
|
+
suggestion: wide ? "Widen the margins, set the body a size larger, or put the text in columns." : "Narrow the margins or set the body a size smaller.",
|
|
15404
|
+
context: {
|
|
15405
|
+
charactersPerLine: fact.charactersPerLine,
|
|
15406
|
+
minimum,
|
|
15407
|
+
maximum
|
|
15408
|
+
},
|
|
15325
15409
|
evidence: {
|
|
15326
|
-
actual: fact.
|
|
15327
|
-
expected,
|
|
15328
|
-
unit: "
|
|
15329
|
-
values: {
|
|
15410
|
+
actual: fact.charactersPerLine,
|
|
15411
|
+
expected: wide ? maximum : minimum,
|
|
15412
|
+
unit: "characters",
|
|
15413
|
+
values: { source: "profile" }
|
|
15414
|
+
}
|
|
15415
|
+
};
|
|
15416
|
+
});
|
|
15417
|
+
}
|
|
15418
|
+
};
|
|
15419
|
+
var docxSectionContentRule = {
|
|
15420
|
+
id: "docx/section-content",
|
|
15421
|
+
description: "A section that renders nothing, or one that carries body copy under no heading. Off until a profile or policy enables it.",
|
|
15422
|
+
code: QUALITY_CODES.SECTION_EMPTY,
|
|
15423
|
+
category: "hierarchy",
|
|
15424
|
+
defaultSeverity: "warning",
|
|
15425
|
+
defaultCertainty: "deterministic",
|
|
15426
|
+
formats: ["docx"],
|
|
15427
|
+
defaultEnabled: false,
|
|
15428
|
+
defaultParameters: { minimumWordsForHeading: 60 },
|
|
15429
|
+
evaluate: ({ facts, configuration }) => {
|
|
15430
|
+
const minimumWords = numberParameter(
|
|
15431
|
+
configuration.parameters,
|
|
15432
|
+
"minimumWordsForHeading",
|
|
15433
|
+
60
|
|
15434
|
+
);
|
|
15435
|
+
const sections = facts.filter(
|
|
15436
|
+
(fact) => fact.kind === "docx/section"
|
|
15437
|
+
);
|
|
15438
|
+
return sections.flatMap((fact) => {
|
|
15439
|
+
if (fact.words === 0 && fact.exhibits === 0)
|
|
15440
|
+
return [
|
|
15441
|
+
{
|
|
15442
|
+
path: fact.path,
|
|
15443
|
+
code: QUALITY_CODES.SECTION_EMPTY,
|
|
15444
|
+
message: "This section renders nothing: no text, no table, no figure. It still starts a page.",
|
|
15445
|
+
suggestion: "Fill the section, or remove it so the document does not open a page on nothing.",
|
|
15446
|
+
context: { index: fact.index }
|
|
15447
|
+
}
|
|
15448
|
+
];
|
|
15449
|
+
if (fact.headings === 0 && fact.words >= minimumWords)
|
|
15450
|
+
return [
|
|
15451
|
+
{
|
|
15452
|
+
path: fact.path,
|
|
15453
|
+
code: QUALITY_CODES.SECTION_UNTITLED,
|
|
15454
|
+
message: `This section runs to ${fact.words} words under no heading, so nothing names it in the outline or the contents.`,
|
|
15455
|
+
suggestion: "Open the section with a heading \u2014 a section-opener block, or a level-1 heading.",
|
|
15456
|
+
context: { index: fact.index, words: fact.words },
|
|
15457
|
+
evidence: {
|
|
15458
|
+
actual: 0,
|
|
15459
|
+
expected: 1,
|
|
15460
|
+
unit: "headings",
|
|
15461
|
+
values: { source: "profile" }
|
|
15462
|
+
}
|
|
15463
|
+
}
|
|
15464
|
+
];
|
|
15465
|
+
return [];
|
|
15466
|
+
});
|
|
15467
|
+
}
|
|
15468
|
+
};
|
|
15469
|
+
var docxHeadingKeepNextRule = {
|
|
15470
|
+
id: "docx/heading-keep-next",
|
|
15471
|
+
description: "A heading that is not bound to the content under it, so a page break can strand it; the fix sets keepNext on an authored heading. Off until a profile or policy enables it.",
|
|
15472
|
+
code: QUALITY_CODES.HEADING_ORPHAN,
|
|
15473
|
+
category: "hierarchy",
|
|
15474
|
+
defaultSeverity: "warning",
|
|
15475
|
+
defaultCertainty: "deterministic",
|
|
15476
|
+
formats: ["docx"],
|
|
15477
|
+
defaultEnabled: false,
|
|
15478
|
+
evaluate: ({ facts }) => facts.filter(
|
|
15479
|
+
(fact) => fact.kind === "docx/heading" && !fact.keepNext
|
|
15480
|
+
).map((fact) => {
|
|
15481
|
+
const heading = /\/props\/level$/.test(fact.path) ? fact.path.replace(/\/props\/level$/, "") : void 0;
|
|
15482
|
+
return {
|
|
15483
|
+
path: heading ?? fact.path,
|
|
15484
|
+
message: "This heading is not bound to the content under it: a page break can leave it alone at the foot of a page.",
|
|
15485
|
+
suggestion: heading ? "Set keepNext on the heading, or give the theme\u2019s heading style keepNext so every level is bound." : "Give the theme\u2019s heading style keepNext, or set it in the block definition that draws this heading.",
|
|
15486
|
+
context: { level: fact.level },
|
|
15487
|
+
evidence: { values: { source: "profile" } },
|
|
15488
|
+
...heading && {
|
|
15489
|
+
fixes: [
|
|
15490
|
+
{
|
|
15491
|
+
op: "add",
|
|
15492
|
+
path: `${heading}/props/keepNext`,
|
|
15493
|
+
value: true
|
|
15494
|
+
}
|
|
15495
|
+
]
|
|
15496
|
+
}
|
|
15497
|
+
};
|
|
15498
|
+
})
|
|
15499
|
+
};
|
|
15500
|
+
var docxFigureLabelRule = {
|
|
15501
|
+
id: "docx/figure-label",
|
|
15502
|
+
description: "An image with neither a caption beside it nor alt text on it. Off until a profile or policy enables it.",
|
|
15503
|
+
code: QUALITY_CODES.FIGURE_UNLABELLED,
|
|
15504
|
+
category: "accessibility",
|
|
15505
|
+
defaultSeverity: "warning",
|
|
15506
|
+
defaultCertainty: "deterministic",
|
|
15507
|
+
formats: ["docx"],
|
|
15508
|
+
defaultEnabled: false,
|
|
15509
|
+
evaluate: ({ facts }) => facts.filter(
|
|
15510
|
+
(fact) => fact.kind === "docx/image" && fact.alt === void 0 && !fact.captioned
|
|
15511
|
+
).map((fact) => ({
|
|
15512
|
+
path: fact.path,
|
|
15513
|
+
message: "This figure carries neither a caption nor alt text, so nothing in the document says what it shows.",
|
|
15514
|
+
suggestion: "Put the image in a figure block, which numbers and captions it, or write alt text on the image.",
|
|
15515
|
+
context: {}
|
|
15516
|
+
}))
|
|
15517
|
+
};
|
|
15518
|
+
var docxImageAspectRule = {
|
|
15519
|
+
id: "docx/image-aspect",
|
|
15520
|
+
description: "An image drawn at an aspect the asset does not have, where the asset can be read from the document.",
|
|
15521
|
+
code: QUALITY_CODES.IMAGE_ASPECT,
|
|
15522
|
+
category: "integrity",
|
|
15523
|
+
defaultSeverity: "warning",
|
|
15524
|
+
defaultCertainty: "deterministic",
|
|
15525
|
+
formats: ["docx"],
|
|
15526
|
+
defaultParameters: { tolerance: DEFAULT_IMAGE_ASPECT_TOLERANCE },
|
|
15527
|
+
evaluate: ({ facts, configuration }) => {
|
|
15528
|
+
const tolerance = numberParameter(
|
|
15529
|
+
configuration.parameters,
|
|
15530
|
+
"tolerance",
|
|
15531
|
+
DEFAULT_IMAGE_ASPECT_TOLERANCE
|
|
15532
|
+
);
|
|
15533
|
+
return facts.filter(
|
|
15534
|
+
(fact) => fact.kind === "docx/image" && fact.drawnRatio !== void 0 && fact.naturalRatio !== void 0
|
|
15535
|
+
).flatMap((fact) => {
|
|
15536
|
+
const finding = imageAspectFinding(
|
|
15537
|
+
{
|
|
15538
|
+
path: fact.path,
|
|
15539
|
+
drawn: fact.drawnRatio,
|
|
15540
|
+
natural: fact.naturalRatio
|
|
15330
15541
|
},
|
|
15331
|
-
|
|
15332
|
-
|
|
15333
|
-
|
|
15334
|
-
|
|
15542
|
+
"page",
|
|
15543
|
+
tolerance
|
|
15544
|
+
);
|
|
15545
|
+
return finding ? [finding] : [];
|
|
15546
|
+
});
|
|
15547
|
+
}
|
|
15548
|
+
};
|
|
15549
|
+
var docxContentsRule = {
|
|
15550
|
+
id: "docx/contents-missing",
|
|
15551
|
+
description: "More headings than minimumHeadings with no table of contents. Off at 0.",
|
|
15552
|
+
code: QUALITY_CODES.CONTENTS_MISSING,
|
|
15553
|
+
category: "hierarchy",
|
|
15554
|
+
defaultSeverity: "info",
|
|
15555
|
+
defaultCertainty: "deterministic",
|
|
15556
|
+
formats: ["docx"],
|
|
15557
|
+
defaultParameters: { minimumHeadings: 0 },
|
|
15558
|
+
evaluate: ({ facts, configuration, profile }) => {
|
|
15559
|
+
const minimum = numberParameter(
|
|
15560
|
+
configuration.parameters,
|
|
15561
|
+
"minimumHeadings",
|
|
15562
|
+
0
|
|
15563
|
+
);
|
|
15564
|
+
if (minimum <= 0) return [];
|
|
15565
|
+
const outline = facts.find(
|
|
15566
|
+
(fact) => fact.kind === "docx/outline"
|
|
15567
|
+
);
|
|
15568
|
+
if (!outline || outline.contents || outline.headings < minimum) return [];
|
|
15569
|
+
return [
|
|
15570
|
+
{
|
|
15571
|
+
path: outline.path,
|
|
15572
|
+
message: `The document carries ${outline.headings} headings and no table of contents; the ${profile?.id ?? "selected"} profile expects one from ${minimum}.`,
|
|
15573
|
+
suggestion: "Add a `toc` component after the cover. Word fills it from the headings already there.",
|
|
15574
|
+
context: { headings: outline.headings, minimum },
|
|
15575
|
+
evidence: {
|
|
15576
|
+
actual: outline.headings,
|
|
15577
|
+
expected: minimum,
|
|
15578
|
+
unit: "headings",
|
|
15579
|
+
values: { source: "profile" }
|
|
15580
|
+
}
|
|
15581
|
+
}
|
|
15582
|
+
];
|
|
15335
15583
|
}
|
|
15336
15584
|
};
|
|
15337
15585
|
var DOCX_QUALITY_RULES = {
|
|
@@ -15354,14 +15602,26 @@ var DOCX_QUALITY_RULES = {
|
|
|
15354
15602
|
docxExhibitRequiredRule,
|
|
15355
15603
|
docxTypeScaleRule,
|
|
15356
15604
|
docxSizeCountRule,
|
|
15357
|
-
docxRoleDriftRule
|
|
15605
|
+
docxRoleDriftRule,
|
|
15606
|
+
docxBodyMeasureRule,
|
|
15607
|
+
docxSectionContentRule,
|
|
15608
|
+
docxHeadingKeepNextRule,
|
|
15609
|
+
docxFigureLabelRule,
|
|
15610
|
+
docxImageAspectRule,
|
|
15611
|
+
docxContentsRule
|
|
15358
15612
|
]
|
|
15359
15613
|
};
|
|
15614
|
+
var REPORT_MEASURE = {
|
|
15615
|
+
"docx/body-measure": {
|
|
15616
|
+
enabled: true,
|
|
15617
|
+
parameters: { maximumCharacters: 125 }
|
|
15618
|
+
}
|
|
15619
|
+
};
|
|
15360
15620
|
var DOCX_QUALITY_PROFILES = {
|
|
15361
15621
|
"client-report": {
|
|
15362
15622
|
id: "client-report",
|
|
15363
15623
|
formats: ["docx"],
|
|
15364
|
-
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, every size on the theme scale with at most eight in play, no page rendered empty or left half blank under the running head, and at least one chart or table.",
|
|
15624
|
+
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, every size on the theme scale with at most eight in play, a readable measure, no empty or untitled section, every heading bound to what follows and every figure named, no page rendered empty or left half blank under the running head, and at least one chart or table.",
|
|
15365
15625
|
rules: {
|
|
15366
15626
|
"docx/required-chrome": {
|
|
15367
15627
|
parameters: { required: ["takeaway", "source"] }
|
|
@@ -15385,7 +15645,11 @@ var DOCX_QUALITY_PROFILES = {
|
|
|
15385
15645
|
// judge calls a page two-thirds blank a defect.
|
|
15386
15646
|
"rendered/page-underfilled": { severity: "warning" },
|
|
15387
15647
|
// A client report argues numbers: at least one chart or real table.
|
|
15388
|
-
"docx/exhibit-required": { enabled: true }
|
|
15648
|
+
"docx/exhibit-required": { enabled: true },
|
|
15649
|
+
...REPORT_MEASURE,
|
|
15650
|
+
"docx/section-content": { enabled: true },
|
|
15651
|
+
"docx/heading-keep-next": { enabled: true },
|
|
15652
|
+
"docx/figure-label": { enabled: true }
|
|
15389
15653
|
}
|
|
15390
15654
|
},
|
|
15391
15655
|
"executive-report": {
|
|
@@ -15393,13 +15657,15 @@ var DOCX_QUALITY_PROFILES = {
|
|
|
15393
15657
|
formats: ["docx"],
|
|
15394
15658
|
description: "Short decision document with strict outline continuity.",
|
|
15395
15659
|
rules: {
|
|
15396
|
-
"docx/heading-hierarchy": { severity: "warning" }
|
|
15660
|
+
"docx/heading-hierarchy": { severity: "warning" },
|
|
15661
|
+
"docx/section-content": { enabled: true },
|
|
15662
|
+
"docx/heading-keep-next": { enabled: true }
|
|
15397
15663
|
}
|
|
15398
15664
|
},
|
|
15399
15665
|
"technical-report": {
|
|
15400
15666
|
id: "technical-report",
|
|
15401
15667
|
formats: ["docx"],
|
|
15402
|
-
description: "Technical report or memo: numbered sections under a running head with page numbers on every section after the cover, a source wherever a block declares one, no heading skipped, every size on the theme scale with at most nine in play, no page rendered empty or left half blank, and at least one chart or table.",
|
|
15668
|
+
description: "Technical report or memo: numbered sections under a running head with page numbers on every section after the cover, a source wherever a block declares one, no heading skipped, every size on the theme scale with at most nine in play, a readable measure, no empty or untitled section, every heading bound to what follows, every figure named, a contents page past eight headings, no page rendered empty or left half blank, and at least one chart or table.",
|
|
15403
15669
|
rules: {
|
|
15404
15670
|
// A figure or table in a technical report cites where its numbers come
|
|
15405
15671
|
// from; the takeaway is the client report's ask, the caption is the
|
|
@@ -15420,7 +15686,17 @@ var DOCX_QUALITY_PROFILES = {
|
|
|
15420
15686
|
"rendered/empty-page": { severity: "warning" },
|
|
15421
15687
|
"rendered/page-underfilled": { severity: "warning" },
|
|
15422
15688
|
// A technical report argues from measurements: a runs table, a chart.
|
|
15423
|
-
"docx/exhibit-required": { enabled: true }
|
|
15689
|
+
"docx/exhibit-required": { enabled: true },
|
|
15690
|
+
...REPORT_MEASURE,
|
|
15691
|
+
"docx/section-content": { enabled: true },
|
|
15692
|
+
"docx/heading-keep-next": { enabled: true },
|
|
15693
|
+
"docx/figure-label": { enabled: true },
|
|
15694
|
+
// Numbered sections are meant to be reached by number: past eight
|
|
15695
|
+
// headings a technical report owes the reader a contents page.
|
|
15696
|
+
"docx/contents-missing": {
|
|
15697
|
+
severity: "warning",
|
|
15698
|
+
parameters: { minimumHeadings: 8 }
|
|
15699
|
+
}
|
|
15424
15700
|
}
|
|
15425
15701
|
},
|
|
15426
15702
|
general: {
|