Skip to content

Commit effb8e0

Browse files
committed
Use libraqm for text in vector outputs
1 parent 73441fc commit effb8e0

File tree

5 files changed

+117
-36
lines changed

5 files changed

+117
-36
lines changed

lib/matplotlib/_text_helpers.py

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,8 @@
44

55
from __future__ import annotations
66

7-
import dataclasses
8-
97
from . import _api
10-
from .ft2font import FT2Font, Kerning, LoadFlags
11-
12-
13-
@dataclasses.dataclass(frozen=True)
14-
class LayoutItem:
15-
ft_object: FT2Font
16-
char: str
17-
glyph_idx: int
18-
x: float
19-
prev_kern: float
8+
from .ft2font import Kerning, LoadFlags
209

2110

2211
def warn_on_missing_glyph(codepoint, fontnames):
@@ -46,20 +35,6 @@ def layout(string, font, *, kern_mode=Kerning.DEFAULT):
4635
------
4736
LayoutItem
4837
"""
49-
x = 0
50-
prev_glyph_idx = None
51-
char_to_font = font._get_fontmap(string)
52-
base_font = font
53-
for char in string:
54-
# This has done the fallback logic
55-
font = char_to_font.get(char, base_font)
56-
glyph_idx = font.get_char_index(ord(char))
57-
kern = (
58-
base_font.get_kerning(prev_glyph_idx, glyph_idx, kern_mode) / 64
59-
if prev_glyph_idx is not None else 0.
60-
)
61-
x += kern
62-
glyph = font.load_glyph(glyph_idx, flags=LoadFlags.NO_HINTING)
63-
yield LayoutItem(font, char, glyph_idx, x, kern)
64-
x += glyph.linearHoriAdvance / 65536
65-
prev_glyph_idx = glyph_idx
38+
for raqm_item in font._layout(string, LoadFlags.NO_HINTING):
39+
raqm_item.ft_object.load_glyph(raqm_item.glyph_idx, flags=LoadFlags.NO_HINTING)
40+
yield raqm_item

lib/matplotlib/backends/backend_ps.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -796,19 +796,18 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
796796

797797
else:
798798
font = self._get_font_ttf(prop)
799-
self._character_tracker.track(font, s)
800799
for item in _text_helpers.layout(s, font):
800+
self._character_tracker.track_glyph(item.font, item.glyph_idx)
801801
ps_name = (item.ft_object.postscript_name
802802
.encode("ascii", "replace").decode("ascii"))
803803
glyph_name = item.ft_object.get_glyph_name(item.glyph_idx)
804-
stream.append((ps_name, item.x, glyph_name))
804+
stream.append((ps_name, item.x, item.y, glyph_name))
805805
self.set_color(*gc.get_rgb())
806806

807-
for ps_name, group in itertools. \
808-
groupby(stream, lambda entry: entry[0]):
807+
for ps_name, group in itertools.groupby(stream, lambda entry: entry[0]):
809808
self.set_font(ps_name, prop.get_size_in_points(), False)
810-
thetext = "\n".join(f"{x:g} 0 m /{name:s} glyphshow"
811-
for _, x, name in group)
809+
thetext = "\n".join(f"{x:g} {y:g} m /{name:s} glyphshow"
810+
for _, x, y, name in group)
812811
self._pswriter.write(f"""\
813812
gsave
814813
{self._get_clip_cmd(gc)}

lib/matplotlib/textpath.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,15 +144,16 @@ def get_glyphs_with_font(self, font, s, glyph_map=None,
144144
glyph_map_new = glyph_map
145145

146146
xpositions = []
147+
ypositions = []
147148
glyph_ids = []
148149
for item in _text_helpers.layout(s, font):
149150
char_id = self._get_char_id(item.ft_object, ord(item.char))
150151
glyph_ids.append(char_id)
151152
xpositions.append(item.x)
153+
ypositions.append(item.y)
152154
if char_id not in glyph_map:
153155
glyph_map_new[char_id] = item.ft_object.get_path()
154156

155-
ypositions = [0] * len(xpositions)
156157
sizes = [1.] * len(xpositions)
157158

158159
rects = []

src/ft2font.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,16 @@ class FT2Font
143143
return FT_HAS_KERNING(face);
144144
}
145145

146+
void set_parent(void *parent)
147+
{
148+
_parent = parent;
149+
}
150+
151+
void *get_parent() const
152+
{
153+
return _parent;
154+
}
155+
146156
private:
147157
WarnFunc ft_glyph_warn;
148158
bool warn_if_used;
@@ -158,6 +168,9 @@ class FT2Font
158168
long hinting_factor;
159169
int kerning_factor;
160170

171+
// Holds the parent PyFT2Font object.
172+
void *_parent;
173+
161174
// prevent copying
162175
FT2Font(const FT2Font &);
163176
FT2Font &operator=(const FT2Font &);

src/ft2font_wrapper.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,7 @@ PyFT2Font_init(py::object filename, long hinting_factor = 8,
499499

500500
self->x = new FT2Font(open_args, hinting_factor, fallback_fonts, ft_glyph_warn,
501501
warn_if_used);
502+
self->x->set_parent(self);
502503

503504
self->x->set_kerning_factor(kerning_factor);
504505

@@ -1471,6 +1472,81 @@ PyFT2Font__get_type1_encoding_vector(PyFT2Font *self)
14711472
return indices;
14721473
}
14731474

1475+
/**********************************************************************
1476+
* Layout items
1477+
* */
1478+
1479+
struct LayoutItem {
1480+
PyFT2Font *ft_object;
1481+
std::u32string character;
1482+
int glyph_idx;
1483+
double x;
1484+
double y;
1485+
double prev_kern;
1486+
1487+
LayoutItem(PyFT2Font *f, std::u32string c, int i, double x, double y, double k) :
1488+
ft_object(f), character(c), glyph_idx(i), x(x), y(y), prev_kern(k) {}
1489+
1490+
std::string to_string()
1491+
{
1492+
std::ostringstream out;
1493+
out << "LayoutItem(ft_object=" << PyFT2Font_fname(ft_object);
1494+
out << ", char=" << character[0];
1495+
out << ", glyph_idx=" << glyph_idx;
1496+
out << ", x=" << x;
1497+
out << ", y=" << y;
1498+
out << ", prev_kern=" << prev_kern;
1499+
out << ")";
1500+
return out.str();
1501+
}
1502+
};
1503+
1504+
const char *PyFT2Font_layout__doc__ = R"""(
1505+
Layout a string and yield information about each used glyph.
1506+
1507+
.. warning::
1508+
This API uses the fallback list and is both private and provisional: do not use
1509+
it directly.
1510+
1511+
Parameters
1512+
----------
1513+
text : str
1514+
The characters for which to find fonts.
1515+
1516+
Returns
1517+
-------
1518+
list[LayoutItem]
1519+
)""";
1520+
1521+
static auto
1522+
PyFT2Font_layout(PyFT2Font *self, std::u32string text, LoadFlags flags)
1523+
{
1524+
auto load_flags = static_cast<FT_Int32>(flags);
1525+
1526+
std::set<FT_String*> glyph_seen_fonts;
1527+
std::vector<raqm_glyph_t> glyphs;
1528+
self->x->layout(text, load_flags, glyph_seen_fonts, glyphs);
1529+
1530+
std::vector<LayoutItem> items;
1531+
1532+
double x = 0.0;
1533+
double y = 0.0;
1534+
for (auto &glyph : glyphs) {
1535+
auto ft_object = static_cast<FT2Font *>(glyph.ftface->generic.data);
1536+
auto pyft_object = static_cast<PyFT2Font *>(ft_object->get_parent());
1537+
items.emplace_back(pyft_object, text.substr(glyph.cluster, 1), glyph.index,
1538+
(x + glyph.x_offset) / 64, (y + glyph.y_offset) / 64, 0);
1539+
x += glyph.x_advance;
1540+
y += glyph.y_advance;
1541+
}
1542+
1543+
return items;
1544+
}
1545+
1546+
/**********************************************************************
1547+
* Deprecations
1548+
* */
1549+
14741550
static py::object
14751551
ft2font__getattr__(std::string name) {
14761552
auto api = py::module_::import("matplotlib._api");
@@ -1601,6 +1677,21 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
16011677
.def_property_readonly("bbox", &PyGlyph_get_bbox,
16021678
"The control box of the glyph.");
16031679

1680+
py::class_<LayoutItem>(m, "LayoutItem", py::is_final())
1681+
.def_readonly("ft_object", &LayoutItem::ft_object,
1682+
"The FT_Face of the item.")
1683+
.def_readonly("char", &LayoutItem::character,
1684+
"The character code for the item.")
1685+
.def_readonly("glyph_idx", &LayoutItem::glyph_idx,
1686+
"The glyph index for the item.")
1687+
.def_readonly("x", &LayoutItem::x,
1688+
"The x position of the item.")
1689+
.def_readonly("y", &LayoutItem::y,
1690+
"The y position of the item.")
1691+
.def_readonly("prev_kern", &LayoutItem::prev_kern,
1692+
"The kerning between this item and the previous one.")
1693+
.def("__str__", &LayoutItem::to_string);
1694+
16041695
py::class_<PyFT2Font>(m, "FT2Font", py::is_final(), py::buffer_protocol(),
16051696
PyFT2Font__doc__)
16061697
.def(py::init(&PyFT2Font_init),
@@ -1617,6 +1708,8 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
16171708
PyFT2Font_select_charmap__doc__)
16181709
.def("get_kerning", &PyFT2Font_get_kerning, "left"_a, "right"_a, "mode"_a,
16191710
PyFT2Font_get_kerning__doc__)
1711+
.def("_layout", &PyFT2Font_layout, "string"_a, "flags"_a,
1712+
PyFT2Font_layout__doc__)
16201713
.def("set_text", &PyFT2Font_set_text,
16211714
"string"_a, "angle"_a=0.0, "flags"_a=LoadFlags::FORCE_AUTOHINT,
16221715
PyFT2Font_set_text__doc__)

0 commit comments

Comments
 (0)