feat: 完成 DOCX R4 元素级视觉门禁
建立 Chromium、Word 与 WPS 的可编辑内容、段落几何、页眉页脚、封面、媒体和逐页视觉比较链路。 支持封面独立分节、正文页码重启、主题页边距与横向纸张,并将自然分页差异降为诊断项。 修正代码块内边距和折叠表格单元格边框映射,14 套主题生产矩阵与六组布局视觉矩阵通过。
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
readGeneratedDocxPackage,
|
||||
readReferenceDocxPackage,
|
||||
writeGeneratedDocxPackage,
|
||||
type PandocMediaLayoutItem,
|
||||
type PandocStructurePlan
|
||||
} from "../src/index.js";
|
||||
import { createTestBaselineReference } from "./reference-test-fixture.js";
|
||||
@@ -15,6 +16,12 @@ const word =
|
||||
"http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
||||
const relationships =
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
||||
const wordprocessingDrawing =
|
||||
"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
|
||||
const drawing =
|
||||
"http://schemas.openxmlformats.org/drawingml/2006/main";
|
||||
const picture =
|
||||
"http://schemas.openxmlformats.org/drawingml/2006/picture";
|
||||
|
||||
function generatedFixture() {
|
||||
const baseline = readReferenceDocxPackage(
|
||||
@@ -26,11 +33,17 @@ function generatedFixture() {
|
||||
encoder.encode(
|
||||
`<w:document xmlns:w="${word}" xmlns:r="${relationships}"><w:body>` +
|
||||
`<w:p><w:r><w:t>CONTAINER_START</w:t></w:r></w:p>` +
|
||||
`<w:p><w:pPr><w:pStyle w:val="MdOfficialIssueRow"/></w:pPr><w:r><w:t>发文字号</w:t></w:r><w:r><w:tab/></w:r><w:r><w:t>签发人</w:t></w:r></w:p>` +
|
||||
`<w:p><w:pPr><w:pStyle w:val="MdTenderTitle"/></w:pPr><w:r><w:t>可编辑封面</w:t></w:r></w:p>` +
|
||||
`<w:p><w:r><w:t>CONTAINER_END</w:t></w:r></w:p>` +
|
||||
`<w:p><w:r><w:t>SECTION_BREAK</w:t></w:r></w:p>` +
|
||||
`<w:p><w:pPr><w:pStyle w:val="Heading1"/></w:pPr><w:r><w:t>正文标题</w:t></w:r></w:p>` +
|
||||
`<w:tbl><w:tblPr><w:tblW w:w="0" w:type="auto"/></w:tblPr><w:tblGrid><w:gridCol w:w="1000"/><w:gridCol w:w="2000"/></w:tblGrid><w:tr><w:tc><w:tcPr/><w:p><w:r><w:t>A</w:t></w:r></w:p></w:tc><w:tc><w:tcPr/><w:p><w:r><w:t>B</w:t></w:r></w:p></w:tc></w:tr></w:tbl>` +
|
||||
`<w:p><w:pPr><w:pStyle w:val="FirstParagraph"/></w:pPr><w:r><w:drawing>` +
|
||||
`<wp:inline xmlns:wp="${wordprocessingDrawing}"><wp:extent cx="100" cy="200"/><wp:docPr id="1" name="Picture" descr="Mermaid 图表 1" title="mdtp-media:docx-media-1"/>` +
|
||||
`<a:graphic xmlns:a="${drawing}"><a:graphicData uri="${picture}"><pic:pic xmlns:pic="${picture}"><pic:nvPicPr><pic:cNvPr id="0" name="image.png"/></pic:nvPicPr><pic:blipFill><a:blip r:embed="rIdImage"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm rot="60000" flipH="1"><a:off x="0" y="0"/><a:ext cx="100" cy="200"/></a:xfrm></pic:spPr></pic:pic></a:graphicData></a:graphic>` +
|
||||
`</wp:inline></w:drawing></w:r></w:p>` +
|
||||
`<w:tbl><w:tblPr><w:tblW w:w="0" w:type="auto"/></w:tblPr><w:tblGrid><w:gridCol w:w="1000"/><w:gridCol w:w="2000"/></w:tblGrid><w:tr><w:trPr><w:tblHeader/></w:trPr><w:tc><w:tcPr/><w:p><w:r><w:t>A</w:t></w:r></w:p></w:tc><w:tc><w:tcPr/><w:p><w:r><w:t>B</w:t></w:r></w:p></w:tc></w:tr><w:tr><w:tc><w:tcPr/><w:p><w:r><w:t>C</w:t></w:r></w:p></w:tc><w:tc><w:tcPr/><w:p><w:r><w:t>D</w:t></w:r></w:p></w:tc></w:tr></w:tbl>` +
|
||||
`<w:p><w:pPr><w:pStyle w:val="MdOfficialSignatureDate"/></w:pPr><w:r><w:t>2026年7月29日</w:t></w:r></w:p>` +
|
||||
`<w:sectPr><w:footerReference w:type="default" r:id="rIdFooter"/><w:pgSz w:w="11906" w:h="16838"/><w:pgMar w:top="1000" w:right="1000" w:bottom="1000" w:left="1000"/><w:pgNumType w:start="5"/><w:titlePg/></w:sectPr>` +
|
||||
`</w:body></w:document>`
|
||||
)
|
||||
@@ -46,6 +59,9 @@ function generatedFixture() {
|
||||
|
||||
const plan: PandocStructurePlan = {
|
||||
schemaVersion: 1,
|
||||
metadataPolicy: {
|
||||
author: "suppress"
|
||||
},
|
||||
titlePolicy: {
|
||||
metadataTitle: "suppress",
|
||||
firstBodyHeading: "keep"
|
||||
@@ -57,7 +73,15 @@ const plan: PandocStructurePlan = {
|
||||
slot: "tender-cover",
|
||||
startMarker: "CONTAINER_START",
|
||||
endMarker: "CONTAINER_END",
|
||||
blocks: []
|
||||
blocks: [
|
||||
{
|
||||
kind: "paragraph",
|
||||
styleId: "MdOfficialIssueRow",
|
||||
layout: "space-between",
|
||||
segments: ["发文字号", "签发人"],
|
||||
separator: "tab"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
kind: "section-break",
|
||||
@@ -85,6 +109,17 @@ const tokens: DocxThemeTokenSet = {
|
||||
style: {
|
||||
fontCandidates: [],
|
||||
backgroundColor: "#f5f5f5",
|
||||
spacingBeforePt: 7,
|
||||
spacingAfterPt: 51,
|
||||
paddingPt: {
|
||||
top: 3,
|
||||
right: 5,
|
||||
bottom: 11,
|
||||
left: 4
|
||||
},
|
||||
widthPercent: 52,
|
||||
leftIndentPt: 230.4,
|
||||
rightIndentPt: 0,
|
||||
pageBreakAfter: true,
|
||||
keepLines: true,
|
||||
borders: {
|
||||
@@ -121,6 +156,30 @@ const tokens: DocxThemeTokenSet = {
|
||||
keepLines: true
|
||||
}
|
||||
},
|
||||
{
|
||||
slot: "table-cell",
|
||||
source: "computed-css",
|
||||
confidence: "exact",
|
||||
style: {
|
||||
fontCandidates: [],
|
||||
fontSizePt: 10,
|
||||
lineSpacing: 1.8
|
||||
}
|
||||
},
|
||||
{
|
||||
slot: "table-header",
|
||||
source: "computed-css",
|
||||
confidence: "exact",
|
||||
style: {
|
||||
fontCandidates: [],
|
||||
fontSizePt: 10,
|
||||
bold: true,
|
||||
lineSpacing: 1.5,
|
||||
alignment: "center",
|
||||
color: "#ffffff",
|
||||
backgroundColor: "#17324d"
|
||||
}
|
||||
},
|
||||
{
|
||||
slot: "heading-1",
|
||||
source: "computed-css",
|
||||
@@ -134,8 +193,23 @@ const tokens: DocxThemeTokenSet = {
|
||||
diagnostics: []
|
||||
};
|
||||
|
||||
const mediaLayout: PandocMediaLayoutItem[] = [
|
||||
{
|
||||
id: "docx-media-1",
|
||||
binding: "mdtp-media:docx-media-1",
|
||||
kind: "mermaid",
|
||||
ordinal: 1,
|
||||
altText: "处理流程",
|
||||
alignment: "right",
|
||||
displayWidthPx: 320,
|
||||
displayHeightPx: 180,
|
||||
widthEmu: 3_048_000,
|
||||
heightEmu: 1_714_500
|
||||
}
|
||||
];
|
||||
|
||||
describe("生成 DOCX 结构收口", () => {
|
||||
it("生成真实分节、正文页码、节总页数和固定宽度表格", () => {
|
||||
it("生成真实分节、正文页码、节总页数和全宽自适应表格", () => {
|
||||
const result = finalizeGeneratedDocxStructure(
|
||||
generatedFixture(),
|
||||
plan,
|
||||
@@ -170,26 +244,367 @@ describe("生成 DOCX 结构收口", () => {
|
||||
'<w:tblW w:w="5000" w:type="pct"/>'
|
||||
);
|
||||
expect(documentXml).toContain(
|
||||
'<w:tblLayout w:type="fixed"/>'
|
||||
'<w:tblLayout w:type="autofit"/>'
|
||||
);
|
||||
expect(documentXml).toContain('<w:gridCol w:w="3302"/>');
|
||||
expect(documentXml).toContain('<w:gridCol w:w="6604"/>');
|
||||
expect(documentXml).toMatch(
|
||||
/<w:pPr><w:pStyle w:val="Figure"\/>[\s\S]*?<w:autoSpaceDE w:val="0"\/><w:autoSpaceDN w:val="0"\/><w:spacing[^>]*w:line="240"[^>]*w:lineRule="auto"[^>]*w:before="300"[^>]*w:after="300"\/><w:jc w:val="center"\/>/u
|
||||
);
|
||||
expect(documentXml).toContain('<w:gridCol w:w="1000"/>');
|
||||
expect(documentXml).toContain('<w:gridCol w:w="2000"/>');
|
||||
expect(documentXml).toContain('<w:tcW w:w="0" w:type="auto"/>');
|
||||
expect(
|
||||
documentXml.match(
|
||||
/<w:spacing[^>]*w:before="0"[^>]*w:after="0"[^>]*w:line="300"[^>]*w:lineRule="exact"/gu
|
||||
)
|
||||
).toHaveLength(2);
|
||||
expect(
|
||||
documentXml.match(
|
||||
/<w:spacing[^>]*w:before="0"[^>]*w:after="0"[^>]*w:line="360"[^>]*w:lineRule="exact"/gu
|
||||
)
|
||||
).toHaveLength(2);
|
||||
expect(documentXml.match(/w:fill="17324D"/gu)).toHaveLength(2);
|
||||
expect(documentXml.match(/w:val="FFFFFF"/gu)).toHaveLength(2);
|
||||
expect(
|
||||
documentXml.match(/<w:jc w:val="center"\/>/gu)?.length ?? 0
|
||||
).toBeGreaterThanOrEqual(2);
|
||||
const tableParagraph = documentXml.match(
|
||||
/<w:p>[\s\S]*?<w:t>A<\/w:t>[\s\S]*?<\/w:p>/u
|
||||
)?.[0];
|
||||
expect(tableParagraph).toContain('<w:autoSpaceDE w:val="0"/>');
|
||||
expect(tableParagraph).toContain('<w:autoSpaceDN w:val="0"/>');
|
||||
expect(documentXml).toContain("<w:cantSplit/>");
|
||||
expect(documentXml).toContain(
|
||||
'<w:tab w:val="right" w:pos="9806"/>'
|
||||
);
|
||||
expect(documentXml).toContain('w:fill="F5F5F5"');
|
||||
expect(documentXml).toContain(
|
||||
'<w:spacing w:before="140"/>'
|
||||
);
|
||||
expect(documentXml).toContain(
|
||||
'<w:spacing w:after="1020"/>'
|
||||
);
|
||||
expect(
|
||||
documentXml.match(
|
||||
/<w:ind w:left="4835" w:right="100"\/>/gu
|
||||
)
|
||||
).toHaveLength(2);
|
||||
expect(documentXml).toContain(
|
||||
'<w:top w:val="single" w:sz="8" w:space="3" w:color="111111"/>'
|
||||
);
|
||||
expect(documentXml).toContain(
|
||||
'<w:bottom w:val="single" w:sz="8" w:space="11" w:color="111111"/>'
|
||||
);
|
||||
expect(
|
||||
documentXml.match(/<w:br w:type="page"\/>/gu)
|
||||
).toHaveLength(1);
|
||||
const coverParagraph = documentXml.match(
|
||||
/<w:p(?:\s|>)[\s\S]*?可编辑封面[\s\S]*?<\/w:p>/u
|
||||
)?.[0];
|
||||
expect(coverParagraph).toContain("<w:sectPr>");
|
||||
expect(coverParagraph).not.toContain(
|
||||
'<w:br w:type="page"/>'
|
||||
);
|
||||
expect(footerXml).toContain("SECTIONPAGES");
|
||||
expect(footerXml).not.toContain(" NUMPAGES ");
|
||||
expect(result.report).toEqual({
|
||||
containerCount: 1,
|
||||
editableContainerTableCount: 0,
|
||||
sectionCount: 1,
|
||||
tableCount: 1,
|
||||
pageBreakAfterCount: 1,
|
||||
sectionPageFieldCount: 1
|
||||
sectionPageFieldCount: 1,
|
||||
spaceBetweenParagraphCount: 1,
|
||||
mediaDrawingCount: 1
|
||||
});
|
||||
});
|
||||
|
||||
it("按稳定媒体计划规范化内联尺寸、比例锁和对齐", () => {
|
||||
const result = finalizeGeneratedDocxStructure(
|
||||
generatedFixture(),
|
||||
plan,
|
||||
tokens,
|
||||
mediaLayout
|
||||
);
|
||||
const documentXml = decoder.decode(
|
||||
readGeneratedDocxPackage(result.content).entries.get(
|
||||
"word/document.xml"
|
||||
)!
|
||||
);
|
||||
|
||||
expect(result.report.mediaDrawingCount).toBe(1);
|
||||
expect(documentXml).toMatch(
|
||||
/<wp:inline\b[^>]*distT="0"[^>]*distB="0"[^>]*distL="0"[^>]*distR="0"[^>]*>/u
|
||||
);
|
||||
expect(documentXml).toContain(
|
||||
'<wp:extent cx="3048000" cy="1714500"/>'
|
||||
);
|
||||
expect(documentXml).toContain(
|
||||
'<a:ext cx="3048000" cy="1714500"/>'
|
||||
);
|
||||
expect(documentXml).toMatch(
|
||||
/<a:graphicFrameLocks\b[^>]*noChangeAspect="1"[^>]*\/>/u
|
||||
);
|
||||
expect(documentXml).toContain(
|
||||
'descr="处理流程" title=""'
|
||||
);
|
||||
expect(documentXml).toContain('<w:jc w:val="right"/>');
|
||||
expect(documentXml).not.toContain("mdtp-media:");
|
||||
expect(documentXml).not.toContain('rot="60000"');
|
||||
expect(documentXml).not.toContain('flipH="1"');
|
||||
});
|
||||
|
||||
it("拒绝缺失或重复的媒体布局绑定", () => {
|
||||
expect(() =>
|
||||
finalizeGeneratedDocxStructure(
|
||||
generatedFixture(),
|
||||
plan,
|
||||
tokens,
|
||||
[{ ...mediaLayout[0]!, binding: "mdtp-media:missing" }]
|
||||
)
|
||||
).toThrow("稳定绑定");
|
||||
|
||||
expect(() =>
|
||||
finalizeGeneratedDocxStructure(
|
||||
generatedFixture(),
|
||||
plan,
|
||||
tokens,
|
||||
[mediaLayout[0]!, { ...mediaLayout[0]!, id: "duplicate" }]
|
||||
)
|
||||
).toThrow("重复绑定");
|
||||
});
|
||||
|
||||
it("将带最小高度的分节封面映射为可编辑单元格容器", () => {
|
||||
const tableTokens: DocxThemeTokenSet = {
|
||||
...tokens,
|
||||
slots: tokens.slots.map((entry) =>
|
||||
entry.slot === "tender-cover"
|
||||
? {
|
||||
...entry,
|
||||
style: {
|
||||
...entry.style,
|
||||
minimumHeightPt: 620,
|
||||
verticalAlignment: "center" as const,
|
||||
childAlignment: "center" as const
|
||||
}
|
||||
}
|
||||
: entry
|
||||
).concat({
|
||||
slot: "official-issue-row" as const,
|
||||
source: "computed-css" as const,
|
||||
confidence: "exact" as const,
|
||||
style: {
|
||||
fontCandidates: [],
|
||||
spacingAfterPt: 4
|
||||
}
|
||||
}, {
|
||||
slot: "tender-title" as const,
|
||||
source: "computed-css" as const,
|
||||
confidence: "exact" as const,
|
||||
style: {
|
||||
fontCandidates: [],
|
||||
fontSizePt: 20,
|
||||
letterSpacingPt: 1,
|
||||
spacingBeforePt: 6,
|
||||
borders: {
|
||||
bottom: {
|
||||
widthPt: 1,
|
||||
style: "single" as const,
|
||||
color: "#111111"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
const result = finalizeGeneratedDocxStructure(
|
||||
generatedFixture(),
|
||||
plan,
|
||||
tableTokens
|
||||
);
|
||||
const documentXml = decoder.decode(
|
||||
readGeneratedDocxPackage(result.content).entries.get(
|
||||
"word/document.xml"
|
||||
)!
|
||||
);
|
||||
|
||||
expect(documentXml).toContain("<w:tbl>");
|
||||
expect(documentXml).toContain(
|
||||
'<w:trHeight w:val="12400" w:hRule="exact"/>'
|
||||
);
|
||||
expect(documentXml).toContain(
|
||||
'<w:tblInd w:w="4608" w:type="dxa"/>'
|
||||
);
|
||||
expect(documentXml).toContain('<w:vAlign w:val="top"/>');
|
||||
expect(documentXml).toContain("<w:tblBorders>");
|
||||
expect(documentXml).toContain('<w:insideH w:val="nil"/>');
|
||||
expect(documentXml).toContain('<w:insideV w:val="nil"/>');
|
||||
expect(documentXml).toMatch(
|
||||
/<w:pPr><w:pStyle w:val="MdTenderTitle"\/><w:autoSpaceDE w:val="0"\/><w:autoSpaceDN w:val="0"\/><w:spacing w:before="0" w:after="0"\/><w:ind w:left="1456" w:right="1475"\/><w:jc w:val="center"\/>/u
|
||||
);
|
||||
expect(documentXml).toMatch(
|
||||
/<w:pPr><w:pStyle w:val="MdTenderTitle"\/>[\s\S]*?<w:rPr><w:spacing w:val="20"\/><\/w:rPr><w:t>可编辑封面<\/w:t>/u
|
||||
);
|
||||
expect(documentXml).toContain('<w:tblLook w:val="0000"');
|
||||
expect(documentXml).toMatch(
|
||||
/<\/w:tbl><w:p><w:pPr><w:autoSpaceDE w:val="0"\/><w:autoSpaceDN w:val="0"\/><w:sectPr>/u
|
||||
);
|
||||
expect(
|
||||
documentXml.match(/<w:tbl(?:\s|>)/gu)
|
||||
).toHaveLength(2);
|
||||
expect(result.report).toMatchObject({
|
||||
containerCount: 1,
|
||||
editableContainerTableCount: 1,
|
||||
sectionCount: 1,
|
||||
tableCount: 1
|
||||
});
|
||||
});
|
||||
|
||||
it("将封面最小高度封顶到当前节的页面内容区", () => {
|
||||
const oversizedTokens: DocxThemeTokenSet = {
|
||||
...tokens,
|
||||
slots: tokens.slots.map((entry) =>
|
||||
entry.slot === "tender-cover"
|
||||
? {
|
||||
...entry,
|
||||
style: {
|
||||
...entry.style,
|
||||
minimumHeightPt: 900,
|
||||
verticalAlignment: "center" as const,
|
||||
childAlignment: "left" as const
|
||||
}
|
||||
}
|
||||
: entry
|
||||
).concat(
|
||||
{
|
||||
slot: "official-issue-row" as const,
|
||||
source: "computed-css" as const,
|
||||
confidence: "exact" as const,
|
||||
style: {
|
||||
fontCandidates: [],
|
||||
fontSizePt: 12,
|
||||
lineSpacing: 1.5
|
||||
}
|
||||
},
|
||||
{
|
||||
slot: "tender-title" as const,
|
||||
source: "computed-css" as const,
|
||||
confidence: "exact" as const,
|
||||
style: {
|
||||
fontCandidates: [],
|
||||
fontSizePt: 20,
|
||||
lineSpacing: 1.2
|
||||
}
|
||||
}
|
||||
)
|
||||
};
|
||||
const result = finalizeGeneratedDocxStructure(
|
||||
generatedFixture(),
|
||||
plan,
|
||||
oversizedTokens
|
||||
);
|
||||
const documentXml = decoder.decode(
|
||||
readGeneratedDocxPackage(result.content).entries.get(
|
||||
"word/document.xml"
|
||||
)!
|
||||
);
|
||||
|
||||
expect(documentXml).toContain(
|
||||
'<w:trHeight w:val="14838" w:hRule="exact"/>'
|
||||
);
|
||||
expect(documentXml).toContain('<w:top w:w="0" w:type="dxa"/>');
|
||||
expect(documentXml).toContain('<w:bottom w:w="0" w:type="dxa"/>');
|
||||
expect(documentXml).toContain('<w:vAlign w:val="top"/>');
|
||||
expect(documentXml.match(/<w:cantSplit\/>/gu)).toHaveLength(3);
|
||||
expect(documentXml.match(/<w:t>\.<\/w:t>/gu)).toHaveLength(2);
|
||||
expect(documentXml.match(/<w:tbl(?:\s|>)/gu)).toHaveLength(2);
|
||||
expect(documentXml.match(/<w:trHeight\b/gu)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("将主题项目符号位置与 Word 悬挂缩进合并", () => {
|
||||
const source = readGeneratedDocxPackage(generatedFixture());
|
||||
const entries = new Map(source.entries);
|
||||
entries.set(
|
||||
"word/numbering.xml",
|
||||
encoder.encode(
|
||||
`<w:numbering xmlns:w="${word}"><w:abstractNum w:abstractNumId="1"><w:lvl w:ilvl="0"><w:numFmt w:val="bullet"/><w:pPr><w:ind w:left="720" w:hanging="360"/></w:pPr></w:lvl><w:lvl w:ilvl="1"><w:numFmt w:val="bullet"/><w:pPr><w:ind w:left="1440" w:hanging="360"/></w:pPr></w:lvl></w:abstractNum></w:numbering>`
|
||||
)
|
||||
);
|
||||
const listTokens: DocxThemeTokenSet = {
|
||||
...tokens,
|
||||
slots: tokens.slots.concat({
|
||||
slot: "unordered-list" as const,
|
||||
source: "computed-css" as const,
|
||||
confidence: "exact" as const,
|
||||
style: {
|
||||
fontCandidates: [],
|
||||
paddingPt: {
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 30
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
const result = finalizeGeneratedDocxStructure(
|
||||
writeGeneratedDocxPackage(entries),
|
||||
plan,
|
||||
listTokens
|
||||
);
|
||||
const numberingXml = decoder.decode(
|
||||
readGeneratedDocxPackage(result.content).entries.get(
|
||||
"word/numbering.xml"
|
||||
)!
|
||||
);
|
||||
|
||||
expect(numberingXml).toContain(
|
||||
'<w:ind w:left="960" w:hanging="360"/>'
|
||||
);
|
||||
expect(numberingXml).toContain(
|
||||
'<w:ind w:left="1680" w:hanging="360"/>'
|
||||
);
|
||||
});
|
||||
|
||||
it("按最终页面内容区重算右置百分比容器缩进", () => {
|
||||
const relativeTokens: DocxThemeTokenSet = {
|
||||
...tokens,
|
||||
slots: tokens.slots.map((entry) =>
|
||||
entry.slot === "tender-cover"
|
||||
? {
|
||||
...entry,
|
||||
confidence: "exact" as const,
|
||||
style: {
|
||||
fontCandidates: [],
|
||||
widthPercent: 52,
|
||||
leftIndentPt: 230.4,
|
||||
rightIndentPt: 0,
|
||||
keepLines: true
|
||||
}
|
||||
}
|
||||
: entry
|
||||
)
|
||||
};
|
||||
const result = finalizeGeneratedDocxStructure(
|
||||
generatedFixture(),
|
||||
plan,
|
||||
relativeTokens
|
||||
);
|
||||
const documentXml = decoder.decode(
|
||||
readGeneratedDocxPackage(result.content).entries.get(
|
||||
"word/document.xml"
|
||||
)!
|
||||
);
|
||||
|
||||
// A4 内容区为 11906 - 1000 - 1000 = 9906 twips;
|
||||
// 52% 右置容器的左侧空余应为 9906 × 48% = 4755 twips。
|
||||
expect(documentXml).toMatch(
|
||||
/<w:pPr><w:pStyle w:val="MdOfficialIssueRow"\/>[\s\S]*?<w:ind w:left="4755" w:right="0"\/>/u
|
||||
);
|
||||
expect(documentXml).not.toContain('w:left="4608"');
|
||||
expect(documentXml).toMatch(
|
||||
/<w:pPr><w:pStyle w:val="MdOfficialSignatureDate"\/><w:autoSpaceDE w:val="0"\/><w:autoSpaceDN w:val="0"\/>/u
|
||||
);
|
||||
});
|
||||
|
||||
it("拒绝缺失或重复的内部结构标记", () => {
|
||||
expect(() =>
|
||||
finalizeGeneratedDocxStructure(
|
||||
|
||||
Reference in New Issue
Block a user