Skip to content

Commit c1d3c87

Browse files
authored
Merge pull request #31085 from QuLogic/render-glyphs
Refactor RendererAgg.draw_{mathtext,text,tex} to use same base algorithm
2 parents 65fbe95 + 754bdab commit c1d3c87

File tree

2 files changed

+37
-94
lines changed

2 files changed

+37
-94
lines changed

lib/matplotlib/backends/backend_agg.py

Lines changed: 37 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -172,41 +172,41 @@ def draw_path(self, gc, path, transform, rgbFace=None):
172172

173173
raise OverflowError(msg) from None
174174

175-
def draw_mathtext(self, gc, x, y, s, prop, angle):
176-
"""Draw mathtext using :mod:`matplotlib.mathtext`."""
175+
def _draw_text_glyphs_and_boxes(self, gc, x, y, angle, glyphs, boxes):
177176
# y is downwards.
178-
parse = self.mathtext_parser.parse(
179-
s, self.dpi, prop, antialiased=gc.get_antialiased())
180177
cos = math.cos(math.radians(angle))
181178
sin = math.sin(math.radians(angle))
182-
for font, size, _char, glyph_index, dx, dy in parse.glyphs: # dy is upwards.
179+
load_flags = get_hinting_flag()
180+
for font, size, glyph_index, slant, extend, dx, dy in glyphs: # dy is upwards.
183181
font.set_size(size, self.dpi)
184182
hf = font._hinting_factor
185183
font._set_transform(
186-
[[round(0x10000 * cos / hf), round(0x10000 * -sin)],
187-
[round(0x10000 * sin / hf), round(0x10000 * cos)]],
184+
(0x10000 * np.array([[cos, -sin], [sin, cos]])
185+
@ [[extend, extend * slant], [0, 1]]
186+
@ [[1 / hf, 0], [0, 1]]).round().astype(int),
188187
[round(0x40 * (x + dx * cos - dy * sin)),
189188
# FreeType's y is upwards.
190189
round(0x40 * (self.height - y + dx * sin + dy * cos))]
191190
)
192191
bitmap = font._render_glyph(
193-
glyph_index, get_hinting_flag(),
192+
glyph_index, load_flags,
194193
RenderMode.NORMAL if gc.get_antialiased() else RenderMode.MONO)
195-
buffer = np.asarray(bitmap.buffer)
194+
buffer = bitmap.buffer
196195
if not gc.get_antialiased():
197196
buffer *= 0xff
198197
# draw_text_image's y is downwards & the bitmap bottom side.
199198
self._renderer.draw_text_image(
200199
buffer,
201200
bitmap.left, int(self.height) - bitmap.top + buffer.shape[0],
202201
0, gc)
202+
203203
rgba = gc.get_rgb()
204204
if len(rgba) == 3 or gc.get_forced_alpha():
205205
rgba = rgba[:3] + (gc.get_alpha(),)
206206
gc1 = self.new_gc()
207207
gc1.set_linewidth(0)
208208
gc1.set_snap(gc.get_snap())
209-
for dx, dy, w, h in parse.rects: # dy is upwards.
209+
for dx, dy, w, h in boxes: # dy is upwards.
210210
if gc1.get_snap() in [None, True]:
211211
# Prevent thin bars from disappearing by growing symmetrically.
212212
if w < 1:
@@ -224,25 +224,31 @@ def draw_mathtext(self, gc, x, y, s, prop, angle):
224224
rgba)
225225
gc1.restore()
226226

227+
def draw_mathtext(self, gc, x, y, s, prop, angle):
228+
"""Draw mathtext using :mod:`matplotlib.mathtext`."""
229+
parse = self.mathtext_parser.parse(
230+
s, self.dpi, prop, antialiased=gc.get_antialiased())
231+
self._draw_text_glyphs_and_boxes(
232+
gc, x, y, angle,
233+
((font, size, glyph_index, 0, 1, dx, dy)
234+
for font, size, _char, glyph_index, dx, dy in parse.glyphs),
235+
parse.rects)
236+
227237
def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
228238
# docstring inherited
229239
if ismath:
230240
return self.draw_mathtext(gc, x, y, s, prop, angle)
231241
font = self._prepare_font(prop)
232-
font.set_text(s, angle, flags=get_hinting_flag(),
233-
features=mtext.get_fontfeatures() if mtext is not None else None,
234-
language=mtext.get_language() if mtext is not None else None)
235-
for bitmap in font._render_glyphs(
236-
x, self.height - y,
237-
RenderMode.NORMAL if gc.get_antialiased() else RenderMode.MONO,
238-
):
239-
buffer = bitmap.buffer
240-
if not gc.get_antialiased():
241-
buffer *= 0xff
242-
self._renderer.draw_text_image(
243-
buffer,
244-
bitmap.left, int(self.height) - bitmap.top + buffer.shape[0],
245-
0, gc)
242+
items = font._layout(
243+
s, flags=get_hinting_flag(),
244+
features=mtext.get_fontfeatures() if mtext is not None else None,
245+
language=mtext.get_language() if mtext is not None else None)
246+
size = prop.get_size_in_points()
247+
self._draw_text_glyphs_and_boxes(
248+
gc, x, y, angle,
249+
((item.ft_object, size, item.glyph_index, 0, 1, item.x, item.y)
250+
for item in items),
251+
[])
246252

247253
def get_text_width_height_descent(self, s, prop, ismath):
248254
# docstring inherited
@@ -285,66 +291,13 @@ def draw_tex(self, gc, x, y, s, prop, angle, *, mtext=None):
285291
with Dvi(dvifile, self.dpi) as dvi:
286292
page, = dvi
287293

288-
cos = math.cos(math.radians(angle))
289-
sin = math.sin(math.radians(angle))
290-
291-
for text in page.text:
292-
hf = mpl.rcParams["text.hinting_factor"]
293-
# Resolving text.index will implicitly call get_font(), which
294-
# resets the font transform, so it has to be done before explicitly
295-
# setting the font transform below.
296-
index = text.index
297-
font = get_font(text.font_path)
298-
font.set_size(text.font_size, self.dpi)
299-
slant = text.font_effects.get("slant", 0)
300-
extend = text.font_effects.get("extend", 1)
301-
font._set_transform(
302-
(0x10000 * np.array([[cos, -sin], [sin, cos]])
303-
@ [[extend, extend * slant], [0, 1]]
304-
@ [[1 / hf, 0], [0, 1]]).round().astype(int),
305-
[round(0x40 * (x + text.x * cos - text.y * sin)),
306-
# FreeType's y is upwards.
307-
round(0x40 * (self.height - y + text.x * sin + text.y * cos))]
308-
)
309-
bitmap = font._render_glyph(
310-
index, get_hinting_flag(),
311-
RenderMode.NORMAL if gc.get_antialiased() else RenderMode.MONO)
312-
buffer = np.asarray(bitmap.buffer)
313-
if not gc.get_antialiased():
314-
buffer *= 0xff
315-
# draw_text_image's y is downwards & the bitmap bottom side.
316-
self._renderer.draw_text_image(
317-
buffer,
318-
bitmap.left, int(self.height) - bitmap.top + buffer.shape[0],
319-
0, gc)
320-
321-
rgba = gc.get_rgb()
322-
if len(rgba) == 3 or gc.get_forced_alpha():
323-
rgba = rgba[:3] + (gc.get_alpha(),)
324-
gc1 = self.new_gc()
325-
gc1.set_linewidth(0)
326-
gc1.set_snap(gc.get_snap())
327-
for box in page.boxes:
328-
bx = box.x
329-
by = box.y
330-
bw = box.width
331-
bh = box.height
332-
if gc1.get_snap() in [None, True]:
333-
# Prevent thin bars from disappearing by growing symmetrically.
334-
if bw < 1:
335-
bx -= (1 - bw) / 2
336-
bw = 1
337-
if bh < 1:
338-
by -= (1 - bh) / 2
339-
bh = 1
340-
path = Path._create_closed([
341-
(bx, by), (bx + bw, by), (bx + bw, by + bh), (bx, by + bh)])
342-
self._renderer.draw_path(
343-
gc1, path,
344-
mpl.transforms.Affine2D()
345-
.rotate_deg(angle).translate(x, self.height - y),
346-
rgba)
347-
gc1.restore()
294+
self._draw_text_glyphs_and_boxes(
295+
gc, x, y, angle,
296+
((get_font(text.font_path), text.font_size, text.index,
297+
text.font_effects.get('slant', 0), text.font_effects.get('extend', 1),
298+
text.x, text.y)
299+
for text in page.text),
300+
((box.x, box.y, box.width, box.height) for box in page.boxes))
348301

349302
def get_canvas_width_height(self):
350303
# docstring inherited

src/ft2font_wrapper.cpp

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1933,16 +1933,6 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
19331933
FT_CHECK(FT_Render_Glyph, face->glyph, render_mode);
19341934
return PyPositionedBitmap{face->glyph};
19351935
})
1936-
.def("_render_glyphs",
1937-
[](PyFT2Font *self, double x, double y, FT_Render_Mode render_mode) {
1938-
auto origin = FT_Vector{std::lround(x * 64), std::lround(y * 64)};
1939-
auto pbs = std::vector<PyPositionedBitmap>{};
1940-
for (auto &g: self->get_glyphs()) {
1941-
FT_CHECK(FT_Glyph_To_Bitmap, &g, render_mode, &origin, 1);
1942-
pbs.emplace_back(reinterpret_cast<FT_BitmapGlyph>(g));
1943-
}
1944-
return pbs;
1945-
})
19461936
;
19471937

19481938
m.attr("__freetype_version__") = version_string;

0 commit comments

Comments
 (0)