Skip to content

Commit a0e93a3

Browse files
authored
Merge pull request matplotlib#20749 from QuLogic/font-cleanups
Cleanup font subsetting code
2 parents ab95c91 + 8118ec3 commit a0e93a3

File tree

3 files changed

+66
-80
lines changed

3 files changed

+66
-80
lines changed

lib/matplotlib/backends/_backend_pdf_ps.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,11 @@ def __init__(self):
6161

6262
def track(self, font, s):
6363
"""Record that string *s* is being typeset using font *font*."""
64-
if isinstance(font, str):
65-
# Unused, can be removed after removal of track_characters.
66-
fname = font
67-
else:
68-
fname = font.fname
69-
self.used.setdefault(fname, set()).update(map(ord, s))
70-
71-
# Not public, can be removed when pdf/ps merge_used_characters is removed.
72-
def merge(self, other):
73-
"""Update self with a font path to character codepoints."""
74-
for fname, charset in other.items():
75-
self.used.setdefault(fname, set()).update(charset)
64+
self.used.setdefault(font.fname, set()).update(map(ord, s))
65+
66+
def track_glyph(self, font, glyph):
67+
"""Record that codepoint *glyph* is being typeset using font *font*."""
68+
self.used.setdefault(font.fname, set()).add(glyph)
7669

7770

7871
class RendererPDFPSBase(RendererBase):

lib/matplotlib/backends/backend_pdf.py

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -323,18 +323,18 @@ def pdfRepr(obj):
323323
.format(type(obj)))
324324

325325

326-
def _font_supports_char(fonttype, char):
326+
def _font_supports_glyph(fonttype, glyph):
327327
"""
328-
Returns True if the font is able to provide *char* in a PDF.
328+
Returns True if the font is able to provide codepoint *glyph* in a PDF.
329329
330330
For a Type 3 font, this method returns True only for single-byte
331-
chars. For Type 42 fonts this method return True if the char is from
332-
the Basic Multilingual Plane.
331+
characters. For Type 42 fonts this method return True if the character is
332+
from the Basic Multilingual Plane.
333333
"""
334334
if fonttype == 3:
335-
return ord(char) <= 255
335+
return glyph <= 255
336336
if fonttype == 42:
337-
return ord(char) <= 65535
337+
return glyph <= 65535
338338
raise NotImplementedError()
339339

340340

@@ -1227,13 +1227,9 @@ def embedTTFType42(font, characters, descriptor):
12271227
wObject = self.reserveObject('Type 0 widths')
12281228
toUnicodeMapObject = self.reserveObject('ToUnicode map')
12291229

1230-
_log.debug(
1231-
"SUBSET %s characters: %s",
1232-
filename, "".join(chr(c) for c in characters)
1233-
)
1234-
fontdata = _backend_pdf_ps.get_glyphs_subset(
1235-
filename, "".join(chr(c) for c in characters)
1236-
)
1230+
subset_str = "".join(chr(c) for c in characters)
1231+
_log.debug("SUBSET %s characters: %s", filename, subset_str)
1232+
fontdata = _backend_pdf_ps.get_glyphs_subset(filename, subset_str)
12371233
_log.debug(
12381234
"SUBSET %s %d -> %d", filename,
12391235
os.stat(filename).st_size, fontdata.getbuffer().nbytes
@@ -1327,7 +1323,7 @@ def embedTTFType42(font, characters, descriptor):
13271323
# Add XObjects for unsupported chars
13281324
glyph_ids = []
13291325
for ccode in characters:
1330-
if not _font_supports_char(fonttype, chr(ccode)):
1326+
if not _font_supports_glyph(fonttype, ccode):
13311327
gind = full_font.get_char_index(ccode)
13321328
glyph_ids.append(gind)
13331329

@@ -2193,10 +2189,9 @@ def draw_mathtext(self, gc, x, y, s, prop, angle):
21932189

21942190
self.file.output(Op.begin_text)
21952191
for font, fontsize, num, ox, oy in glyphs:
2196-
char = chr(num)
2197-
self.file._character_tracker.track(font, char)
2192+
self.file._character_tracker.track_glyph(font, num)
21982193
fontname = font.fname
2199-
if not _font_supports_char(fonttype, char):
2194+
if not _font_supports_glyph(fonttype, num):
22002195
# Unsupported chars (i.e. multibyte in Type 3 or beyond BMP in
22012196
# Type 42) must be emitted separately (below).
22022197
unsupported_chars.append((font, fontsize, ox, oy, num))
@@ -2383,7 +2378,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
23832378
prev_was_multibyte = True
23842379
for item in _text_helpers.layout(
23852380
s, font, kern_mode=KERNING_UNFITTED):
2386-
if _font_supports_char(fonttype, item.char):
2381+
if _font_supports_glyph(fonttype, ord(item.char)):
23872382
if prev_was_multibyte:
23882383
singlebyte_chunks.append((item.x, []))
23892384
if item.prev_kern:

lib/matplotlib/backends/backend_ps.py

Lines changed: 48 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -121,16 +121,16 @@ def _move_path_to_path_or_stream(src, dst):
121121
shutil.move(src, dst, copy_function=shutil.copyfile)
122122

123123

124-
def _font_to_ps_type3(font_path, glyph_ids):
124+
def _font_to_ps_type3(font_path, chars):
125125
"""
126-
Subset *glyph_ids* from the font at *font_path* into a Type 3 font.
126+
Subset *chars* from the font at *font_path* into a Type 3 font.
127127
128128
Parameters
129129
----------
130130
font_path : path-like
131131
Path to the font to be subsetted.
132-
glyph_ids : list of int
133-
The glyph indices to include in the subsetted font.
132+
chars : str
133+
The characters to include in the subsetted font.
134134
135135
Returns
136136
-------
@@ -139,6 +139,7 @@ def _font_to_ps_type3(font_path, glyph_ids):
139139
verbatim into a PostScript file.
140140
"""
141141
font = get_font(font_path, hinting_factor=1)
142+
glyph_ids = [font.get_char_index(c) for c in chars]
142143

143144
preamble = """\
144145
%!PS-Adobe-3.0 Resource-Font
@@ -201,6 +202,44 @@ def _font_to_ps_type3(font_path, glyph_ids):
201202
return preamble + "\n".join(entries) + postamble
202203

203204

205+
def _font_to_ps_type42(font_path, chars, fh):
206+
"""
207+
Subset *chars* from the font at *font_path* into a Type 42 font at *fh*.
208+
209+
Parameters
210+
----------
211+
font_path : path-like
212+
Path to the font to be subsetted.
213+
chars : str
214+
The characters to include in the subsetted font.
215+
fh : file-like
216+
Where to write the font.
217+
"""
218+
subset_str = ''.join(chr(c) for c in chars)
219+
_log.debug("SUBSET %s characters: %s", font_path, subset_str)
220+
try:
221+
fontdata = _backend_pdf_ps.get_glyphs_subset(font_path, subset_str)
222+
_log.debug("SUBSET %s %d -> %d", font_path, os.stat(font_path).st_size,
223+
fontdata.getbuffer().nbytes)
224+
225+
# Give ttconv a subsetted font along with updated glyph_ids.
226+
font = FT2Font(fontdata)
227+
glyph_ids = [font.get_char_index(c) for c in chars]
228+
with TemporaryDirectory() as tmpdir:
229+
tmpfile = os.path.join(tmpdir, "tmp.ttf")
230+
231+
with open(tmpfile, 'wb') as tmp:
232+
tmp.write(fontdata.getvalue())
233+
234+
# TODO: allow convert_ttf_to_ps to input file objects (BytesIO)
235+
convert_ttf_to_ps(os.fsencode(tmpfile), fh, 42, glyph_ids)
236+
except RuntimeError:
237+
_log.warning(
238+
"The PostScript backend does not currently "
239+
"support the selected font.")
240+
raise
241+
242+
204243
def _log_if_debug_on(meth):
205244
"""
206245
Wrap `RendererPS` method *meth* to emit a PS comment with the method name,
@@ -657,7 +696,7 @@ def draw_mathtext(self, gc, x, y, s, prop, angle):
657696
f"{angle:f} rotate\n")
658697
lastfont = None
659698
for font, fontsize, num, ox, oy in glyphs:
660-
self._character_tracker.track(font, chr(num))
699+
self._character_tracker.track_glyph(font, num)
661700
if (font.postscript_name, fontsize) != lastfont:
662701
lastfont = font.postscript_name, fontsize
663702
self._pswriter.write(
@@ -931,56 +970,15 @@ def print_figure_impl(fh):
931970
in ps_renderer._character_tracker.used.items():
932971
if not chars:
933972
continue
934-
font = get_font(font_path)
935-
glyph_ids = [font.get_char_index(c) for c in chars]
936973
fonttype = mpl.rcParams['ps.fonttype']
937974
# Can't use more than 255 chars from a single Type 3 font.
938-
if len(glyph_ids) > 255:
975+
if len(chars) > 255:
939976
fonttype = 42
940977
fh.flush()
941978
if fonttype == 3:
942-
fh.write(_font_to_ps_type3(font_path, glyph_ids))
943-
else:
944-
try:
945-
_log.debug(
946-
"SUBSET %s characters: %s", font_path,
947-
''.join(chr(c) for c in chars)
948-
)
949-
fontdata = _backend_pdf_ps.get_glyphs_subset(
950-
font_path, "".join(chr(c) for c in chars)
951-
)
952-
_log.debug(
953-
"SUBSET %s %d -> %d", font_path,
954-
os.stat(font_path).st_size,
955-
fontdata.getbuffer().nbytes
956-
)
957-
958-
# give ttconv a subsetted font
959-
# along with updated glyph_ids
960-
with TemporaryDirectory() as tmpdir:
961-
tmpfile = os.path.join(tmpdir, "tmp.ttf")
962-
font = FT2Font(fontdata)
963-
glyph_ids = [
964-
font.get_char_index(c) for c in chars
965-
]
966-
967-
with open(tmpfile, 'wb') as tmp:
968-
tmp.write(fontdata.getvalue())
969-
tmp.flush()
970-
971-
# TODO: allow convert_ttf_to_ps
972-
# to input file objects (BytesIO)
973-
convert_ttf_to_ps(
974-
os.fsencode(tmpfile),
975-
fh,
976-
fonttype,
977-
glyph_ids,
978-
)
979-
except RuntimeError:
980-
_log.warning(
981-
"The PostScript backend does not currently "
982-
"support the selected font.")
983-
raise
979+
fh.write(_font_to_ps_type3(font_path, chars))
980+
else: # Type 42 only.
981+
_font_to_ps_type42(font_path, chars, fh)
984982
print("end", file=fh)
985983
print("%%EndProlog", file=fh)
986984

0 commit comments

Comments
 (0)