向 Red Hat Universal Base Image 镜像中添加中文字体

2024-09-29, 星期日, 16:06

DevOpsContainer培训

当一个应用需要生成含有中文字符的文件(PDF、图片等)时,程序的资源目录或运行环境应当提供相应的字体,否则就会得到一堆方块(或者干脆什么都没有)。

出于版权 / 方便性 / 覆盖程度等方面的考虑,笔者通常使用 Google Noto 系列的字体,简单的做法是将字体文件打包在资源目录中随程序包发布,在 Java 技术栈中,添加到 resources/ 目录。

不过在应用容器化后,这个方法无法享受到分层镜像的好处。此外笔者想要将字体资源重新定义为运行环境资源,因此选择了在创建容器时安装字体的方案(如果你就这一个项目使用,那在 Dockerfile 中使用 COPY 也没什么问题)。

容器使用 Eclipse Temurin JDK 的 UBI(Red Hat Universal Base Image)镜像 eclipse-temurin:17-jdk-ubi9-minimal 作为基底。ubi-minimal 中的包管理器是 microdnfmicrodnf 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-fontsgoogle-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);