Skip to content

Commit 7d53c12

Browse files
committed
Add a wrapper around layouting
1 parent 062b130 commit 7d53c12

File tree

1 file changed

+127
-0
lines changed

1 file changed

+127
-0
lines changed

src/ft2font_wrapper.cpp

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
14121513
static py::object
14131514
ft2font__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

Comments
 (0)