fix: 重建DOCX视觉一致性门禁

This commit is contained in:
SkyJourney
2026-08-03 10:09:43 +08:00
parent 842c3f1782
commit 28c88faabb
11 changed files with 811 additions and 49 deletions
@@ -5,6 +5,7 @@ import { describe, expect, it } from "vitest";
import {
createPdfVisualDiffReport,
getBlockingVisualDiffIssues,
renderPdfVisualDiffHtml,
serializePdfVisualDiffJson,
type PdfDocumentSnapshot,
@@ -30,6 +31,40 @@ function raster(color: string): PdfPageRaster {
};
}
function flowRaster(color: string): PdfPageRaster {
const canvas = createCanvas(40, 50);
const context = canvas.getContext("2d");
context.fillStyle = "#ffffff";
context.fillRect(0, 0, 40, 50);
context.fillStyle = color;
context.fillRect(34, 5, 6, 40);
const png = Uint8Array.from(canvas.toBuffer("image/png"));
return {
widthPx: 40,
heightPx: 50,
dpi: 144,
sha256: createHash("sha256").update(png).digest("hex"),
png,
};
}
function blockRaster(color: string): PdfPageRaster {
const canvas = createCanvas(40, 50);
const context = canvas.getContext("2d");
context.fillStyle = "#ffffff";
context.fillRect(0, 0, 40, 50);
context.fillStyle = color;
context.fillRect(8, 12, 20, 10);
const png = Uint8Array.from(canvas.toBuffer("image/png"));
return {
widthPx: 40,
heightPx: 50,
dpi: 144,
sha256: createHash("sha256").update(png).digest("hex"),
png,
};
}
function snapshot(label: string, pageRaster: PdfPageRaster): PdfDocumentSnapshot {
return {
schemaVersion: 1,
@@ -82,7 +117,7 @@ function flowSnapshot(
items: [],
lines: [...lines],
contentText: lines.map((line) => line.normalizedText).join(""),
raster: raster(color),
raster: flowRaster(color),
})),
};
}
@@ -387,7 +422,7 @@ describe("PDF 视觉差异报告", () => {
);
});
it("整段跨页同时发生换行变化时仍保持严格失败", async () => {
it("整段跨页同时发生换行变化时由语义块门禁失败", async () => {
const baseline = flowSnapshot(
"baseline",
[[contentLine("甲", 8)], [contentLine("乙丙", 8)]],
@@ -411,7 +446,7 @@ describe("PDF 视觉差异报告", () => {
expect(report.status).toBe("failed");
expect(report.basic.bodyFlow).toMatchObject({ detected: true, legal: false });
expect(report.pages.every(
(page) => page.rasterComparisonMode === "strict",
(page) => page.rasterComparisonMode === "body-block",
)).toBe(true);
expect(report.issues.map((issue) => issue.code)).toContain(
"TEXT_BLOCK_LINE_COUNT_MISMATCH",
@@ -498,4 +533,78 @@ describe("PDF 视觉差异报告", () => {
"PAGE_NUMBER_VISIBILITY_MISMATCH",
);
});
it("正文整页差异只作诊断,但语义块局部视觉变化必须失败", async () => {
const baseline = flowSnapshot(
"baseline",
[[contentLine("甲", 8)]],
"#111111",
);
const candidate = flowSnapshot(
"candidate",
[[contentLine("甲", 8)]],
"#111111",
);
baseline.pages[0]!.raster = blockRaster("#111111");
candidate.pages[0]!.raster = blockRaster("#cc0000");
const report = await createPdfVisualDiffReport(baseline, candidate, {
expectedEditableText: "甲",
expectedEditableParagraphs: [flowParagraphs[0]!],
pageSemantics: bodySemantics(1),
});
expect(report.status).toBe("failed");
expect(report.pages[0]?.rasterComparisonMode).toBe("body-block");
expect(report.issues.map((issue) => issue.code)).toContain(
"BODY_PAGE_RASTER_DIFFERENCE",
);
expect(report.issues.map((issue) => issue.code)).toContain(
"SEMANTIC_BLOCK_RASTER_MISMATCH",
);
});
it("缺少语义块证据时不得把正文整页栅格自动降级", async () => {
const report = await createPdfVisualDiffReport(
snapshot("baseline", raster("#111111")),
snapshot("candidate", raster("#cc0000")),
{ pageSemantics: bodySemantics(1) },
);
expect(report.status).toBe("failed");
expect(report.pages[0]?.rasterComparisonMode).toBe("strict");
expect(report.issues.map((issue) => issue.code)).toContain(
"CHANGED_PIXEL_RATIO_EXCEEDED",
);
});
it("所有 failure 级视觉问题都必须进入发布阻断集合", () => {
const failure = {
code: "INK_IOU_BELOW_THRESHOLD" as const,
severity: "failure" as const,
message: "墨迹 IoU 未达到门限",
};
const warning = {
code: "BODY_FLOW_RASTER_DIFFERENCE" as const,
severity: "warning" as const,
message: "正文自然分页差异",
};
expect(getBlockingVisualDiffIssues({ issues: [failure, warning] })).toEqual([
failure,
]);
});
it("正文自然分页 warning 不阻断发布", () => {
expect(
getBlockingVisualDiffIssues({
issues: [
{
code: "BODY_FLOW_RASTER_DIFFERENCE",
severity: "warning",
message: "正文自然分页差异",
},
],
}),
).toEqual([]);
});
});