Skip to content

Commit d56936b

Browse files
authored
Merge pull request matplotlib#30335 from QuLogic/vector-glyphs
Use glyph indices for font tracking in vector formats
2 parents bb37cf9 + f630b48 commit d56936b

File tree

12 files changed

+191
-173
lines changed

12 files changed

+191
-173
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
``mathtext.VectorParse`` now includes glyph indices
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
For a *path*-outputting `.MathTextParser`, in the return value of
5+
`~.MathTextParser.parse`, (a `.VectorParse`), the *glyphs* field is now a list
6+
containing tuples of:
7+
8+
- font: `.FT2Font`
9+
- fontsize: `float`
10+
- character code: `int`
11+
- glyph index: `int`
12+
- x: `float`
13+
- y: `float`
14+
15+
Specifically, the glyph index was added after the character code.

lib/matplotlib/_mathtext.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838

3939
if T.TYPE_CHECKING:
4040
from collections.abc import Iterable
41-
from .ft2font import CharacterCodeType, Glyph
41+
from .ft2font import CharacterCodeType, Glyph, GlyphIndexType
4242

4343

4444
ParserElement.enable_packrat()
@@ -87,7 +87,7 @@ class VectorParse(NamedTuple):
8787
width: float
8888
height: float
8989
depth: float
90-
glyphs: list[tuple[FT2Font, float, CharacterCodeType, float, float]]
90+
glyphs: list[tuple[FT2Font, float, CharacterCodeType, GlyphIndexType, float, float]]
9191
rects: list[tuple[float, float, float, float]]
9292

9393
VectorParse.__module__ = "matplotlib.mathtext"
@@ -132,7 +132,8 @@ def __init__(self, box: Box):
132132
def to_vector(self) -> VectorParse:
133133
w, h, d = map(
134134
np.ceil, [self.box.width, self.box.height, self.box.depth])
135-
gs = [(info.font, info.fontsize, info.num, ox, h - oy + info.offset)
135+
gs = [(info.font, info.fontsize, info.num, info.glyph_index,
136+
ox, h - oy + info.offset)
136137
for ox, oy, info in self.glyphs]
137138
rs = [(x1, h - y2, x2 - x1, y2 - y1)
138139
for x1, y1, x2, y2 in self.rects]
@@ -215,6 +216,7 @@ class FontInfo(NamedTuple):
215216
postscript_name: str
216217
metrics: FontMetrics
217218
num: CharacterCodeType
219+
glyph_index: GlyphIndexType
218220
glyph: Glyph
219221
offset: float
220222

@@ -375,7 +377,8 @@ def _get_info(self, fontname: str, font_class: str, sym: str, fontsize: float,
375377
dpi: float) -> FontInfo:
376378
font, num, slanted = self._get_glyph(fontname, font_class, sym)
377379
font.set_size(fontsize, dpi)
378-
glyph = font.load_char(num, flags=self.load_glyph_flags)
380+
glyph_index = font.get_char_index(num)
381+
glyph = font.load_glyph(glyph_index, flags=self.load_glyph_flags)
379382

380383
xmin, ymin, xmax, ymax = (val / 64 for val in glyph.bbox)
381384
offset = self._get_offset(font, glyph, fontsize, dpi)
@@ -398,6 +401,7 @@ def _get_info(self, fontname: str, font_class: str, sym: str, fontsize: float,
398401
postscript_name=font.postscript_name,
399402
metrics=metrics,
400403
num=num,
404+
glyph_index=glyph_index,
401405
glyph=glyph,
402406
offset=offset
403407
)
@@ -427,8 +431,7 @@ def get_kern(self, font1: str, fontclass1: str, sym1: str, fontsize1: float,
427431
info1 = self._get_info(font1, fontclass1, sym1, fontsize1, dpi)
428432
info2 = self._get_info(font2, fontclass2, sym2, fontsize2, dpi)
429433
font = info1.font
430-
return font.get_kerning(font.get_char_index(info1.num),
431-
font.get_char_index(info2.num),
434+
return font.get_kerning(info1.glyph_index, info2.glyph_index,
432435
Kerning.DEFAULT) / 64
433436
return super().get_kern(font1, fontclass1, sym1, fontsize1,
434437
font2, fontclass2, sym2, fontsize2, dpi)

lib/matplotlib/_text_helpers.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
class LayoutItem:
1515
ft_object: FT2Font
1616
char: str
17-
glyph_idx: GlyphIndexType
17+
glyph_index: GlyphIndexType
1818
x: float
1919
prev_kern: float
2020

@@ -47,19 +47,19 @@ def layout(string, font, *, kern_mode=Kerning.DEFAULT):
4747
LayoutItem
4848
"""
4949
x = 0
50-
prev_glyph_idx = None
50+
prev_glyph_index = None
5151
char_to_font = font._get_fontmap(string)
5252
base_font = font
5353
for char in string:
5454
# This has done the fallback logic
5555
font = char_to_font.get(char, base_font)
56-
glyph_idx = font.get_char_index(ord(char))
56+
glyph_index = font.get_char_index(ord(char))
5757
kern = (
58-
base_font.get_kerning(prev_glyph_idx, glyph_idx, kern_mode) / 64
59-
if prev_glyph_idx is not None else 0.
58+
base_font.get_kerning(prev_glyph_index, glyph_index, kern_mode) / 64
59+
if prev_glyph_index is not None else 0.
6060
)
6161
x += kern
62-
glyph = font.load_glyph(glyph_idx, flags=LoadFlags.NO_HINTING)
63-
yield LayoutItem(font, char, glyph_idx, x, kern)
62+
glyph = font.load_glyph(glyph_index, flags=LoadFlags.NO_HINTING)
63+
yield LayoutItem(font, char, glyph_index, x, kern)
6464
x += glyph.linearHoriAdvance / 65536
65-
prev_glyph_idx = glyph_idx
65+
prev_glyph_index = glyph_index

lib/matplotlib/backends/_backend_pdf_ps.py

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
Common functionality between the PDF and PS backends.
33
"""
44

5+
from __future__ import annotations
6+
57
from io import BytesIO
68
import functools
79
import logging
10+
import typing
811

912
from fontTools import subset
1013

@@ -14,33 +17,38 @@
1417
from ..backend_bases import RendererBase
1518

1619

20+
if typing.TYPE_CHECKING:
21+
from .ft2font import FT2Font, GlyphIndexType
22+
from fontTools.ttLib import TTFont
23+
24+
1725
@functools.lru_cache(50)
1826
def _cached_get_afm_from_fname(fname):
1927
with open(fname, "rb") as fh:
2028
return AFM(fh)
2129

2230

23-
def get_glyphs_subset(fontfile, characters):
31+
def get_glyphs_subset(fontfile: str, glyphs: set[GlyphIndexType]) -> TTFont:
2432
"""
25-
Subset a TTF font
33+
Subset a TTF font.
2634
27-
Reads the named fontfile and restricts the font to the characters.
35+
Reads the named fontfile and restricts the font to the glyphs.
2836
2937
Parameters
3038
----------
3139
fontfile : str
3240
Path to the font file
33-
characters : str
34-
Continuous set of characters to include in subset
41+
glyphs : set[GlyphIndexType]
42+
Set of glyph indices to include in subset.
3543
3644
Returns
3745
-------
3846
fontTools.ttLib.ttFont.TTFont
3947
An open font object representing the subset, which needs to
4048
be closed by the caller.
4149
"""
42-
43-
options = subset.Options(glyph_names=True, recommended_glyphs=True)
50+
options = subset.Options(glyph_names=True, recommended_glyphs=True,
51+
retain_gids=True)
4452

4553
# Prevent subsetting extra tables.
4654
options.drop_tables += [
@@ -71,7 +79,7 @@ def get_glyphs_subset(fontfile, characters):
7179

7280
font = subset.load_font(fontfile, options)
7381
subsetter = subset.Subsetter(options=options)
74-
subsetter.populate(text=characters)
82+
subsetter.populate(gids=glyphs)
7583
subsetter.subset(font)
7684
return font
7785

@@ -97,24 +105,25 @@ def font_as_file(font):
97105

98106
class CharacterTracker:
99107
"""
100-
Helper for font subsetting by the pdf and ps backends.
108+
Helper for font subsetting by the PDF and PS backends.
101109
102-
Maintains a mapping of font paths to the set of character codepoints that
103-
are being used from that font.
110+
Maintains a mapping of font paths to the set of glyphs that are being used from that
111+
font.
104112
"""
105113

106-
def __init__(self):
107-
self.used = {}
114+
def __init__(self) -> None:
115+
self.used: dict[str, set[GlyphIndexType]] = {}
108116

109-
def track(self, font, s):
117+
def track(self, font: FT2Font, s: str) -> None:
110118
"""Record that string *s* is being typeset using font *font*."""
111119
char_to_font = font._get_fontmap(s)
112120
for _c, _f in char_to_font.items():
113-
self.used.setdefault(_f.fname, set()).add(ord(_c))
121+
glyph_index = _f.get_char_index(ord(_c))
122+
self.used.setdefault(_f.fname, set()).add(glyph_index)
114123

115-
def track_glyph(self, font, glyph):
116-
"""Record that codepoint *glyph* is being typeset using font *font*."""
117-
self.used.setdefault(font.fname, set()).add(glyph)
124+
def track_glyph(self, font: FT2Font, glyph_index: GlyphIndexType) -> None:
125+
"""Record that glyph index *glyph_index* is being typeset using font *font*."""
126+
self.used.setdefault(font.fname, set()).add(glyph_index)
118127

119128

120129
class RendererPDFPSBase(RendererBase):

lib/matplotlib/backends/backend_cairo.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import functools
1010
import gzip
11+
import itertools
1112
import math
1213

1314
import numpy as np
@@ -248,13 +249,12 @@ def _draw_mathtext(self, gc, x, y, s, prop, angle):
248249
if angle:
249250
ctx.rotate(np.deg2rad(-angle))
250251

251-
for font, fontsize, idx, ox, oy in glyphs:
252+
for (font, fontsize), font_glyphs in itertools.groupby(
253+
glyphs, key=lambda info: (info[0], info[1])):
252254
ctx.new_path()
253-
ctx.move_to(ox, -oy)
254-
ctx.select_font_face(
255-
*_cairo_font_args_from_font_prop(ttfFontProperty(font)))
255+
ctx.select_font_face(*_cairo_font_args_from_font_prop(ttfFontProperty(font)))
256256
ctx.set_font_size(self.points_to_pixels(fontsize))
257-
ctx.show_text(chr(idx))
257+
ctx.show_glyphs([(idx, ox, -oy) for _, _, idx, ox, oy in font_glyphs])
258258

259259
for ox, oy, w, h in rects:
260260
ctx.new_path()

0 commit comments

Comments
 (0)