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
+25 -65
View File
@@ -1,9 +1,9 @@
# 可选字体包构建
# 内置字体包集合构建
本目录保存字体包配方、许可证、来源说明和正式冻结字体资产。字体二进制
位于固定的 `font-packs/assets/`,统一由 Git LFS 管理,普通 Git 对象只保存
LFS 指针。普通 Desktop 安装包不包含字体;组合 Desktop 发行把字体作为
同目录可选附件,Docker 发行构建则把本次受校验的字体包写入镜像层
LFS 指针。Desktop 安装版、便携版和 Docker 镜像都直接包含本次受校验的
完整字体包集合,不再依赖用户另行安装字体附件
## 源目录
@@ -16,10 +16,18 @@ font-packs/assets/mdtp-serif-sc/1.0.0/
├── MdTPSerifSC-Regular.woff2
├── MdTPSerifSC-Bold.ttf
└── 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。
构建器不会联网下载、转换或自动替换字体。每个输入文件都必须与配方声明的
构建器不会联网下载或自动替换字体。派生字体通过
`font-packs/scripts/build-noto-static.py` 从来源说明固定的 Noto CJK
可变字体生成;每个发行输入文件都必须与配方声明的
字节数和 SHA-256 完全一致;TTF 还必须是无需运行时规范化的原生静态
TrueType,并具有可安装或可编辑的文档嵌入权限。
@@ -30,17 +38,13 @@ npm run build: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 内容时,字体构建会因文件大小
或 SHA-256 不匹配而拒绝继续,不允许使用系统字体或相近字体静默替代。
默认运行时字体包根目录为 `output/font-packs/root`,构建报告写入
`output/font-packs/build-report.json`,两者均被 Git 忽略。输出布局为:
受版本控制的 `font-packs/bundle.json` 是正式字体包集合清单。构建命令会
逐包构建、验证,并拒绝 `output/font-packs/root` 中出现清单外字体包,
避免本地残留进入发行产物。各包报告写入
`output/font-packs/<pack-id>-build-report.json`。输出布局为:
```text
root/<pack-id>/<semver>/font-pack.json
@@ -53,34 +57,12 @@ root/<pack-id>/<semver>/font-pack.json
`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 工具链构建独立
字体包安装器,不需要额外安装系统 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 伴随发行
## Desktop 内置发行
组合构建命令为:
@@ -88,32 +70,10 @@ output/font-packs/windows/
npm run desktop:make:with-font-pack
```
该命令把主安装器、字体包安装器和字体包 ZIP 作为同目录文件发行。主安装器
只编译字体包安装器的文件名与 SHA-256,不嵌入字体二进制;仅当同目录文件
存在且哈希完全一致时,交互安装向导才显示默认勾选的字体包选项。字体
失败不回滚已安装的主程序
主安装器静默运行时默认不安装伴随字体包;只有显式传入以下参数才会安装:
```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 两类字体资产;相同输入会生成逐字节一致的附件。
该命令先重建完整字体包集合,再构建主安装器和便携 ZIP。electron-builder
`output/font-packs/root` 复制到 `resources/font-packs`Desktop 生产
运行时优先读取该只读内置根目录,再读取用户目录中的额外字体包。内置
因此不会被缺失、旧版或同 ID 用户包替换
## 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()