test: 完成 DOCX 全主题视觉验收
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
WORD_NAMESPACE,
|
||||
appendElement,
|
||||
colorValue,
|
||||
millimetersToTwips,
|
||||
parseXmlPart,
|
||||
pointsToHalfPoints,
|
||||
serializeXmlPart,
|
||||
@@ -173,37 +174,50 @@ function appendFooterContent(
|
||||
}
|
||||
}
|
||||
|
||||
function appendThreeColumnTable(
|
||||
root: XmlElement,
|
||||
function appendParagraphProperties(
|
||||
paragraph: XmlElement,
|
||||
options: {
|
||||
alignment?: "left" | "center" | "right";
|
||||
position: "header" | "footer";
|
||||
divider: boolean;
|
||||
dividerColor: string;
|
||||
alignments: readonly ["left", "center", "right"];
|
||||
render: (
|
||||
paragraph: XmlElement,
|
||||
alignment: "left" | "center" | "right"
|
||||
) => void;
|
||||
centerTabTwips?: number;
|
||||
rightTabTwips?: number;
|
||||
}
|
||||
) {
|
||||
const table = appendElement(root, WORD_NAMESPACE, "w:tbl");
|
||||
const tableProperties = appendElement(
|
||||
table,
|
||||
const properties = appendElement(
|
||||
paragraph,
|
||||
WORD_NAMESPACE,
|
||||
"w:tblPr"
|
||||
"w:pPr"
|
||||
);
|
||||
appendElement(tableProperties, WORD_NAMESPACE, "w:tblW", {
|
||||
"w:w": "5000",
|
||||
"w:type": "pct"
|
||||
});
|
||||
appendElement(tableProperties, WORD_NAMESPACE, "w:tblLayout", {
|
||||
"w:type": "fixed"
|
||||
appendElement(properties, WORD_NAMESPACE, "w:spacing", {
|
||||
"w:before": "0",
|
||||
"w:after": "0"
|
||||
});
|
||||
if (options.alignment) {
|
||||
appendElement(properties, WORD_NAMESPACE, "w:jc", {
|
||||
"w:val": options.alignment
|
||||
});
|
||||
}
|
||||
if (
|
||||
options.centerTabTwips !== undefined &&
|
||||
options.rightTabTwips !== undefined
|
||||
) {
|
||||
const tabs = appendElement(properties, WORD_NAMESPACE, "w:tabs");
|
||||
appendElement(tabs, WORD_NAMESPACE, "w:tab", {
|
||||
"w:val": "center",
|
||||
"w:pos": String(options.centerTabTwips)
|
||||
});
|
||||
appendElement(tabs, WORD_NAMESPACE, "w:tab", {
|
||||
"w:val": "right",
|
||||
"w:pos": String(options.rightTabTwips)
|
||||
});
|
||||
}
|
||||
if (options.divider) {
|
||||
const borders = appendElement(
|
||||
tableProperties,
|
||||
properties,
|
||||
WORD_NAMESPACE,
|
||||
"w:tblBorders"
|
||||
"w:pBdr"
|
||||
);
|
||||
appendElement(
|
||||
borders,
|
||||
@@ -217,41 +231,46 @@ function appendThreeColumnTable(
|
||||
}
|
||||
);
|
||||
}
|
||||
const grid = appendElement(table, WORD_NAMESPACE, "w:tblGrid");
|
||||
for (let index = 0; index < 3; index += 1) {
|
||||
appendElement(grid, WORD_NAMESPACE, "w:gridCol", {
|
||||
"w:w": "2400"
|
||||
});
|
||||
}
|
||||
const row = appendElement(table, WORD_NAMESPACE, "w:tr");
|
||||
for (const alignment of options.alignments) {
|
||||
const cell = appendElement(row, WORD_NAMESPACE, "w:tc");
|
||||
const cellProperties = appendElement(
|
||||
cell,
|
||||
WORD_NAMESPACE,
|
||||
"w:tcPr"
|
||||
);
|
||||
appendElement(cellProperties, WORD_NAMESPACE, "w:tcW", {
|
||||
"w:w": "1667",
|
||||
"w:type": "pct"
|
||||
});
|
||||
const paragraph = appendElement(cell, WORD_NAMESPACE, "w:p");
|
||||
const paragraphProperties = appendElement(
|
||||
paragraph,
|
||||
WORD_NAMESPACE,
|
||||
"w:pPr"
|
||||
);
|
||||
appendElement(paragraphProperties, WORD_NAMESPACE, "w:jc", {
|
||||
"w:val": alignment
|
||||
});
|
||||
options.render(paragraph, alignment);
|
||||
}
|
||||
|
||||
function appendTab(paragraph: XmlElement) {
|
||||
const run = appendElement(paragraph, WORD_NAMESPACE, "w:r");
|
||||
appendElement(run, WORD_NAMESPACE, "w:tab");
|
||||
}
|
||||
|
||||
function appendHeaderParagraph(
|
||||
root: XmlElement,
|
||||
options: {
|
||||
divider: boolean;
|
||||
dividerColor: string;
|
||||
contentWidthMm: number;
|
||||
render: (
|
||||
paragraph: XmlElement,
|
||||
alignment: "left" | "center" | "right"
|
||||
) => void;
|
||||
}
|
||||
) {
|
||||
const paragraph = appendElement(root, WORD_NAMESPACE, "w:p");
|
||||
const contentWidthTwips = millimetersToTwips(options.contentWidthMm);
|
||||
appendParagraphProperties(paragraph, {
|
||||
position: "header",
|
||||
divider: options.divider,
|
||||
dividerColor: options.dividerColor,
|
||||
centerTabTwips: Math.round(contentWidthTwips / 2),
|
||||
rightTabTwips: contentWidthTwips
|
||||
});
|
||||
options.render(paragraph, "left");
|
||||
appendTab(paragraph);
|
||||
options.render(paragraph, "center");
|
||||
appendTab(paragraph);
|
||||
options.render(paragraph, "right");
|
||||
}
|
||||
|
||||
function createHeaderPart(
|
||||
header: HeaderConfig,
|
||||
options: DynamicReferenceDocxOptions,
|
||||
fallbackFont: string
|
||||
fallbackFont: string,
|
||||
contentWidthMm: number
|
||||
) {
|
||||
const document = createWordPart("hdr");
|
||||
const style: RunStyle = {
|
||||
@@ -259,11 +278,10 @@ function createHeaderPart(
|
||||
sizePt: (lengthToMillimeters(header.fontSize) * 72) / 25.4,
|
||||
color: header.color
|
||||
};
|
||||
appendThreeColumnTable(document.documentElement!, {
|
||||
position: "header",
|
||||
appendHeaderParagraph(document.documentElement!, {
|
||||
divider: header.showDivider,
|
||||
dividerColor: header.color,
|
||||
alignments: ["left", "center", "right"],
|
||||
contentWidthMm,
|
||||
render: (paragraph, alignment) => {
|
||||
const slot = header[alignment];
|
||||
if (slot.enabled) {
|
||||
@@ -290,17 +308,20 @@ function createFooterPart(
|
||||
sizePt: (lengthToMillimeters(footer.fontSize) * 72) / 25.4,
|
||||
color: footer.color
|
||||
};
|
||||
appendThreeColumnTable(document.documentElement!, {
|
||||
const paragraph = appendElement(
|
||||
document.documentElement!,
|
||||
WORD_NAMESPACE,
|
||||
"w:p"
|
||||
);
|
||||
appendParagraphProperties(paragraph, {
|
||||
position: "footer",
|
||||
divider: footer.showDivider,
|
||||
dividerColor: footer.color,
|
||||
alignments: ["left", "center", "right"],
|
||||
render: (paragraph, cellAlignment) => {
|
||||
if (!empty && cellAlignment === alignment) {
|
||||
appendFooterContent(paragraph, footer, style);
|
||||
}
|
||||
}
|
||||
alignment
|
||||
});
|
||||
if (!empty) {
|
||||
appendFooterContent(paragraph, footer, style);
|
||||
}
|
||||
return serializeXmlPart(document);
|
||||
}
|
||||
|
||||
@@ -427,7 +448,8 @@ export function createHeaderFooterParts(
|
||||
options: DynamicReferenceDocxOptions,
|
||||
header: HeaderConfig,
|
||||
footer: FooterConfig,
|
||||
fallbackFont: string
|
||||
fallbackFont: string,
|
||||
contentWidthMm: number
|
||||
): HeaderFooterTransformResult {
|
||||
const entries = new Map(sourceEntries);
|
||||
removeExistingHeaderFooterParts(entries);
|
||||
@@ -447,7 +469,8 @@ export function createHeaderFooterParts(
|
||||
const headerContent = createHeaderPart(
|
||||
header,
|
||||
options,
|
||||
fallbackFont
|
||||
fallbackFont,
|
||||
contentWidthMm
|
||||
);
|
||||
for (const type of [
|
||||
"default",
|
||||
|
||||
@@ -91,7 +91,8 @@ export function createDynamicReferenceDocx(
|
||||
options,
|
||||
page.header,
|
||||
page.footer,
|
||||
style.body.fonts.eastAsia
|
||||
style.body.fonts.eastAsia,
|
||||
page.dimensions.width - page.margins.left - page.margins.right
|
||||
);
|
||||
const entries = withDecorations.entries;
|
||||
entries.set(
|
||||
|
||||
@@ -191,6 +191,13 @@ function validateReferencedParts(
|
||||
`${relationship.targetPart} 的页眉页脚根元素无效`
|
||||
);
|
||||
}
|
||||
if (
|
||||
root.getElementsByTagNameNS(WORD_NAMESPACE, "tbl").length > 0
|
||||
) {
|
||||
throw new Error(
|
||||
`${relationship.targetPart} 不得使用表格模拟页眉页脚布局`
|
||||
);
|
||||
}
|
||||
}
|
||||
return references.length;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user