Skip to content
0

macOS 下 Zathura 渲染中文 PDF 乱码修复指南

在 macOS 原生环境下,解决 Zathura 中文乱码的思路需要从 Homebrew 依赖和 macOS 字体路径入手。Zathura 在 Mac 上通常通过 poppler 引擎渲染,而 poppler 默认不带中文字符映射表。

处理这类问题的过程确实非常繁琐,涉及到了 macOS 系统字体、Homebrew 隔离环境以及 Linux 移植软件 (Zathura/Poppler) 之间的三方博弈。

问题背景

在 macOS 上使用 zathura (基于 poppler 引擎) 打开某些 PDF(如亚马逊 FBA 标签、电子发票)时,如果 PDF 未嵌入中文字体且使用了特定的 CJK 编码(如 UniGB-UCS2-H),会导致界面显示乱码或字符偏移。

第一阶段:环境与编码准备

1. 安装编码数据包 (Poppler Data)

Poppler 需要特定的“地图”来理解 CJK 编码。由于 brew install poppler-data 在某些环境下可能会失败,手动下载并部署 CMap 数据是最可靠的方法。

1.1 下载官方编码数据

我们需要从 Poppler 官网下载最新的编码数据包:

bash
# 进入临时目录
cd /tmp

# 下载压缩包 (这是官方发布的 CJK 映射表)
curl -O https://poppler.freedesktop.org/poppler-data-0.4.12.tar.gz

# 解压
tar -xvf poppler-data-0.4.12.tar.gz
cd poppler-data-0.4.12

1.2 将数据部署到 macOS 共享目录

我们需要把解压出来的 cMap 等文件夹移动到 poppler 引擎搜索的路径下。

bash
# 创建目标路径 (基于你的 brew prefix)
sudo mkdir -p /opt/homebrew/share/poppler

# 拷贝数据文件
sudo cp -r * /opt/homebrew/share/poppler/

1.3 验证安装

现在检查一下目录结构是否正确:

bash
ls -F /opt/homebrew/share/poppler/

如果安装成功,你应该能看到以下输出:

bash
cidToUnicode/  cMap/   COPYING.adobe Makefile       poppler-data.pc    README
CMakeLists.txt COPYING COPYING.gpl2  nameToUnicode/ poppler-data.pc.in unicodeMap/

为什么亚马逊 FBA 贴纸需要这些?

亚马逊的 PDF 贴纸通常使用 Adobe-GB1 字符集。如果没有你刚刚拷贝的那些 cMap 文件,Poppler 就像一个“没有字典的翻译官”,它知道那里有字符,但不知道对应的中文形状是什么,所以只能显示乱码或方块。

如果有了“字典”(cMap) 还是乱码,可能是 PDF 根本没有自带字体。请在打开该 PDF 后,在终端运行:

bash
cd Downloads
pdffonts FBA19BBHP56S-1775785007292.pdf

观察结果:

  • 如果 emb 列全是 yes,说明字体已嵌入
  • 如果 embnotypeTrueTypeCID,说明 PDF 依赖你系统里的中文字体
name                                 type              encoding         emb sub uni object ID
------------------------------------ ----------------- ---------------- --- --- --- ---------
Helvetica                            Type 1            WinAnsi          no  no  no       2  0
STSong-Light-UniGB-UCS2-H            CID Type 0        UniGB-UCS2-H     no  no  no      42  0

诊断结果出来了。pdffonts 的输出清晰地揭示了问题根源:

  1. 字体未嵌入 (emb=no): 亚马逊生成的这个 PDF 并没有把中文字体包含在文件内。它假设你的系统里有一个叫 STSong-Light (华文宋体) 的字体。
  2. 编码缺失 (uni=no): 它使用了 UniGB-UCS2-H 编码,这正是需要我们之前安装的 cMap 来“翻译”的。
  3. 核心矛盾: 在 macOS 上,Preview 能显示是因为它能自动用系统里的“华文宋体”去补位;但 Zathura 依赖的 fontconfig 找不到 STSong-Light,且 poppler 依然没有识别到你手动安装的 cMap。

2. 安装标准中文字体

由于你在处理亚马逊 FBA 贴纸,这些文件经常调用 Adobe 系列字体或 MS Unicode 字体。

推荐使用 Google 的 Noto Sans CJK SC 系列,它在开源渲染引擎中兼容性最好,且字符覆盖最广。通过 Homebrew 补全最常用的开源中文字体:

zsh
brew install --cask font-noto-sans-cjk-sc

第二阶段:配置 Fontconfig 字体映射

这是最关键的一步。由于 PDF 往往指定了 STSong-Light (华文宋体),而 Poppler 在 macOS 下无法直接通过该名称调用系统字体,我们需要手动强制重定向到 Noto Sans。

1. 创建/编辑配置文件

~/.config/fontconfig/fonts.conf 中写入以下内容:

xml
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
    <dir>/System/Library/Fonts</dir>
    <dir>/Library/Fonts</dir>
    <dir>~/Library/Fonts</dir>

    <match target="pattern">
        <test name="family"><string>STSong-Light</string></test>
        <edit name="family" mode="assign" binding="strong">
            <string>Noto Sans CJK SC</string>
        </edit>
    </match>
    <match target="pattern">
        <test name="family"><string>STSong</string></test>
        <edit name="family" mode="assign" binding="strong">
            <string>Noto Sans CJK SC</string>
        </edit>
    </match>
</fontconfig>

2. 为什么需要双重映射

之所以需要第二个 <match> 标签,是因为 PDF 渲染引擎 (Poppler) 解析字体名称的逻辑Fontconfig 的匹配机制 之间存在一个“模糊地带”。

简单来说,这是因为 PDF 文件内部引用的字体名称与你系统实际安装的字体名称不一致导致的。

1.1 PDF 内部的“套娃”逻辑

根据你之前的 pdffonts 输出:

  • 该文件请求的字体是:STSong-Light
  • 该字体对应的编码是:UniGB-UCS2-H

在 PDF 标准中,STSong-Light 往往被视为 STSong(华文宋体)的一个变体或特定子集。

1.2 为什么第一个 match (STSong-Light) 有时会失效?

当你只写第一个 match 时:

xml
<test name="family"><string>STSong-Light</string></test>
  • 理想情况: Zathura 告诉 Fontconfig:“我需要 STSong-Light”,Fontconfig 返回 Noto Sans
  • 现实情况: 很多 PDF 渲染引擎在查找字体前会进行一次“预处理”。它可能会认为 STSong-Light基础家族名称 (Base Family)STSong
  • 结果: 引擎最终向系统请求的名称可能变成了 STSong 而不是原始的 STSong-Light。因为你的 fonts.conf 里没有定义 STSong 的去向,系统就会 fallback 到它认为最接近的字体(即 macOS 自带的 Songti.ttc)。

1.3 第二个 match (STSong) 的“补网”作用

当你添加了第二个标签:

xml
<test name="family"><string>STSong</string></test>

你实际上是封锁了所有的退路。无论渲染引擎是直接请求全名 STSong-Light,还是自作聪明地简化成 STSong 去请求,Fontconfig 都会强行把请求拦截并导向 Noto Sans CJK SC

1.4 编码 (CMap) 与字体的协同效应

这里有一个关键的技术细节:华文宋体 (Songti.ttc) 与 UniGB-UCS2-H 编码的八字不合。

  • 当你使用系统自带的 STSong (华文宋体) 时: 由于它是 macOS 系统的原生字体,Poppler 在 Linux 兼容层下调用它时,往往无法正确通过 UniGB-UCS2-H 映射表找到对应的字符位置,结果就是乱码。
  • 当你强制映射到 Noto Sans CJK SC 时: 这个字体是现代的 OpenType 格式,内置了极其完整的字符映射表。即便 Poppler 对编码的解析不够完美,由于 Noto 字体对 CJK 字符的覆盖极其宽泛且符合现代标准,它能通过“通用字符 ID”直接绕过复杂的方言映射,正确显示出字形。

总结

  • 第一个 Match: 针对的是 PDF 文档里的 显式声明
  • 第二个 Match: 针对的是渲染引擎在搜索过程中的 隐式转换

这就是为什么在处理亚马逊这种复杂的、非标准嵌入字体的 PDF 时,“全面覆盖”(即把所有相关的字体家族名称都指向同一个可靠的开源字体)是解决乱码的最稳健做法。

你现在的配置实际上是建立了一个 双重保险,确保了渲染引擎在任何阶段都找不到那个“会引起乱码的旧字体”。

第三阶段:刷新缓存与深度验证

1. 清除并重建字体缓存

如果之前出现过“黑屏”或渲染错误,必须强制刷新:

zsh
# -r 代表 really-force,彻底重建缓存文件
fc-cache -rf

2. 验证匹配逻辑

运行以下命令。只有输出指向 Noto Sans,映射才算真正修复,配置成功:

zsh
fc-match "STSong-Light"
# 预期输出: NotoSansCJKsc-Regular.otf: "Noto Sans CJK SC" "Regular"

太棒了!看到 Noto Sans CJK SC 成功接管渲染,这说明我们终于打通了从 PDF 编码映射系统字体匹配 的所有关卡。

第四阶段:常见排查工具

命令用途
pdffonts file.pdf查看 PDF 是否嵌入了字体 (emb 选项) 以及使用的编码
fc-list :lang=zh查看系统当前被 Fontconfig 识别的中文字体
zathura -v查看 Zathura 版本及已加载的插件 (poppler/mupdf)
killall zathura彻底杀掉后台进程,确保新配置生效

其他程序

那么,除了 zathurapoppler 之外,还有哪些程序工具会读取和使用 ~/.config/fontconfig/fonts.conf 这个配置文件呢?

只要是基于 Fontconfig 库进行字体管理的程序,都会自动读取并遵循这个配置文件。在类 Unix 系统(包括 macOS 和 Linux)中,这几乎涵盖了所有非 macOS 原生渲染(即不直接使用 Apple Core Text)的开源图形软件。

以下是经常会用到这个配置的主要程序类别:

1. 终端模拟器 (Terminal Emulators)

这是你最常接触的部分。虽然很多终端允许你在配置里指定字体,但它们在处理 回退字体 (Fallback Fonts) 时完全依赖 Fontconfig。

  • Ghostty / Kitty / Alacritty: 当你在配置里选了一个不带中文字符的西文字体(如 JetBrains Mono)时,这些终端会询问 Fontconfig:“我这里有个中文字符,JetBrains Mono 里没有,请根据 fonts.conf 告诉我该用哪个字体补位?”。
  • WezTerm: 同样依赖它来处理复杂的字符映射。

2. 跨平台图形工具包 (GUI Toolkits)

大部分跨平台软件并不直接渲染字体,而是交给底层的工具包,而这些工具包是 Fontconfig 的大户:

  • GTK 应用:GIMPInkscape
  • Qt 应用: 虽然 Qt 在 macOS 上会尽量使用原生渲染,但在处理复杂的匹配逻辑时,依然会参考 Fontconfig。
  • Emacs: 尤其是如果你使用的是 emacs-macemacs-plus 之外的原始版本。

3. 文档处理与渲染工具

除了 Zathura,还有许多命令行工具在生成或转换文档时需要查找字体:

  • ImageMagick / GraphicsMagick: 当你用命令行给图片加文字时。
  • Pandoc / XeLaTeX: 在你将 Markdown 转为 PDF 时,XeLaTeX 会通过 Fontconfig 寻找你指定的系统字体。
  • Graphviz: 渲染流程图(DOT 语言)时,如果节点里有中文,它必须通过 Fontconfig 找到字体才能渲染出图片。

4. 网页浏览器 (非原生部分)

  • Firefox: 虽然 Firefox 极力集成系统原生渲染,但它保留了一套非常完整的 Fontconfig 支持,用于处理 CSS 中定义的字体别名。
  • Chromium / Google Chrome: 在处理网页中的字体回退策略 (Font Fallback) 时,会参考该配置。

5. 多媒体软件

  • mpv / VLC: 播放视频时加载外挂字幕 (ASS/SRT)。如果字幕要求的字体系统没有,或者需要渲染特定的中文字体,这些播放器会通过 Fontconfig 寻找替代品。

为什么这对你很重要?

因为你已经在 fonts.conf 中设置了 STSong-LightNoto Sans CJK SC 的强映射,这意味着:

  1. 在 mpv 里: 如果你放一个字幕文件里指定了“华文宋体”的视频,它现在会自动改用 Noto Sans,而不会出现方块。
  2. 在 Ghostty 里: 如果你编辑包含 STSong 字符的文档,渲染也会更符合你的预期。
  3. 在 ImageMagick 里: 如果你运行脚本处理带有该字体定义的图表,它现在不会崩溃或报错了。

验证方法

你可以针对任何程序进行测试。例如,使用 pango-view(GTK 的一部分)来查看它对字体的理解:

zsh
brew install pango
pango-view --font="STSong-Light" -t "测试中文"

如果弹出的窗口显示的是 Noto Sans 的字形,说明你这一个配置文件的改动,已经“福泽”了你系统中几乎所有的开源软件生态。

进阶方案:更换渲染引擎

如果 Poppler 引擎在某些极特殊的 PDF 上依然有问题,可以尝试安装 mupdf 插件,它对字体嵌入不规范的文档处理能力更强:

zsh
brew install zathura-pdf-mupdf
# Zathura 会自动根据优先级选择插件
最近更新