Skip to content

Commit ab11cef

Browse files
committed
pdf/ps: Support any font in a collection
1 parent 99ae95e commit ab11cef

File tree

7 files changed

+101
-26
lines changed

7 files changed

+101
-26
lines changed

lib/matplotlib/backends/_backend_pdf_ps.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def get_glyphs_subset(fontfile: str, glyphs: set[GlyphIndexType]) -> TTFont:
3636
3737
Parameters
3838
----------
39-
fontfile : str
39+
fontfile : FontPath
4040
Path to the font file
4141
glyphs : set[GlyphIndexType]
4242
Set of glyph indices to include in subset.
@@ -74,8 +74,7 @@ def get_glyphs_subset(fontfile: str, glyphs: set[GlyphIndexType]) -> TTFont:
7474
'xref', # The cross-reference table (some Apple font tooling information).
7575
]
7676
# if fontfile is a ttc, specify font number
77-
if fontfile.endswith(".ttc"):
78-
options.font_number = 0
77+
options.font_number = fontfile.face_index
7978

8079
font = subset.load_font(fontfile, options)
8180
subsetter = subset.Subsetter(options=options)
@@ -168,7 +167,8 @@ def track(self, font: FT2Font, s: str) -> list[tuple[int, CharacterCodeType]]:
168167
else:
169168
subset = 0
170169
subset_charcode = charcode
171-
self.used.setdefault((_f.fname, subset), {})[subset_charcode] = glyph_index
170+
font_path = font_manager.FontPath(_f.fname, _f.face_index)
171+
self.used.setdefault((font_path, subset), {})[subset_charcode] = glyph_index
172172
font_glyphs.append((subset, subset_charcode))
173173
return font_glyphs
174174

@@ -202,7 +202,8 @@ def track_glyph(
202202
else:
203203
subset = 0
204204
subset_charcode = charcode
205-
self.used.setdefault((font.fname, subset), {})[subset_charcode] = glyph
205+
font_path = font_manager.FontPath(font.fname, font.face_index)
206+
self.used.setdefault((font_path, subset), {})[subset_charcode] = glyph
206207
return (subset, subset_charcode)
207208

208209
def subset_to_unicode(self, index: int,

lib/matplotlib/backends/backend_pdf.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
RendererBase)
3333
from matplotlib.backends.backend_mixed import MixedModeRenderer
3434
from matplotlib.figure import Figure
35-
from matplotlib.font_manager import get_font, fontManager as _fontManager
35+
from matplotlib.font_manager import FontPath, get_font, fontManager as _fontManager
3636
from matplotlib._afm import AFM
3737
from matplotlib.ft2font import FT2Font, FaceFlags, Kerning, LoadFlags, StyleFlags
3838
from matplotlib.transforms import Affine2D, BboxBase
@@ -899,8 +899,10 @@ def fontName(self, fontprop, subset=0):
899899
as the filename of the font.
900900
"""
901901

902-
if isinstance(fontprop, str):
902+
if isinstance(fontprop, FontPath):
903903
filenames = [fontprop]
904+
elif isinstance(fontprop, str):
905+
filenames = [FontPath(fontprop, 0)]
904906
elif mpl.rcParams['pdf.use14corefonts']:
905907
filenames = _fontManager._find_fonts_by_props(
906908
fontprop, fontext='afm', directory=RendererPdf._afm_font_dir
@@ -940,7 +942,7 @@ def writeFonts(self):
940942
_log.debug('Embedding Type-1 font %s from dvi.', dvifont.texname)
941943
fonts[pdfname] = self._embedTeXFont(dvifont)
942944
for (filename, subset), Fx in sorted(self._fontNames.items()):
943-
_log.debug('Embedding font %s:%d.', filename, subset)
945+
_log.debug('Embedding font %r:%d.', filename, subset)
944946
if filename.endswith('.afm'):
945947
# from pdf.use14corefonts
946948
_log.debug('Writing AFM font.')
@@ -992,7 +994,8 @@ def _embedTeXFont(self, dvifont):
992994

993995
# Reduce the font to only the glyphs used in the document, get the encoding
994996
# for that subset, and compute various properties based on the encoding.
995-
charmap = self._character_tracker.used[(dvifont.fname, 0)]
997+
font_path = FontPath(dvifont.fname, dvifont.face_index)
998+
charmap = self._character_tracker.used[(font_path, 0)]
996999
chars = frozenset(charmap.keys())
9971000
t1font = t1font.subset(chars, self._get_subset_prefix(charmap.values()))
9981001
fontdict['BaseFont'] = Name(t1font.prop['FontName'])
@@ -1241,12 +1244,12 @@ def embedTTFType42(font, subset_index, charmap, descriptor):
12411244
wObject = self.reserveObject('Type 0 widths')
12421245
toUnicodeMapObject = self.reserveObject('ToUnicode map')
12431246

1244-
_log.debug("SUBSET %s:%d characters: %s", filename, subset_index, charmap)
1247+
_log.debug("SUBSET %r:%d characters: %s", filename, subset_index, charmap)
12451248
with _backend_pdf_ps.get_glyphs_subset(filename,
12461249
charmap.values()) as subset:
12471250
fontdata = _backend_pdf_ps.font_as_file(subset)
12481251
_log.debug(
1249-
"SUBSET %s:%d %d -> %d", filename, subset_index,
1252+
"SUBSET %r:%d %d -> %d", filename, subset_index,
12501253
os.stat(filename).st_size, fontdata.getbuffer().nbytes
12511254
)
12521255

@@ -2137,13 +2140,13 @@ def draw_mathtext(self, gc, x, y, s, prop, angle):
21372140
for font, fontsize, ccode, glyph_index, ox, oy in glyphs:
21382141
subset_index, subset_charcode = self.file._character_tracker.track_glyph(
21392142
font, ccode, glyph_index)
2140-
fontname = font.fname
2143+
font_path = FontPath(font.fname, font.face_index)
21412144
self._setup_textpos(ox, oy, 0, oldx, oldy)
21422145
oldx, oldy = ox, oy
2143-
if (fontname, subset_index, fontsize) != prev_font:
2144-
self.file.output(self.file.fontName(fontname, subset_index), fontsize,
2146+
if (font_path, subset_index, fontsize) != prev_font:
2147+
self.file.output(self.file.fontName(font_path, subset_index), fontsize,
21452148
Op.selectfont)
2146-
prev_font = fontname, subset_index, fontsize
2149+
prev_font = font_path, subset_index, fontsize
21472150
self.file.output(self._encode_glyphs([subset_charcode], fonttype),
21482151
Op.show)
21492152
self.file.output(Op.end_text)
@@ -2329,7 +2332,9 @@ def output_singlebyte_chunk(kerns_or_chars):
23292332
if (item.ft_object, subset) != prev_font:
23302333
if singlebyte_chunk:
23312334
output_singlebyte_chunk(singlebyte_chunk)
2332-
ft_name = self.file.fontName(item.ft_object.fname, subset)
2335+
font_path = FontPath(item.ft_object.fname,
2336+
item.ft_object.face_index)
2337+
ft_name = self.file.fontName(font_path, subset)
23332338
self.file.output(ft_name, fontsize, Op.selectfont)
23342339
self._setup_textpos(item.x, 0, 0, prev_start_x, 0, 0)
23352340
singlebyte_chunk = []

lib/matplotlib/backends/backend_ps.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def _font_to_ps_type3(font_path, glyph_indices):
9494
9595
Parameters
9696
----------
97-
font_path : path-like
97+
font_path : FontPath
9898
Path to the font to be subsetted.
9999
glyph_indices : set[int]
100100
The glyphs to include in the subsetted font.
@@ -174,7 +174,7 @@ def _font_to_ps_type42(font_path, glyph_indices, fh):
174174
175175
Parameters
176176
----------
177-
font_path : path-like
177+
font_path : FontPath
178178
Path to the font to be subsetted.
179179
glyph_indices : set[int]
180180
The glyphs to include in the subsetted font.
@@ -183,12 +183,8 @@ def _font_to_ps_type42(font_path, glyph_indices, fh):
183183
"""
184184
_log.debug("SUBSET %s characters: %s", font_path, glyph_indices)
185185
try:
186-
kw = {}
187-
# fix this once we support loading more fonts from a collection
188-
# https://github.com/matplotlib/matplotlib/issues/3135#issuecomment-571085541
189-
if font_path.endswith('.ttc'):
190-
kw['fontNumber'] = 0
191-
with (fontTools.ttLib.TTFont(font_path, **kw) as font,
186+
with (fontTools.ttLib.TTFont(font_path.path,
187+
fontNumber=font_path.face_index) as font,
192188
_backend_pdf_ps.get_glyphs_subset(font_path, glyph_indices) as subset):
193189
fontdata = _backend_pdf_ps.font_as_file(subset).getvalue()
194190
_log.debug(

lib/matplotlib/dviread.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,10 @@ def fname(self):
719719
"""A fake filename"""
720720
return self.texname.decode('latin-1')
721721

722+
@property
723+
def face_index(self): # For compatibility with FT2Font.
724+
return 0
725+
722726
def _get_fontmap(self, string):
723727
"""Get the mapping from characters to the font that includes them.
724728

lib/matplotlib/dviread.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ class DviFont:
7878
def widths(self) -> list[int]: ...
7979
@property
8080
def fname(self) -> str: ...
81+
@property
82+
def face_index(self) -> int: ...
8183
def resolve_path(self) -> Path: ...
8284
@property
8385
def subfont(self) -> int: ...

lib/matplotlib/tests/test_backend_pdf.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io
44
import os
55
from pathlib import Path
6+
import string
67

78
import numpy as np
89
import pytest
@@ -365,7 +366,7 @@ def test_glyphs_subset():
365366

366367
# subsetted FT2Font
367368
glyph_indices = {nosubcmap[ord(c)] for c in chars}
368-
with get_glyphs_subset(fpath, glyph_indices) as subset:
369+
with get_glyphs_subset(fm.FontPath(fpath, 0), glyph_indices) as subset:
369370
subfont = FT2Font(font_as_file(subset))
370371
subfont.set_text(chars)
371372
subcmap = subfont.get_charmap()
@@ -402,6 +403,38 @@ def test_multi_font_type42():
402403
horizontalalignment='center', verticalalignment='center')
403404

404405

406+
@image_comparison(['ttc_type3.pdf'], style='mpl20')
407+
def test_ttc_type3():
408+
fp = fm.FontProperties(family=['WenQuanYi Zen Hei'])
409+
if Path(fm.findfont(fp)).name != 'wqy-zenhei.ttc':
410+
pytest.skip('Font wqy-zenhei.ttc may be missing')
411+
412+
fonts = ['WenQuanYi Zen Hei', 'WenQuanYi Zen Hei Mono']
413+
plt.rc('font', size=16)
414+
plt.rc('pdf', fonttype=3)
415+
416+
figs = plt.figure(figsize=(7, len(fonts) / 2)).subfigures(len(fonts))
417+
for font, fig in zip(fonts, figs):
418+
fig.text(0.5, 0.5, f'{font}: {string.ascii_uppercase}', font=font,
419+
horizontalalignment='center', verticalalignment='center')
420+
421+
422+
@image_comparison(['ttc_type42.pdf'], style='mpl20')
423+
def test_ttc_type42():
424+
fp = fm.FontProperties(family=['WenQuanYi Zen Hei'])
425+
if Path(fm.findfont(fp)).name != 'wqy-zenhei.ttc':
426+
pytest.skip('Font wqy-zenhei.ttc may be missing')
427+
428+
fonts = ['WenQuanYi Zen Hei', 'WenQuanYi Zen Hei Mono']
429+
plt.rc('font', size=16)
430+
plt.rc('pdf', fonttype=42)
431+
432+
figs = plt.figure(figsize=(7, len(fonts) / 2)).subfigures(len(fonts))
433+
for font, fig in zip(fonts, figs):
434+
fig.text(0.5, 0.5, f'{font}: {string.ascii_uppercase}', font=font,
435+
horizontalalignment='center', verticalalignment='center')
436+
437+
405438
@pytest.mark.parametrize('family_name, file_name',
406439
[("Noto Sans", "NotoSans-Regular.otf"),
407440
("FreeMono", "FreeMono.otf")])

lib/matplotlib/tests/test_backend_ps.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
from collections import Counter
22
import io
3+
from pathlib import Path
34
import re
5+
import string
46
import tempfile
57

68
import numpy as np
79
import pytest
810

9-
from matplotlib import cbook, path, patheffects
11+
from matplotlib import cbook, font_manager, path, patheffects
1012
from matplotlib.figure import Figure
1113
from matplotlib.patches import Ellipse
1214
from matplotlib.testing import _gen_multi_font_text
@@ -340,6 +342,38 @@ def test_multi_font_type42():
340342
horizontalalignment='center', verticalalignment='center')
341343

342344

345+
@image_comparison(['ttc_type3.eps'], style='mpl20')
346+
def test_ttc_type3():
347+
fp = font_manager.FontProperties(family=['WenQuanYi Zen Hei'])
348+
if Path(font_manager.findfont(fp)).name != 'wqy-zenhei.ttc':
349+
pytest.skip('Font wqy-zenhei.ttc may be missing')
350+
351+
fonts = ['WenQuanYi Zen Hei', 'WenQuanYi Zen Hei Mono']
352+
plt.rc('font', size=16)
353+
plt.rc('pdf', fonttype=3)
354+
355+
figs = plt.figure(figsize=(7, len(fonts) / 2)).subfigures(len(fonts))
356+
for font, fig in zip(fonts, figs):
357+
fig.text(0.5, 0.5, f'{font}: {string.ascii_uppercase}', font=font,
358+
horizontalalignment='center', verticalalignment='center')
359+
360+
361+
@image_comparison(['ttc_type42.eps'], style='mpl20')
362+
def test_ttc_type42():
363+
fp = font_manager.FontProperties(family=['WenQuanYi Zen Hei'])
364+
if Path(font_manager.findfont(fp)).name != 'wqy-zenhei.ttc':
365+
pytest.skip('Font wqy-zenhei.ttc may be missing')
366+
367+
fonts = ['WenQuanYi Zen Hei', 'WenQuanYi Zen Hei Mono']
368+
plt.rc('font', size=16)
369+
plt.rc('pdf', fonttype=42)
370+
371+
figs = plt.figure(figsize=(7, len(fonts) / 2)).subfigures(len(fonts))
372+
for font, fig in zip(fonts, figs):
373+
fig.text(0.5, 0.5, f'{font}: {string.ascii_uppercase}', font=font,
374+
horizontalalignment='center', verticalalignment='center')
375+
376+
343377
@image_comparison(["scatter.eps"])
344378
def test_path_collection():
345379
rng = np.random.default_rng(19680801)

0 commit comments

Comments
 (0)