@@ -1409,6 +1409,119 @@ 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+ .. versionadded:: 3.11
1436+
1437+ Parameters
1438+ ----------
1439+ text : str
1440+ The characters for which to find fonts.
1441+ flags : LoadFlags, default: `.LoadFlags.FORCE_AUTOHINT`
1442+ Any bitwise-OR combination of the `.LoadFlags` flags.
1443+ features : tuple[str, ...], optional
1444+ The font feature tags to use for the font.
1445+
1446+ Available font feature tags may be found at
1447+ https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
1448+ language : str, optional
1449+ The language of the text in a format accepted by libraqm, namely `a BCP47
1450+ language code <https://www.w3.org/International/articles/language-tags/>`_.
1451+
1452+ Returns
1453+ -------
1454+ list[LayoutItem]
1455+ )""" ;
1456+
1457+ static auto
1458+ PyFT2Font_layout (PyFT2Font *self, std::u32string text, LoadFlags flags,
1459+ std::optional<std::vector<std::string>> features = std::nullopt ,
1460+ std::variant<FT2Font::LanguageType, std::string> languages_or_str = nullptr )
1461+ {
1462+ const auto hinting_factor = self->get_hinting_factor ();
1463+ const auto load_flags = static_cast <FT_Int32>(flags);
1464+
1465+ FT2Font::LanguageType languages;
1466+ if (auto value = std::get_if<FT2Font::LanguageType>(&languages_or_str)) {
1467+ languages = std::move (*value);
1468+ } else if (auto value = std::get_if<std::string>(&languages_or_str)) {
1469+ languages = std::vector<FT2Font::LanguageRange>{
1470+ FT2Font::LanguageRange{*value, 0 , text.size ()}
1471+ };
1472+ } else {
1473+ // NOTE: this can never happen as pybind11 would have checked the type in the
1474+ // Python wrapper before calling this function, but we need to keep the
1475+ // std::get_if instead of std::get for macOS 10.12 compatibility.
1476+ throw py::type_error (" languages must be str or list of tuple" );
1477+ }
1478+
1479+ std::set<FT_String*> glyph_seen_fonts;
1480+ auto glyphs = self->layout (text, load_flags, features, languages, glyph_seen_fonts);
1481+
1482+ std::set<decltype (raqm_glyph_t ::cluster)> clusters;
1483+ for (auto &glyph : glyphs) {
1484+ clusters.emplace (glyph.cluster );
1485+ }
1486+
1487+ std::vector<LayoutItem> items;
1488+
1489+ double x = 0.0 ;
1490+ double y = 0.0 ;
1491+ std::optional<double > prev_advance = std::nullopt ;
1492+ double prev_x = 0.0 ;
1493+ for (auto &glyph : glyphs) {
1494+ auto ft_object = static_cast <PyFT2Font *>(glyph.ftface ->generic .data );
1495+
1496+ ft_object->load_glyph (glyph.index , load_flags);
1497+
1498+ double prev_kern = 0.0 ;
1499+ if (prev_advance) {
1500+ double actual_advance = (x + glyph.x_offset ) - prev_x;
1501+ prev_kern = actual_advance - *prev_advance;
1502+ }
1503+
1504+ auto next = clusters.upper_bound (glyph.cluster );
1505+ auto end = (next != clusters.end ()) ? *next : text.size ();
1506+ auto substr = text.substr (glyph.cluster , end - glyph.cluster );
1507+
1508+ items.emplace_back (ft_object, substr, glyph.index ,
1509+ (x + glyph.x_offset ) / 64.0 , (y + glyph.y_offset ) / 64.0 ,
1510+ prev_kern / 64.0 );
1511+ prev_x = x + glyph.x_offset ;
1512+ x += glyph.x_advance ;
1513+ y += glyph.y_advance ;
1514+ // Note, linearHoriAdvance is a 16.16 instead of 26.6 fixed-point value.
1515+ prev_advance = ft_object->get_face ()->glyph ->linearHoriAdvance / 1024.0 / hinting_factor;
1516+ }
1517+
1518+ return items;
1519+ }
1520+
1521+ /* *********************************************************************
1522+ * Deprecations
1523+ * */
1524+
14121525static py::object
14131526ft2font__getattr__ (std::string name) {
14141527 auto api = py::module_::import (" matplotlib._api" );
@@ -1543,6 +1656,32 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
15431656 .def_property_readonly (" bbox" , &PyGlyph_get_bbox,
15441657 " The control box of the glyph." );
15451658
1659+ py::class_<LayoutItem>(m, " LayoutItem" , py::is_final ())
1660+ .def (py::init<>([]() -> LayoutItem {
1661+ // LayoutItem is not useful from Python, so mark it as not constructible.
1662+ throw std::runtime_error (" LayoutItem is not constructible" );
1663+ }))
1664+ .def_readonly (" ft_object" , &LayoutItem::ft_object,
1665+ " The FT_Face of the item." )
1666+ .def_readonly (" char" , &LayoutItem::character,
1667+ " The character code for the item." )
1668+ .def_readonly (" glyph_index" , &LayoutItem::glyph_index,
1669+ " The glyph index for the item." )
1670+ .def_readonly (" x" , &LayoutItem::x,
1671+ " The x position of the item." )
1672+ .def_readonly (" y" , &LayoutItem::y,
1673+ " The y position of the item." )
1674+ .def_readonly (" prev_kern" , &LayoutItem::prev_kern,
1675+ " The kerning between this item and the previous one." )
1676+ .def (" __str__" ,
1677+ [](const LayoutItem& item) {
1678+ return
1679+ " LayoutItem(ft_object={}, char={!r}, glyph_index={}, " _s
1680+ " x={}, y={}, prev_kern={})" _s.format (
1681+ PyFT2Font_fname (item.ft_object ), item.character ,
1682+ item.glyph_index , item.x , item.y , item.prev_kern );
1683+ });
1684+
15461685 auto cls = py::class_<PyFT2Font>(m, " FT2Font" , py::is_final (), py::buffer_protocol (),
15471686 PyFT2Font__doc__)
15481687 .def (py::init (&PyFT2Font_init),
@@ -1559,6 +1698,9 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
15591698 PyFT2Font_select_charmap__doc__)
15601699 .def (" get_kerning" , &PyFT2Font_get_kerning, " left" _a, " right" _a, " mode" _a,
15611700 PyFT2Font_get_kerning__doc__)
1701+ .def (" _layout" , &PyFT2Font_layout, " string" _a, " flags" _a, py::kw_only (),
1702+ " features" _a=nullptr , " language" _a=nullptr ,
1703+ PyFT2Font_layout__doc__)
15621704 .def (" set_text" , &PyFT2Font_set_text,
15631705 " string" _a, " angle" _a=0.0 , " flags" _a=LoadFlags::FORCE_AUTOHINT, py::kw_only (),
15641706 " features" _a=nullptr , " language" _a=nullptr ,
0 commit comments