Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2118966

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 04c8eef commit 2118966

File tree

10 files changed

+150
-153
lines changed

10 files changed

+150
-153
lines changed

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: 26 additions & 17 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,23 +105,24 @@ 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*."""
124+
def track_glyph(self, font: FT2Font, glyph: GlyphIndexType) -> None:
125+
"""Record that glyph index *glyph* is being typeset using font *font*."""
117126
self.used.setdefault(font.fname, set()).add(glyph)
118127

119128

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)