@@ -1409,6 +1409,107 @@ 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
+ 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
+
1412
1513
static py::object
1413
1514
ft2font__getattr__ (std::string name) {
1414
1515
auto api = py::module_::import (" matplotlib._api" );
@@ -1543,6 +1644,28 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
1543
1644
.def_property_readonly (" bbox" , &PyGlyph_get_bbox,
1544
1645
" The control box of the glyph." );
1545
1646
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
+
1546
1669
auto cls = py::class_<PyFT2Font>(m, " FT2Font" , py::is_final (), py::buffer_protocol (),
1547
1670
PyFT2Font__doc__)
1548
1671
.def (py::init (&PyFT2Font_init),
@@ -1559,6 +1682,10 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
1559
1682
PyFT2Font_select_charmap__doc__)
1560
1683
.def (" get_kerning" , &PyFT2Font_get_kerning, " left" _a, " right" _a, " mode" _a,
1561
1684
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__)
1562
1689
.def (" set_text" , &PyFT2Font_set_text,
1563
1690
" string" _a, " angle" _a=0.0 , " flags" _a=LoadFlags::FORCE_AUTOHINT, py::kw_only (),
1564
1691
" features" _a=nullptr , " language" _a=nullptr ,
0 commit comments