feat: 实现动态 DOCX 模板与样式映射
This commit is contained in:
@@ -0,0 +1,684 @@
|
||||
import type { DocxFontFamily } from "@md-to-pdf/core";
|
||||
import type { ResolvedDocxThemeStyle } from "./style-presets.js";
|
||||
import {
|
||||
DRAWING_NAMESPACE,
|
||||
WORD_NAMESPACE,
|
||||
appendElement,
|
||||
colorValue,
|
||||
directChildren,
|
||||
ensureDirectElement,
|
||||
firstDirectChild,
|
||||
millimetersToTwips,
|
||||
parseXmlPart,
|
||||
pointsToHalfPoints,
|
||||
pointsToTwips,
|
||||
removeDirectChildren,
|
||||
serializeXmlPart,
|
||||
type XmlElement
|
||||
} from "./ooxml.js";
|
||||
|
||||
function setFonts(parent: XmlElement, fonts: DocxFontFamily) {
|
||||
removeDirectChildren(parent, WORD_NAMESPACE, "rFonts");
|
||||
appendElement(parent, WORD_NAMESPACE, "w:rFonts", {
|
||||
"w:ascii": fonts.latin,
|
||||
"w:hAnsi": fonts.latin,
|
||||
"w:eastAsia": fonts.eastAsia,
|
||||
"w:cs": fonts.complexScript ?? fonts.latin
|
||||
});
|
||||
}
|
||||
|
||||
function setRunStyle(
|
||||
parent: XmlElement,
|
||||
options: {
|
||||
fonts?: DocxFontFamily;
|
||||
sizePt?: number;
|
||||
color?: string;
|
||||
bold?: boolean;
|
||||
italic?: boolean;
|
||||
underline?: boolean;
|
||||
backgroundColor?: string;
|
||||
}
|
||||
) {
|
||||
removeDirectChildren(
|
||||
parent,
|
||||
WORD_NAMESPACE,
|
||||
"rFonts",
|
||||
"sz",
|
||||
"szCs",
|
||||
"color",
|
||||
"b",
|
||||
"bCs",
|
||||
"i",
|
||||
"iCs",
|
||||
"u",
|
||||
"shd"
|
||||
);
|
||||
if (options.fonts) {
|
||||
setFonts(parent, options.fonts);
|
||||
}
|
||||
if (options.bold) {
|
||||
appendElement(parent, WORD_NAMESPACE, "w:b");
|
||||
appendElement(parent, WORD_NAMESPACE, "w:bCs");
|
||||
}
|
||||
if (options.italic) {
|
||||
appendElement(parent, WORD_NAMESPACE, "w:i");
|
||||
appendElement(parent, WORD_NAMESPACE, "w:iCs");
|
||||
}
|
||||
if (options.color) {
|
||||
appendElement(parent, WORD_NAMESPACE, "w:color", {
|
||||
"w:val": colorValue(options.color)
|
||||
});
|
||||
}
|
||||
if (options.underline) {
|
||||
appendElement(parent, WORD_NAMESPACE, "w:u", {
|
||||
"w:val": "single"
|
||||
});
|
||||
}
|
||||
if (options.backgroundColor) {
|
||||
appendElement(parent, WORD_NAMESPACE, "w:shd", {
|
||||
"w:val": "clear",
|
||||
"w:color": "auto",
|
||||
"w:fill": colorValue(options.backgroundColor)
|
||||
});
|
||||
}
|
||||
if (options.sizePt !== undefined) {
|
||||
const size = pointsToHalfPoints(options.sizePt);
|
||||
appendElement(parent, WORD_NAMESPACE, "w:sz", {
|
||||
"w:val": size
|
||||
});
|
||||
appendElement(parent, WORD_NAMESPACE, "w:szCs", {
|
||||
"w:val": size
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setParagraphSpacing(
|
||||
parent: XmlElement,
|
||||
options: {
|
||||
beforePt: number;
|
||||
afterPt: number;
|
||||
lineSpacing: number;
|
||||
firstLineIndentChars?: number;
|
||||
}
|
||||
) {
|
||||
removeDirectChildren(parent, WORD_NAMESPACE, "spacing", "ind");
|
||||
appendElement(parent, WORD_NAMESPACE, "w:spacing", {
|
||||
"w:before": pointsToTwips(options.beforePt),
|
||||
"w:after": pointsToTwips(options.afterPt),
|
||||
"w:line": String(Math.round(options.lineSpacing * 240)),
|
||||
"w:lineRule": "auto"
|
||||
});
|
||||
if (options.firstLineIndentChars !== undefined) {
|
||||
appendElement(parent, WORD_NAMESPACE, "w:ind", {
|
||||
"w:firstLineChars": String(
|
||||
Math.round(options.firstLineIndentChars * 100)
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function findStyle(styles: XmlElement, styleId: string) {
|
||||
return Array.from(
|
||||
styles.getElementsByTagNameNS(WORD_NAMESPACE, "style")
|
||||
).find(
|
||||
(element) =>
|
||||
element.getAttributeNS(WORD_NAMESPACE, "styleId") === styleId
|
||||
);
|
||||
}
|
||||
|
||||
function ensureStyle(
|
||||
styles: XmlElement,
|
||||
styleId: string,
|
||||
type: "paragraph" | "character" | "table",
|
||||
basedOn?: string
|
||||
) {
|
||||
const existing = findStyle(styles, styleId);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
const style = appendElement(styles, WORD_NAMESPACE, "w:style", {
|
||||
"w:type": type,
|
||||
"w:customStyle": "1",
|
||||
"w:styleId": styleId
|
||||
});
|
||||
appendElement(style, WORD_NAMESPACE, "w:name", {
|
||||
"w:val": styleId.replace(/([a-z])([A-Z])/gu, "$1 $2")
|
||||
});
|
||||
if (basedOn) {
|
||||
appendElement(style, WORD_NAMESPACE, "w:basedOn", {
|
||||
"w:val": basedOn
|
||||
});
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
function applyBodyStyle(
|
||||
styles: XmlElement,
|
||||
style: ResolvedDocxThemeStyle
|
||||
) {
|
||||
const defaults = firstDirectChild(
|
||||
styles,
|
||||
WORD_NAMESPACE,
|
||||
"docDefaults"
|
||||
);
|
||||
if (!defaults) {
|
||||
throw new Error("word/styles.xml 缺少 w:docDefaults");
|
||||
}
|
||||
const defaultRun = ensureDirectElement(
|
||||
ensureDirectElement(
|
||||
defaults,
|
||||
WORD_NAMESPACE,
|
||||
"w:rPrDefault"
|
||||
),
|
||||
WORD_NAMESPACE,
|
||||
"w:rPr"
|
||||
);
|
||||
setRunStyle(defaultRun, {
|
||||
fonts: style.body.fonts,
|
||||
sizePt: style.body.sizePt,
|
||||
color: style.body.color
|
||||
});
|
||||
const defaultParagraph = ensureDirectElement(
|
||||
ensureDirectElement(
|
||||
defaults,
|
||||
WORD_NAMESPACE,
|
||||
"w:pPrDefault"
|
||||
),
|
||||
WORD_NAMESPACE,
|
||||
"w:pPr"
|
||||
);
|
||||
setParagraphSpacing(defaultParagraph, {
|
||||
beforePt: style.body.spacingBeforePt,
|
||||
afterPt: style.body.spacingAfterPt,
|
||||
lineSpacing: style.body.lineSpacing,
|
||||
firstLineIndentChars: style.body.firstLineIndentChars
|
||||
});
|
||||
|
||||
for (const styleId of [
|
||||
"Normal",
|
||||
"BodyText",
|
||||
"FirstParagraph",
|
||||
"FootnoteText",
|
||||
"Definition",
|
||||
"Figure"
|
||||
]) {
|
||||
const target = ensureStyle(
|
||||
styles,
|
||||
styleId,
|
||||
"paragraph",
|
||||
styleId === "Normal" ? undefined : "Normal"
|
||||
);
|
||||
const pPr = ensureDirectElement(
|
||||
target,
|
||||
WORD_NAMESPACE,
|
||||
"w:pPr"
|
||||
);
|
||||
setParagraphSpacing(pPr, {
|
||||
beforePt: style.body.spacingBeforePt,
|
||||
afterPt: style.body.spacingAfterPt,
|
||||
lineSpacing: style.body.lineSpacing,
|
||||
firstLineIndentChars: style.body.firstLineIndentChars
|
||||
});
|
||||
const rPr = ensureDirectElement(
|
||||
target,
|
||||
WORD_NAMESPACE,
|
||||
"w:rPr"
|
||||
);
|
||||
setRunStyle(rPr, {
|
||||
fonts: style.body.fonts,
|
||||
sizePt: style.body.sizePt,
|
||||
color: style.body.color
|
||||
});
|
||||
}
|
||||
|
||||
const compact = ensureStyle(
|
||||
styles,
|
||||
"Compact",
|
||||
"paragraph",
|
||||
"BodyText"
|
||||
);
|
||||
setParagraphSpacing(
|
||||
ensureDirectElement(compact, WORD_NAMESPACE, "w:pPr"),
|
||||
{
|
||||
beforePt: 0,
|
||||
afterPt: 0,
|
||||
lineSpacing: style.body.lineSpacing,
|
||||
firstLineIndentChars: 0
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function applyHeadings(
|
||||
styles: XmlElement,
|
||||
style: ResolvedDocxThemeStyle
|
||||
) {
|
||||
for (let level = 1; level <= 6; level += 1) {
|
||||
const sizePt = style.headings.sizesPt[level - 1]!;
|
||||
for (const styleId of [
|
||||
`Heading${level}`,
|
||||
`Heading${level}Char`
|
||||
]) {
|
||||
const target = ensureStyle(
|
||||
styles,
|
||||
styleId,
|
||||
styleId.endsWith("Char") ? "character" : "paragraph",
|
||||
styleId.endsWith("Char")
|
||||
? "DefaultParagraphFont"
|
||||
: "Normal"
|
||||
);
|
||||
if (!styleId.endsWith("Char")) {
|
||||
const pPr = ensureDirectElement(
|
||||
target,
|
||||
WORD_NAMESPACE,
|
||||
"w:pPr"
|
||||
);
|
||||
setParagraphSpacing(pPr, {
|
||||
beforePt: style.headings.spacingBeforePt,
|
||||
afterPt: style.headings.spacingAfterPt,
|
||||
lineSpacing: 1.25
|
||||
});
|
||||
if (!firstDirectChild(pPr, WORD_NAMESPACE, "keepNext")) {
|
||||
appendElement(pPr, WORD_NAMESPACE, "w:keepNext");
|
||||
}
|
||||
if (!firstDirectChild(pPr, WORD_NAMESPACE, "keepLines")) {
|
||||
appendElement(pPr, WORD_NAMESPACE, "w:keepLines");
|
||||
}
|
||||
}
|
||||
setRunStyle(
|
||||
ensureDirectElement(target, WORD_NAMESPACE, "w:rPr"),
|
||||
{
|
||||
fonts: style.headings.fonts,
|
||||
sizePt,
|
||||
color: style.headings.color,
|
||||
bold: style.headings.bold
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [styleId, sizePt] of [
|
||||
["Title", style.headings.sizesPt[0] + 4],
|
||||
["TitleChar", style.headings.sizesPt[0] + 4],
|
||||
["Subtitle", style.headings.sizesPt[1]],
|
||||
["SubtitleChar", style.headings.sizesPt[1]]
|
||||
] as const) {
|
||||
const target = ensureStyle(
|
||||
styles,
|
||||
styleId,
|
||||
styleId.endsWith("Char") ? "character" : "paragraph",
|
||||
styleId.endsWith("Char")
|
||||
? "DefaultParagraphFont"
|
||||
: "Normal"
|
||||
);
|
||||
if (!styleId.endsWith("Char")) {
|
||||
const pPr = ensureDirectElement(
|
||||
target,
|
||||
WORD_NAMESPACE,
|
||||
"w:pPr"
|
||||
);
|
||||
setParagraphSpacing(pPr, {
|
||||
beforePt: 0,
|
||||
afterPt: style.headings.spacingAfterPt,
|
||||
lineSpacing: 1.25
|
||||
});
|
||||
removeDirectChildren(pPr, WORD_NAMESPACE, "jc");
|
||||
appendElement(pPr, WORD_NAMESPACE, "w:jc", {
|
||||
"w:val": "center"
|
||||
});
|
||||
}
|
||||
setRunStyle(
|
||||
ensureDirectElement(target, WORD_NAMESPACE, "w:rPr"),
|
||||
{
|
||||
fonts: style.headings.fonts,
|
||||
sizePt,
|
||||
color: style.headings.color,
|
||||
bold: styleId.startsWith("Title")
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function applyCodeAndQuote(
|
||||
styles: XmlElement,
|
||||
style: ResolvedDocxThemeStyle
|
||||
) {
|
||||
const inlineCode = ensureStyle(
|
||||
styles,
|
||||
"VerbatimChar",
|
||||
"character",
|
||||
"BodyTextChar"
|
||||
);
|
||||
setRunStyle(
|
||||
ensureDirectElement(inlineCode, WORD_NAMESPACE, "w:rPr"),
|
||||
{
|
||||
fonts: style.code.fonts,
|
||||
sizePt: style.code.sizePt,
|
||||
color: style.code.color,
|
||||
backgroundColor: style.code.backgroundColor
|
||||
}
|
||||
);
|
||||
|
||||
const sourceCode = ensureStyle(
|
||||
styles,
|
||||
"SourceCode",
|
||||
"paragraph",
|
||||
"Normal"
|
||||
);
|
||||
const codePPr = ensureDirectElement(
|
||||
sourceCode,
|
||||
WORD_NAMESPACE,
|
||||
"w:pPr"
|
||||
);
|
||||
setParagraphSpacing(codePPr, {
|
||||
beforePt: 3,
|
||||
afterPt: 3,
|
||||
lineSpacing: style.code.lineSpacing,
|
||||
firstLineIndentChars: 0
|
||||
});
|
||||
removeDirectChildren(codePPr, WORD_NAMESPACE, "shd", "pBdr");
|
||||
appendElement(codePPr, WORD_NAMESPACE, "w:shd", {
|
||||
"w:val": "clear",
|
||||
"w:color": "auto",
|
||||
"w:fill": colorValue(style.code.backgroundColor)
|
||||
});
|
||||
const codeBorders = appendElement(
|
||||
codePPr,
|
||||
WORD_NAMESPACE,
|
||||
"w:pBdr"
|
||||
);
|
||||
for (const side of ["top", "left", "bottom", "right"]) {
|
||||
appendElement(codeBorders, WORD_NAMESPACE, `w:${side}`, {
|
||||
"w:val": "single",
|
||||
"w:sz": "4",
|
||||
"w:space": "2",
|
||||
"w:color": colorValue(style.code.borderColor)
|
||||
});
|
||||
}
|
||||
setRunStyle(
|
||||
ensureDirectElement(sourceCode, WORD_NAMESPACE, "w:rPr"),
|
||||
{
|
||||
fonts: style.code.fonts,
|
||||
sizePt: style.code.sizePt,
|
||||
color: style.code.color,
|
||||
backgroundColor: style.code.backgroundColor
|
||||
}
|
||||
);
|
||||
|
||||
const quote = ensureStyle(
|
||||
styles,
|
||||
"BlockText",
|
||||
"paragraph",
|
||||
"BodyText"
|
||||
);
|
||||
const quotePPr = ensureDirectElement(
|
||||
quote,
|
||||
WORD_NAMESPACE,
|
||||
"w:pPr"
|
||||
);
|
||||
setParagraphSpacing(quotePPr, {
|
||||
beforePt: 6,
|
||||
afterPt: 6,
|
||||
lineSpacing: style.body.lineSpacing,
|
||||
firstLineIndentChars: 0
|
||||
});
|
||||
removeDirectChildren(quotePPr, WORD_NAMESPACE, "ind", "shd", "pBdr");
|
||||
appendElement(quotePPr, WORD_NAMESPACE, "w:ind", {
|
||||
"w:leftChars": String(style.blockQuote.leftIndentChars * 100)
|
||||
});
|
||||
appendElement(quotePPr, WORD_NAMESPACE, "w:shd", {
|
||||
"w:val": "clear",
|
||||
"w:color": "auto",
|
||||
"w:fill": colorValue(style.blockQuote.backgroundColor)
|
||||
});
|
||||
const quoteBorders = appendElement(
|
||||
quotePPr,
|
||||
WORD_NAMESPACE,
|
||||
"w:pBdr"
|
||||
);
|
||||
appendElement(quoteBorders, WORD_NAMESPACE, "w:left", {
|
||||
"w:val": "single",
|
||||
"w:sz": "18",
|
||||
"w:space": "6",
|
||||
"w:color": colorValue(style.blockQuote.borderColor)
|
||||
});
|
||||
setRunStyle(
|
||||
ensureDirectElement(quote, WORD_NAMESPACE, "w:rPr"),
|
||||
{
|
||||
fonts: style.body.fonts,
|
||||
sizePt: style.body.sizePt,
|
||||
color: style.blockQuote.color,
|
||||
italic: style.blockQuote.italic
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function applyTableAndCaption(
|
||||
styles: XmlElement,
|
||||
style: ResolvedDocxThemeStyle
|
||||
) {
|
||||
const table = ensureStyle(styles, "Table", "table", "TableNormal");
|
||||
const tblPr = ensureDirectElement(
|
||||
table,
|
||||
WORD_NAMESPACE,
|
||||
"w:tblPr"
|
||||
);
|
||||
removeDirectChildren(tblPr, WORD_NAMESPACE, "tblCellMar", "tblBorders");
|
||||
const margins = appendElement(
|
||||
tblPr,
|
||||
WORD_NAMESPACE,
|
||||
"w:tblCellMar"
|
||||
);
|
||||
const cellMargin = String(
|
||||
millimetersToTwips(style.table.cellMarginMm)
|
||||
);
|
||||
for (const side of ["top", "left", "bottom", "right"]) {
|
||||
appendElement(margins, WORD_NAMESPACE, `w:${side}`, {
|
||||
"w:w": cellMargin,
|
||||
"w:type": "dxa"
|
||||
});
|
||||
}
|
||||
const borders = appendElement(
|
||||
tblPr,
|
||||
WORD_NAMESPACE,
|
||||
"w:tblBorders"
|
||||
);
|
||||
for (const side of [
|
||||
"top",
|
||||
"left",
|
||||
"bottom",
|
||||
"right",
|
||||
"insideH",
|
||||
"insideV"
|
||||
]) {
|
||||
appendElement(borders, WORD_NAMESPACE, `w:${side}`, {
|
||||
"w:val": "single",
|
||||
"w:sz": "4",
|
||||
"w:space": "0",
|
||||
"w:color": colorValue(style.table.borderColor)
|
||||
});
|
||||
}
|
||||
setRunStyle(
|
||||
ensureDirectElement(table, WORD_NAMESPACE, "w:rPr"),
|
||||
{
|
||||
fonts: style.table.fonts,
|
||||
sizePt: style.table.sizePt,
|
||||
color: style.table.color
|
||||
}
|
||||
);
|
||||
let firstRow = directChildren(
|
||||
table,
|
||||
WORD_NAMESPACE,
|
||||
"tblStylePr"
|
||||
).find(
|
||||
(element) =>
|
||||
element.getAttributeNS(WORD_NAMESPACE, "type") === "firstRow"
|
||||
);
|
||||
if (!firstRow) {
|
||||
firstRow = appendElement(
|
||||
table,
|
||||
WORD_NAMESPACE,
|
||||
"w:tblStylePr",
|
||||
{ "w:type": "firstRow" }
|
||||
);
|
||||
}
|
||||
setRunStyle(
|
||||
ensureDirectElement(firstRow, WORD_NAMESPACE, "w:rPr"),
|
||||
{
|
||||
fonts: style.table.fonts,
|
||||
sizePt: style.table.sizePt,
|
||||
color: style.table.headerColor,
|
||||
bold: true
|
||||
}
|
||||
);
|
||||
const cellProperties = ensureDirectElement(
|
||||
firstRow,
|
||||
WORD_NAMESPACE,
|
||||
"w:tcPr"
|
||||
);
|
||||
removeDirectChildren(cellProperties, WORD_NAMESPACE, "shd");
|
||||
appendElement(cellProperties, WORD_NAMESPACE, "w:shd", {
|
||||
"w:val": "clear",
|
||||
"w:color": "auto",
|
||||
"w:fill": colorValue(style.table.headerBackgroundColor)
|
||||
});
|
||||
|
||||
for (const styleId of ["Caption", "TableCaption", "ImageCaption"]) {
|
||||
const caption = ensureStyle(
|
||||
styles,
|
||||
styleId,
|
||||
"paragraph",
|
||||
styleId === "Caption" ? "Normal" : "Caption"
|
||||
);
|
||||
const pPr = ensureDirectElement(
|
||||
caption,
|
||||
WORD_NAMESPACE,
|
||||
"w:pPr"
|
||||
);
|
||||
setParagraphSpacing(pPr, {
|
||||
beforePt: 3,
|
||||
afterPt: 6,
|
||||
lineSpacing: 1.25
|
||||
});
|
||||
removeDirectChildren(pPr, WORD_NAMESPACE, "jc");
|
||||
appendElement(pPr, WORD_NAMESPACE, "w:jc", {
|
||||
"w:val": style.caption.alignment
|
||||
});
|
||||
setRunStyle(
|
||||
ensureDirectElement(caption, WORD_NAMESPACE, "w:rPr"),
|
||||
{
|
||||
fonts: style.caption.fonts,
|
||||
sizePt: style.caption.sizePt,
|
||||
color: style.caption.color,
|
||||
italic: style.caption.italic
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const hyperlink = ensureStyle(
|
||||
styles,
|
||||
"Hyperlink",
|
||||
"character",
|
||||
"BodyTextChar"
|
||||
);
|
||||
setRunStyle(
|
||||
ensureDirectElement(hyperlink, WORD_NAMESPACE, "w:rPr"),
|
||||
{
|
||||
color: style.hyperlink.color,
|
||||
underline: style.hyperlink.underline
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function transformStylesXml(
|
||||
content: Uint8Array,
|
||||
style: ResolvedDocxThemeStyle
|
||||
) {
|
||||
const document = parseXmlPart(content, "word/styles.xml");
|
||||
const styles = document.documentElement!;
|
||||
if (
|
||||
styles.namespaceURI !== WORD_NAMESPACE ||
|
||||
styles.localName !== "styles"
|
||||
) {
|
||||
throw new Error("word/styles.xml 根元素无效");
|
||||
}
|
||||
applyBodyStyle(styles, style);
|
||||
applyHeadings(styles, style);
|
||||
applyCodeAndQuote(styles, style);
|
||||
applyTableAndCaption(styles, style);
|
||||
return serializeXmlPart(document);
|
||||
}
|
||||
|
||||
export function transformFontTableXml(
|
||||
content: Uint8Array,
|
||||
style: ResolvedDocxThemeStyle
|
||||
) {
|
||||
const document = parseXmlPart(content, "word/fontTable.xml");
|
||||
const root = document.documentElement!;
|
||||
const requiredFonts = new Set<string>();
|
||||
for (const fonts of [
|
||||
style.body.fonts,
|
||||
style.headings.fonts,
|
||||
style.code.fonts,
|
||||
style.table.fonts,
|
||||
style.caption.fonts
|
||||
]) {
|
||||
requiredFonts.add(fonts.latin);
|
||||
requiredFonts.add(fonts.eastAsia);
|
||||
if (fonts.complexScript) {
|
||||
requiredFonts.add(fonts.complexScript);
|
||||
}
|
||||
}
|
||||
const existing = new Set(
|
||||
Array.from(
|
||||
root.getElementsByTagNameNS(WORD_NAMESPACE, "font")
|
||||
).map((element) => element.getAttributeNS(WORD_NAMESPACE, "name"))
|
||||
);
|
||||
for (const name of requiredFonts) {
|
||||
if (!existing.has(name)) {
|
||||
const font = appendElement(root, WORD_NAMESPACE, "w:font", {
|
||||
"w:name": name
|
||||
});
|
||||
appendElement(font, WORD_NAMESPACE, "w:family", {
|
||||
"w:val": name === style.code.fonts.latin ? "modern" : "auto"
|
||||
});
|
||||
}
|
||||
}
|
||||
return serializeXmlPart(document);
|
||||
}
|
||||
|
||||
export function transformThemeFontsXml(
|
||||
content: Uint8Array,
|
||||
style: ResolvedDocxThemeStyle
|
||||
) {
|
||||
const document = parseXmlPart(content, "word/theme/theme1.xml");
|
||||
for (const schemeName of ["majorFont", "minorFont"]) {
|
||||
const scheme = document.getElementsByTagNameNS(
|
||||
DRAWING_NAMESPACE,
|
||||
schemeName
|
||||
)[0];
|
||||
if (!scheme) {
|
||||
continue;
|
||||
}
|
||||
const latin = scheme.getElementsByTagNameNS(
|
||||
DRAWING_NAMESPACE,
|
||||
"latin"
|
||||
)[0];
|
||||
const eastAsia = scheme.getElementsByTagNameNS(
|
||||
DRAWING_NAMESPACE,
|
||||
"ea"
|
||||
)[0];
|
||||
latin?.setAttribute(
|
||||
"typeface",
|
||||
schemeName === "majorFont"
|
||||
? style.headings.fonts.latin
|
||||
: style.body.fonts.latin
|
||||
);
|
||||
eastAsia?.setAttribute(
|
||||
"typeface",
|
||||
schemeName === "majorFont"
|
||||
? style.headings.fonts.eastAsia
|
||||
: style.body.fonts.eastAsia
|
||||
);
|
||||
}
|
||||
return serializeXmlPart(document);
|
||||
}
|
||||
Reference in New Issue
Block a user