From a3c14cf407684a4a286df24bacda45d9af2e9af1 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 29 Mar 2025 16:21:16 +0100 Subject: [PATCH] Rework mapping of dvi glyph indices to freetype indices. In 89a7e19, an API for converting "dvi glyph indices" (as stored in a dvi file) to FreeType-compatible keys (either "indices into the native charmap" or "glyph names") was introduced. It was intended that end users (i.e., backends) would check the type of `text.glyph_name_or_index` ((A) int or (B) str) and load the glyph accordingly ((A) `FT_Set_Charmap(native_cmap); FT_Load_Char(index);` or (B) `FT_Load_Glyph(FT_Get_Name_Index(name));`); however, with the future introduction of {xe,lua}tex support, this kind of type checking becomes inconvenient, because {xe,lua}tex's "dvi glyph indices", which are directly equal to FreeType glyph indices (i.e. they would be loaded with `FT_Load_Glyph(index);`), would normally also be converted to ints. This PR introduces a new API (`Text.index`) that performs this mapping (via the new `DviFont._index_dvi_to_freetype`), always mapping to FreeType glyph indices (i.e. one can always just call `FT_Load_Glyph` on the result). To do so, in case (A) it loads itself the native charmap (something the end user needed to do by themselves previously) and performs the cmap-to-index conversion (`FT_Get_Char_Index`) previously implicit in `FT_Load_Char`; in case (B) it performs itself the name-to-index conversion (`FT_Get_Name_Index`). When {xe,lua}tex support is introduced in the future, `_index_dvi_to_freetype` will just return the index as is. The old APIs are not deprecated yet, as other changes will occur for {xe,lua}tex support (e.g. font_effects will go away and be replaced by `.font.effects`, as {xe,lua}tex don't store that info in the pdftexmap entry), and grouping all API changes together seems nicer (to only require a single adaptation step by the API consumer). --- lib/matplotlib/dviread.py | 54 +++++++++++++++++++++++++++++--------- lib/matplotlib/dviread.pyi | 2 ++ lib/matplotlib/textpath.py | 12 +-------- 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/lib/matplotlib/dviread.py b/lib/matplotlib/dviread.py index c1d1a93f55bf..a588979f5fad 100644 --- a/lib/matplotlib/dviread.py +++ b/lib/matplotlib/dviread.py @@ -31,7 +31,7 @@ import numpy as np -from matplotlib import _api, cbook +from matplotlib import _api, cbook, font_manager _log = logging.getLogger(__name__) @@ -71,8 +71,8 @@ class Text(namedtuple('Text', 'x y font glyph width')): *glyph*, and *width* attributes are kept public for back-compatibility, but users wanting to draw the glyph themselves are encouraged to instead load the font specified by `font_path` at `font_size`, warp it with the - effects specified by `font_effects`, and load the glyph specified by - `glyph_name_or_index`. + effects specified by `font_effects`, and load the glyph at the FreeType + glyph `index`. """ def _get_pdftexmap_entry(self): @@ -105,6 +105,14 @@ def font_effects(self): return self._get_pdftexmap_entry().effects @property + def index(self): + """ + The FreeType index of this glyph (that can be passed to FT_Load_Glyph). + """ + # See DviFont._index_dvi_to_freetype for details on the index mapping. + return self.font._index_dvi_to_freetype(self.glyph) + + @property # To be deprecated together with font_size, font_effects. def glyph_name_or_index(self): """ Either the glyph name or the native charmap glyph index. @@ -579,7 +587,7 @@ class DviFont: Size of the font in Adobe points, converted from the slightly smaller TeX points. """ - __slots__ = ('texname', 'size', '_scale', '_vf', '_tfm') + __slots__ = ('texname', 'size', '_scale', '_vf', '_tfm', '_encoding') def __init__(self, scale, tfm, texname, vf): _api.check_isinstance(bytes, texname=texname) @@ -588,6 +596,7 @@ def __init__(self, scale, tfm, texname, vf): self.texname = texname self._vf = vf self.size = scale * (72.0 / (72.27 * 2**16)) + self._encoding = None widths = _api.deprecated("3.11")(property(lambda self: [ (1000 * self._tfm.width.get(char, 0)) >> 20 @@ -630,6 +639,33 @@ def _height_depth_of(self, char): hd[-1] = 0 return hd + def _index_dvi_to_freetype(self, idx): + """Convert dvi glyph indices to FreeType ones.""" + # Glyphs indices stored in the dvi file map to FreeType glyph indices + # (i.e., which can be passed to FT_Load_Glyph) in various ways: + # - if pdftex.map specifies an ".enc" file for the font, that file maps + # dvi indices to Adobe glyph names, which can then be converted to + # FreeType glyph indices with FT_Get_Name_Index. + # - if no ".enc" file is specified, then the font must be a Type 1 + # font, and dvi indices directly index into the font's CharStrings + # vector. + # - (xetex & luatex, currently unsupported, can also declare "native + # fonts", for which dvi indices are equal to FreeType indices.) + if self._encoding is None: + psfont = PsfontsMap(find_tex_file("pdftex.map"))[self.texname] + if psfont.filename is None: + raise ValueError("No usable font file found for {} ({}); " + "the font may lack a Type-1 version" + .format(psfont.psname.decode("ascii"), + psfont.texname.decode("ascii"))) + face = font_manager.get_font(psfont.filename) + if psfont.encoding: + self._encoding = [face.get_name_index(name) + for name in _parse_enc(psfont.encoding)] + else: + self._encoding = face._get_type1_encoding_vector() + return self._encoding[idx] + class Vf(Dvi): r""" @@ -1023,8 +1059,7 @@ def _parse_enc(path): Returns ------- list - The nth entry of the list is the PostScript glyph name of the nth - glyph. + The nth list item is the PostScript glyph name of the nth glyph. """ no_comments = re.sub("%.*", "", Path(path).read_text(encoding="ascii")) array = re.search(r"(?s)\[(.*)\]", no_comments).group(1) @@ -1156,12 +1191,7 @@ def _print_fields(*args): face = FT2Font(fontpath) _print_fields("x", "y", "glyph", "chr", "w") for text in group: - if psfont.encoding: - glyph_name = _parse_enc(psfont.encoding)[text.glyph] - else: - encoding_vector = face._get_type1_encoding_vector() - glyph_name = face.get_glyph_name(encoding_vector[text.glyph]) - glyph_str = fontTools.agl.toUnicode(glyph_name) + glyph_str = fontTools.agl.toUnicode(face.get_glyph_name(text.index)) _print_fields(text.x, text.y, text.glyph, glyph_str, text.width) if page.boxes: print("--- BOXES ---") diff --git a/lib/matplotlib/dviread.pyi b/lib/matplotlib/dviread.pyi index f8d8f979fd8c..41799c083218 100644 --- a/lib/matplotlib/dviread.pyi +++ b/lib/matplotlib/dviread.pyi @@ -41,6 +41,8 @@ class Text(NamedTuple): @property def font_effects(self) -> dict[str, float]: ... @property + def index(self) -> int: ... # type: ignore[override] + @property def glyph_name_or_index(self) -> int | str: ... class Dvi: diff --git a/lib/matplotlib/textpath.py b/lib/matplotlib/textpath.py index 35adfdd77899..b57597ded363 100644 --- a/lib/matplotlib/textpath.py +++ b/lib/matplotlib/textpath.py @@ -239,17 +239,7 @@ def get_glyphs_tex(self, prop, s, glyph_map=None, if char_id not in glyph_map: font.clear() font.set_size(self.FONT_SCALE, self.DPI) - glyph_name_or_index = text.glyph_name_or_index - if isinstance(glyph_name_or_index, str): - index = font.get_name_index(glyph_name_or_index) - elif isinstance(glyph_name_or_index, int): - if font not in t1_encodings: - t1_encodings[font] = font._get_type1_encoding_vector() - index = t1_encodings[font][glyph_name_or_index] - else: # Should not occur. - raise TypeError(f"Glyph spec of unexpected type: " - f"{glyph_name_or_index!r}") - font.load_glyph(index, flags=LoadFlags.TARGET_LIGHT) + font.load_glyph(text.index, flags=LoadFlags.TARGET_LIGHT) glyph_map_new[char_id] = font.get_path() glyph_ids.append(char_id) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy