Skip to content

Commit 3d5e48c

Browse files
committed
Use glyph indices for font tracking in vector formats
With libraqm, string layout produces glyph indices, not character codes, and font features may even produce different glyphs for the same character code (e.g., by picking a different Stylistic Set). Thus we cannot rely on character codes as unique items within a font, and must move toward glyph indices everywhere.
1 parent 9766cbd commit 3d5e48c

File tree

10 files changed

+142
-146
lines changed

10 files changed

+142
-146
lines changed

lib/matplotlib/_mathtext.py

Lines changed: 8 additions & 8 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, GlyphIndexType, float, float]]
9191
rects: list[tuple[float, float, float, float]]
9292

9393
VectorParse.__module__ = "matplotlib.mathtext"
@@ -132,7 +132,7 @@ 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.glyph_index, ox, h - oy + info.offset)
136136
for ox, oy, info in self.glyphs]
137137
rs = [(x1, h - y2, x2 - x1, y2 - y1)
138138
for x1, y1, x2, y2 in self.rects]
@@ -214,7 +214,7 @@ class FontInfo(NamedTuple):
214214
fontsize: float
215215
postscript_name: str
216216
metrics: FontMetrics
217-
num: CharacterCodeType
217+
glyph_index: GlyphIndexType
218218
glyph: Glyph
219219
offset: float
220220

@@ -375,7 +375,8 @@ def _get_info(self, fontname: str, font_class: str, sym: str, fontsize: float,
375375
dpi: float) -> FontInfo:
376376
font, num, slanted = self._get_glyph(fontname, font_class, sym)
377377
font.set_size(fontsize, dpi)
378-
glyph = font.load_char(num, flags=self.load_glyph_flags)
378+
glyph_index = font.get_char_index(num)
379+
glyph = font.load_glyph(glyph_index, flags=self.load_glyph_flags)
379380

380381
xmin, ymin, xmax, ymax = (val / 64 for val in glyph.bbox)
381382
offset = self._get_offset(font, glyph, fontsize, dpi)
@@ -397,7 +398,7 @@ def _get_info(self, fontname: str, font_class: str, sym: str, fontsize: float,
397398
fontsize=fontsize,
398399
postscript_name=font.postscript_name,
399400
metrics=metrics,
400-
num=num,
401+
glyph_index=glyph_index,
401402
glyph=glyph,
402403
offset=offset
403404
)
@@ -427,8 +428,7 @@ def get_kern(self, font1: str, fontclass1: str, sym1: str, fontsize1: float,
427428
info1 = self._get_info(font1, fontclass1, sym1, fontsize1, dpi)
428429
info2 = self._get_info(font2, fontclass2, sym2, fontsize2, dpi)
429430
font = info1.font
430-
return font.get_kerning(font.get_char_index(info1.num),
431-
font.get_char_index(info2.num),
431+
return font.get_kerning(info1.glyph_index, info2.glyph_index,
432432
Kerning.DEFAULT) / 64
433433
return super().get_kern(font1, fontclass1, sym1, fontsize1,
434434
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

@@ -64,19 +64,19 @@ def layout(string, font, *, kern_mode=Kerning.DEFAULT):
6464
LayoutItem
6565
"""
6666
x = 0
67-
prev_glyph_idx = None
67+
prev_glyph_index = None
6868
char_to_font = font._get_fontmap(string)
6969
base_font = font
7070
for char in string:
7171
# This has done the fallback logic
7272
font = char_to_font.get(char, base_font)
73-
glyph_idx = font.get_char_index(ord(char))
73+
glyph_index = font.get_char_index(ord(char))
7474
kern = (
75-
base_font.get_kerning(prev_glyph_idx, glyph_idx, kern_mode) / 64
76-
if prev_glyph_idx is not None else 0.
75+
base_font.get_kerning(prev_glyph_index, glyph_index, kern_mode) / 64
76+
if prev_glyph_index is not None else 0.
7777
)
7878
x += kern
79-
glyph = font.load_glyph(glyph_idx, flags=LoadFlags.NO_HINTING)
80-
yield LayoutItem(font, char, glyph_idx, x, kern)
79+
glyph = font.load_glyph(glyph_index, flags=LoadFlags.NO_HINTING)
80+
yield LayoutItem(font, char, glyph_index, x, kern)
8181
x += glyph.linearHoriAdvance / 65536
82-
prev_glyph_idx = glyph_idx
82+
prev_glyph_index = glyph_index

lib/matplotlib/backends/_backend_pdf_ps.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,27 @@ def _cached_get_afm_from_fname(fname):
2020
return AFM(fh)
2121

2222

23-
def get_glyphs_subset(fontfile, characters):
23+
def get_glyphs_subset(fontfile, glyphs):
2424
"""
25-
Subset a TTF font
25+
Subset a TTF font.
2626
27-
Reads the named fontfile and restricts the font to the characters.
27+
Reads the named fontfile and restricts the font to the glyphs.
2828
2929
Parameters
3030
----------
3131
fontfile : str
3232
Path to the font file
33-
characters : str
34-
Continuous set of characters to include in subset
33+
glyphs : set[int]
34+
Set of glyph IDs to include in subset.
3535
3636
Returns
3737
-------
3838
fontTools.ttLib.ttFont.TTFont
3939
An open font object representing the subset, which needs to
4040
be closed by the caller.
4141
"""
42-
43-
options = subset.Options(glyph_names=True, recommended_glyphs=True)
42+
options = subset.Options(glyph_names=True, recommended_glyphs=True,
43+
retain_gids=True)
4444

4545
# Prevent subsetting extra tables.
4646
options.drop_tables += [
@@ -71,7 +71,7 @@ def get_glyphs_subset(fontfile, characters):
7171

7272
font = subset.load_font(fontfile, options)
7373
subsetter = subset.Subsetter(options=options)
74-
subsetter.populate(text=characters)
74+
subsetter.populate(gids=glyphs)
7575
subsetter.subset(font)
7676
return font
7777

@@ -97,10 +97,10 @@ def font_as_file(font):
9797

9898
class CharacterTracker:
9999
"""
100-
Helper for font subsetting by the pdf and ps backends.
100+
Helper for font subsetting by the PDF and PS backends.
101101
102-
Maintains a mapping of font paths to the set of character codepoints that
103-
are being used from that font.
102+
Maintains a mapping of font paths to the set of glyphs that are being used from that
103+
font.
104104
"""
105105

106106
def __init__(self):
@@ -110,10 +110,11 @@ def track(self, font, s):
110110
"""Record that string *s* is being typeset using font *font*."""
111111
char_to_font = font._get_fontmap(s)
112112
for _c, _f in char_to_font.items():
113-
self.used.setdefault(_f.fname, set()).add(ord(_c))
113+
glyph_index = _f.get_char_index(ord(_c))
114+
self.used.setdefault(_f.fname, set()).add(glyph_index)
114115

115116
def track_glyph(self, font, glyph):
116-
"""Record that codepoint *glyph* is being typeset using font *font*."""
117+
"""Record that glyph index *glyph* is being typeset using font *font*."""
117118
self.used.setdefault(font.fname, set()).add(glyph)
118119

119120

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)