Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Rework mapping of dvi glyph indices to freetype indices. #29829

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 42 additions & 12 deletions 54 lib/matplotlib/dviread.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

import numpy as np

from matplotlib import _api, cbook
from matplotlib import _api, cbook, font_manager

_log = logging.getLogger(__name__)

Expand Down Expand Up @@ -71,8 +71,8 @@
*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):
Expand Down Expand Up @@ -105,6 +105,14 @@
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.
Expand Down Expand Up @@ -579,7 +587,7 @@
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)
Expand All @@ -588,6 +596,7 @@
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
Expand Down Expand Up @@ -630,6 +639,33 @@
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.
Comment on lines +649 to +651
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just checking but is this why this step is no longer needed: index = t1_encodings[font][glyph_name_or_index]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still happens at the bottom: self._encoding = face._get_t1_encoding_vector() (likewise caching the value of the encoding vector, but this now occurs on the DviFont object) then return self._encoding[idx].

# - (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]
QuLogic marked this conversation as resolved.
Show resolved Hide resolved
if psfont.filename is None:
raise ValueError("No usable font file found for {} ({}); "

Check warning on line 657 in lib/matplotlib/dviread.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/dviread.py#L657

Added line #L657 was not covered by tests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add a test for coverage?

Copy link
Contributor Author

@anntzer anntzer May 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gets further refactored in the followup PR #29939 into DviFont.path, which does have test coverage (because it is also used elsewhere -- see https://app.codecov.io/gh/matplotlib/matplotlib/pull/29939#4c703021fcf83cd901550e2c525d78ae-R733). I can move code around so that the intermediate stage also has full coverage if you prefer, but this split seems more logical to me.

"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"""
Expand Down Expand Up @@ -1023,8 +1059,7 @@
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)
Expand Down Expand Up @@ -1156,12 +1191,7 @@
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 ---")
Expand Down
2 changes: 2 additions & 0 deletions 2 lib/matplotlib/dviread.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
12 changes: 1 addition & 11 deletions 12 lib/matplotlib/textpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.