当一个应用需要生成含有中文字符的文件(PDF、图片等)时,程序的资源目录或运行环境应当提供相应的字体,否则就会得到一堆方块(或者干脆什么都没有)。
出于版权 / 方便性 / 覆盖程度等方面的考虑,笔者通常使用 Google Noto 系列的字体,简单的做法是将字体文件打包在资源目录中随程序包发布,在 Java 技术栈中,添加到 resources/
目录。
不过在应用容器化后,这个方法无法享受到分层镜像的好处。此外笔者想要将字体资源重新定义为运行环境资源,因此选择了在创建容器时安装字体的方案(如果你就这一个项目使用,那在 Dockerfile
中使用 COPY
也没什么问题)。
容器使用 Eclipse Temurin JDK 的 UBI(Red Hat Universal Base Image)镜像 eclipse-temurin:17-jdk-ubi9-minimal
作为基底。ubi-minimal
中的包管理器是 microdnf
,microdnf queryrepo/install
分别用于搜索和安装软件包,不过一番搜索后 ubi-9-appstream-rpms
似乎并没有 google-noto-cjk
相关的包。
你可以使用浏览器访问 https://cdn-ubi.redhat.com/content/public/ubi/dist/ubi9/9/x86_64/appstream/os/Packages/g/ ,使用 Ctrl + F 搜索想要的软件包。这在某些网络环境下应该会比
queryrepo
来得快些。
由于字体文件没有什么平台 / 架构差异,可以添加一个 AlmaLinux 的源。在 Dockerfile 中添加如下内容,安装 Google Noto 的 CJK 衬线 / 无衬线字体。
// ……
RUN echo -e "[appstream]\n\
name=AlmaLinux $releasever - AppStream\n\
baseurl=http://mirrors.tencent.com/almalinux/9.4/AppStream/x86_64/os/\n\
enabled=1\n\
gpgcheck=0" \
>> /etc/yum.repos.d/alma.repo
RUN microdnf install google-noto-sans-cjk-ttc-fonts google-noto-serif-cjk-ttc-fonts -y
// ……
如果你添加了 Fedora 的源,那么软件包的名字应该是
google-noto-sans-cjk-fonts
和google-noto-serif-cjk-fonts
。
安装成功后得到一堆 TrueType Collection 格式的字体。
> ls /usr/share/fonts/google-noto-cjk/
NotoSansCJK-Black.ttc NotoSansCJK-DemiLight.ttc NotoSansCJK-Medium.ttc NotoSansCJK-Thin.ttc NotoSerifCJK-Bold.ttc NotoSerifCJK-Light.ttc NotoSerifCJK-Regular.ttc
NotoSansCJK-Bold.ttc NotoSansCJK-Light.ttc NotoSansCJK-Regular.ttc NotoSerifCJK-Black.ttc NotoSerifCJK-ExtraLight.ttc NotoSerifCJK-Medium.ttc NotoSerifCJK-SemiBold.ttc
motto-html@1.2.2 - GitHub 增加了 cc.ddrpa.motto.html.DocumentBuilder#loadPreinstalledFontsAsCJKFont
静态方法,可以加载用户和系统目录下预装的字体,其中就包含了 /usr/share/fonts
目录(在 Linux 环境下运行时)。
利用该项目编写一个 CLI 程序,调用这些字体生成 font book。
// 便于演示,隐去了异常处理之类的代码
DocumentBuilder.loadPreinstalledFontsAsCJKFont();
DocumentBuilder builder = new DocumentBuilder();
builder.loadTemplate("font-book.html");
builder.merge(Map.of("font_families", DocumentBuilder.listFontFamily()));
FileOutputStream fileOutputStream = new FileOutputStream("font-book.pdf");
builder.save(fileOutputStream);