Skip to content

Commit 5ca3116

Browse files
committed
ft2font: Add a wrapper around layouting for vector usage
1 parent 062b130 commit 5ca3116

File tree

1 file changed

+138
-0
lines changed

1 file changed

+138
-0
lines changed

src/ft2font_wrapper.cpp

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
14121525
static py::object
14131526
ft2font__getattr__(std::string name) {
14141527
auto api = py::module_::import("matplotlib._api");
@@ -1543,6 +1656,28 @@ 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_readonly("ft_object", &LayoutItem::ft_object,
1661+
"The FT_Face of the item.")
1662+
.def_readonly("char", &LayoutItem::character,
1663+
"The character code for the item.")
1664+
.def_readonly("glyph_index", &LayoutItem::glyph_index,
1665+
"The glyph index for the item.")
1666+
.def_readonly("x", &LayoutItem::x,
1667+
"The x position of the item.")
1668+
.def_readonly("y", &LayoutItem::y,
1669+
"The y position of the item.")
1670+
.def_readonly("prev_kern", &LayoutItem::prev_kern,
1671+
"The kerning between this item and the previous one.")
1672+
.def("__str__",
1673+
[](const LayoutItem& item) {
1674+
return
1675+
"LayoutItem(ft_object={}, char={!r}, glyph_index={}, "_s
1676+
"x={}, y={}, prev_kern={})"_s.format(
1677+
PyFT2Font_fname(item.ft_object), item.character,
1678+
item.glyph_index, item.x, item.y, item.prev_kern);
1679+
});
1680+
15461681
auto cls = py::class_<PyFT2Font>(m, "FT2Font", py::is_final(), py::buffer_protocol(),
15471682
PyFT2Font__doc__)
15481683
.def(py::init(&PyFT2Font_init),
@@ -1559,6 +1694,9 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
15591694
PyFT2Font_select_charmap__doc__)
15601695
.def("get_kerning", &PyFT2Font_get_kerning, "left"_a, "right"_a, "mode"_a,
15611696
PyFT2Font_get_kerning__doc__)
1697+
.def("_layout", &PyFT2Font_layout, "string"_a, "flags"_a, py::kw_only(),
1698+
"features"_a=nullptr, "language"_a=nullptr,
1699+
PyFT2Font_layout__doc__)
15621700
.def("set_text", &PyFT2Font_set_text,
15631701
"string"_a, "angle"_a=0.0, "flags"_a=LoadFlags::FORCE_AUTOHINT, py::kw_only(),
15641702
"features"_a=nullptr, "language"_a=nullptr,

0 commit comments

Comments
 (0)