fix: 内置DOCX双中文字体包

This commit is contained in:
SkyJourney
2026-08-03 12:07:12 +08:00
parent a094dabf02
commit 6a6972ae48
24 changed files with 508 additions and 180 deletions
+5
View File
@@ -41,6 +41,11 @@ module.exports = {
to: "samples", to: "samples",
filter: ["**/*"] filter: ["**/*"]
}, },
{
from: "../../output/font-packs/root",
to: "font-packs",
filter: ["**/*"]
},
{ {
from: `.runtime/pandoc/${pandocRuntime.version}/windows-x86_64`, from: `.runtime/pandoc/${pandocRuntime.version}/windows-x86_64`,
to: `pandoc/${pandocRuntime.version}/windows-x86_64`, to: `pandoc/${pandocRuntime.version}/windows-x86_64`,
+11 -2
View File
@@ -130,7 +130,7 @@ function getDesktopThemeDirectory() {
: path.resolve(currentDirectory, "../../..", ".local", "themes"); : path.resolve(currentDirectory, "../../..", ".local", "themes");
} }
function getDesktopFontPackDirectory() { function getDesktopUserFontPackDirectory() {
if (!app.isPackaged) { if (!app.isPackaged) {
return path.resolve( return path.resolve(
currentDirectory, currentDirectory,
@@ -147,6 +147,15 @@ function getDesktopFontPackDirectory() {
function createDesktopApplicationService() { function createDesktopApplicationService() {
const projectRoot = path.resolve(currentDirectory, "../../.."); const projectRoot = path.resolve(currentDirectory, "../../..");
const fontPackRoots = app.isPackaged
? [
path.join(process.resourcesPath, "font-packs"),
getDesktopUserFontPackDirectory()
]
: [
path.join(projectRoot, "output", "font-packs", "root"),
getDesktopUserFontPackDirectory()
];
return createApplicationService({ return createApplicationService({
bundledRoot: app.isPackaged bundledRoot: app.isPackaged
? path.join(process.resourcesPath, "themes") ? path.join(process.resourcesPath, "themes")
@@ -155,7 +164,7 @@ function createDesktopApplicationService() {
createAssetUrl: (themeId, assetPath) => createAssetUrl: (themeId, assetPath) =>
`${APP_SCHEME}://${THEME_HOST}/${encodeURIComponent(themeId)}/${encodeThemeAssetPath(assetPath)}`, `${APP_SCHEME}://${THEME_HOST}/${encodeURIComponent(themeId)}/${encodeThemeAssetPath(assetPath)}`,
fontPacks: { fontPacks: {
roots: [getDesktopFontPackDirectory()], roots: fontPackRoots,
appVersion: app.getVersion(), appVersion: app.getVersion(),
createAssetUrl: ( createAssetUrl: (
packId, packId,
+12 -16
View File
@@ -72,7 +72,7 @@ describe("桌面发行构建链", () => {
"npm run prepare:pandoc" "npm run prepare:pandoc"
); );
expect(rootManifest.scripts?.["desktop:package"]).toBe( expect(rootManifest.scripts?.["desktop:package"]).toBe(
"npm run package -w @md-to-pdf/desktop" "npm run build:font-pack && npm run package -w @md-to-pdf/desktop"
); );
}); });
@@ -180,32 +180,28 @@ describe("桌面发行构建链", () => {
); );
}); });
it("仅在组合构建时接入经过哈希校验的同目录字体包", () => { it("在安装版与便携版中强制内置完整字体包集合", () => {
const builderConfig = readFileSync( const builderConfig = readFileSync(
`${desktopRoot}/electron-builder.config.cjs`, `${desktopRoot}/electron-builder.config.cjs`,
"utf8" "utf8"
); );
const installerInclude = readFileSync( const desktopMain = readFileSync(
`${desktopRoot}/build/installer.nsh`, `${desktopRoot}/src/main.ts`,
"utf8" "utf8"
); );
const companionBuild = readFileSync( const bundledBuild = readFileSync(
`${projectRoot}/scripts/build-desktop-with-font-pack.mjs`, `${projectRoot}/scripts/build-desktop-with-font-pack.mjs`,
"utf8" "utf8"
); );
expect(builderConfig).toContain("MD_TO_PDF_NSIS_INCLUDE"); expect(builderConfig).toContain('../../output/font-packs/root');
expect(installerInclude).toContain("FONT_PACK_COMPANION_SHA256"); expect(builderConfig).toContain('to: "font-packs"');
expect(installerInclude).toContain("StdUtils.HashFile"); expect(desktopMain).toContain(
expect(installerInclude).toContain("/WITHFONTPACK="); 'path.join(process.resourcesPath, "font-packs")'
expect(companionBuild).toContain("createDesktopCompanionInclude");
expect(companionBuild).toContain("MD_TO_PDF_NSIS_INCLUDE");
expect(companionBuild).toContain(
"MorphDoc-FontPack-mdtp-serif-sc-1.0.0-x86_64-Setup.exe"
);
expect(companionBuild).toContain(
"MorphDoc-FontPack-mdtp-serif-sc-1.0.0.zip"
); );
expect(desktopMain).toContain("getDesktopUserFontPackDirectory()");
expect(bundledBuild).not.toContain("createDesktopCompanionInclude");
expect(bundledBuild).toContain("已生成内置字体包");
}); });
it("将 DOCX Lua Filter 复制到桌面主进程资源目录", () => { it("将 DOCX Lua Filter 复制到桌面主进程资源目录", () => {
+1
View File
@@ -166,6 +166,7 @@ ENV HOST=127.0.0.1 \
FONT_PACK_ROOT=/app/.local/font-packs FONT_PACK_ROOT=/app/.local/font-packs
RUN test -f /app/.local/font-packs/mdtp-serif-sc/1.0.0/font-pack.json RUN test -f /app/.local/font-packs/mdtp-serif-sc/1.0.0/font-pack.json
RUN test -f /app/.local/font-packs/mdtp-sans-sc/1.0.0/font-pack.json
USER pwuser USER pwuser
+33 -24
View File
@@ -15,12 +15,10 @@ const image = process.env.MD_TO_PDF_IMAGE?.trim() || "md-to-pdf:local";
const fontPackRoot = path.resolve( const fontPackRoot = path.resolve(
path.join(repositoryRoot, "output", "font-packs", "root") path.join(repositoryRoot, "output", "font-packs", "root")
); );
const manifestPath = path.join( const expectedFontPacks = [
fontPackRoot, { id: "mdtp-serif-sc", version: "1.0.0" },
"mdtp-serif-sc", { id: "mdtp-sans-sc", version: "1.0.0" }
"1.0.0", ];
"font-pack.json"
);
function assert(condition, message) { function assert(condition, message) {
if (!condition) { if (!condition) {
@@ -126,7 +124,14 @@ assert(
fontPackStatus?.isDirectory(), fontPackStatus?.isDirectory(),
`未找到有效字体包根目录:${fontPackRoot}。请先运行 npm run build:font-pack。` `未找到有效字体包根目录:${fontPackRoot}。请先运行 npm run build:font-pack。`
); );
const localManifest = await readFile(manifestPath); const localManifests = await Promise.all(
expectedFontPacks.map(async (pack) => ({
...pack,
content: await readFile(
path.join(fontPackRoot, pack.id, pack.version, "font-pack.json")
)
}))
);
const buildEnvironment = { const buildEnvironment = {
...process.env, ...process.env,
MD_TO_PDF_IMAGE: image MD_TO_PDF_IMAGE: image
@@ -137,22 +142,26 @@ run(
{ env: buildEnvironment } { env: buildEnvironment }
); );
run("docker", ["image", "inspect", image], { capture: true }); run("docker", ["image", "inspect", image], { capture: true });
const embeddedManifestHash = run( const embeddedManifestHashes = {};
"docker", for (const manifest of localManifests) {
[ const embeddedHash = run(
"run", "docker",
"--rm", [
"--entrypoint", "run",
"sha256sum", "--rm",
image, "--entrypoint",
"/app/.local/font-packs/mdtp-serif-sc/1.0.0/font-pack.json" "sha256sum",
], image,
{ capture: true } `/app/.local/font-packs/${manifest.id}/${manifest.version}/font-pack.json`
).split(/\s+/u)[0]; ],
assert( { capture: true }
embeddedManifestHash === sha256(localManifest), ).split(/\s+/u)[0];
"镜像内字体包清单与本次受校验构建产物不一致" assert(
); embeddedHash === sha256(manifest.content),
`镜像内字体包清单不一致:${manifest.id}@${manifest.version}`
);
embeddedManifestHashes[`${manifest.id}@${manifest.version}`] = embeddedHash;
}
const port = await freePort(); const port = await freePort();
const projectName = `morphdoc-font-pack-${randomUUID().slice(0, 8)}`; const projectName = `morphdoc-font-pack-${randomUUID().slice(0, 8)}`;
@@ -237,7 +246,7 @@ try {
`${JSON.stringify( `${JSON.stringify(
{ {
image, image,
embeddedManifestSha256: embeddedManifestHash, embeddedManifestSha256: embeddedManifestHashes,
runtimeFontPackMount: false, runtimeFontPackMount: false,
fontPackAvailable: true, fontPackAvailable: true,
pandocVersion: capability.detectedVersion, pandocVersion: capability.detectedVersion,
+10 -1
View File
@@ -1502,7 +1502,16 @@ ECharts 第二阶段浏览器与 PDF 验证结果:
总计 76 个阻断问题;`formal-feasibility--landscape--binding` 验证 A4 总计 76 个阻断问题;`formal-feasibility--landscape--binding` 验证 A4
横向、非对称装订边距和独立封面整页 strict 门禁,封面因墨迹与边缘 横向、非对称装订边距和独立封面整页 strict 门禁,封面因墨迹与边缘
IoU 不达标被阻断;全仓库测试、类型检查、生产构建和差异检查通过; IoU 不达标被阻断;全仓库测试、类型检查、生产构建和差异检查通过;
- 阶段 D修复主题封面、块元素样式和字体解析、嵌入、回退; - 阶段 D进行中。字体门禁已扩展到 PDF 实际字体解析、DOCX 声明与嵌入
数量,以及 Word/WPS 是否真正采用 Office 嵌入别名。字体资产第一批已
新增 `mdtp-sans-sc@1.0.0` 静态 TrueType/WOFF2,与现有
`mdtp-serif-sc@1.0.0` 组成受 `font-packs/bundle.json` 约束的确定性
双字体包集合;Desktop 安装版和便携版改为直接内置,Docker 同步执行
双清单哈希验证,不再依赖可选伴随安装器。`red-briefing`
`gov-red-standard` 已收敛为每主题三个中文字体面,真实 Chromium、Word、
WPS 输出均实际采用 MdTP 宋体/黑体,DOCX 各包含三个字体部件且字体门禁
失败为 0。下一步将同一映射规则扩展到其余 12 套主题,再修复封面和正文
语义块视觉差异;
- 阶段 E:完成源码服务、Docker Web API、Desktop 安装版发布后复验; - 阶段 E:完成源码服务、Docker Web API、Desktop 安装版发布后复验;
- 阶段 F:完成 `v0.6.1` 版本、发行说明、发布产物、发布提交和标签。 - 阶段 F:完成 `v0.6.1` 版本、发行说明、发布产物、发布提交和标签。
+25 -65
View File
@@ -1,9 +1,9 @@
# 可选字体包构建 # 内置字体包集合构建
本目录保存字体包配方、许可证、来源说明和正式冻结字体资产。字体二进制 本目录保存字体包配方、许可证、来源说明和正式冻结字体资产。字体二进制
位于固定的 `font-packs/assets/`,统一由 Git LFS 管理,普通 Git 对象只保存 位于固定的 `font-packs/assets/`,统一由 Git LFS 管理,普通 Git 对象只保存
LFS 指针。普通 Desktop 安装包不包含字体;组合 Desktop 发行把字体作为 LFS 指针。Desktop 安装版、便携版和 Docker 镜像都直接包含本次受校验的
同目录可选附件,Docker 发行构建则把本次受校验的字体包写入镜像层 完整字体包集合,不再依赖用户另行安装字体附件
## 源目录 ## 源目录
@@ -16,10 +16,18 @@ font-packs/assets/mdtp-serif-sc/1.0.0/
├── MdTPSerifSC-Regular.woff2 ├── MdTPSerifSC-Regular.woff2
├── MdTPSerifSC-Bold.ttf ├── MdTPSerifSC-Bold.ttf
└── MdTPSerifSC-Bold.woff2 └── MdTPSerifSC-Bold.woff2
font-packs/assets/mdtp-sans-sc/1.0.0/
├── MdTPSansSC-Regular.ttf
├── MdTPSansSC-Regular.woff2
├── MdTPSansSC-Bold.ttf
└── MdTPSansSC-Bold.woff2
``` ```
该目录中的 `ttf/otf/woff/woff2``.gitattributes` 强制使用 Git LFS。 该目录中的 `ttf/otf/woff/woff2``.gitattributes` 强制使用 Git LFS。
构建器不会联网下载、转换或自动替换字体。每个输入文件都必须与配方声明的 构建器不会联网下载或自动替换字体。派生字体通过
`font-packs/scripts/build-noto-static.py` 从来源说明固定的 Noto CJK
可变字体生成;每个发行输入文件都必须与配方声明的
字节数和 SHA-256 完全一致;TTF 还必须是无需运行时规范化的原生静态 字节数和 SHA-256 完全一致;TTF 还必须是无需运行时规范化的原生静态
TrueType,并具有可安装或可编辑的文档嵌入权限。 TrueType,并具有可安装或可编辑的文档嵌入权限。
@@ -30,17 +38,13 @@ npm run build:font-pack
npm run verify:font-pack npm run verify:font-pack
``` ```
也可以显式指定源目录:
```powershell
npm run build:font-pack -- --source-root D:/font-pack-sources/mdtp-serif-sc/1.0.0
```
克隆仓库后应先执行 `git lfs pull`。缺少 LFS 内容时,字体构建会因文件大小 克隆仓库后应先执行 `git lfs pull`。缺少 LFS 内容时,字体构建会因文件大小
或 SHA-256 不匹配而拒绝继续,不允许使用系统字体或相近字体静默替代。 或 SHA-256 不匹配而拒绝继续,不允许使用系统字体或相近字体静默替代。
默认运行时字体包根目录为 `output/font-packs/root`,构建报告写入 受版本控制的 `font-packs/bundle.json` 是正式字体包集合清单。构建命令会
`output/font-packs/build-report.json`,两者均被 Git 忽略。输出布局为: 逐包构建、验证,并拒绝 `output/font-packs/root` 中出现清单外字体包,
避免本地残留进入发行产物。各包报告写入
`output/font-packs/<pack-id>-build-report.json`。输出布局为:
```text ```text
root/<pack-id>/<semver>/font-pack.json root/<pack-id>/<semver>/font-pack.json
@@ -53,34 +57,12 @@ root/<pack-id>/<semver>/font-pack.json
`Mdpdf Fandol Song` 的 400/700 字重,不匹配 `SimSun` 等通用别名,避免 `Mdpdf Fandol Song` 的 400/700 字重,不匹配 `SimSun` 等通用别名,避免
错误替换仿宋、黑体、楷体或普通外部主题字体。 错误替换仿宋、黑体、楷体或普通外部主题字体。
## Windows 独立安装器 `mdtp-sans-sc@1.0.0` 提供 `MdTP Sans SC``FandolHei`
`Microsoft YaHei``PingFang SC``Noto Sans CJK SC`
`Source Han Sans SC` 的 400/700 字重映射。主题仍需明确声明所需字体面,
注册器不会对未声明字体进行静默替换。
发布人员可以使用 electron-builder 锁定并校验的 NSIS 工具链构建独立 ## Desktop 内置发行
字体包安装器,不需要额外安装系统 NSIS:
```powershell
npm run build:font-pack:windows
```
默认产物为:
```text
output/font-packs/windows/
└── MorphDoc-FontPack-mdtp-serif-sc-1.0.0-x86_64-Setup.exe
```
安装器使用当前用户权限,将整个 `mdtp-serif-sc` ID 安装到:
```text
%LOCALAPPDATA%/md-to-pdf/font-packs/mdtp-serif-sc/1.0.0/
```
它不向 Windows 系统字体目录注册字体。同一版本重装保持幂等,升级时先把
该 ID 的现有目录移入备份,再原子提升新版本;失败会回滚。独立卸载项只
删除安装器拥有的 `mdtp-serif-sc` ID,不影响其他字体包 ID。安装器支持
`/S` 静默安装和卸载。
## Desktop 伴随发行
组合构建命令为: 组合构建命令为:
@@ -88,32 +70,10 @@ output/font-packs/windows/
npm run desktop:make:with-font-pack npm run desktop:make:with-font-pack
``` ```
该命令把主安装器、字体包安装器和字体包 ZIP 作为同目录文件发行。主安装器 该命令先重建完整字体包集合,再构建主安装器和便携 ZIP。electron-builder
只编译字体包安装器的文件名与 SHA-256,不嵌入字体二进制;仅当同目录文件 `output/font-packs/root` 复制到 `resources/font-packs`Desktop 生产
存在且哈希完全一致时,交互安装向导才显示默认勾选的字体包选项。字体 运行时优先读取该只读内置根目录,再读取用户目录中的额外字体包。内置
失败不回滚已安装的主程序 因此不会被缺失、旧版或同 ID 用户包替换
主安装器静默运行时默认不安装伴随字体包;只有显式传入以下参数才会安装:
```powershell
MorphDoc-<version>-x86_64-Setup.exe /S /WITHFONTPACK=1
```
普通 `npm run make -w @md-to-pdf/desktop` 不启用伴随逻辑,避免本地残留的
生成文件影响常规桌面构建。
## ZIP 发行附件
独立生成可手动部署的确定性 ZIP
```powershell
npm run build:font-pack:archive
```
产物为 `output/font-packs/release/MorphDoc-FontPack-mdtp-serif-sc-1.0.0.zip`
归档顶层已经包含 `mdtp-serif-sc/1.0.0/`,用户可直接解压到
`%LOCALAPPDATA%/md-to-pdf/font-packs`。ZIP 内保留清单、许可证、来源说明
以及 Web/DOCX 两类字体资产;相同输入会生成逐字节一致的附件。
## Docker 内置发行 ## Docker 内置发行
Binary file not shown.
Binary file not shown.
+17
View File
@@ -0,0 +1,17 @@
{
"schemaVersion": 1,
"packs": [
{
"id": "mdtp-serif-sc",
"version": "1.0.0",
"recipe": "font-packs/recipes/mdtp-serif-sc/recipe.json",
"sourceRoot": "font-packs/assets/mdtp-serif-sc/1.0.0"
},
{
"id": "mdtp-sans-sc",
"version": "1.0.0",
"recipe": "font-packs/recipes/mdtp-sans-sc/recipe.json",
"sourceRoot": "font-packs/assets/mdtp-sans-sc/1.0.0"
}
]
}
@@ -0,0 +1,94 @@
Copyright 2012 Google Inc. All Rights Reserved.
This Font Software is licensed under the SIL Open Font License,
Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font
creation efforts of academic and linguistic communities, and to
provide a free and open framework in which fonts may be shared and
improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply to
any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software
components as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to,
deleting, or substituting -- in part or in whole -- any of the
components of the Original Version, by changing formats or by porting
the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed,
modify, redistribute, and sell modified and unmodified copies of the
Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in
Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the
corresponding Copyright Holder. This restriction only applies to the
primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created using
the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+29
View File
@@ -0,0 +1,29 @@
# MdTP Sans SC 字体来源说明
本字体包的 Regular 400 与 Bold 700 字形面派生自 Noto Sans SC 可变字体。
- 上游项目:Noto CJK
- 上游仓库:<https://github.com/notofonts/noto-cjk>
- Google Fonts 源提交:`985fa52c81c1d6692ccdd82bc3656e8fb932fd89`
- 上游源文件:`google-fonts/NotoSansSC[wght].ttf`
- 上游源 SHA-256`b1736bb633392cfc59b50d97c353d234111bd1721c9c83eaf643eaab9392e7f9`
- 许可证:SIL Open Font License 1.1
发行字形面使用 fontTools 4.60.1 从 `wght` 轴固定生成,并将主字体家族改名
`MdTP Sans SC`、PostScript 前缀改名为 `MdTPSansSC`。改名用于明确
标识派生版本,并避免派生字体占用上游字体名称。字形轮廓与字符覆盖未做
删减;`OS/2.fsType` 为 0,允许安装和文档嵌入。
可复现生成命令:
```powershell
python font-packs/scripts/build-noto-static.py `
--source .local/font-pack-upstream/NotoSansSC-wght.ttf `
--output font-packs/assets/mdtp-sans-sc/1.0.0 `
--family "MdTP Sans SC" `
--postscript-family MdTPSansSC
```
字体包构建器不从网络下载字体。发布人员必须先核对固定上游文件的
SHA-256,再生成配方声明的四个冻结资源;构建器会继续校验文件大小、
SHA-256、格式、字体内部家族、字重与嵌入权限。
@@ -0,0 +1,57 @@
{
"recipeVersion": 1,
"manifestVersion": 1,
"id": "mdtp-sans-sc",
"version": "1.0.0",
"name": "MdTP 中文黑体兼容包",
"description": "为现代文档与商务主题提供经过 Word/WPS 验证的原生静态中文黑体 Regular 与 Bold 字形面。",
"internalFamily": "MdTP Sans SC",
"license": "OFL-1.1",
"licenseSource": "OFL-NotoSansSC.txt",
"licensePath": "OFL-NotoSansSC.txt",
"licenseBytes": 4350,
"licenseSha256": "5e0da210fb04058a8c0087985d2d456b931c2579811a49655721d3cf0c36b6d6",
"noticeSource": "SOURCE.md",
"noticePath": "SOURCE.md",
"compatibility": {
"minimumAppVersion": "0.6.0"
},
"faces": [
{
"id": "sans-regular",
"targets": ["MdTP Sans SC", "FandolHei", "Microsoft YaHei", "PingFang SC", "Noto Sans CJK SC", "Source Han Sans SC"],
"weight": 400,
"style": "normal",
"web": {
"source": "MdTPSansSC-Regular.woff2",
"path": "fonts/MdTPSansSC-Regular.woff2",
"bytes": 4192788,
"sha256": "45f4ea0072861106b028f975b05752c13b46541dc84799e2746a657593c0c62d"
},
"docx": {
"source": "MdTPSansSC-Regular.ttf",
"path": "fonts/MdTPSansSC-Regular.ttf",
"bytes": 10596656,
"sha256": "a1d7e9c407d664c6d13ab9c01c48eca6775d77fd6daa3448c41ff7f1a3f316e1"
}
},
{
"id": "sans-bold",
"targets": ["MdTP Sans SC", "FandolHei", "Microsoft YaHei", "PingFang SC", "Noto Sans CJK SC", "Source Han Sans SC"],
"weight": 700,
"style": "normal",
"web": {
"source": "MdTPSansSC-Bold.woff2",
"path": "fonts/MdTPSansSC-Bold.woff2",
"bytes": 4314832,
"sha256": "729e7b445a1db47e588597eaacecebb892ef4de331379ff2439fb0be95aa808a"
},
"docx": {
"source": "MdTPSansSC-Bold.ttf",
"path": "fonts/MdTPSansSC-Bold.ttf",
"bytes": 10586172,
"sha256": "85fcfab7284fd72dfbbdb1a579230a9034dc6f9fc351b40cecd34d46125eebd6"
}
}
]
}
+85
View File
@@ -0,0 +1,85 @@
"""从固定的 Noto CJK 可变字体生成墨呈静态字体面。"""
from __future__ import annotations
import argparse
from pathlib import Path
from fontTools.ttLib import TTFont
from fontTools.varLib.instancer import instantiateVariableFont
def set_name(font: TTFont, name_id: int, value: str) -> None:
name_table = font["name"]
for platform_id, encoding_id, language_id in (
(3, 1, 0x0409),
(1, 0, 0),
):
name_table.setName(
value,
name_id,
platform_id,
encoding_id,
language_id,
)
def build_face(
source: Path,
output_root: Path,
family: str,
postscript_family: str,
weight: int,
style: str,
) -> None:
font = TTFont(source, recalcTimestamp=False)
instantiateVariableFont(font, {"wght": weight}, inplace=True, optimize=True)
set_name(font, 1, family)
set_name(font, 2, style)
set_name(font, 4, f"{family} {style}")
set_name(font, 6, f"{postscript_family}-{style}")
set_name(font, 16, family)
set_name(font, 17, style)
set_name(font, 25, postscript_family)
font["OS/2"].usWeightClass = weight
font["OS/2"].fsType = 0
output_root.mkdir(parents=True, exist_ok=True)
ttf_path = output_root / f"{postscript_family}-{style}.ttf"
font.flavor = None
font.save(ttf_path, reorderTables=False)
woff2_path = output_root / f"{postscript_family}-{style}.woff2"
font.flavor = "woff2"
font.save(woff2_path, reorderTables=False)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--source", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--family", required=True)
parser.add_argument("--postscript-family", required=True)
arguments = parser.parse_args()
build_face(
arguments.source,
arguments.output,
arguments.family,
arguments.postscript_family,
400,
"Regular",
)
build_face(
arguments.source,
arguments.output,
arguments.family,
arguments.postscript_family,
700,
"Bold",
)
if __name__ == "__main__":
main()
+4 -6
View File
@@ -25,13 +25,11 @@
"dev:web": "npm run dev -w @md-to-pdf/web", "dev:web": "npm run dev -w @md-to-pdf/web",
"dev:desktop": "npm run dev -w @md-to-pdf/desktop", "dev:desktop": "npm run dev -w @md-to-pdf/desktop",
"desktop:dev": "npm run build -w @md-to-pdf/markdown-echarts && npm run build -w @md-to-pdf/core && npm run build -w @md-to-pdf/semantic-document && npm run build -w @md-to-pdf/font-pack-registry && npm run build -w @md-to-pdf/docx-engine && npm run build -w @md-to-pdf/renderer && npm run build -w @md-to-pdf/application && npm run build -w @md-to-pdf/preview-engine && concurrently -k -n web,desktop \"npm:dev:web\" \"npm:dev:desktop\"", "desktop:dev": "npm run build -w @md-to-pdf/markdown-echarts && npm run build -w @md-to-pdf/core && npm run build -w @md-to-pdf/semantic-document && npm run build -w @md-to-pdf/font-pack-registry && npm run build -w @md-to-pdf/docx-engine && npm run build -w @md-to-pdf/renderer && npm run build -w @md-to-pdf/application && npm run build -w @md-to-pdf/preview-engine && concurrently -k -n web,desktop \"npm:dev:web\" \"npm:dev:desktop\"",
"desktop:package": "npm run package -w @md-to-pdf/desktop", "desktop:package": "npm run build:font-pack && npm run package -w @md-to-pdf/desktop",
"build:font-pack": "npm run build -w @md-to-pdf/core && npm run build -w @md-to-pdf/docx-theme-engine && npm run build -w @md-to-pdf/font-pack-registry && npm run build -w @md-to-pdf/docx-engine && npm run build -w @md-to-pdf/font-pack-builder && node packages/font-pack-builder/dist/cli.js build", "build:font-pack": "npm run build -w @md-to-pdf/core && npm run build -w @md-to-pdf/docx-theme-engine && npm run build -w @md-to-pdf/font-pack-registry && npm run build -w @md-to-pdf/docx-engine && npm run build -w @md-to-pdf/font-pack-builder && node scripts/build-font-packs.mjs",
"verify:font-pack": "npm run build -w @md-to-pdf/core && npm run build -w @md-to-pdf/docx-theme-engine && npm run build -w @md-to-pdf/font-pack-registry && npm run build -w @md-to-pdf/docx-engine && npm run build -w @md-to-pdf/font-pack-builder && node packages/font-pack-builder/dist/cli.js verify", "verify:font-pack": "npm run build:font-pack",
"verify:docker-font-pack": "npm run verify:font-pack && npm run build:web-runtime && npm run build -w @md-to-pdf/server && node deploy/verify-font-pack-compose.mjs", "verify:docker-font-pack": "npm run verify:font-pack && npm run build:web-runtime && npm run build -w @md-to-pdf/server && node deploy/verify-font-pack-compose.mjs",
"build:font-pack:windows": "npm run verify:font-pack && node packages/font-pack-builder/dist/cli.js windows-installer", "desktop:make:with-font-pack": "npm run build:font-pack && node scripts/build-desktop-with-font-pack.mjs",
"build:font-pack:archive": "npm run verify:font-pack && node packages/font-pack-builder/dist/cli.js archive",
"desktop:make:with-font-pack": "npm run build:font-pack:windows && npm run build:font-pack:archive && node scripts/build-desktop-with-font-pack.mjs",
"release:stage": "node scripts/stage-release.mjs", "release:stage": "node scripts/stage-release.mjs",
"test:release-stage": "node --test scripts/stage-release.test.mjs", "test:release-stage": "node --test scripts/stage-release.test.mjs",
"test:docx-layout-visual-matrix-cases": "node --test scripts/docx-layout-visual-matrix-cases.test.mjs", "test:docx-layout-visual-matrix-cases": "node --test scripts/docx-layout-visual-matrix-cases.test.mjs",
+3 -42
View File
@@ -1,45 +1,13 @@
import { copyFile, mkdir, readFile } from "node:fs/promises"; import { readFile } from "node:fs/promises";
import { spawn } from "node:child_process"; import { spawn } from "node:child_process";
import { dirname, join, resolve } from "node:path"; import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import { createDesktopCompanionInclude } from "../packages/font-pack-builder/dist/index.js";
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const desktopRoot = join(repositoryRoot, "apps", "desktop"); const desktopRoot = join(repositoryRoot, "apps", "desktop");
const desktopManifest = JSON.parse( const desktopManifest = JSON.parse(
await readFile(join(desktopRoot, "package.json"), "utf8") await readFile(join(desktopRoot, "package.json"), "utf8")
); );
const fontInstallerName =
"MorphDoc-FontPack-mdtp-serif-sc-1.0.0-x86_64-Setup.exe";
const fontArchiveName = "MorphDoc-FontPack-mdtp-serif-sc-1.0.0.zip";
const fontInstallerPath = join(
repositoryRoot,
"output",
"font-packs",
"windows",
fontInstallerName
);
const generatedInclude = join(
repositoryRoot,
"output",
"font-packs",
"desktop-companion.nsh"
);
const fontArchivePath = join(
repositoryRoot,
"output",
"font-packs",
"release",
fontArchiveName
);
await createDesktopCompanionInclude({
installerPath: fontInstallerPath,
baseIncludePath: join(desktopRoot, "build", "installer.nsh"),
outputPath: generatedInclude,
displayName: "MdTP 中文宋体兼容包 1.0.0"
});
const npmCli = const npmCli =
process.env.npm_execpath || process.env.npm_execpath ||
join(dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js"); join(dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js");
@@ -49,10 +17,7 @@ await new Promise((resolvePromise, reject) => {
[npmCli, "run", "make", "-w", "@md-to-pdf/desktop"], [npmCli, "run", "make", "-w", "@md-to-pdf/desktop"],
{ {
cwd: repositoryRoot, cwd: repositoryRoot,
env: { env: process.env,
...process.env,
MD_TO_PDF_NSIS_INCLUDE: generatedInclude
},
stdio: "inherit", stdio: "inherit",
windowsHide: true windowsHide: true
} }
@@ -71,10 +36,6 @@ await new Promise((resolvePromise, reject) => {
}); });
}); });
const desktopOutput = join(desktopRoot, "out", `v${desktopManifest.version}`);
await mkdir(desktopOutput, { recursive: true });
await copyFile(fontInstallerPath, join(desktopOutput, fontInstallerName));
await copyFile(fontArchivePath, join(desktopOutput, fontArchiveName));
process.stdout.write( process.stdout.write(
`主安装器、字体包伴随安装器与 ZIP 附件已生成:${desktopOutput}\n` `已生成内置字体包的主安装器与便携 ZIP${join(desktopRoot, "out", `v${desktopManifest.version}`)}\n`
); );
+75
View File
@@ -0,0 +1,75 @@
import { readFile } from "node:fs/promises";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import {
buildFontPack,
verifyFontPack
} from "../packages/font-pack-builder/dist/index.js";
import { discoverFontPacks } from "../packages/font-pack-registry/dist/index.js";
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const bundlePath = join(repositoryRoot, "font-packs", "bundle.json");
const bundle = JSON.parse(await readFile(bundlePath, "utf8"));
const outputRoot = join(repositoryRoot, "output", "font-packs", "root");
const appVersion = process.env.npm_package_version || "0.6.0";
if (
bundle.schemaVersion !== 1 ||
!Array.isArray(bundle.packs) ||
bundle.packs.length === 0
) {
throw new Error("字体包集合清单无效");
}
const identities = new Set();
const reports = [];
for (const pack of bundle.packs) {
const identity = `${pack.id}@${pack.version}`;
if (identities.has(identity)) {
throw new Error(`字体包集合存在重复项:${identity}`);
}
identities.add(identity);
const reportPath = join(
repositoryRoot,
"output",
"font-packs",
`${pack.id}-build-report.json`
);
const built = await buildFontPack({
recipePath: resolve(repositoryRoot, pack.recipe),
sourceRoot: resolve(repositoryRoot, pack.sourceRoot),
outputRoot,
reportPath
});
const verified = await verifyFontPack({
root: outputRoot,
appVersion,
packId: pack.id,
packVersion: pack.version
});
reports.push({ identity, built, verified });
}
const registry = await discoverFontPacks({
roots: [outputRoot],
appVersion
});
const errors = registry.diagnostics.filter((item) => item.severity === "error");
if (errors.length > 0) {
throw new Error(
`字体包集合注册失败:${errors.map((item) => `${item.code} ${item.message}`).join("")}`
);
}
const actualIdentities = registry.packs
.map((pack) => `${pack.id}@${pack.version}`)
.sort();
const expectedIdentities = [...identities].sort();
if (JSON.stringify(actualIdentities) !== JSON.stringify(expectedIdentities)) {
throw new Error(
`字体包输出根目录包含清单外内容:期望 ${expectedIdentities.join(", ")},实际 ${actualIdentities.join(", ")}`
);
}
process.stdout.write(
`${JSON.stringify({ appVersion, outputRoot, registryFingerprint: registry.fingerprint, reports }, null, 2)}\n`
);
+1 -3
View File
@@ -184,9 +184,7 @@ export async function stageRelease(options = {}) {
); );
const desktopNames = [ const desktopNames = [
`MorphDoc-${version}-x86_64-Setup.exe`, `MorphDoc-${version}-x86_64-Setup.exe`,
`MorphDoc-${version}-x86_64.zip`, `MorphDoc-${version}-x86_64.zip`
"MorphDoc-FontPack-mdtp-serif-sc-1.0.0-x86_64-Setup.exe",
"MorphDoc-FontPack-mdtp-serif-sc-1.0.0.zip"
]; ];
const releaseNotesSource = join( const releaseNotesSource = join(
repositoryRoot, repositoryRoot,
+5 -5
View File
@@ -8,7 +8,7 @@
#write { #write {
color: var(--official-ink); color: var(--official-ink);
font-family: font-family:
"FandolFang", "FangSong", "STFangsong", "Noto Serif CJK SC", "FandolSong", "FangSong", "STFangsong", "Noto Serif CJK SC",
"Source Han Serif SC", "SimSun", serif; "Source Han Serif SC", "SimSun", serif;
font-size: 21.333px; font-size: 21.333px;
line-height: 1.75; line-height: 1.75;
@@ -64,13 +64,13 @@
#write .doc-signatory > span { #write .doc-signatory > span {
font-family: font-family:
"FandolFang", "FangSong", "STFangsong", "FandolSong", "FangSong", "STFangsong",
"Noto Serif CJK SC", serif; "Noto Serif CJK SC", serif;
} }
#write .doc-signatory { #write .doc-signatory {
font-family: font-family:
"FandolKai", "KaiTi", "STKaiti", "FandolSong", "KaiTi", "STKaiti",
"Noto Serif CJK SC", serif; "Noto Serif CJK SC", serif;
} }
@@ -110,7 +110,7 @@
#write h5, #write h5,
#write h6 { #write h6 {
font-family: font-family:
"FandolKai", "KaiTi", "STKaiti", "FandolSong", "KaiTi", "STKaiti",
"Noto Serif CJK SC", serif; "Noto Serif CJK SC", serif;
font-weight: 400; font-weight: 400;
} }
@@ -222,7 +222,7 @@
border-bottom: 0.35mm solid var(--official-rule); border-bottom: 0.35mm solid var(--official-rule);
break-inside: avoid; break-inside: avoid;
font-family: font-family:
"FandolFang", "FangSong", "STFangsong", "FandolSong", "FangSong", "STFangsong",
"Noto Serif CJK SC", serif; "Noto Serif CJK SC", serif;
font-size: 0.875em; font-size: 0.875em;
line-height: 1.7; line-height: 1.7;
-12
View File
@@ -77,23 +77,11 @@
"weight": 700, "weight": 700,
"license": "GPL-3.0-or-later WITH Font-exception-2.0" "license": "GPL-3.0-or-later WITH Font-exception-2.0"
}, },
{
"family": "FandolFang",
"aliases": ["Mdpdf Fandol Fang", "FangSong", "STFangsong"],
"source": "theme-shared:official-fonts/FandolFang-Regular.woff2",
"license": "GPL-3.0-or-later WITH Font-exception-2.0"
},
{ {
"family": "FandolHei", "family": "FandolHei",
"aliases": ["Mdpdf Fandol Hei", "SimHei", "Noto Sans CJK SC", "Source Han Sans SC", "Microsoft YaHei"], "aliases": ["Mdpdf Fandol Hei", "SimHei", "Noto Sans CJK SC", "Source Han Sans SC", "Microsoft YaHei"],
"source": "theme-shared:official-fonts/FandolHei-Regular.woff2", "source": "theme-shared:official-fonts/FandolHei-Regular.woff2",
"license": "GPL-3.0-or-later WITH Font-exception-2.0" "license": "GPL-3.0-or-later WITH Font-exception-2.0"
},
{
"family": "FandolKai",
"aliases": ["Mdpdf Fandol Kai", "KaiTi", "STKaiti"],
"source": "theme-shared:official-fonts/FandolKai-Regular.woff2",
"license": "GPL-3.0-or-later WITH Font-exception-2.0"
} }
] ]
}, },
+4 -4
View File
@@ -8,7 +8,7 @@
#write { #write {
color: var(--briefing-ink); color: var(--briefing-ink);
font-family: font-family:
"Noto Serif CJK SC", "Source Han Serif SC", "FandolSong", "Noto Serif CJK SC", "Source Han Serif SC",
"Microsoft YaHei", "SimSun", serif; "Microsoft YaHei", "SimSun", serif;
font-size: 18px; font-size: 18px;
line-height: 1.8; line-height: 1.8;
@@ -27,7 +27,7 @@
border-bottom: 1mm solid var(--briefing-red); border-bottom: 1mm solid var(--briefing-red);
color: var(--briefing-red); color: var(--briefing-red);
font-family: font-family:
"STZhongsong", "Noto Serif CJK SC", "Source Han Serif SC", serif; "FandolSong", "STZhongsong", "Noto Serif CJK SC", "Source Han Serif SC", serif;
font-size: 2.35em; font-size: 2.35em;
font-weight: 700; font-weight: 700;
letter-spacing: 0.18em; letter-spacing: 0.18em;
@@ -55,7 +55,7 @@
#write .doc-title { #write .doc-title {
margin: 0 0 8mm; margin: 0 0 8mm;
font-family: font-family:
"STZhongsong", "Noto Serif CJK SC", "Source Han Serif SC", serif; "FandolSong", "STZhongsong", "Noto Serif CJK SC", "Source Han Serif SC", serif;
font-size: 1.65em; font-size: 1.65em;
font-weight: 700; font-weight: 700;
line-height: 1.4; line-height: 1.4;
@@ -68,7 +68,7 @@
#write h4 { #write h4 {
margin: 1.35em 0 0.5em; margin: 1.35em 0 0.5em;
font-family: font-family:
"Noto Sans CJK SC", "Source Han Sans SC", "Microsoft YaHei", sans-serif; "MdTP Sans SC", "Noto Sans CJK SC", "Source Han Sans SC", "Microsoft YaHei", sans-serif;
font-weight: 700; font-weight: 700;
line-height: 1.5; line-height: 1.5;
} }
+25
View File
@@ -32,5 +32,30 @@
"docxStyle": { "docxStyle": {
"preset": "official" "preset": "official"
}, },
"docxFonts": {
"faces": [
{
"family": "FandolSong",
"aliases": ["Mdpdf Fandol Song", "STZhongsong", "Noto Serif CJK SC", "Source Han Serif SC", "SimSun"],
"source": "theme-shared:official-fonts/FandolSong-Regular.woff2",
"weight": 400,
"license": "GPL-3.0-or-later WITH Font-exception-2.0"
},
{
"family": "FandolSong",
"aliases": ["Mdpdf Fandol Song", "STZhongsong", "Noto Serif CJK SC", "Source Han Serif SC", "SimSun"],
"source": "theme-shared:official-fonts/FandolSong-Bold.woff2",
"weight": 700,
"license": "GPL-3.0-or-later WITH Font-exception-2.0"
},
{
"family": "MdTP Sans SC",
"aliases": ["FandolHei", "Noto Sans CJK SC", "Source Han Sans SC", "Microsoft YaHei"],
"source": "theme-shared:official-fonts/FandolHei-Regular.woff2",
"weight": 700,
"license": "OFL-1.1"
}
]
},
"bundled": true "bundled": true
} }