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