1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0-only 3# Copyright (C) Akira Yokosawa, 2024 4# 5# Ported to Python by (c) Mauro Carvalho Chehab, 2025 6 7""" 8Detect problematic Noto CJK variable fonts. 9 10For "make pdfdocs", reports of build errors of translations.pdf started 11arriving early 2024 [1, 2]. It turned out that Fedora and openSUSE 12tumbleweed have started deploying variable-font [3] format of "Noto CJK" 13fonts [4, 5]. For PDF, a LaTeX package named xeCJK is used for CJK 14(Chinese, Japanese, Korean) pages. xeCJK requires XeLaTeX/XeTeX, which 15does not (and likely never will) understand variable fonts for historical 16reasons. 17 18The build error happens even when both of variable- and non-variable-format 19fonts are found on the build system. To make matters worse, Fedora enlists 20variable "Noto CJK" fonts in the requirements of langpacks-ja, -ko, -zh_CN, 21-zh_TW, etc. Hence developers who have interest in CJK pages are more 22likely to encounter the build errors. 23 24This script is invoked from the error path of "make pdfdocs" and emits 25suggestions if variable-font files of "Noto CJK" fonts are in the list of 26fonts accessible from XeTeX. 27 28References: 29[1]: https://lore.kernel.org/r/8734tqsrt7.fsf@meer.lwn.net/ 30[2]: https://lore.kernel.org/r/1708585803.600323099@f111.i.mail.ru/ 31[3]: https://en.wikipedia.org/wiki/Variable_font 32[4]: https://fedoraproject.org/wiki/Changes/Noto_CJK_Variable_Fonts 33[5]: https://build.opensuse.org/request/show/1157217 34 35#=========================================================================== 36Workarounds for building translations.pdf 37#=========================================================================== 38 39* Denylist "variable font" Noto CJK fonts. 40 - Create $HOME/deny-vf/fontconfig/fonts.conf from template below, with 41 tweaks if necessary. Remove leading "". 42 - Path of fontconfig/fonts.conf can be overridden by setting an env 43 variable FONTS_CONF_DENY_VF. 44 45 * Template: 46----------------------------------------------------------------- 47<?xml version="1.0"?> 48<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd"> 49<fontconfig> 50<!-- 51 Ignore variable-font glob (not to break xetex) 52--> 53 <selectfont> 54 <rejectfont> 55 <!-- 56 for Fedora 57 --> 58 <glob>/usr/share/fonts/google-noto-*-cjk-vf-fonts</glob> 59 <!-- 60 for openSUSE tumbleweed 61 --> 62 <glob>/usr/share/fonts/truetype/Noto*CJK*-VF.otf</glob> 63 </rejectfont> 64 </selectfont> 65</fontconfig> 66----------------------------------------------------------------- 67 68 The denylisting is activated for "make pdfdocs". 69 70* For skipping CJK pages in PDF 71 - Uninstall texlive-xecjk. 72 Denylisting is not needed in this case. 73 74* For printing CJK pages in PDF 75 - Need non-variable "Noto CJK" fonts. 76 * Fedora 77 - google-noto-sans-cjk-fonts 78 - google-noto-serif-cjk-fonts 79 * openSUSE tumbleweed 80 - Non-variable "Noto CJK" fonts are not available as distro packages 81 as of April, 2024. Fetch a set of font files from upstream Noto 82 CJK Font released at: 83 https://github.com/notofonts/noto-cjk/tree/main/Sans#super-otc 84 and at: 85 https://github.com/notofonts/noto-cjk/tree/main/Serif#super-otc 86 , then uncompress and deploy them. 87 - Remember to update fontconfig cache by running fc-cache. 88 89!!! Caution !!! 90 Uninstalling "variable font" packages can be dangerous. 91 They might be depended upon by other packages important for your work. 92 Denylisting should be less invasive, as it is effective only while 93 XeLaTeX runs in "make pdfdocs". 94""" 95 96import os 97import re 98import subprocess 99import textwrap 100import sys 101 102class LatexFontChecker: 103 """ 104 Detect problems with CJK variable fonts that affect PDF builds for 105 translations. 106 """ 107 108 def __init__(self, deny_vf=None): 109 if not deny_vf: 110 deny_vf = os.environ.get('FONTS_CONF_DENY_VF', "~/deny-vf") 111 112 self.environ = os.environ.copy() 113 self.environ['XDG_CONFIG_HOME'] = os.path.expanduser(deny_vf) 114 115 self.re_cjk = re.compile(r"([^:]+):\s*Noto\s+(Sans|Sans Mono|Serif) CJK") 116 117 def description(self): 118 return __doc__ 119 120 def get_noto_cjk_vf_fonts(self): 121 """Get Noto CJK fonts""" 122 123 cjk_fonts = set() 124 cmd = ["fc-list", ":", "file", "family", "variable"] 125 try: 126 result = subprocess.run(cmd,stdout=subprocess.PIPE, 127 stderr=subprocess.PIPE, 128 universal_newlines=True, 129 env=self.environ, 130 check=True) 131 132 except subprocess.CalledProcessError as exc: 133 sys.exit(f"Error running fc-list: {repr(exc)}") 134 135 for line in result.stdout.splitlines(): 136 if 'variable=True' not in line: 137 continue 138 139 match = self.re_cjk.search(line) 140 if match: 141 cjk_fonts.add(match.group(1)) 142 143 return sorted(cjk_fonts) 144 145 def check(self): 146 """Check for problems with CJK fonts""" 147 148 fonts = textwrap.indent("\n".join(self.get_noto_cjk_vf_fonts()), " ") 149 if not fonts: 150 return None 151 152 rel_file = os.path.relpath(__file__, os.getcwd()) 153 154 msg = "=" * 77 + "\n" 155 msg += 'XeTeX is confused by "variable font" files listed below:\n' 156 msg += fonts + "\n" 157 msg += textwrap.dedent(f""" 158 For CJK pages in PDF, they need to be hidden from XeTeX by denylisting. 159 Or, CJK pages can be skipped by uninstalling texlive-xecjk. 160 161 For more info on denylisting, other options, and variable font, run: 162 163 tools/docs/check-variable-fonts.py -h 164 """) 165 msg += "=" * 77 166 167 return msg 168