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

Skip to content

Commit c5fd880

Browse files
aitikguptatacaswell
authored andcommitted
ENH: implement font fallback for PDF
1 parent 7fcde53 commit c5fd880

File tree

6 files changed

+85
-33
lines changed

6 files changed

+85
-33
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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ def _get_font_afm(self, prop):
137137
return _cached_get_afm_from_fname(fname)
138138

139139
def _get_font_ttf(self, prop):
140-
fname = font_manager.findfont(prop)
141-
font = font_manager.get_font(fname)
140+
fnames = font_manager.fontManager._find_fonts_by_props(prop)
141+
font = font_manager.get_font(fnames)
142142
font.clear()
143143
font.set_size(prop.get_size_in_points(), 72)
144144
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):
Binary file not shown.
Binary file not shown.

lib/matplotlib/tests/test_backend_pdf.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
import pytest
1010

1111
import matplotlib as mpl
12-
from matplotlib import pyplot as plt, rcParams
12+
from matplotlib import (
13+
pyplot as plt, rcParams, font_manager as fm
14+
)
1315
from matplotlib.cbook import _get_data_path
1416
from matplotlib.ft2font import FT2Font
1517
from matplotlib.font_manager import findfont, FontProperties
@@ -383,3 +385,29 @@ def test_glyphs_subset():
383385

384386
# since both objects are assigned same characters
385387
assert subfont.get_num_glyphs() == nosubfont.get_num_glyphs()
388+
389+
390+
@image_comparison(["multi_font_type3.pdf"])
391+
def test_multi_font_type3():
392+
fp = fm.FontProperties(family=["WenQuanYi Zen Hei"])
393+
if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc":
394+
pytest.skip("Font may be missing")
395+
396+
plt.rc('font', family=['DejaVu Sans', 'WenQuanYi Zen Hei'], size=27)
397+
plt.rc('pdf', fonttype=3)
398+
399+
fig = plt.figure()
400+
fig.text(0.15, 0.475, "There are 几个汉字 in between!")
401+
402+
403+
@image_comparison(["multi_font_type42.pdf"])
404+
def test_multi_font_type42():
405+
fp = fm.FontProperties(family=["WenQuanYi Zen Hei"])
406+
if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc":
407+
pytest.skip("Font may be missing")
408+
409+
plt.rc('font', family=['DejaVu Sans', 'WenQuanYi Zen Hei'], size=27)
410+
plt.rc('pdf', fonttype=42)
411+
412+
fig = plt.figure()
413+
fig.text(0.15, 0.475, "There are 几个汉字 in between!")

0 commit comments

Comments
 (0)