Skip to content

Commit 62cad79

Browse files
committed
Alternative to matplotlib#2431: Decode the filenames returned from fontconfig in
the filesystemencoding. Fix a number of buglets that did not allow fonts with non-ascii filenames to be opened. This involves taking advantage of code already in FT2Font to use unicode filenames, and encoding the filenames before passing into ttconv.
1 parent 7a9f234 commit 62cad79

File tree

10 files changed

+49
-32
lines changed

10 files changed

+49
-32
lines changed

lib/matplotlib/backends/backend_agg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ def _get_agg_font(self, prop):
262262
font = RendererAgg._fontd.get(fname)
263263
if font is None:
264264
font = FT2Font(
265-
str(fname),
265+
fname,
266266
hinting_factor=rcParams['text.hinting_factor'])
267267
RendererAgg._fontd[fname] = font
268268
RendererAgg._fontd[key] = font

lib/matplotlib/backends/backend_pdf.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ def fontName(self, fontprop):
585585
self.fontNames[filename] = Fx
586586
self.nextFont += 1
587587
matplotlib.verbose.report(
588-
'Assigning font %s = %s' % (Fx, filename),
588+
'Assigning font %s = %r' % (Fx, filename),
589589
'debug')
590590

591591
return Fx
@@ -701,7 +701,7 @@ def createType1Descriptor(self, t1font, fontfile):
701701
if 0: flags |= 1 << 17 # TODO: small caps
702702
if 0: flags |= 1 << 18 # TODO: force bold
703703

704-
ft2font = FT2Font(str(fontfile))
704+
ft2font = FT2Font(fontfile)
705705

706706
descriptor = {
707707
'Type': Name('FontDescriptor'),
@@ -761,7 +761,7 @@ def _get_xobject_symbol_name(self, filename, symbol_name):
761761
def embedTTF(self, filename, characters):
762762
"""Embed the TTF font from the named file into the document."""
763763

764-
font = FT2Font(str(filename))
764+
font = FT2Font(filename)
765765
fonttype = rcParams['pdf.fonttype']
766766

767767
def cvt(length, upe=font.units_per_EM, nearest=True):
@@ -845,7 +845,8 @@ def get_char_width(charcode):
845845

846846
# Make the charprocs array (using ttconv to generate the
847847
# actual outlines)
848-
rawcharprocs = ttconv.get_pdf_charprocs(filename, glyph_ids)
848+
rawcharprocs = ttconv.get_pdf_charprocs(
849+
filename.encode(sys.getfilesystemencoding()), glyph_ids)
849850
charprocs = {}
850851
charprocsRef = {}
851852
for charname, stream in six.iteritems(rawcharprocs):
@@ -2003,7 +2004,7 @@ def _get_font_ttf(self, prop):
20032004
filename = findfont(prop)
20042005
font = self.truetype_font_cache.get(filename)
20052006
if font is None:
2006-
font = FT2Font(str(filename))
2007+
font = FT2Font(filename)
20072008
self.truetype_font_cache[filename] = font
20082009
self.truetype_font_cache[key] = font
20092010
font.clear()

lib/matplotlib/backends/backend_pgf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
system_fonts = []
3434
for f in font_manager.findSystemFonts():
3535
try:
36-
system_fonts.append(FT2Font(str(f)).family_name)
36+
system_fonts.append(FT2Font(f).family_name)
3737
except RuntimeError:
3838
pass # some fonts on osx are known to fail, print?
3939
except:

lib/matplotlib/backends/backend_ps.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ def _get_font_ttf(self, prop):
391391
fname = findfont(prop)
392392
font = self.fontd.get(fname)
393393
if font is None:
394-
font = FT2Font(str(fname))
394+
font = FT2Font(fname)
395395
self.fontd[fname] = font
396396
self.fontd[key] = font
397397
font.clear()
@@ -1131,7 +1131,7 @@ def print_figure_impl():
11311131
if not rcParams['ps.useafm']:
11321132
for font_filename, chars in six.itervalues(ps_renderer.used_characters):
11331133
if len(chars):
1134-
font = FT2Font(str(font_filename))
1134+
font = FT2Font(font_filename)
11351135
cmap = font.get_charmap()
11361136
glyph_ids = []
11371137
for c in chars:
@@ -1153,7 +1153,9 @@ def print_figure_impl():
11531153
raise RuntimeError("OpenType CFF fonts can not be saved using the internal Postscript backend at this time.\nConsider using the Cairo backend.")
11541154
else:
11551155
fh.flush()
1156-
convert_ttf_to_ps(font_filename, raw_fh, fonttype, glyph_ids)
1156+
convert_ttf_to_ps(
1157+
font_filename.encode(sys.getfilesystemencoding()),
1158+
raw_fh, fonttype, glyph_ids)
11571159
print("end", file=fh)
11581160
print("%%EndProlog", file=fh)
11591161

lib/matplotlib/backends/backend_svg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ def _get_font(self, prop):
323323
fname = findfont(prop)
324324
font = self.fontd.get(fname)
325325
if font is None:
326-
font = FT2Font(str(fname))
326+
font = FT2Font(fname)
327327
self.fontd[fname] = font
328328
self.fontd[key] = font
329329
font.clear()

lib/matplotlib/font_manager.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@
6262
parse_fontconfig_pattern, generate_fontconfig_pattern
6363

6464
USE_FONTCONFIG = False
65-
6665
verbose = matplotlib.verbose
6766

6867
font_scalings = {
@@ -276,11 +275,14 @@ def get_fontconfig_fonts(fontext='ttf'):
276275
return fontfiles
277276

278277
if pipe.returncode == 0:
279-
output = str(output)
280-
for line in output.split('\n'):
281-
fname = line.split(':')[0]
278+
# The bulk of the output from fc-list is ascii, so we keep the
279+
# result in bytes and parse it as bytes, until we extract the
280+
# filename, which is in sys.filesystemencoding().
281+
for line in output.split(b'\n'):
282+
fname = line.split(b':')[0]
282283
if (os.path.splitext(fname)[1][1:] in fontext and
283284
os.path.exists(fname)):
285+
fname = six.text_type(fname, sys.getfilesystemencoding())
284286
fontfiles[fname] = 1
285287

286288
return fontfiles
@@ -570,7 +572,7 @@ def createFontList(fontfiles, fontext='ttf'):
570572
continue
571573
else:
572574
try:
573-
font = ft2font.FT2Font(str(fpath))
575+
font = ft2font.FT2Font(fpath)
574576
except RuntimeError:
575577
verbose.report("Could not open font file %s"%fpath)
576578
continue
@@ -720,7 +722,7 @@ def get_name(self):
720722
Return the name of the font that best matches the font
721723
properties.
722724
"""
723-
return ft2font.FT2Font(str(findfont(self))).family_name
725+
return ft2font.FT2Font(findfont(self)).family_name
724726

725727
def get_style(self):
726728
"""
@@ -1246,7 +1248,7 @@ def findfont(self, prop, fontext='ttf', directory=None,
12461248
else:
12471249
verbose.report(
12481250
'findfont: Matching %s to %s (%s) with score of %f' %
1249-
(prop, best_font.name, best_font.fname, best_score))
1251+
(prop, best_font.name, repr(best_font.fname), best_score))
12501252
result = best_font.fname
12511253

12521254
if not os.path.isfile(result):
@@ -1296,15 +1298,19 @@ def fc_match(pattern, fontext):
12961298
output = pipe.communicate()[0]
12971299
except (OSError, IOError):
12981300
return None
1301+
1302+
# The bulk of the output from fc-list is ascii, so we keep the
1303+
# result in bytes and parse it as bytes, until we extract the
1304+
# filename, which is in sys.filesystemencoding().
12991305
if pipe.returncode == 0:
13001306
for match in _fc_match_regex.finditer(output):
13011307
file = match.group(1)
1302-
file = file.decode(sys.getfilesystemencoding())
1308+
file = six.text_type(file, sys.getfilesystemencoding())
13031309
if os.path.splitext(file)[1][1:] in fontexts:
13041310
return file
13051311
return None
13061312

1307-
_fc_match_regex = re.compile(br'\sfile:\s+"([^"]*)"')
1313+
_fc_match_regex = re.compile(br'\sfile:\s+"(.*?)"')
13081314
_fc_match_cache = {}
13091315

13101316
def findfont(prop, fontext='ttf'):

lib/matplotlib/mathtext.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,7 @@ def __init__(self, default_font_prop, mathtext_backend):
566566
self._fonts = {}
567567

568568
filename = findfont(default_font_prop)
569-
default_font = self.CachedFont(FT2Font(str(filename)))
569+
default_font = self.CachedFont(FT2Font(filename))
570570
self._fonts['default'] = default_font
571571
self._fonts['regular'] = default_font
572572

@@ -582,7 +582,7 @@ def _get_font(self, font):
582582

583583
cached_font = self._fonts.get(basename)
584584
if cached_font is None:
585-
font = FT2Font(str(basename))
585+
font = FT2Font(basename)
586586
cached_font = self.CachedFont(font)
587587
self._fonts[basename] = cached_font
588588
self._fonts[font.postscript_name] = cached_font

lib/matplotlib/textpath.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def _get_font(self, prop):
5656
find a ttf font.
5757
"""
5858
fname = font_manager.findfont(prop)
59-
font = FT2Font(str(fname))
59+
font = FT2Font(fname)
6060
font.set_size(self.FONT_SCALE, self.DPI)
6161

6262
return font
@@ -338,7 +338,7 @@ def get_glyphs_tex(self, prop, s, glyph_map=None,
338338
font_bunch = self.tex_font_map[dvifont.texname]
339339

340340
if font_and_encoding is None:
341-
font = FT2Font(str(font_bunch.filename))
341+
font = FT2Font(font_bunch.filename)
342342

343343
for charmap_name, charmap_code in [("ADOBE_CUSTOM",
344344
1094992451),

src/_ttconv.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,11 @@ convert_ttf_to_ps(PyObject* self, PyObject* args, PyObject* kwds)
124124
};
125125
if (! PyArg_ParseTupleAndKeywords
126126
(args, kwds,
127+
#if PY_MAJOR_VERSION == 3
128+
"yO&i|O&:convert_ttf_to_ps",
129+
#else
127130
"sO&i|O&:convert_ttf_to_ps",
131+
#endif
128132
(char**)kwlist,
129133
&filename,
130134
fileobject_to_PythonFileWriter,
@@ -202,7 +206,11 @@ py_get_pdf_charprocs(PyObject* self, PyObject* args, PyObject* kwds)
202206
static const char *kwlist[] = { "filename", "glyph_ids", NULL };
203207
if (! PyArg_ParseTupleAndKeywords
204208
(args, kwds,
209+
#if PY_MAJOR_VERSION == 3
210+
"y|O&:get_pdf_charprocs",
211+
#else
205212
"s|O&:get_pdf_charprocs",
213+
#endif
206214
(char **)kwlist,
207215
&filename,
208216
pyiterable_to_vector_int,

src/ft2font.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -850,7 +850,9 @@ FT2Font::FT2Font(Py::PythonClassInstance *self, Py::Tuple &args, Py::Dict &kwds)
850850
{
851851
FT_Open_Args open_args;
852852

853-
std::string facefile = Py::String(args[0]).encode("utf-8");
853+
/* This string is only used for error messages, so encode it in something
854+
* that we'll always be able to print. */
855+
std::string facefile = Py::String(args[0]).encode("unicode_escape");
854856

855857
args.verify_length(1);
856858

@@ -861,9 +863,8 @@ FT2Font::FT2Font(Py::PythonClassInstance *self, Py::Tuple &args, Py::Dict &kwds)
861863
mem_size = 0;
862864

863865
if (make_open_args(args[0].ptr(), &open_args)) {
864-
std::ostringstream s;
865-
s << "Could not load facefile " << facefile << "; Unknown_File_Format" << std::endl;
866-
throw Py::RuntimeError(s.str());
866+
/* make_open_args sets the Python exception for us. */
867+
throw Py::Exception();
867868
}
868869

869870
int error = FT_Open_Face(_ft2Library, &open_args, 0, &face);
@@ -965,7 +966,7 @@ FT2Font::FT2Font(Py::PythonClassInstance *self, Py::Tuple &args, Py::Dict &kwds)
965966
setattro("underline_thickness", Py::Int(face->underline_thickness));
966967
}
967968

968-
setattro("fname", Py::String(facefile));
969+
setattro("fname", args[0]);
969970

970971
_VERBOSE("FT2Font::FT2Font done");
971972
}
@@ -2092,9 +2093,8 @@ FT2Font::attach_file(const Py::Tuple &args)
20922093

20932094
if (make_open_args(args[0].ptr(), &open_args))
20942095
{
2095-
std::ostringstream s;
2096-
s << "Could not attach file " << filename << std::endl;
2097-
throw Py::RuntimeError(s.str());
2096+
/* make_open_args sets the Python exception for us. */
2097+
throw Py::Exception();
20982098
}
20992099

21002100
FT_Error error = FT_Attach_Stream(face, &open_args);

0 commit comments

Comments
 (0)