feat: 完成 DOCX 视觉门禁与字体兼容映射
This commit is contained in:
@@ -39,6 +39,10 @@ describe("DOCX 字体嵌入", () => {
|
||||
family: "FandolFang",
|
||||
subfamily: "Regular",
|
||||
postscriptName: "FandolFang-Regular",
|
||||
panose: [0, 0, 5, 0, 0, 0, 0, 0, 0, 0],
|
||||
unicodeRanges: [0x80000001, 0x00002000, 0x00000002, 0],
|
||||
codePageRanges: [0x00060007, 0],
|
||||
fixedPitch: false,
|
||||
fsType: 0x0008,
|
||||
permission: "editable",
|
||||
noSubsetting: false,
|
||||
@@ -123,6 +127,9 @@ describe("DOCX 字体嵌入", () => {
|
||||
"FandolHei",
|
||||
"FandolKai"
|
||||
]);
|
||||
expect(
|
||||
prepared.map((font) => font.metadata.weightClass)
|
||||
).toEqual([400, 700, 400, 400, 400]);
|
||||
expect(
|
||||
prepared.every((font) => font.metadata.signature === "OTTO")
|
||||
).toBe(true);
|
||||
|
||||
@@ -97,6 +97,15 @@ describe("DOCX 字体包写入", () => {
|
||||
output.entries.get("word/fontTable.xml")
|
||||
);
|
||||
expect(fontTable).toContain('w:name="FandolFang"');
|
||||
expect(fontTable).toContain(
|
||||
'<w:panose1 w:val="00000500000000000000"/>'
|
||||
);
|
||||
expect(fontTable).toContain('<w:charset w:val="86"/>');
|
||||
expect(fontTable).toContain('<w:family w:val="auto"/>');
|
||||
expect(fontTable).toContain('<w:pitch w:val="variable"/>');
|
||||
expect(fontTable).toContain(
|
||||
'<w:sig w:usb0="80000001" w:usb1="00002000" w:usb2="00000002" w:usb3="00000000" w:csb0="00060007" w:csb1="00000000"/>'
|
||||
);
|
||||
expect(fontTable).toContain("<w:embedRegular");
|
||||
expect(fontTable).toContain(
|
||||
`xmlns:r="${relationshipNamespace}"`
|
||||
@@ -124,6 +133,22 @@ describe("DOCX 字体包写入", () => {
|
||||
expect(validateGeneratedDocx(result.content)).toMatchObject({
|
||||
embeddedFontCount: 1
|
||||
});
|
||||
|
||||
const invalidEntries = new Map(output.entries);
|
||||
invalidEntries.set(
|
||||
"word/fontTable.xml",
|
||||
encoder.encode(
|
||||
fontTable.replace(
|
||||
'<w:charset w:val="86"/>',
|
||||
'<w:charset w:val="00"/>'
|
||||
)
|
||||
)
|
||||
);
|
||||
expect(() =>
|
||||
validateGeneratedDocx(
|
||||
writeGeneratedDocxPackage(invalidEntries)
|
||||
)
|
||||
).toThrow("字体元数据与嵌入字形不匹配");
|
||||
});
|
||||
|
||||
it("没有字体时保持原始 DOCX 字节不变", () => {
|
||||
|
||||
@@ -279,6 +279,15 @@ describe("动态 reference.docx", () => {
|
||||
alignment: "center",
|
||||
keepWithNext: true
|
||||
}
|
||||
},
|
||||
{
|
||||
slot: "official-edition",
|
||||
source: "computed-css",
|
||||
confidence: "exact",
|
||||
style: {
|
||||
fontCandidates: [],
|
||||
lineSpacing: 1.5
|
||||
}
|
||||
}
|
||||
])
|
||||
}
|
||||
@@ -300,6 +309,13 @@ describe("动态 reference.docx", () => {
|
||||
expect(stylesXml).toContain('w:val="AA0000"');
|
||||
expect(stylesXml).toContain('<w:jc w:val="both"/>');
|
||||
expect(stylesXml).not.toContain('w:val="justify"');
|
||||
expect(stylesXml).toContain(
|
||||
'w:line="504" w:lineRule="exact"'
|
||||
);
|
||||
expect(stylesXml).toMatch(
|
||||
/w:style[^>]*w:styleId="MdOfficialEdition"[\s\S]*?<w:spacing[^>]*w:line="315"[^>]*w:lineRule="exact"/u
|
||||
);
|
||||
expect(stylesXml).not.toContain('w:lineRule="auto"');
|
||||
expect(stylesXml).not.toContain("<w:tblW");
|
||||
expect(stylesXml).toContain('w:color="445566"');
|
||||
expect(fontTableXml).toContain(
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { SfntFontMetadata } from "../src/font-embedding.js";
|
||||
import { createWordFontMetadata } from "../src/word-font-metadata.js";
|
||||
|
||||
function metadata(
|
||||
overrides: Partial<SfntFontMetadata> = {}
|
||||
): SfntFontMetadata {
|
||||
return {
|
||||
family: "Test Font",
|
||||
subfamily: "Regular",
|
||||
weightClass: 400,
|
||||
panose: [2, 2, 5, 3, 5, 4, 5, 2, 3, 4],
|
||||
unicodeRanges: [
|
||||
0x20000083,
|
||||
0x2adf3c10,
|
||||
0x00000016,
|
||||
0
|
||||
],
|
||||
codePageRanges: [0x60060107, 0],
|
||||
fixedPitch: false,
|
||||
fsType: 0,
|
||||
permission: "installable",
|
||||
noSubsetting: false,
|
||||
signature: "\u0000\u0001\u0000\u0000",
|
||||
...overrides
|
||||
};
|
||||
}
|
||||
|
||||
describe("Word 字体元数据映射", () => {
|
||||
it("将 CJK Serif 的 OS/2 范围映射为简体中文 Word 字体签名", () => {
|
||||
expect(createWordFontMetadata(metadata())).toEqual({
|
||||
panose1: "02020503050405020304",
|
||||
charset: "86",
|
||||
family: "roman",
|
||||
pitch: "variable",
|
||||
signature: {
|
||||
usb0: "20000083",
|
||||
usb1: "2ADF3C10",
|
||||
usb2: "00000016",
|
||||
usb3: "00000000",
|
||||
csb0: "60060107",
|
||||
csb1: "00000000"
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("根据 Panose 与固定字距通用推导 Word 字体族", () => {
|
||||
expect(
|
||||
createWordFontMetadata(
|
||||
metadata({
|
||||
panose: [2, 11, 5, 3, 5, 4, 5, 2, 3, 4]
|
||||
})
|
||||
).family
|
||||
).toBe("swiss");
|
||||
expect(
|
||||
createWordFontMetadata(
|
||||
metadata({
|
||||
panose: [3, 0, 5, 3, 5, 4, 5, 2, 3, 4]
|
||||
})
|
||||
).family
|
||||
).toBe("script");
|
||||
expect(
|
||||
createWordFontMetadata(
|
||||
metadata({
|
||||
fixedPitch: true
|
||||
})
|
||||
)
|
||||
).toMatchObject({
|
||||
family: "modern",
|
||||
pitch: "fixed"
|
||||
});
|
||||
});
|
||||
|
||||
it("在没有 CJK code-page 位时选择对应 charset 或默认 ANSI", () => {
|
||||
expect(
|
||||
createWordFontMetadata(
|
||||
metadata({ codePageRanges: [1 << 2, 0] })
|
||||
).charset
|
||||
).toBe("CC");
|
||||
expect(
|
||||
createWordFontMetadata(
|
||||
metadata({ codePageRanges: [0, 0] })
|
||||
).charset
|
||||
).toBe("00");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user