feat: 完成 DOCX R4 元素级视觉门禁

建立 Chromium、Word 与 WPS 的可编辑内容、段落几何、页眉页脚、封面、媒体和逐页视觉比较链路。

支持封面独立分节、正文页码重启、主题页边距与横向纸张,并将自然分页差异降为诊断项。

修正代码块内边距和折叠表格单元格边框映射,14 套主题生产矩阵与六组布局视觉矩阵通过。
This commit is contained in:
SkyJourney
2026-08-02 03:27:11 +08:00
parent f9f5fccfc9
commit 58f87cc19f
100 changed files with 10409 additions and 386 deletions
@@ -9,7 +9,9 @@ import {
import {
PandocDocxConverter,
PandocRuntime,
inspectDocxAcceptance
createDocxMediaAcceptanceExpectation,
inspectDocxAcceptance,
resolveReferencePageOptions
} from "@md-to-pdf/docx-engine";
import { DOCX_STYLE_SLOT_NAMES } from "@md-to-pdf/docx-theme-engine";
@@ -49,6 +51,7 @@ function mediaResource(kind, ordinal, dimensions, altText, caption) {
kindOrdinal: 1,
altText,
...(caption ? { caption } : {}),
alignment: "center",
displayWidthPx: dimensions.width,
displayHeightPx: dimensions.height,
captureX: 0,
@@ -115,6 +118,58 @@ function createThemeTokens(theme) {
const technicalTheme = loadTheme("typora-like");
const officialTheme = loadTheme("gov-red-standard");
const coverTheme = loadTheme("formal-feasibility");
const semanticDocument = {
schemaVersion: 1,
titlePolicy: {
metadataTitle: "emit",
firstBodyHeading: "keep"
},
regions: []
};
const coverSemanticDocument = {
schemaVersion: 1,
profile: "project-report",
titlePolicy: {
metadataTitle: "suppress",
firstBodyHeading: "keep"
},
regions: [
{
kind: "cover",
nodes: [
{
kind: "group",
role: "project-report-cover",
children: [
{
kind: "text",
role: "project-report-project-name",
text: "智慧园区建设项目"
},
{
kind: "text",
role: "project-report-title",
text: "可行性研究报告"
},
{
kind: "text",
role: "project-report-owner",
label: "建设单位:",
text: "示例建设集团"
}
]
}
],
section: {
headerFooter: "none",
pageNumber: "hidden",
breakAfter: "next-page",
followingPageNumberStart: 1
}
}
]
};
const commonExpectation = {
requiredText: [
"可编辑的中文正文",
@@ -214,6 +269,18 @@ const landscapeLetterConfig = {
startFrom: 3
}
};
const coverA4Config = {
...defaultExportConfig,
name: "可研封面 A4 纵向验收",
themeId: coverTheme.id,
pageDecorationsMode: "theme",
paper: {
...defaultExportConfig.paper,
format: "A4",
orientation: "portrait",
marginMode: "theme"
}
};
const variants = [
{
@@ -275,6 +342,41 @@ const variants = [
}
}
}
},
{
id: "project-cover-a4-portrait",
theme: coverTheme,
exportConfig: coverA4Config,
semanticDocument: coverSemanticDocument,
expectation: {
...commonExpectation,
id: "project-cover-a4-portrait",
requiredText: [
...commonExpectation.requiredText,
"智慧园区建设项目",
"可行性研究报告"
],
page: {
widthTwips: 11906,
heightTwips: 16838,
orientation: "portrait",
marginsTwips: {
top: 1417,
right: 1134,
bottom: 1417,
left: 1701
}
},
minimumSections: 2,
requireFirstSectionWithoutHeaderFooter: true,
finalPageNumberStart: 1,
requireSectionPagesField: true,
requiredParagraphStyleIds: [
"MdProjectReportProjectName",
"MdProjectReportTitle",
"MdProjectReportOwner"
]
}
}
];
@@ -299,33 +401,51 @@ fs.mkdirSync(outputDirectory, { recursive: true });
const results = [];
for (const variant of variants) {
const variantSemanticDocument =
variant.semanticDocument ?? semanticDocument;
const fileName = `${variant.id}.md`;
const metadata = {
title: "v0.6.0 DOCX 自动验收",
author: "Markdown PDF 导出器研发组",
subject: "Word 与 WPS 可编辑性验收",
keywords: ["DOCX", "Pandoc", "OOXML"],
language: "zh-CN"
};
const resolvedPage = resolveReferencePageOptions({
exportConfig: variant.exportConfig,
theme: variant.theme,
fileName,
metadata
});
const media = createMedia();
const result = await converter.convert({
markdown,
fileName: `${variant.id}.md`,
fileName,
language: "zh-CN",
exportConfig: variant.exportConfig,
theme: variant.theme,
themeTokens: createThemeTokens(variant.theme),
metadata: {
title: "v0.6.0 DOCX 自动验收",
author: "Markdown PDF 导出器研发组",
subject: "Word 与 WPS 可编辑性验收",
keywords: ["DOCX", "Pandoc", "OOXML"],
language: "zh-CN"
},
semanticDocument: {
schemaVersion: 1,
titlePolicy: {
metadataTitle: "emit",
firstBodyHeading: "keep"
},
regions: []
},
media: createMedia()
fonts: [],
metadata,
semanticDocument: variantSemanticDocument,
media
});
const report = inspectDocxAcceptance(
result.docx,
variant.expectation
{
...variant.expectation,
media: createDocxMediaAcceptanceExpectation(
media,
variant.expectation.page
),
pageDecorations: {
exportConfig: {
header: resolvedPage.header,
footer: resolvedPage.footer
},
semanticDocument: variantSemanticDocument
}
}
);
const outputPath = path.join(outputDirectory, `${variant.id}.docx`);
fs.writeFileSync(outputPath, result.docx);