8000 Merge pull request #22609 from anntzer/dvifonthelpers · matplotlib/matplotlib@c6c7ec1 · GitHub
[go: up one dir, main page]

Skip to content

Commit c6c7ec1

Browse files
authored
Merge pull request #22609 from anntzer/dvifonthelpers
Improve usability of dviread.Text by third parties.
2 parents f569f21 + 89a7e19 commit c6c7ec1

File tree

3 files changed

+93
-56
lines changed

3 files changed

+93
-56
lines changed

lib/matplotlib/dviread.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,71 @@
5858

5959
# The marks on a page consist of text and boxes. A page also has dimensions.
6060
Page = namedtuple('Page', 'text boxes height width descent')
61-
Text = namedtuple('Text', 'x y font glyph width')
6261
Box = namedtuple('Box', 'x y height width')
6362

6463

64+
# Also a namedtuple, for backcompat.
65+
class Text(namedtuple('Text', 'x y font glyph width')):
66+
"""
67+
A glyph in the dvi file.
68+
69+
The *x* and *y* attributes directly position the glyph. The *font*,
70+
*glyph*, and *width* attributes are kept public for back-compatibility,
71+
but users wanting to draw the glyph themselves are encouraged to instead
72+
load the font specified by `font_path` at `font_size`, warp it with the
73+
effects specified by `font_effects`, and load the glyph specified by
74+
`glyph_name_or_index`.
75+
"""
76+
77+
def _get_pdftexmap_entry(self):
78+
return PsfontsMap(find_tex_file("pdftex.map"))[self.font.texname]
79+
80+
@property
81+
def font_path(self):
82+
"""The `~pathlib.Path` to the font for this glyph."""
83+
psfont = self._get_pdftexmap_entry()
84+
if psfont.filename is None:
85+
raise ValueError("No usable font file found for {} ({}); "
86+
"the font may lack a Type-1 version"
87+
.format(psfont.psname.decode("ascii"),
88+
psfont.texname.decode("ascii")))
89+
return Path(psfont.filename)
90+
91+
@property
92+
def font_size(self):
93+
"""The font size."""
94+
return self.font.size
95+
96+
@property
97+
def font_effects(self):
98+
"""
99+
The "font effects" dict for this glyph.
100+
101+
This dict contains the values for this glyph of SlantFont and
102+
ExtendFont (if any), read off :file:`pdftex.map`.
103+
"""
104+
return self._get_pdftexmap_entry().effects
105+
106+
@property
107+
def glyph_name_or_index(self):
108+
"""
109+
Either the glyph name or the native charmap glyph index.
110+
111+
If :file:`pdftex.map` specifies an encoding for this glyph's font, that
112+
is a mapping of glyph indices to Adobe glyph names; use it to convert
113+
dvi indices to glyph names. Callers can then convert glyph names to
114+
glyph indices (with FT_Get_Name_Index/get_name_index), and load the
115+
glyph using FT_Load_Glyph/load_glyph.
116+
117+
If :file:`pdftex.map` specifies no encoding, the indices directly map
118+
to the font's "native" charmap; glyphs should directly loaded using
119+
FT_Load_Char/load_char after selecting the native charmap.
120+
"""
121+
entry = self._get_pdftexmap_entry()
122+
return (_parse_enc(entry.encoding)[self.glyph]
123+
if entry.encoding is not None else self.glyph)
124+
125+
65126
# Opcode argument parsing
66127
#
67128
# Each of the following functions takes a Dvi object and delta,

lib/matplotlib/tests/test_usetex.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def test_missing_psfont(fmt, monkeypatch):
131131
monkeypatch.setattr(
132132
dviread.PsfontsMap, '__getitem__',
133133
lambda self, k: dviread.PsFont(
134-
texname='texfont', psname='Some Font',
134+
texname=b'texfont', psname=b'Some Font',
135135
effects=None, encoding=None, filename=None))
136136
mpl.rcParams['text.usetex'] = True
137137
fig, ax = plt.subplots()

lib/matplotlib/textpath.py

Lines changed: 30 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from collections import OrderedDict
2-
import functools
32
import logging
43
import urllib.parse
54

@@ -242,25 +241,29 @@ def get_glyphs_tex(self, prop, s, glyph_map=None,
242241

243242
# Gather font information and do some setup for combining
244243
# characters into strings.
245-
for x1, y1, dvifont, glyph, width in page.text:
246-
font, enc = self._get_ps_font_and_encoding(dvifont.texname)
247-
char_id = self._get_char_id(font, glyph)
248-
244+
for text in page.text:
245+
font = get_font(text.font_path)
246+
char_id = self._get_char_id(font, text.glyph)
249247
if char_id not in glyph_map:
250248
font.clear()
251249
font.set_size(self.FONT_SCALE, self.DPI)
252-
# See comments in _get_ps_font_and_encoding.
253-
if enc is not None:
254-
index = font.get_name_index(enc[glyph])
250+
glyph_name_or_index = text.glyph_name_or_index
251+
if isinstance(glyph_name_or_index, str):
252+
index = font.get_name_index(glyph_name_or_index)
255253
font.load_glyph(index, flags=LOAD_TARGET_LIGHT)
256-
else:
257-
font.load_char(glyph, flags=LOAD_TARGET_LIGHT)
254+
elif isinstance(glyph_name_or_index, int):
255+
self._select_native_charmap(font)
256+
font.load_char(
257+
glyph_name_or_index, flags=LOAD_TARGET_LIGHT)
258+
else: # Should not occur.
259+
raise TypeError(f"Glyph spec of unexpected type: "
260+
f"{glyph_name_or_index!r}")
258261
glyph_map_new[char_id] = font.get_path()
259262

260263
glyph_ids.append(char_id)
261-
xpositions.append(x1)
262-
ypositions.append(y1)
263-
sizes.append(dvifont.size / self.FONT_SCALE)
264+
xpositions.append(text.x)
265+
ypositions.append(text.y)
266+
sizes.append(text.font_size / self.FONT_SCALE)
264267

265268
myrects = []
266269

@@ -276,48 +279,21 @@ def get_glyphs_tex(self, prop, s, glyph_map=None,
276279
glyph_map_new, myrects)
277280

278281
@staticmethod
279-
@functools.lru_cache(50)
280-
def _get_ps_font_and_encoding(texname):
281-
tex_font_map = dviread.PsfontsMap(dviread._find_tex_file('pdftex.map'))
282-
psfont = tex_font_map[texname]
283-
if psfont.filename is None:
284-
raise ValueError(
285-
f"No usable font file found for {psfont.psname} ({texname}). "
286-
f"The font may lack a Type-1 version.")
287-
288-
font = get_font(psfont.filename)
289-
290-
if psfont.encoding:
291-
# If psfonts.map specifies an encoding, use it: it gives us a
292-
# mapping of glyph indices to Adobe glyph names; use it to convert
293-
# dvi indices to glyph names and use the FreeType-synthesized
294-
# Unicode charmap to convert glyph names to glyph indices (with
295-
# FT_Get_Name_Index/get_name_index), and load the glyph using
296-
# FT_Load_Glyph/load_glyph. (That charmap has a coverage at least
297-
# as good as, and possibly better than, the native charmaps.)
298-
enc = dviread._parse_enc(psfont.encoding)
299-
else:
300-
# If psfonts.map specifies no encoding, the indices directly
301-
# map to the font's "native" charmap; so don't use the
302-
# FreeType-synthesized charmap but the native ones (we can't
303-
# directly identify it but it's typically an Adobe charmap), and
304-
# directly load the dvi glyph indices using FT_Load_Char/load_char.
305-
for charmap_code in [
306-
1094992451, # ADOBE_CUSTOM.
307-
1094995778, # ADOBE_STANDARD.
308-
]:
309-
try:
310-
font.select_charmap(charmap_code)
311-
except (ValueError, RuntimeError):
312-
pass
313-
else:
314-
break
282+
def _select_native_charmap(font):
283+
# Select the native charmap. (we can't directly identify it but it's
284+
# typically an Adobe charmap).
285+
for charmap_code in [
286+
1094992451, # ADOBE_CUSTOM.
287+
1094995778, # ADOBE_STANDARD.
288+
]:
289+
try:
290+
font.select_charmap(charmap_code)
291+
except (ValueError, RuntimeError):
292+
pass
315293
else:
316-
_log.warning("No supported encoding in font (%s).",
317-
psfont.filename)
318-
enc = None
319-
320-
return font, enc
294+
break
295+
else:
296+
_log.warning("No supported encoding in font (%s).", font.fname)
321297

322298

323299
text_to_path = TextToPath()

0 commit comments

Comments
 (0)
0