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