diff --git a/lib/matplotlib/dviread.py b/lib/matplotlib/dviread.py index 3f05e1cf0c80..c1d1a93f55bf 100644 --- a/lib/matplotlib/dviread.py +++ b/lib/matplotlib/dviread.py @@ -1132,7 +1132,6 @@ def _fontfile(cls, suffix, texname): import fontTools.agl from matplotlib.ft2font import FT2Font - from matplotlib.textpath import TextToPath parser = ArgumentParser() parser.add_argument("filename") @@ -1155,14 +1154,13 @@ def _print_fields(*args): print(f"font: {font.texname.decode('latin-1')} " f"(scale: {font._scale / 2 ** 20}) at {fontpath}") face = FT2Font(fontpath) - TextToPath._select_native_charmap(face) _print_fields("x", "y", "glyph", "chr", "w") for text in group: if psfont.encoding: glyph_name = _parse_enc(psfont.encoding)[text.glyph] else: - glyph_name = face.get_glyph_name( - face.get_char_index(text.glyph)) + encoding_vector = face._get_type1_encoding_vector() + glyph_name = face.get_glyph_name(encoding_vector[text.glyph]) glyph_str = fontTools.agl.toUnicode(glyph_name) _print_fields(text.x, text.y, text.glyph, glyph_str, text.width) if page.boxes: diff --git a/lib/matplotlib/textpath.py b/lib/matplotlib/textpath.py index 83182e3f5400..35adfdd77899 100644 --- a/lib/matplotlib/textpath.py +++ b/lib/matplotlib/textpath.py @@ -232,6 +232,7 @@ def get_glyphs_tex(self, prop, s, glyph_map=None, # Gather font information and do some setup for combining # characters into strings. + t1_encodings = {} for text in page.text: font = get_font(text.font_path) char_id = self._get_char_id(font, text.glyph) @@ -241,14 +242,14 @@ def get_glyphs_tex(self, prop, s, glyph_map=None, glyph_name_or_index = text.glyph_name_or_index if isinstance(glyph_name_or_index, str): index = font.get_name_index(glyph_name_or_index) - font.load_glyph(index, flags=LoadFlags.TARGET_LIGHT) elif isinstance(glyph_name_or_index, int): - self._select_native_charmap(font) - font.load_char( - glyph_name_or_index, flags=LoadFlags.TARGET_LIGHT) + if font not in t1_encodings: + t1_encodings[font] = font._get_type1_encoding_vector() + index = t1_encodings[font][glyph_name_or_index] else: # Should not occur. raise TypeError(f"Glyph spec of unexpected type: " f"{glyph_name_or_index!r}") + font.load_glyph(index, flags=LoadFlags.TARGET_LIGHT) glyph_map_new[char_id] = font.get_path() glyph_ids.append(char_id) @@ -269,23 +270,6 @@ def get_glyphs_tex(self, prop, s, glyph_map=None, return (list(zip(glyph_ids, xpositions, ypositions, sizes)), glyph_map_new, myrects) - @staticmethod - def _select_native_charmap(font): - # Select the native charmap. (we can't directly identify it but it's - # typically an Adobe charmap). - for charmap_code in [ - 1094992451, # ADOBE_CUSTOM. - 1094995778, # ADOBE_STANDARD. - ]: - try: - font.select_charmap(charmap_code) - except (ValueError, RuntimeError): - pass - else: - break - else: - _log.warning("No supported encoding in font (%s).", font.fname) - text_to_path = TextToPath() diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index 6d4e715d2099..4f3c2fd00d52 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -498,6 +498,16 @@ PyFT2Font_init(py::object filename, long hinting_factor = 8, return self; } +static py::str +PyFT2Font_fname(PyFT2Font *self) +{ + if (self->stream.close) { // Called passed a filename to the constructor. + return self->py_file.attr("name"); + } else { + return py::cast(self->py_file); + } +} + const char *PyFT2Font_clear__doc__ = "Clear all the glyphs, reset for a new call to `.set_text`."; @@ -1431,6 +1441,32 @@ PyFT2Font_get_image(PyFT2Font *self) return py::array_t(dims, im.get_buffer()); } +const char *PyFT2Font__get_type1_encoding_vector__doc__ = R"""( + Return a list mapping CharString indices of a Type 1 font to FreeType glyph indices. + + Returns + ------- + list[int] +)"""; + +static std::array +PyFT2Font__get_type1_encoding_vector(PyFT2Font *self) +{ + auto face = self->x->get_face(); + auto indices = std::array{}; + for (auto i = 0u; i < indices.size(); ++i) { + auto len = FT_Get_PS_Font_Value(face, PS_DICT_ENCODING_ENTRY, i, nullptr, 0); + if (len == -1) { + // Explicitly ignore missing entries (mapped to glyph 0 = .notdef). + continue; + } + auto buf = std::make_unique(len); + FT_Get_PS_Font_Value(face, PS_DICT_ENCODING_ENTRY, i, buf.get(), len); + indices[i] = FT_Get_Name_Index(face, buf.get()); + } + return indices; +} + static const char * PyFT2Font_postscript_name(PyFT2Font *self) { @@ -1569,16 +1605,6 @@ PyFT2Font_underline_thickness(PyFT2Font *self) return self->x->get_face()->underline_thickness; } -static py::str -PyFT2Font_fname(PyFT2Font *self) -{ - if (self->stream.close) { // Called passed a filename to the constructor. - return self->py_file.attr("name"); - } else { - return py::cast(self->py_file); - } -} - static py::object ft2font__getattr__(std::string name) { auto api = py::module_::import("matplotlib._api"); @@ -1761,6 +1787,8 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used()) PyFT2Font_get_sfnt_table__doc__) .def("get_path", &PyFT2Font_get_path, PyFT2Font_get_path__doc__) .def("get_image", &PyFT2Font_get_image, PyFT2Font_get_image__doc__) + .def("_get_type1_encoding_vector", &PyFT2Font__get_type1_encoding_vector, + PyFT2Font__get_type1_encoding_vector__doc__) .def_property_readonly("postscript_name", &PyFT2Font_postscript_name, "PostScript name of the font.")