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