@@ -1409,6 +1409,119 @@ PyFT2Font__get_type1_encoding_vector(PyFT2Font *self)
1409
1409
return indices;
1410
1410
}
1411
1411
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
+
1412
1525
static py::object
1413
1526
ft2font__getattr__ (std::string name) {
1414
1527
auto api = py::module_::import (" matplotlib._api" );
@@ -1543,6 +1656,32 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
1543
1656
.def_property_readonly (" bbox" , &PyGlyph_get_bbox,
1544
1657
" The control box of the glyph." );
1545
1658
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
+
1546
1685
auto cls = py::class_<PyFT2Font>(m, " FT2Font" , py::is_final (), py::buffer_protocol (),
1547
1686
PyFT2Font__doc__)
1548
1687
.def (py::init (&PyFT2Font_init),
@@ -1559,6 +1698,9 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
1559
1698
PyFT2Font_select_charmap__doc__)
1560
1699
.def (" get_kerning" , &PyFT2Font_get_kerning, " left" _a, " right" _a, " mode" _a,
1561
1700
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__)
1562
1704
.def (" set_text" , &PyFT2Font_set_text,
1563
1705
" string" _a, " angle" _a=0.0 , " flags" _a=LoadFlags::FORCE_AUTOHINT, py::kw_only (),
1564
1706
" features" _a=nullptr , " language" _a=nullptr ,
0 commit comments