@@ -1409,6 +1409,107 @@ PyFT2Font__get_type1_encoding_vector(PyFT2Font *self)
14091409 return indices;
14101410}
14111411
1412+ /* *********************************************************************
1413+ * Layout items
1414+ * */
1415+
1416+ struct LayoutItem {
1417+ PyFT2Font *ft_object;
1418+ std::u32string character;
1419+ int glyph_index;
1420+ double x;
1421+ double y;
1422+ double prev_kern;
1423+
1424+ LayoutItem (PyFT2Font *f, std::u32string c, int i, double x, double y, double k) :
1425+ ft_object (f), character(c), glyph_index(i), x(x), y(y), prev_kern(k) {}
1426+ };
1427+
1428+ const char *PyFT2Font_layout__doc__ = R"""(
1429+ Layout a string and yield information about each used glyph.
1430+
1431+ .. warning::
1432+ This API uses the fallback list and is both private and provisional: do not use
1433+ it directly.
1434+
1435+ Parameters
1436+ ----------
1437+ text : str
1438+ The characters for which to find fonts.
1439+
1440+ Returns
1441+ -------
1442+ list[LayoutItem]
1443+ )""" ;
1444+
1445+ static auto
1446+ PyFT2Font_layout (PyFT2Font *self, std::u32string text, LoadFlags flags,
1447+ std::optional<std::vector<std::string>> features = std::nullopt ,
1448+ std::variant<FT2Font::LanguageType, std::string> languages_or_str = nullptr )
1449+ {
1450+ const auto hinting_factor = self->get_hinting_factor ();
1451+ const auto load_flags = static_cast <FT_Int32>(flags);
1452+
1453+ FT2Font::LanguageType languages;
1454+ if (auto value = std::get_if<FT2Font::LanguageType>(&languages_or_str)) {
1455+ languages = std::move (*value);
1456+ } else if (auto value = std::get_if<std::string>(&languages_or_str)) {
1457+ languages = std::vector<FT2Font::LanguageRange>{
1458+ FT2Font::LanguageRange{*value, 0 , text.size ()}
1459+ };
1460+ } else {
1461+ // NOTE: this can never happen as pybind11 would have checked the type in the
1462+ // Python wrapper before calling this function, but we need to keep the
1463+ // std::get_if instead of std::get for macOS 10.12 compatibility.
1464+ throw py::type_error (" languages must be str or list of tuple" );
1465+ }
1466+
1467+ std::set<FT_String*> glyph_seen_fonts;
1468+ auto glyphs = self->layout (text, load_flags, features, languages, glyph_seen_fonts);
1469+
1470+ std::set<decltype (raqm_glyph_t ::cluster)> clusters;
1471+ for (auto &glyph : glyphs) {
1472+ clusters.emplace (glyph.cluster );
1473+ }
1474+
1475+ std::vector<LayoutItem> items;
1476+
1477+ double x = 0.0 ;
1478+ double y = 0.0 ;
1479+ std::optional<double > prev_advance = std::nullopt ;
1480+ double prev_x = 0.0 ;
1481+ for (auto &glyph : glyphs) {
1482+ auto ft_object = static_cast <PyFT2Font *>(glyph.ftface ->generic .data );
1483+
1484+ ft_object->load_glyph (glyph.index , load_flags);
1485+
1486+ double prev_kern = 0.0 ;
1487+ if (prev_advance) {
1488+ double actual_advance = (x + glyph.x_offset ) - prev_x;
1489+ prev_kern = actual_advance - *prev_advance;
1490+ }
1491+
1492+ auto next = clusters.upper_bound (glyph.cluster );
1493+ auto end = (next != clusters.end ()) ? *next : text.size ();
1494+ auto substr = text.substr (glyph.cluster , end - glyph.cluster );
1495+
1496+ items.emplace_back (ft_object, substr, glyph.index ,
1497+ (x + glyph.x_offset ) / 64.0 , (y + glyph.y_offset ) / 64.0 ,
1498+ prev_kern / 64.0 );
1499+ prev_x = x + glyph.x_offset ;
1500+ x += glyph.x_advance ;
1501+ y += glyph.y_advance ;
1502+ // Note, linearHoriAdvance is a 16.16 instead of 26.6 fixed-point value.
1503+ prev_advance = ft_object->get_face ()->glyph ->linearHoriAdvance / 1024.0 / hinting_factor;
1504+ }
1505+
1506+ return items;
1507+ }
1508+
1509+ /* *********************************************************************
1510+ * Deprecations
1511+ * */
1512+
14121513static py::object
14131514ft2font__getattr__ (std::string name) {
14141515 auto api = py::module_::import (" matplotlib._api" );
@@ -1543,6 +1644,28 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
15431644 .def_property_readonly (" bbox" , &PyGlyph_get_bbox,
15441645 " The control box of the glyph." );
15451646
1647+ py::class_<LayoutItem>(m, " LayoutItem" , py::is_final ())
1648+ .def_readonly (" ft_object" , &LayoutItem::ft_object,
1649+ " The FT_Face of the item." )
1650+ .def_readonly (" char" , &LayoutItem::character,
1651+ " The character code for the item." )
1652+ .def_readonly (" glyph_index" , &LayoutItem::glyph_index,
1653+ " The glyph index for the item." )
1654+ .def_readonly (" x" , &LayoutItem::x,
1655+ " The x position of the item." )
1656+ .def_readonly (" y" , &LayoutItem::y,
1657+ " The y position of the item." )
1658+ .def_readonly (" prev_kern" , &LayoutItem::prev_kern,
1659+ " The kerning between this item and the previous one." )
1660+ .def (" __str__" ,
1661+ [](const LayoutItem& item) {
1662+ return
1663+ " LayoutItem(ft_object={}, char={!r}, glyph_index={}, " _s
1664+ " x={}, y={}, prev_kern={})" _s.format (
1665+ PyFT2Font_fname (item.ft_object ), item.character ,
1666+ item.glyph_index , item.x , item.y , item.prev_kern );
1667+ });
1668+
15461669 auto cls = py::class_<PyFT2Font>(m, " FT2Font" , py::is_final (), py::buffer_protocol (),
15471670 PyFT2Font__doc__)
15481671 .def (py::init (&PyFT2Font_init),
@@ -1559,6 +1682,10 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
15591682 PyFT2Font_select_charmap__doc__)
15601683 .def (" get_kerning" , &PyFT2Font_get_kerning, " left" _a, " right" _a, " mode" _a,
15611684 PyFT2Font_get_kerning__doc__)
1685+ .def (" _layout" , &PyFT2Font_layout, " string" _a, " flags" _a, py::kw_only (),
1686+ " features" _a=nullptr ,
1687+ " language" _a=nullptr ,
1688+ PyFT2Font_layout__doc__)
15621689 .def (" set_text" , &PyFT2Font_set_text,
15631690 " string" _a, " angle" _a=0.0 , " flags" _a=LoadFlags::FORCE_AUTOHINT, py::kw_only (),
15641691 " features" _a=nullptr , " language" _a=nullptr ,
0 commit comments