Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 695e9a1

Browse files
authored
Merge pull request #30039 from anntzer/dvirender
Rasterize dvi files without dvipng.
2 parents da4d754 + 7627118 commit 695e9a1

5 files changed

Lines changed: 102 additions & 10 deletions

File tree

lib/matplotlib/backends/backend_agg.py

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from matplotlib import _api, cbook
3232
from matplotlib.backend_bases import (
3333
_Backend, FigureCanvasBase, FigureManagerBase, RendererBase)
34+
from matplotlib.dviread import Dvi
3435
from matplotlib.font_manager import fontManager as _fontManager, get_font
3536
from matplotlib.ft2font import LoadFlags, RenderMode
3637
from matplotlib.mathtext import MathTextParser
@@ -266,19 +267,84 @@ def get_text_width_height_descent(self, s, prop, ismath):
266267
def draw_tex(self, gc, x, y, s, prop, angle, *, mtext=None):
267268
# docstring inherited
268269
# todo, handle props, angle, origins
270+
269271
size = prop.get_size_in_points()
270272

271-
texmanager = self.get_texmanager()
273+
if mpl.rcParams["text.latex.engine"] == "latex+dvipng":
274+
Z = self.get_texmanager().get_grey(s, size, self.dpi)
275+
Z = (Z * 0xff).astype(np.uint8)
276+
w, h, d = self.get_text_width_height_descent(s, prop, ismath="TeX")
277+
xd = d * math.sin(math.radians(angle))
278+
yd = d * math.cos(math.radians(angle))
279+
x = round(x + xd)
280+
y = round(y + yd)
281+
self._renderer.draw_text_image(Z, x, y, angle, gc)
282+
return
283+
284+
dvifile = self.get_texmanager().make_dvi(s, size)
285+
with Dvi(dvifile, self.dpi) as dvi:
286+
page, = dvi
272287

273-
Z = texmanager.get_grey(s, size, self.dpi)
274-
Z = np.array(Z * 255.0, np.uint8)
288+
cos = math.cos(math.radians(angle))
289+
sin = math.sin(math.radians(angle))
275290

276-
w, h, d = self.get_text_width_height_descent(s, prop, ismath="TeX")
277-
xd = d * math.sin(math.radians(angle))
278-
yd = d * math.cos(math.radians(angle))
279-
x = round(x + xd)
280-
y = round(y + yd)
281-
self._renderer.draw_text_image(Z, x, y, angle, gc)
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()
282348

283349
def get_canvas_width_height(self):
284350
# docstring inherited

lib/matplotlib/mpl-data/matplotlibrc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,16 @@
331331
# zapf chancery, charter, serif, sans-serif, helvetica,
332332
# avant garde, courier, monospace, computer modern roman,
333333
# computer modern sans serif, computer modern typewriter
334+
335+
## The TeX engine/format to use. The following values are supported:
336+
## - "latex": The classic TeX engine (the current default). All backends render
337+
## TeX's output by parsing the DVI output into glyphs and boxes and emitting
338+
## those one by one.
339+
## - "latex+dvipng": The same as "latex", with the exception that Agg-based
340+
## backends rely on dvipng to rasterize TeX's output. This value was the
341+
## default up to Matplotlib 3.10.
342+
#text.latex.engine: latex
343+
334344
#text.latex.preamble: # IMPROPER USE OF THIS FEATURE WILL LEAD TO LATEX FAILURES
335345
# AND IS THEREFORE UNSUPPORTED. PLEASE DO NOT ASK FOR HELP
336346
# IF THIS FEATURE DOES NOT DO WHAT YOU EXPECT IT TO.

lib/matplotlib/rcsetup.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,7 @@ def _convert_validator_spec(key, conv):
10731073
# text props
10741074
"text.color": validate_color,
10751075
"text.usetex": validate_bool,
1076+
"text.latex.engine": ["latex", "latex+dvipng"],
10761077
"text.latex.preamble": validate_string,
10771078
"text.hinting": ["default", "no_autohint", "force_autohint",
10781079
"no_hinting", "auto", "native", "either", "none"],
@@ -1831,6 +1832,20 @@ class _Param:
18311832
"monospace, computer modern roman, computer modern sans serif, "
18321833
"computer modern typewriter"
18331834
),
1835+
_Param(
1836+
"text.latex.engine",
1837+
default="latex",
1838+
validator=["latex", "latex+dvipng"],
1839+
description=(
1840+
"The TeX engine/format to use. The following values are supported:\n"
1841+
"- 'latex': The classic TeX engine (the current default). All backends "
1842+
"render TeX's output by parsing the DVI output into glyphs and boxes and "
1843+
"emitting those one by one.\n"
1844+
"- 'latex+dvipng': The same as 'latex', with the exception that Agg-based "
1845+
"backends rely on dvipng to rasterize TeX's output. This value was the "
1846+
"default up to Matplotlib 3.10."
1847+
)
1848+
),
18341849
_Param(
18351850
"text.latex.preamble",
18361851
default="",

lib/matplotlib/typing.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,7 @@
452452
"text.hinting_factor",
453453
"text.kerning_factor",
454454
"text.language",
455+
"text.latex.engine",
455456
"text.latex.preamble",
456457
"text.parse_math",
457458
"text.usetex",

src/ft2font_wrapper.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -971,7 +971,7 @@ const char *PyFT2Font_draw_glyph_to_bitmap__doc__ = R"""(
971971
image : 2d array of uint8
972972
The image buffer on which to draw the glyph.
973973
x, y : int
974-
The pixel location at which to draw the glyph.
974+
The position of the glyph's top left corner.
975975
glyph : Glyph
976976
The glyph to draw.
977977
antialiased : bool, default: True

0 commit comments

Comments
 (0)