Skip to content

Commit 76647cb

Browse files
committed
Rework mapping of dvi glyph indices to freetype indices.
In 89a7e19, an API for converting "dvi glyph indices" (as stored in a dvi file) to FreeType-compatible keys (either "indices into the native charmap" or "glyph names") was introduced. It was intended that end users (i.e., backends) would check the type of `text.glyph_name_or_index` ((A) int or (B) str) and load the glyph accordingly ((A) `FT_Set_Charmap(native_cmap); FT_Load_Char(index);` or (B) `FT_Load_Glyph(FT_Get_Name_Index(name));`); however, with the future introduction of {xe,lua}tex support, this kind of type checking becomes inconvenient, because {xe,lua}tex's "dvi glyph indices", which are directly equal to FreeType glyph indices (i.e. they would be loaded with `FT_Load_Glyph(index);`), would normally also be converted to ints. This PR introduces a new API (`_index_dvi_to_freetype`) to perform this mapping, always mapping to FreeType glyph indices (i.e. one can always just call `FT_Load_Glyph` on the result). To do so, in case (A) it loads itself the native charmap (something the end user needed to do by themselves previously) and performs the cmap-to-index conversion (`FT_Get_Char_Index`) previously implicit in `FT_Load_Char`; in case (B) it performs itself the name-to-index conversion (`FT_Get_Name_Index`). When {xe,lua}tex support is introduced in the future, `_index_dvi_to_freetype` will just return the index as is. Note that this API is intentionally kept private for now (even though it is used by textpath) and the old APIs are not deprecated yet; I intend to wait until {xe,lua}tex support is actually merged to do so, to avoid possible future back-and-forth changes on the public APIs. In case (A), this PR also improves on the detection of the native charmap, which was previously detected via heuristics (`_select_native_charmap`), but is now read by directly accessing the Type 1 font "encoding vector".
1 parent 05663f7 commit 76647cb

File tree

3 files changed

+63
-32
lines changed

3 files changed

+63
-32
lines changed

lib/matplotlib/dviread.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
import numpy as np
3232

33-
from matplotlib import _api, cbook
33+
from matplotlib import _api, cbook, font_manager
3434

3535
_log = logging.getLogger(__name__)
3636

@@ -578,7 +578,7 @@ class DviFont:
578578
Size of the font in Adobe points, converted from the slightly
579579
smaller TeX points.
580580
"""
581-
__slots__ = ('texname', 'size', '_scale', '_vf', '_tfm')
581+
__slots__ = ('texname', 'size', '_scale', '_vf', '_tfm', '_encoding')
582582

583583
def __init__(self, scale, tfm, texname, vf):
584584
_api.check_isinstance(bytes, texname=texname)
@@ -587,6 +587,7 @@ def __init__(self, scale, tfm, texname, vf):
587587
self.texname = texname
588588
self._vf = vf
589589
self.size = scale * (72.0 / (72.27 * 2**16))
590+
self._encoding = None
590591

591592
widths = _api.deprecated("3.11")(property(lambda self: [
592593
(1000 * self._tfm.width.get(char, 0)) >> 20
@@ -631,6 +632,35 @@ def _height_depth_of(self, char):
631632
result[-1] = 0
632633
return result
633634

635+
# TODO: Make this public when {xe,lua}tex support is merged; simultaneously
636+
# deprecate Text.glyph_name_or_index.
637+
def _index_dvi_to_freetype(self, idx):
638+
"""Convert dvi glyph indices to FreeType ones."""
639+
# Glyphs indices stored in the dvi file map to FreeType glyph indices
640+
# (i.e., which can be passed to FT_Load_Glyph) in various ways:
641+
# - if pdftex.map specifies an ".enc" file for the font, that file maps
642+
# dvi indices to Adobe glyph names, which can then be converted to
643+
# FreeType glyph indices with FT_Get_Name_Index.
644+
# - if no ".enc" file is specified, then the font must be a Type 1
645+
# font, and dvi indices directly index into the font's CharStrings
646+
# vector.
647+
# - (xetex & luatex, currently unsupported, can also declare "native
648+
# fonts", for which dvi indices are equal to FreeType indices.)
649+
if self._encoding is None:
650+
psfont = PsfontsMap(find_tex_file("pdftex.map"))[self.texname]
651+
if psfont.filename is None:
652+
raise ValueError("No usable font file found for {} ({}); "
653+
"the font may lack a Type-1 version"
654+
.format(psfont.psname.decode("ascii"),
655+
psfont.texname.decode("ascii")))
656+
face = font_manager.get_font(psfont.filename)
657+
if psfont.encoding:
658+
self._encoding = [face.get_name_index(name)
659+
for name in _parse_enc(psfont.encoding)]
660+
else:
661+
self._encoding = face._get_type1_encoding_vector()
662+
return self._encoding[idx]
663+
634664

635665
class Vf(Dvi):
636666
r"""
@@ -1002,8 +1032,7 @@ def _parse_enc(path):
10021032
Returns
10031033
-------
10041034
list
1005-
The nth entry of the list is the PostScript glyph name of the nth
1006-
glyph.
1035+
The nth list item is the PostScript glyph name of the nth glyph.
10071036
"""
10081037
no_comments = re.sub("%.*", "", Path(path).read_text(encoding="ascii"))
10091038
array = re.search(r"(?s)\[(.*)\]", no_comments).group(1)

lib/matplotlib/textpath.py

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -238,17 +238,8 @@ def get_glyphs_tex(self, prop, s, glyph_map=None,
238238
if char_id not in glyph_map:
239239
font.clear()
240240
font.set_size(self.FONT_SCALE, self.DPI)
241-
glyph_name_or_index = text.glyph_name_or_index
242-
if isinstance(glyph_name_or_index, str):
243-
index = font.get_name_index(glyph_name_or_index)
244-
font.load_glyph(index, flags=LoadFlags.TARGET_LIGHT)
245-
elif isinstance(glyph_name_or_index, int):
246-
self._select_native_charmap(font)
247-
font.load_char(
248-
glyph_name_or_index, flags=LoadFlags.TARGET_LIGHT)
249-
else: # Should not occur.
250-
raise TypeError(f"Glyph spec of unexpected type: "
251-
f"{glyph_name_or_index!r}")
241+
idx = text.font._index_dvi_to_freetype(text.glyph)
242+
font.load_glyph(idx, flags=LoadFlags.TARGET_LIGHT)
252243
glyph_map_new[char_id] = font.get_path()
253244

254245
glyph_ids.append(char_id)
@@ -269,23 +260,6 @@ def get_glyphs_tex(self, prop, s, glyph_map=None,
269260
return (list(zip(glyph_ids, xpositions, ypositions, sizes)),
270261
glyph_map_new, myrects)
271262

272-
@staticmethod
273-
def _select_native_charmap(font):
274-
# Select the native charmap. (we can't directly identify it but it's
275-
# typically an Adobe charmap).
276-
for charmap_code in [
277-
1094992451, # ADOBE_CUSTOM.
278-
1094995778, # ADOBE_STANDARD.
279-
]:
280-
try:
281-
font.select_charmap(charmap_code)
282-
except (ValueError, RuntimeError):
283-
pass
284-
else:
285-
break
286-
else:
287-
_log.warning("No supported encoding in font (%s).", font.fname)
288-
289263

290264
text_to_path = TextToPath()
291265

src/ft2font_wrapper.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1431,6 +1431,32 @@ PyFT2Font_get_image(PyFT2Font *self)
14311431
return py::array_t<unsigned char>(dims, im.get_buffer());
14321432
}
14331433

1434+
const char *PyFT2Font__get_type1_encoding_vector__doc__ = R"""(
1435+
Return a list mapping CharString indices of a Type 1 font to FreeType glyph indices.
1436+
1437+
Returns
1438+
-------
1439+
list[int]
1440+
)""";
1441+
1442+
static std::array<FT_UInt, 256>
1443+
PyFT2Font__get_type1_encoding_vector(PyFT2Font *self)
1444+
{
1445+
auto face = self->x->get_face();
1446+
auto indices = std::array<FT_UInt, 256>{};
1447+
for (auto i = 0; i < indices.size(); ++i) {
1448+
auto len = FT_Get_PS_Font_Value(face, PS_DICT_ENCODING_ENTRY, i, nullptr, 0);
1449+
if (len == -1) {
1450+
throw std::runtime_error{
1451+
"FT_Get_PS_Font_Value tried to access a non-existent value"};
1452+
}
1453+
auto buf = std::unique_ptr<char[]>{new char[len]};
1454+
FT_Get_PS_Font_Value(face, PS_DICT_ENCODING_ENTRY, i, buf.get(), len);
1455+
indices[i] = FT_Get_Name_Index(face, buf.get());
1456+
}
1457+
return indices;
1458+
}
1459+
14341460
static const char *
14351461
PyFT2Font_postscript_name(PyFT2Font *self)
14361462
{
@@ -1761,6 +1787,8 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
17611787
PyFT2Font_get_sfnt_table__doc__)
17621788
.def("get_path", &PyFT2Font_get_path, PyFT2Font_get_path__doc__)
17631789
.def("get_image", &PyFT2Font_get_image, PyFT2Font_get_image__doc__)
1790+
.def("_get_type1_encoding_vector", &PyFT2Font__get_type1_encoding_vector,
1791+
PyFT2Font__get_type1_encoding_vector__doc__)
17641792

17651793
.def_property_readonly("postscript_name", &PyFT2Font_postscript_name,
17661794
"PostScript name of the font.")

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy