news 2026/9/12 5:21:59

D2 字体嵌入机制全解析:SVG 内嵌字体、自定义字体接入与跨平台回退

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
D2 字体嵌入机制全解析:SVG 内嵌字体、自定义字体接入与跨平台回退

D2 字体嵌入机制全解析:SVG 内嵌字体、自定义字体接入与跨平台回退

【免费下载链接】d2D2 is a modern diagram scripting language that turns text to diagrams.项目地址: https://gitcode.com/GitHub_Trending/d2/d2

D2(d2lang/d2)是一款"把文本变成图表"的现代图表脚本语言,其 SVG 渲染器会把字体文件直接以 base64 数据的形式内嵌进 SVG,从而保证输出结果确定、且无需任何网络请求即可在浏览器中完整呈现。本篇指南以d2renderers/d2fonts/README.md为核心骨架,结合仓库源码,系统讲解 D2 的内嵌字体机制、如何为 D2 接入自己的字体(如 Helvetica)、内置字体家族的资源组织方式,以及主字体缺字时的回退策略,读完你就能自行定制 D2 输出中的字体并理解其底层原理。

为什么要把字体直接嵌进 SVG

d2renderers/d2fonts/README.md开篇就给出了这一设计的两个核心动机:

  1. 确定性输出(deterministic outputs):字体随 SVG 一起打包,不依赖查看端机器上是否安装了对应字体,同一份.d2源码在任何环境渲染出的字形完全一致;
  2. 无网络调用(load without a network call):SVG 打开即完整渲染,不依赖外链的字体 CDN,也不受离线场景限制。

因此,D2 的 SVG 输出中会携带形如data:application/font-woff;base64,AAAA...的字体 data URI(见 d2fonts_common.go),这些 base64 字符串正是嵌入字体的载体。

内置字体家族与资源目录布局

d2fonts包默认内置三个字体家族(定义于 d2fonts_common.go):

字体家族类型用途
SourceSansPro无衬线体默认字体(对应--theme下的普通文本)
SourceCodePro等宽字体代码块 / 等宽场景(对应mono
HandDrawn(FuzzyBubbles)手绘风格草图/手绘主题

每个家族按四种字型(style)组织,常量定义在 d2fonts_common.go:regularboldsemibolditalic。字号从 XS 到 XXXL 共 8 档,取值 13、14、16、20、24、28、32(见 d2fonts_common.go)。

资源文件按构建目标分两种形态存放于 d2renderers/d2fonts/encoded/:

  • 非 WASM(原生)构建:嵌入encoded/*.txt(即 base64 编码的 WOFF 文本)与ttf/*.ttf原文件,见 d2fonts_embed.go 同目录的 d2fonts_embed.go 中//go:embed encoded/SourceSansPro-Regular.txt等指令;
  • WASM 构建:嵌入encoded/*.txt.br(Brotli 压缩后的编码文本),运行时解压,见 d2fonts_embed_wasm.go 与 d2fonts_embed_wasm.go 中的compression.DecompressBrotli解压逻辑。两种形态的存在是为了压缩 JS/WASM 包的体积,源码注释(d2fonts_common.go)对此有明确说明。

每个内嵌字体的字型映射在init()中注册到FontEncodings(编码字符串)与FontFaces(TTF 字节)两个全局注册表(d2fonts_embed.go)。其中HandDrawn家族没有独立的 italic 与 semibold 字型,会分别复用 regular 和 bold(d2fonts_embed.go)。

此外,D2 还把内置字体(含 Noto Color Emoji)的 SHA-256 摘要固定注册,供光栅化管线做身份校验,见 bundled_faces.go 中的bundledFaceSpecsfontface.RegisterBundledFace调用。

如何接入自定义字体(以 Helvetica 为例)

README 以Helvetica为例给出了添加自定义字体的完整要求。第一步:提供 Truetype 字形文件(.ttf,放入d2renderers/d2fonts/ttf/目录:

./ttf/Helvetica-Bold.ttf ./ttf/Helvetica-Italic.ttf ./ttf/Helvetica-Regular.ttf

第二步:提供这些字体的编码版本,mimetype 必须为application/font-woff,放入d2renderers/d2fonts/encoded/目录,与 TTF 一一对应:

./ttf/Helvetica-Bold.txt ./ttf/Helvetica-Italic.txt ./ttf/Helvetica-Regular.txt

注意两点:其一,编码文件内容本质上就是data:application/font-woff;base64,<base64>形式的 WOFF 数据文本(README 中的./ttf/Helvetica-*.txt为相对encoded/目录的示意路径,实际目录见 d2renderers/d2fonts/encoded/);其二,若想贡献字体给 D2 仓库,该字体必须具有开源许可证(README 明确要求:"If you include a font to contribute, it must have an open license.")。

仓库源码为"先转 WOFF 再 base64"这一流程提供了直接实现:AddFontStyle会调用fontlib.Sfnt2Woff(ttf)把 TTF 转成 WOFF,再用base64.StdEncoding.EncodeToString编码并拼接data:application/font-woff;base64,前缀(d2fonts_common.go)。

缺失字型的回退行为

AddFontFamily是运行时注册自定义字族的入口(d2fonts_common.go),签名如下:

func AddFontFamily(name string, regularTTF, italicTTF, boldTTF, semiboldTTF []byte) (*FontFamily, error)
  • 传入的四个字型 TTF 均可为空(nil);
  • 某个字型缺失时,会自动回退到 SourceSansPro 的对应字型(如无 italic 则用 SourceSansPro-Italic),保证图表的粗体、斜体仍然成立(见 d2fonts_common.go 等处的 fallback 分支);
  • 注册成功后,该自定义家族会被追加进全局FontFamilies列表,从而参与后续渲染。

运行时的字形子集化

D2 并非把整份字体嵌入每个 SVG,而是按文本语料做子集化Font.GetEncodedSubset(corpus string)会先去重提取语料中的唯一字符(uniqueChars),调用font.UTF8CutFont(fontBuf, uniqueChars)裁剪出仅含所需字形的最小 TTF,再经fontlib.Sfnt2Woff转 WOFF 并 base64 编码(d2fonts_common.go)。若子集化失败(如字体结构特殊),则回退到完整字体的编码(FontEncodings.Get(f)),保证功能不中断。这解释了为什么最终 SVG 既自带字体、又不会过分臃肿。

CLI 层面对字体的配置

自定义字体不仅能在仓库内硬编码,还可在命令行直接指定 TTF 路径。d2cli提供了 8 个字体相关参数(main.go),均有环境变量与 flag 两种写法:

环境变量Flag说明缺省值
D2_FONT_REGULAR--font-regular常规字体 TTF 路径Source Sans Pro Regular
D2_FONT_ITALIC--font-italic斜体字体 TTF 路径Source Sans Pro Italic
D2_FONT_BOLD--font-bold粗体字体 TTF 路径Source Sans Pro Bold
D2_FONT_SEMIBOLD--font-semibold半粗体字体 TTF 路径Source Sans Pro Semibold
D2_FONT_MONO--font-mono等宽常规字体 TTF 路径Source Code Pro Regular
D2_FONT_MONO_BOLD--font-mono-bold等宽粗体 TTF 路径Source Code Pro Bold
D2_FONT_MONO_ITALIC--font-mono-italic等宽斜体 TTF 路径Source Code Pro Italic
D2_FONT_MONO_SEMIBOLD--font-mono-semibold等宽半粗体 TTF 路径Source Code Pro Semibold

例如:

d2 --font-regular=/path/to/Helvetica-Regular.ttf \ --font-bold=/path/to/Helvetica-Bold.ttf \ --font-italic=/path/to/Helvetica-Italic.ttf \ --font-semibold=/path/to/Helvetica-Semibold.ttf \ input.d2 output.svg

loadFonts实现(main.go)会读取各路径的 TTF 字节,并调用d2fonts.AddFontFamily("custom", ...)d2fonts.AddFontFamily("customMono", ...)分别注册主字体族与等宽字体族。也就是说,CLI 方式无需预先准备 WOFF 编码文本——TTF 会在加载时由Sfnt2Woff现场转换,而 README 所述的*.txt编码文件是为"内置/贡献字体"这种静态嵌入场景准备的。

主字体缺字时的回退解析

当文本中的字符超出主字体覆盖范围(如中文、阿拉伯文、表情符号)时,d2fonts提供两级回退:

  1. 内置 Noto Color Emoji 兜底BundledFallbackResolver优先用仓库固定的 Noto Color Emoji(NotoColorEmoji-COLRv1-v2.051.ttf.br,见 encoded/)解析支持的符号与 emoji,保证跨机器字形确定(bundled_fallback.go)。在 js/wasm 构建下该内置字形缺失,请求直接透传给下游;
  2. 系统字体回退SystemFallbackResolver按操作系统约定的字体根目录扫描候选字体(macOS 的/System/Library/Fonts/Library/Fonts,Linux/BSD 的/usr/share/fonts/usr/local/share/fonts,Windows 的%WINDIR%\Fonts,见 fallback.go),仅当内置字体无法覆盖时才动用。它通过 cmap 探针轻量检查覆盖范围(fallback.go),并按字形权重、斜体、等宽等属性为候选排序(fallback.go),同时用SystemFallbackLimits(目录条目数、文件数、扫描字节、覆盖检查次数等)约束整个检索过程(fallback.go),避免无界开销。

这一两级回退机制在 fallback_test.go 等测试中都有覆盖,包括"回退字节必须独立拷贝、不得与原文件共享底层数组"等细节。

小结

  • D2 通过data:application/font-woff;base64,...将字体内嵌进 SVG,兼顾确定性输出与离线可用;
  • 内置家族为 SourceSansPro、SourceCodePro、HandDrawn(FuzzyBubbles),每个家族含 regular/bold/semibold/italic 四种字型;原生构建嵌入encoded/*.txt,WASM 构建嵌入 Brotli 压缩的encoded/*.txt.br
  • 自定义字体需提供 TTF 及其 WOFF base64 编码文本(mimetype 为application/font-woff),贡献给仓库的字体必须有开源许可证;
  • 运行时按语料做字形子集化后再编码,避免 SVG 冗余;CLI 提供--font-regular等 8 个参数直接加载外部 TTF;
  • 主字体缺字时,先走内置 Noto Color Emoji 兜底,再按严格限额扫描系统字体目录,保证任意脚本文本都能确定性渲染。

相关源码入口:d2fonts_common.go、d2fonts_embed.go、d2fonts_embed_wasm.go、fallback.go、bundled_fallback.go、CLI 字体参数。

【免费下载链接】d2D2 is a modern diagram scripting language that turns text to diagrams.项目地址: https://gitcode.com/GitHub_Trending/d2/d2

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 5:18:35

Dancing Links算法:精确覆盖问题的高效解法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 5:17:24

CTF实战:从Web漏洞到隐写分析,系统梳理获取FLAG的常见方法

1. 内容整体设计与思路拆解1.1 FLAG 为什么是 CTF 的“终极目标”CTF&#xff08;Capture The Flag&#xff0c;夺旗赛&#xff09;的核心玩法很简单——题目里藏着一个字符串&#xff0c;叫 FLAG&#xff0c;你把它找出来、提交上去&#xff0c;就能得分。比赛排名看的就是谁能…

作者头像 李华
网站建设 2026/9/12 5:15:27

GPT Image 2实战指南:从多模态原理到提示词工程的资源合集

GPT Image 2发布以后&#xff0c;身边不少做设计、运营、内容创作的朋友都在问同一个问题&#xff1a;这个模型到底能干什么&#xff0c;和之前的版本比有什么不同&#xff0c;怎么才能真正把它用起来而不是只会生成几张好看的图&#xff1f;我搜集整理了一段时间的资料&#x…

作者头像 李华