latex_fonts.py 5.8 KB

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