release: 发布 v0.6.2 DOCX 真实文档修复
新增能力:将 DOCX 发布验收拆分为四套独立 140,支持真实语料冻结、指纹复用、失败与基础设施错误独立统计,并为表格换行、全 JSON 围栏、代码连续性和长文档分页建立通用门禁。 问题修复:冻结 Paged.js 分片前的逻辑表格列轨并传递打印几何,统一 Markdown 表格换行、代码、段落与 OOXML 翻译;改进 PDF 文本流排序、语义块映射、颜色与栅格比较,消除窄字符重叠和跨行范围符号误报。 兼容与部署:版本统一为 0.6.2;正式 Docker 镜像内置固定 Chromium、Pandoc 3.9.0.2 和 Serif/Sans/Mono 字体;Desktop NSIS 与 ZIP 继续直接内置字体,无需系统字体安装。 验证结果:合成基线与长庆严格 280/280,M4N 140/140;健康数据残余误报 6/115(5.22%),均核查为重复表头自动对齐/取样误报且基础设施错误为 0。全项目测试、类型检查、生产构建和 git diff --check 通过;正式 Docker、NSIS、ZIP、离线镜像、部署包、清单及 SHA-256 均已生成并校验。
This commit is contained in:
@@ -4,6 +4,7 @@ import path from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { DOMParser } from "@xmldom/xmldom";
|
||||
import { unzipSync } from "fflate";
|
||||
import matter from "gray-matter";
|
||||
|
||||
import {
|
||||
createPdfDocumentSnapshot,
|
||||
@@ -11,6 +12,8 @@ import {
|
||||
createWordPdfAdapter,
|
||||
createWpsPdfAdapter,
|
||||
getBlockingVisualDiffIssues,
|
||||
isCrossEngineRasterEquivalent,
|
||||
normalizePdfEditableText,
|
||||
renderPdfVisualDiffHtml,
|
||||
serializePdfVisualDiffJson
|
||||
} from "../packages/document-visual-diff/dist/index.js";
|
||||
@@ -21,9 +24,14 @@ import {
|
||||
import {
|
||||
createDocxLayoutVisualMatrixCases,
|
||||
getDocxFontGateFailures,
|
||||
isCorpusTextColorEquivalent,
|
||||
readBundledThemeMatrixDefinitions,
|
||||
resolveEffectiveCoverPageCount,
|
||||
summarizeDocxLayoutVisualMatrix
|
||||
} from "./docx-layout-visual-matrix-cases.mjs";
|
||||
import {
|
||||
DOCX_REAL_WORLD_CORPUS
|
||||
} from "./docx-real-world-corpus.mjs";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const WORD_NAMESPACE =
|
||||
@@ -32,10 +40,10 @@ const MATH_NAMESPACE =
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/math";
|
||||
const decoder = new TextDecoder();
|
||||
const repositoryDirectory = path.resolve(import.meta.dirname, "..");
|
||||
const outputDirectory = path.join(
|
||||
const outputDirectory = path.resolve(
|
||||
repositoryDirectory,
|
||||
"output",
|
||||
"docx-layout-visual-matrix"
|
||||
process.env.MD_TO_PDF_LAYOUT_VISUAL_OUTPUT_DIR?.trim() ||
|
||||
path.join("output", "docx-layout-visual-matrix")
|
||||
);
|
||||
const matrixScript = path.join(
|
||||
repositoryDirectory,
|
||||
@@ -64,6 +72,23 @@ const selectedOrientation =
|
||||
process.env.MD_TO_PDF_LAYOUT_VISUAL_ORIENTATION?.trim() || undefined;
|
||||
const selectedMarginScenarioId =
|
||||
process.env.MD_TO_PDF_LAYOUT_VISUAL_MARGIN?.trim() || undefined;
|
||||
const expectInlineCodeProbes =
|
||||
process.env.MD_TO_PDF_R4_APPEND_INLINE_CODE_PROBES?.trim() !== "0";
|
||||
const selectedCorpusId =
|
||||
process.env.MD_TO_PDF_CORPUS_ID?.trim() || undefined;
|
||||
const corpusDefinition = selectedCorpusId
|
||||
? DOCX_REAL_WORLD_CORPUS.find((entry) => entry.id === selectedCorpusId)
|
||||
: undefined;
|
||||
const corpusSourceMetadata = selectedScope === "corpus" &&
|
||||
process.env.MD_TO_PDF_R4_MARKDOWN_PATH?.trim()
|
||||
? matter(fs.readFileSync(
|
||||
path.resolve(
|
||||
repositoryDirectory,
|
||||
process.env.MD_TO_PDF_R4_MARKDOWN_PATH.trim()
|
||||
),
|
||||
"utf8"
|
||||
)).data
|
||||
: undefined;
|
||||
const scopedCases = selectedCaseId
|
||||
? cases.filter((entry) => entry.id === selectedCaseId)
|
||||
: selectedScope === "cover"
|
||||
@@ -185,6 +210,43 @@ function paragraphBlockKind(paragraph, styleId) {
|
||||
return "paragraph";
|
||||
}
|
||||
|
||||
function directWordChildren(node, localName) {
|
||||
return Array.from(node?.childNodes ?? []).filter(
|
||||
(child) =>
|
||||
child.nodeType === 1 &&
|
||||
child.localName === localName &&
|
||||
child.namespaceURI === WORD_NAMESPACE
|
||||
);
|
||||
}
|
||||
|
||||
function nearestWordAncestor(node, localName) {
|
||||
let current = node?.parentNode;
|
||||
while (current) {
|
||||
if (
|
||||
current.localName === localName &&
|
||||
current.namespaceURI === WORD_NAMESPACE
|
||||
) {
|
||||
return current;
|
||||
}
|
||||
current = current.parentNode;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function paragraphTableCoordinates(paragraph, tableIndexes) {
|
||||
const cell = nearestWordAncestor(paragraph, "tc");
|
||||
const row = nearestWordAncestor(paragraph, "tr");
|
||||
const table = nearestWordAncestor(paragraph, "tbl");
|
||||
if (!cell || !row || !table) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
tableGroupId: `table-${tableIndexes.get(table)}`,
|
||||
tableRowIndex: directWordChildren(table, "tr").indexOf(row),
|
||||
tableColumnIndex: directWordChildren(row, "tc").indexOf(cell)
|
||||
};
|
||||
}
|
||||
|
||||
function extractEditableContract(docxPath) {
|
||||
const entries = unzipSync(Uint8Array.from(fs.readFileSync(docxPath)));
|
||||
const documentXml = entries["word/document.xml"];
|
||||
@@ -193,13 +255,83 @@ function extractEditableContract(docxPath) {
|
||||
decoder.decode(documentXml),
|
||||
"application/xml"
|
||||
);
|
||||
const tableIndexes = new Map(
|
||||
Array.from(document.getElementsByTagName("*")).filter(
|
||||
(node) => node.localName === "tbl" && node.namespaceURI === WORD_NAMESPACE
|
||||
).map((table, index) => [table, index])
|
||||
);
|
||||
let section = "cover";
|
||||
let hasCoverSection = false;
|
||||
const paragraphs = [];
|
||||
let inlineCodeParagraphCount = 0;
|
||||
let inlineCodeExactLineRuleCount = 0;
|
||||
let pureMathTableParagraphCount = 0;
|
||||
let stabilizedPureMathTableParagraphCount = 0;
|
||||
for (const paragraph of Array.from(document.getElementsByTagName("*")).filter(
|
||||
(node) => node.localName === "p" && node.namespaceURI === WORD_NAMESPACE
|
||||
)) {
|
||||
const descendants = Array.from(paragraph.getElementsByTagName("*"));
|
||||
const inlineCode = descendants.some(
|
||||
(node) =>
|
||||
node.localName === "rStyle" &&
|
||||
node.namespaceURI === WORD_NAMESPACE &&
|
||||
(node.getAttribute("w:val") || node.getAttribute("val")) ===
|
||||
"VerbatimChar"
|
||||
);
|
||||
if (inlineCode) {
|
||||
inlineCodeParagraphCount += 1;
|
||||
if (
|
||||
descendants.some(
|
||||
(node) =>
|
||||
node.localName === "spacing" &&
|
||||
node.namespaceURI === WORD_NAMESPACE &&
|
||||
(node.getAttribute("w:lineRule") ||
|
||||
node.getAttribute("lineRule")) === "exact"
|
||||
)
|
||||
) {
|
||||
inlineCodeExactLineRuleCount += 1;
|
||||
}
|
||||
}
|
||||
if (hasAncestor(paragraph, "tc")) {
|
||||
const directContent = Array.from(paragraph.childNodes).filter(
|
||||
(node) =>
|
||||
node.nodeType === 1 &&
|
||||
!(
|
||||
node.localName === "pPr" &&
|
||||
node.namespaceURI === WORD_NAMESPACE
|
||||
)
|
||||
);
|
||||
const directMath = directContent.filter(
|
||||
(node) =>
|
||||
node.localName === "oMath" &&
|
||||
node.namespaceURI === MATH_NAMESPACE
|
||||
);
|
||||
const stabilizers = directContent.filter(
|
||||
(node) =>
|
||||
node.localName === "r" &&
|
||||
node.namespaceURI === WORD_NAMESPACE &&
|
||||
Array.from(node.getElementsByTagName("*")).some(
|
||||
(descendant) =>
|
||||
descendant.localName === "noProof" &&
|
||||
descendant.namespaceURI === WORD_NAMESPACE
|
||||
) &&
|
||||
Array.from(node.getElementsByTagName("*")).some(
|
||||
(descendant) =>
|
||||
descendant.localName === "t" &&
|
||||
descendant.namespaceURI === WORD_NAMESPACE &&
|
||||
descendant.textContent === "\u200B"
|
||||
)
|
||||
);
|
||||
if (
|
||||
directMath.length > 0 &&
|
||||
directContent.length === directMath.length + stabilizers.length
|
||||
) {
|
||||
pureMathTableParagraphCount += 1;
|
||||
if (stabilizers.length > 0) {
|
||||
stabilizedPureMathTableParagraphCount += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
const hasSectionBreak = descendants.some(
|
||||
(node) =>
|
||||
node.localName === "sectPr" && node.namespaceURI === WORD_NAMESPACE
|
||||
@@ -209,20 +341,53 @@ function extractEditableContract(docxPath) {
|
||||
section = "body";
|
||||
continue;
|
||||
}
|
||||
const text = descendants.flatMap((node) =>
|
||||
node.localName === "t" &&
|
||||
(node.namespaceURI === WORD_NAMESPACE ||
|
||||
node.namespaceURI === MATH_NAMESPACE)
|
||||
? [node.textContent ?? ""]
|
||||
: []
|
||||
).join("");
|
||||
const hardBreakSegments = [""];
|
||||
const mathCharacterIndexes = [];
|
||||
let normalizedCharacterOffset = 0;
|
||||
for (const node of descendants) {
|
||||
if (
|
||||
node.localName === "t" &&
|
||||
(node.namespaceURI === WORD_NAMESPACE ||
|
||||
node.namespaceURI === MATH_NAMESPACE)
|
||||
) {
|
||||
const nodeText = (node.textContent ?? "").replace(
|
||||
/[\u200B\u2060\uFEFF]/gu,
|
||||
""
|
||||
);
|
||||
hardBreakSegments[hardBreakSegments.length - 1] += nodeText;
|
||||
const normalizedNodeLength = Array.from(
|
||||
normalizePdfEditableText(nodeText)
|
||||
).length;
|
||||
if (node.namespaceURI === MATH_NAMESPACE) {
|
||||
for (let index = 0; index < normalizedNodeLength; index += 1) {
|
||||
mathCharacterIndexes.push(normalizedCharacterOffset + index);
|
||||
}
|
||||
}
|
||||
normalizedCharacterOffset += normalizedNodeLength;
|
||||
} else if (
|
||||
node.localName === "br" &&
|
||||
node.namespaceURI === WORD_NAMESPACE
|
||||
) {
|
||||
hardBreakSegments.push("");
|
||||
}
|
||||
}
|
||||
const text = hardBreakSegments.join("");
|
||||
if (!text || isInternalLayoutSpacerParagraph(descendants, text)) {
|
||||
continue;
|
||||
}
|
||||
const styleId = paragraphStyleId(paragraph);
|
||||
const tableCoordinates = paragraphTableCoordinates(paragraph, tableIndexes);
|
||||
paragraphs.push({
|
||||
index: paragraphs.length,
|
||||
text,
|
||||
...(mathCharacterIndexes.length > 0
|
||||
? { mathCharacterIndexes }
|
||||
: {}),
|
||||
...(hardBreakSegments.length > 1
|
||||
? { hardBreakSegments }
|
||||
: {}),
|
||||
...(inlineCode ? { hasInlineCode: true } : {}),
|
||||
...(tableCoordinates ?? {}),
|
||||
...(styleId ? { styleId } : {}),
|
||||
role: paragraphRole(styleId),
|
||||
blockKind: paragraphBlockKind(paragraph, styleId),
|
||||
@@ -236,7 +401,13 @@ function extractEditableContract(docxPath) {
|
||||
}
|
||||
return {
|
||||
text: paragraphs.map((paragraph) => paragraph.text).join(""),
|
||||
paragraphs
|
||||
paragraphs,
|
||||
translationInvariants: {
|
||||
inlineCodeParagraphCount,
|
||||
inlineCodeExactLineRuleCount,
|
||||
pureMathTableParagraphCount,
|
||||
stabilizedPureMathTableParagraphCount
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -338,6 +509,82 @@ function inspectInlineCodeContinuity(snapshot, label) {
|
||||
};
|
||||
}
|
||||
|
||||
function inspectCodeIndentation(snapshot, label, probe) {
|
||||
if (!probe) {
|
||||
return {
|
||||
label,
|
||||
applicable: false,
|
||||
passed: true,
|
||||
failures: []
|
||||
};
|
||||
}
|
||||
for (const page of snapshot.pages) {
|
||||
const nestedIndex = page.lines.findIndex((line) =>
|
||||
line.text.includes(probe.nestedLineIncludes)
|
||||
);
|
||||
if (nestedIndex < 0) {
|
||||
continue;
|
||||
}
|
||||
const nested = page.lines[nestedIndex];
|
||||
const root = page.lines
|
||||
.slice(0, nestedIndex)
|
||||
.reverse()
|
||||
.find(
|
||||
(line) =>
|
||||
line.text.trim() === probe.rootLine &&
|
||||
nested.baselineY - line.baselineY <= 30
|
||||
);
|
||||
if (!root) {
|
||||
continue;
|
||||
}
|
||||
const indentPt = nested.bounds.x - root.bounds.x;
|
||||
const passed = indentPt >= probe.minimumIndentPt;
|
||||
return {
|
||||
label,
|
||||
applicable: true,
|
||||
passed,
|
||||
pageNumber: page.pageNumber,
|
||||
rootText: root.text,
|
||||
nestedText: nested.text,
|
||||
rootXPt: root.bounds.x,
|
||||
nestedXPt: nested.bounds.x,
|
||||
indentPt,
|
||||
failures: passed
|
||||
? []
|
||||
: [
|
||||
`${label}: CODE_INDENTATION_MISMATCH - JSON 嵌套行缩进 ${indentPt.toFixed(2)}pt,小于 ${probe.minimumIndentPt}pt`
|
||||
]
|
||||
};
|
||||
}
|
||||
return {
|
||||
label,
|
||||
applicable: true,
|
||||
passed: false,
|
||||
failures: [
|
||||
`${label}: CODE_INDENTATION_PROBE_UNAVAILABLE - 未定位 JSON 根行与嵌套行`
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function docxTranslationInvariantFailures(invariants) {
|
||||
const failures = [];
|
||||
if (invariants.inlineCodeExactLineRuleCount > 0) {
|
||||
failures.push(
|
||||
`DOCX: INLINE_CODE_EXACT_LINE_BOX - ${invariants.inlineCodeExactLineRuleCount} 个行内代码段落仍使用 exact 行盒`
|
||||
);
|
||||
}
|
||||
if (
|
||||
invariants.pureMathTableParagraphCount !==
|
||||
invariants.stabilizedPureMathTableParagraphCount
|
||||
) {
|
||||
failures.push(
|
||||
"DOCX: TABLE_MATH_ALIGNMENT_UNSTABLE - " +
|
||||
`${invariants.pureMathTableParagraphCount - invariants.stabilizedPureMathTableParagraphCount} 个纯公式表格段落缺少对齐稳定结构`
|
||||
);
|
||||
}
|
||||
return failures;
|
||||
}
|
||||
|
||||
function buildPageSemantics(pageCount, coverPageCount, config) {
|
||||
assert(
|
||||
pageCount > coverPageCount,
|
||||
@@ -423,6 +670,108 @@ function reportCoverGateFailures(report, label) {
|
||||
.map((issue) => `${label}: ${issue.code} - ${issue.message}`);
|
||||
}
|
||||
|
||||
function metricRatio(first, second) {
|
||||
const maximum = Math.max(first, second);
|
||||
return maximum > 0 ? Math.min(first, second) / maximum : 1;
|
||||
}
|
||||
|
||||
const MAXIMUM_CORPUS_ELEMENT_COLOR_DELTA = 55;
|
||||
|
||||
function reportCorpusGateFailures(
|
||||
report,
|
||||
label,
|
||||
coverPageCount
|
||||
) {
|
||||
const failures = [];
|
||||
if ((report.basic.candidateEditableSimilarity ?? 0) < 0.995) {
|
||||
failures.push(
|
||||
`${label}: EDITABLE_CONTENT_SIMILARITY - Office 可编辑正文相似度 ` +
|
||||
`${((report.basic.candidateEditableSimilarity ?? 0) * 100).toFixed(3)}% 低于 99.5%`
|
||||
);
|
||||
}
|
||||
const strictKinds = new Set([
|
||||
"heading",
|
||||
"title",
|
||||
"caption",
|
||||
"table-header",
|
||||
"code-block"
|
||||
]);
|
||||
for (const comparison of report.basic.semanticBlockVisuals ?? []) {
|
||||
const index = comparison.expectation.index + 1;
|
||||
const visualUnavailable =
|
||||
comparison.status === "unavailable" ||
|
||||
comparison.lines.length === 0 ||
|
||||
comparison.issues?.some(
|
||||
(issue) => issue.code === "SEMANTIC_BLOCK_VISUAL_UNAVAILABLE"
|
||||
);
|
||||
if (visualUnavailable) {
|
||||
failures.push(
|
||||
`${label}: SEMANTIC_BLOCK_VISUAL_UNAVAILABLE - 第 ${index} 个元素块无法建立视觉观测`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
for (const line of comparison.lines) {
|
||||
const metrics = line.metrics;
|
||||
if (!metrics) {
|
||||
continue;
|
||||
}
|
||||
const heightRatio = metricRatio(
|
||||
metrics.baselineHeightPx,
|
||||
metrics.candidateHeightPx
|
||||
);
|
||||
if (heightRatio < 0.78) {
|
||||
failures.push(
|
||||
`${label}: ELEMENT_LINE_BOX_MISMATCH - 第 ${index} 个元素块第 ${line.lineIndex + 1} 行高度比例 ${heightRatio.toFixed(3)} 低于 0.78`
|
||||
);
|
||||
}
|
||||
const widthRatio = metricRatio(
|
||||
metrics.baselineWidthPx,
|
||||
metrics.candidateWidthPx
|
||||
);
|
||||
if (comparison.lines.length === 1 && widthRatio < 0.85) {
|
||||
failures.push(
|
||||
`${label}: ELEMENT_WIDTH_MISMATCH - 第 ${index} 个单行元素块宽度比例 ${widthRatio.toFixed(3)} 低于 0.85`
|
||||
);
|
||||
}
|
||||
const minimumIou = strictKinds.has(
|
||||
comparison.expectation.blockKind
|
||||
) ? 0.45 : 0.3;
|
||||
if (
|
||||
comparison.lines.length === 1 &&
|
||||
metrics.inkIou < minimumIou &&
|
||||
metrics.edgeIou < minimumIou
|
||||
) {
|
||||
failures.push(
|
||||
`${label}: ELEMENT_RASTER_MISMATCH - 第 ${index} 个元素块第 ${line.lineIndex + 1} 行墨迹与边缘 IoU 均低于 ${minimumIou}`
|
||||
);
|
||||
}
|
||||
const textReflow = line.issues?.some(
|
||||
(issue) => issue.details?.textReflow === true
|
||||
);
|
||||
const crossEngineRasterEquivalent = isCrossEngineRasterEquivalent(
|
||||
metrics,
|
||||
textReflow,
|
||||
comparison.expectation.blockKind,
|
||||
comparison.expectation.hasInlineCode === true
|
||||
);
|
||||
if (!textReflow &&
|
||||
!crossEngineRasterEquivalent &&
|
||||
!isCorpusTextColorEquivalent(metrics) && (
|
||||
metrics.backgroundColorDelta > MAXIMUM_CORPUS_ELEMENT_COLOR_DELTA ||
|
||||
metrics.foregroundColorDelta > MAXIMUM_CORPUS_ELEMENT_COLOR_DELTA
|
||||
)) {
|
||||
failures.push(
|
||||
`${label}: ELEMENT_COLOR_MISMATCH - 第 ${index} 个元素块第 ${line.lineIndex + 1} 行颜色差异超限`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (coverPageCount > 0) {
|
||||
failures.push(...reportCoverGateFailures(report, label));
|
||||
}
|
||||
return [...new Set(failures)];
|
||||
}
|
||||
|
||||
function reportSummary(report) {
|
||||
const paragraphIssues = report.basic.paragraphLayouts?.flatMap(
|
||||
(paragraph) => paragraph.issues
|
||||
@@ -599,9 +948,18 @@ assert(
|
||||
`视觉矩阵用例不存在:${selectedCaseId}`
|
||||
);
|
||||
assert(
|
||||
selectedScope === "full" || selectedScope === "cover",
|
||||
["full", "cover", "corpus"].includes(selectedScope),
|
||||
`不支持的视觉矩阵作用域:${selectedScope}`
|
||||
);
|
||||
assert(
|
||||
selectedScope !== "corpus" || corpusDefinition,
|
||||
`真实语料门禁缺少有效 MD_TO_PDF_CORPUS_ID:${selectedCorpusId}`
|
||||
);
|
||||
assert(
|
||||
selectedScope !== "corpus" ||
|
||||
Boolean(process.env.MD_TO_PDF_R4_MARKDOWN_PATH?.trim()),
|
||||
"真实语料门禁必须显式提供 MD_TO_PDF_R4_MARKDOWN_PATH"
|
||||
);
|
||||
assert(
|
||||
!selectedOrientation || ["portrait", "landscape"].includes(selectedOrientation),
|
||||
`不支持的视觉矩阵方向:${selectedOrientation}`
|
||||
@@ -632,6 +990,11 @@ for (const caseDefinition of selectedCases) {
|
||||
);
|
||||
const docxPath = path.resolve(repositoryDirectory, result.docx.outputFile);
|
||||
const editable = extractEditableContract(docxPath);
|
||||
const effectiveCoverPageCount = resolveEffectiveCoverPageCount({
|
||||
caseDefinition,
|
||||
scope: selectedScope,
|
||||
sourceMetadata: corpusSourceMetadata
|
||||
});
|
||||
const wordGeneration = await wordAdapter.generate({ docxPath });
|
||||
const wpsGeneration = await wpsAdapter.generate({ docxPath });
|
||||
const wordPdfPath = path.join(caseDirectory, "word.pdf");
|
||||
@@ -668,17 +1031,17 @@ for (const caseDefinition of selectedCases) {
|
||||
});
|
||||
const chromiumPageSemantics = buildPageSemantics(
|
||||
chromium.pageCount,
|
||||
caseDefinition.coverPageCount,
|
||||
effectiveCoverPageCount,
|
||||
result.exportConfig
|
||||
);
|
||||
const wordPageSemantics = buildPageSemantics(
|
||||
word.pageCount,
|
||||
caseDefinition.coverPageCount,
|
||||
effectiveCoverPageCount,
|
||||
result.exportConfig
|
||||
);
|
||||
const wpsPageSemantics = buildPageSemantics(
|
||||
wps.pageCount,
|
||||
caseDefinition.coverPageCount,
|
||||
effectiveCoverPageCount,
|
||||
result.exportConfig
|
||||
);
|
||||
const reportOptions = {
|
||||
@@ -715,6 +1078,23 @@ for (const caseDefinition of selectedCases) {
|
||||
word: inspectInlineCodeContinuity(word, "Microsoft Word"),
|
||||
wps: inspectInlineCodeContinuity(wps, "WPS Writer")
|
||||
};
|
||||
const codeIndentation = {
|
||||
chromium: inspectCodeIndentation(
|
||||
chromium,
|
||||
"Chromium",
|
||||
corpusDefinition?.codeIndentationProbe
|
||||
),
|
||||
word: inspectCodeIndentation(
|
||||
word,
|
||||
"Microsoft Word",
|
||||
corpusDefinition?.codeIndentationProbe
|
||||
),
|
||||
wps: inspectCodeIndentation(
|
||||
wps,
|
||||
"WPS Writer",
|
||||
corpusDefinition?.codeIndentationProbe
|
||||
)
|
||||
};
|
||||
|
||||
const rasterDirectory = path.join(caseDirectory, "raster-pages");
|
||||
fs.rmSync(rasterDirectory, { recursive: true, force: true });
|
||||
@@ -742,9 +1122,13 @@ for (const caseDefinition of selectedCases) {
|
||||
renderedFonts: wps.fonts,
|
||||
engine: "wps"
|
||||
}),
|
||||
...inlineCodeContinuity.chromium.failures,
|
||||
...inlineCodeContinuity.word.failures,
|
||||
...inlineCodeContinuity.wps.failures,
|
||||
...(expectInlineCodeProbes
|
||||
? [
|
||||
...inlineCodeContinuity.chromium.failures,
|
||||
...inlineCodeContinuity.word.failures,
|
||||
...inlineCodeContinuity.wps.failures
|
||||
]
|
||||
: []),
|
||||
...reportGateFailures(wordReport, "Chromium/Word"),
|
||||
...reportGateFailures(wpsReport, "Chromium/WPS"),
|
||||
...reportGateFailures(officeReport, "Word/WPS")
|
||||
@@ -755,7 +1139,46 @@ for (const caseDefinition of selectedCases) {
|
||||
...reportCoverGateFailures(wpsReport, "Chromium/WPS"),
|
||||
...reportCoverGateFailures(officeReport, "Word/WPS")
|
||||
])]
|
||||
: allCurrentFailures;
|
||||
: selectedScope === "corpus"
|
||||
? [...new Set([
|
||||
...pageGeometryFailures(chromium, caseDefinition, "Chromium"),
|
||||
...pageGeometryFailures(word, caseDefinition, "Microsoft Word"),
|
||||
...pageGeometryFailures(wps, caseDefinition, "WPS Writer"),
|
||||
...getDocxFontGateFailures({
|
||||
theme: themeDefinitions.find((theme) => theme.id === caseDefinition.themeId),
|
||||
inspection: result.docx.inspection,
|
||||
renderedFonts: word.fonts,
|
||||
engine: "word"
|
||||
}),
|
||||
...getDocxFontGateFailures({
|
||||
theme: themeDefinitions.find((theme) => theme.id === caseDefinition.themeId),
|
||||
inspection: result.docx.inspection,
|
||||
renderedFonts: wps.fonts,
|
||||
engine: "wps"
|
||||
}),
|
||||
...docxTranslationInvariantFailures(
|
||||
editable.translationInvariants
|
||||
),
|
||||
...codeIndentation.chromium.failures,
|
||||
...codeIndentation.word.failures,
|
||||
...codeIndentation.wps.failures,
|
||||
...reportCorpusGateFailures(
|
||||
wordReport,
|
||||
"Chromium/Word",
|
||||
effectiveCoverPageCount
|
||||
),
|
||||
...reportCorpusGateFailures(
|
||||
wpsReport,
|
||||
"Chromium/WPS",
|
||||
effectiveCoverPageCount
|
||||
),
|
||||
...reportCorpusGateFailures(
|
||||
officeReport,
|
||||
"Word/WPS",
|
||||
effectiveCoverPageCount
|
||||
)
|
||||
])]
|
||||
: allCurrentFailures;
|
||||
gateFailures.push(
|
||||
...currentFailures.map((failure) => `${caseDefinition.id}: ${failure}`)
|
||||
);
|
||||
@@ -772,7 +1195,7 @@ for (const caseDefinition of selectedCases) {
|
||||
themeDefaultMargins: caseDefinition.themeDefaultMargins,
|
||||
paper: caseDefinition.paper,
|
||||
exportConfig: result.exportConfig,
|
||||
coverPageCount: caseDefinition.coverPageCount,
|
||||
coverPageCount: effectiveCoverPageCount,
|
||||
pages: {
|
||||
chromium: chromium.pageCount,
|
||||
word: word.pageCount,
|
||||
@@ -787,7 +1210,9 @@ for (const caseDefinition of selectedCases) {
|
||||
wps: wps.fonts
|
||||
},
|
||||
editableParagraphCount: editable.paragraphs.length,
|
||||
translationInvariants: editable.translationInvariants,
|
||||
inlineCodeContinuity,
|
||||
codeIndentation,
|
||||
reports: {
|
||||
word: reportSummary(wordReport),
|
||||
wps: reportSummary(wpsReport),
|
||||
@@ -811,6 +1236,14 @@ const summary = {
|
||||
matrixDefinition,
|
||||
selectedCaseId,
|
||||
selectedScope,
|
||||
corpus: corpusDefinition
|
||||
? {
|
||||
id: corpusDefinition.id,
|
||||
label: corpusDefinition.label,
|
||||
markdownPath: corpusDefinition.markdownPath,
|
||||
requiredFeatures: corpusDefinition.requiredFeatures
|
||||
}
|
||||
: undefined,
|
||||
selectedOrientation,
|
||||
selectedMarginScenarioId,
|
||||
execution: {
|
||||
|
||||
Reference in New Issue
Block a user