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

Skip to content

Commit a947b38

Browse files
authored
Merge pull request #23559 from tacaswell/pdf-fallback
re-base of font fallback for pdf and eps output + SVG support
2 parents 59e021a + 5d6ca83 commit a947b38

22 files changed

+1715
-118
lines changed

lib/matplotlib/_text_helpers.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010

1111
LayoutItem = dataclasses.make_dataclass(
12-
"LayoutItem", ["char", "glyph_idx", "x", "prev_kern"])
12+
"LayoutItem", ["ft_object", "char", "glyph_idx", "x", "prev_kern"])
1313

1414

1515
def warn_on_missing_glyph(codepoint):
@@ -57,12 +57,18 @@ def layout(string, font, *, kern_mode=KERNING_DEFAULT):
5757
"""
5858
x = 0
5959
prev_glyph_idx = None
60+
char_to_font = font._get_fontmap(string)
61+
base_font = font
6062
for char in string:
63+
# This has done the fallback logic
64+
font = char_to_font.get(char, base_font)
6165
glyph_idx = font.get_char_index(ord(char))
62-
kern = (font.get_kerning(prev_glyph_idx, glyph_idx, kern_mode) / 64
63-
if prev_glyph_idx is not None else 0.)
66+
kern = (
67+
base_font.get_kerning(prev_glyph_idx, glyph_idx, kern_mode) / 64
68+
if prev_glyph_idx is not None else 0.
69+
)
6470
x += kern
6571
glyph = font.load_glyph(glyph_idx, flags=LOAD_NO_HINTING)
66-
yield LayoutItem(char, glyph_idx, x, kern)
72+
yield LayoutItem(font, char, glyph_idx, x, kern)
6773
x += glyph.linearHoriAdvance / 65536
6874
prev_glyph_idx = glyph_idx

lib/matplotlib/backends/_backend_pdf_ps.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ def __init__(self):
6565

6666
def track(self, font, s):
6767
"""Record that string *s* is being typeset using font *font*."""
68-
self.used.setdefault(font.fname, set()).update(map(ord, s))
68+
char_to_font = font._get_fontmap(s)
69+
for _c, _f in char_to_font.items():
70+
self.used.setdefault(_f.fname, set()).add(ord(_c))
6971

7072
def track_glyph(self, font, glyph):
7173
"""Record that codepoint *glyph* is being typeset using font *font*."""
@@ -135,8 +137,8 @@ def _get_font_afm(self, prop):
135137
return _cached_get_afm_from_fname(fname)
136138

137139
def _get_font_ttf(self, prop):
138-
fname = font_manager.findfont(prop)
139-
font = font_manager.get_font(fname)
140+
fnames = font_manager.fontManager._find_fonts_by_props(prop)
141+
font = font_manager.get_font(fnames)
140142
font.clear()
141143
font.set_size(prop.get_size_in_points(), 72)
142144
return font

lib/matplotlib/backends/backend_pdf.py

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
RendererBase)
3333
from matplotlib.backends.backend_mixed import MixedModeRenderer
3434
from matplotlib.figure import Figure
35-
from matplotlib.font_manager import findfont, get_font
35+
from matplotlib.font_manager import (
36+
findfont, get_font, fontManager as _fontManager
37+
)
3638
from matplotlib._afm import AFM
3739
from matplotlib.ft2font import (FIXED_WIDTH, ITALIC, LOAD_NO_SCALE,
3840
LOAD_NO_HINTING, KERNING_UNFITTED, FT2Font)
@@ -925,20 +927,28 @@ def fontName(self, fontprop):
925927
"""
926928

927929
if isinstance(fontprop, str):
928-
filename = fontprop
930+
filenames = [fontprop]
929931
elif mpl.rcParams['pdf.use14corefonts']:
930-
filename = findfont(
931-
fontprop, fontext='afm', directory=RendererPdf._afm_font_dir)
932+
filenames = _fontManager._find_fonts_by_props(
933+
fontprop, fontext='afm', directory=RendererPdf._afm_font_dir
934+
)
932935
else:
933-
filename = findfont(fontprop)
934-
935-
Fx = self.fontNames.get(filename)
936-
if Fx is None:
937-
Fx = next(self._internal_font_seq)
938-
self.fontNames[filename] = Fx
939-
_log.debug('Assigning font %s = %r', Fx, filename)
940-
941-
return Fx
936+
filenames = _fontManager._find_fonts_by_props(fontprop)
937+
first_Fx = None
938+
for fname in filenames:
939+
Fx = self.fontNames.get(fname)
940+
if not first_Fx:
941+
first_Fx = Fx
942+
if Fx is None:
943+
Fx = next(self._internal_font_seq)
944+
self.fontNames[fname] = Fx
945+
_log.debug('Assigning font %s = %r', Fx, fname)
946+
if not first_Fx:
947+
first_Fx = Fx
948+
949+
# find_fontsprop's first value always adheres to
950+
# findfont's value, so technically no behaviour change
951+
return first_Fx
942952

943953
def dviFontName(self, dvifont):
944954
"""
@@ -1204,7 +1214,6 @@ def get_char_width(charcode):
12041214
width = font.load_char(
12051215
s, flags=LOAD_NO_SCALE | LOAD_NO_HINTING).horiAdvance
12061216
return cvt(width)
1207-
12081217
with warnings.catch_warnings():
12091218
# Ignore 'Required glyph missing from current font' warning
12101219
# from ft2font: here we're just building the widths table, but
@@ -2389,22 +2398,27 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
23892398
# the regular text show command (TJ) with appropriate kerning between
23902399
# chunks, whereas multibyte characters use the XObject command (Do).
23912400
else:
2392-
# List of (start_x, [prev_kern, char, char, ...]), w/o zero kerns.
2401+
# List of (ft_object, start_x, [prev_kern, char, char, ...]),
2402+
# w/o zero kerns.
23932403
singlebyte_chunks = []
2394-
# List of (start_x, glyph_index).
2404+
# List of (ft_object, start_x, glyph_index).
23952405
multibyte_glyphs = []
23962406
prev_was_multibyte = True
2407+
prev_font = font
23972408
for item in _text_helpers.layout(
23982409
s, font, kern_mode=KERNING_UNFITTED):
23992410
if _font_supports_glyph(fonttype, ord(item.char)):
2400-
if prev_was_multibyte:
2401-
singlebyte_chunks.append((item.x, []))
2411+
if prev_was_multibyte or item.ft_object != prev_font:
2412+
singlebyte_chunks.append((item.ft_object, item.x, []))
2413+
prev_font = item.ft_object
24022414
if item.prev_kern:
2403-
singlebyte_chunks[-1][1].append(item.prev_kern)
2404-
singlebyte_chunks[-1][1].append(item.char)
2415+
singlebyte_chunks[-1][2].append(item.prev_kern)
2416+
singlebyte_chunks[-1][2].append(item.char)
24052417
prev_was_multibyte = False
24062418
else:
2407-
multibyte_glyphs.append((item.x, item.glyph_idx))
2419+
multibyte_glyphs.append(
2420+
(item.ft_object, item.x, item.glyph_idx)
2421+
)
24082422
prev_was_multibyte = True
24092423
# Do the rotation and global translation as a single matrix
24102424
# concatenation up front
@@ -2414,10 +2428,12 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
24142428
-math.sin(a), math.cos(a),
24152429
x, y, Op.concat_matrix)
24162430
# Emit all the 1-byte characters in a BT/ET group.
2417-
self.file.output(Op.begin_text,
2418-
self.file.fontName(prop), fontsize, Op.selectfont)
2431+
2432+
self.file.output(Op.begin_text)
24192433
prev_start_x = 0
2420-
for start_x, kerns_or_chars in singlebyte_chunks:
2434+
for ft_object, start_x, kerns_or_chars in singlebyte_chunks:
2435+
ft_name = self.file.fontName(ft_object.fname)
2436+
self.file.output(ft_name, fontsize, Op.selectfont)
24212437
self._setup_textpos(start_x, 0, 0, prev_start_x, 0, 0)
24222438
self.file.output(
24232439
# See pdf spec "Text space details" for the 1000/fontsize
@@ -2429,8 +2445,10 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
24292445
prev_start_x = start_x
24302446
self.file.output(Op.end_text)
24312447
# Then emit all the multibyte characters, one at a time.
2432-
for start_x, glyph_idx in multibyte_glyphs:
2433-
self._draw_xobject_glyph(font, fontsize, glyph_idx, start_x, 0)
2448+
for ft_object, start_x, glyph_idx in multibyte_glyphs:
2449+
self._draw_xobject_glyph(
2450+
ft_object, fontsize, glyph_idx, start_x, 0
2451+
)
24342452
self.file.output(Op.grestore)
24352453

24362454
def _draw_xobject_glyph(self, font, fontsize, glyph_idx, x, y):

lib/matplotlib/backends/backend_ps.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
631631
if mpl.rcParams['ps.useafm']:
632632
font = self._get_font_afm(prop)
633633
scale = 0.001 * prop.get_size_in_points()
634-
634+
stream = []
635635
thisx = 0
636636
last_name = None # kerns returns 0 for None.
637637
xs_names = []
@@ -647,21 +647,36 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
647647
thisx += kern * scale
648648
xs_names.append((thisx, name))
649649
thisx += width * scale
650+
ps_name = (font.postscript_name
651+
.encode("ascii", "replace").decode("ascii"))
652+
stream.append((ps_name, xs_names))
650653

651654
else:
652655
font = self._get_font_ttf(prop)
653-
font.set_text(s, 0, flags=LOAD_NO_HINTING)
654656
self._character_tracker.track(font, s)
655-
xs_names = [(item.x, font.get_glyph_name(item.glyph_idx))
656-
for item in _text_helpers.layout(s, font)]
657+
stream = []
658+
prev_font = curr_stream = None
659+
for item in _text_helpers.layout(s, font):
660+
ps_name = (item.ft_object.postscript_name
661+
.encode("ascii", "replace").decode("ascii"))
662+
if item.ft_object is not prev_font:
663+
if curr_stream:
664+
stream.append(curr_stream)
665+
prev_font = item.ft_object
666+
curr_stream = [ps_name, []]
667+
curr_stream[1].append(
668+
(item.x, item.ft_object.get_glyph_name(item.glyph_idx))
669+
)
670+
# append the last entry
671+
stream.append(curr_stream)
657672

658673
self.set_color(*gc.get_rgb())
659-
ps_name = (font.postscript_name
660-
.encode("ascii", "replace").decode("ascii"))
661-
self.set_font(ps_name, prop.get_size_in_points())
662-
thetext = "\n".join(f"{x:g} 0 m /{name:s} glyphshow"
663-
for x, name in xs_names)
664-
self._pswriter.write(f"""\
674+
675+
for ps_name, xs_names in stream:
676+
self.set_font(ps_name, prop.get_size_in_points(), False)
677+
thetext = "\n".join(f"{x:g} 0 m /{name:s} glyphshow"
678+
for x, name in xs_names)
679+
self._pswriter.write(f"""\
665680
gsave
666681
{self._get_clip_cmd(gc)}
667682
{x:g} {y:g} translate

lib/matplotlib/backends/backend_svg.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1130,7 +1130,8 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):
11301130
font_parts.append(f'{weight}')
11311131
font_parts.extend([
11321132
f'{_short_float_fmt(prop.get_size())}px',
1133-
f'{prop.get_family()[0]!r}', # ensure quoting
1133+
# ensure quoting
1134+
f'{", ".join(repr(f) for f in prop.get_family())}',
11341135
])
11351136
style['font'] = ' '.join(font_parts)
11361137
if prop.get_stretch() != 'normal':

lib/matplotlib/testing/_markers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import pytest
99

1010
import matplotlib.testing
11+
import matplotlib.testing.compare
1112
from matplotlib import _get_executable_info, ExecutableNotFoundError
1213

1314

Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)