Skip to content

FIX: Add mtext into RendererBase._draw_as_path() arguments - #31910

Merged
timhoffm merged 2 commits into
matplotlib:mainfrom
mervyzr:patch-1
Jun 27, 2026
Merged

FIX: Add mtext into RendererBase._draw_as_path() arguments#31910
timhoffm merged 2 commits into
matplotlib:mainfrom
mervyzr:patch-1

Conversation

@mervyzr

@mervyzr mervyzr commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

PR summary

Why is this change necessary?

User might be unable to use LaTeX formatting for plots.

What problem does it solve?

Fixes the TypeError with missing positional argument for RendererBase._draw_text_as_path() by adding mtext=mtext into the arguments.

What is the reasoning for this implementation?

The function that calls RendererBase._draw_text_as_path() already includes mtext=None as a default argument. Therefore, this mtext is just passed into RendererBase._draw_text_as_path() too.

AI Disclosure

All fixes and code were done manually.

PR checklist

@github-actions

Copy link
Copy Markdown

Thank you for opening your first PR into Matplotlib!

If you have not heard from us in a week or so, please leave a new comment below and that should bring it to our attention. Most of our reviewers are volunteers and sometimes things fall through the cracks. We also ask that you please finish addressing any review comments on this PR and wait for it to be merged (or closed) before opening a new one, as it can be a valuable learning experience to go through the review process.

You can also join us on discourse chat for real-time discussion.

For details on testing, writing docs, and our review process, please see the developer guide.
Please let us know if (and how) you use AI, it will help us give you better feedback on your PR.

We strive to be a welcoming and open project. Please follow our Code of Conduct.

@story645

Copy link
Copy Markdown
Member

Thanks! Can you please add a test to ensure this works as expected?

@mervyzr

mervyzr commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Ah yes, sorry! I have included a test below for this:

>>> import matplotlib as mpl
>>> import matplotlib.pyplot as plt
>>> mpl.rcParams['text.usetex'] = True
>>> plt.rcParams['text.latex.preamble'] = r"\usepackage{lmodern}"
>>> x = list(range(5))
>>> plt.plot(x,x)
>>> plt.savefig('test.png', backend='cairo')

Based on the information above, it seems that the error occurs only when the backend='cairo' is called. I was not able to get the error if the backend was not explicitly defined.

Thank you!

@story645

Copy link
Copy Markdown
Member

it seems that the error occurs only when the backend='cairo'

Any idea why?

And can you add a test to test_text.py, something like:

@image_comparison(['draw_text_fallback.png'], style='mpl20')
def test_draw_text_as_path_fallback(monkeypatch):
# Delete RendererAgg.draw_text so that we use the RendererBase.draw_text fallback.
monkeypatch.delattr('matplotlib.backends.backend_agg.RendererAgg.draw_text')
heights = [2, 1.5, 3]
fig = plt.figure(figsize=(6, sum(heights)))
subfig = fig.subfigures(3, 1, height_ratios=heights)
_test_complex_shaping(subfig[0])
_test_text_features(subfig[1])
_test_text_language(subfig[2])

@mervyzr

mervyzr commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

I think the error occurs mainly because the mtext argument in draw_tex(...) was not passed into the next function self._draw_text_as_path(...) in the RendererBase class:

def draw_tex(self, gc, x, y, s, prop, angle, *, mtext=None):
"""
Draw a TeX instance.
Parameters
----------
gc : `.GraphicsContextBase`
The graphics context.
x : float
The x location of the text in display coords.
y : float
The y location of the text baseline in display coords.
s : str
The TeX text string.
prop : `~matplotlib.font_manager.FontProperties`
The font properties.
angle : float
The rotation angle in degrees anti-clockwise.
mtext : `~matplotlib.text.Text`
The original text object to be rendered.
"""
self._draw_text_as_path(gc, x, y, s, prop, angle, ismath="TeX")

More specifically, with cairo the figure tries to draw the image with self.figure.draw(self._renderer) in backend_cairo.py:

def _get_printed_image_surface(self):
self._renderer.dpi = self.figure.dpi
width, height = self.get_width_height()
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
self._renderer.set_context(cairo.Context(surface))
self.figure.draw(self._renderer)
return surface

This then calls the draw() function in text.py, where draw_tex(...) is used in line 905:

def draw(self, renderer):
# docstring inherited
if renderer is not None:
self._renderer = renderer
if not self.get_visible():
return
if self.get_text() == '':
return
renderer.open_group('text', self.get_gid())
bbox, info, _ = self._get_layout(renderer)
trans = self.get_transform()
# don't use self.get_position here, which refers to text
# position in Text:
x, y = self._x, self._y
if np.ma.is_masked(x):
x = np.nan
if np.ma.is_masked(y):
y = np.nan
posx = float(self.convert_xunits(x))
posy = float(self.convert_yunits(y))
posx, posy = trans.transform((posx, posy))
if np.isnan(posx) or np.isnan(posy):
return # don't throw a warning here
if not np.isfinite(posx) or not np.isfinite(posy):
_log.warning("posx and posy should be finite values")
return
canvasw, canvash = renderer.get_canvas_width_height()
# Update the location and size of the bbox
# (`.patches.FancyBboxPatch`), and draw it.
if self._bbox_patch:
self.update_bbox_position_size(renderer)
self._bbox_patch.draw(renderer)
gc = renderer.new_gc()
gc.set_foreground(mcolors.to_rgba(self.get_color()), isRGBA=True)
gc.set_alpha(self.get_alpha())
gc.set_url(self._url)
gc.set_antialiased(self._antialiased)
gc.set_snap(self.get_snap())
self._set_gc_clip(gc)
angle = self.get_rotation()
for line, wad, (x, y) in info:
mtext = self if len(info) == 1 else None
x = x + posx
y = y + posy
if renderer.flipy():
y = canvash - y
clean_line, ismath = self._preprocess_math(line)
if self.get_path_effects():
from matplotlib.patheffects import PathEffectRenderer
textrenderer = PathEffectRenderer(self.get_path_effects(), renderer)
else:
textrenderer = renderer
if self.get_usetex():
textrenderer.draw_tex(gc, x, y, clean_line,
self._fontproperties, angle,
mtext=mtext)
else:
textrenderer.draw_text(gc, x, y, clean_line,
self._fontproperties, angle,
ismath=ismath, mtext=mtext)
gc.restore()
renderer.close_group('text')
self.stale = False

The TypeError then occurs after that since mtext was not passed into the next function.


I followed the traceback messages a little bit and tried to identity the root cause. I checked two other backend files backend_agg.py

def draw(self):
# docstring inherited
self.renderer = self.get_renderer()
self.renderer.clear()
# Acquire a lock on the shared font cache.
with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
else nullcontext()):
self.figure.draw(self.renderer)
# A GUI class may be need to update a window using this draw, so
# don't forget to call the superclass.
super().draw()

and backend_pdf.py for comparison.
def print_pdf(self, filename, *,
bbox_inches_restore=None, metadata=None):
dpi = self.figure.dpi
self.figure.dpi = 72 # there are 72 pdf points to an inch
width, height = self.figure.get_size_inches()
if isinstance(filename, PdfPages):
file = filename._ensure_file()
else:
file = PdfFile(filename, metadata=metadata)
try:
file.newPage(width, height)
renderer = MixedModeRenderer(
self.figure, width, height, dpi,
RendererPdf(file, dpi, height, width),
bbox_inches_restore=bbox_inches_restore)
self.figure.draw(renderer)
renderer.finalize()
if not isinstance(filename, PdfPages):
file.finalize()
finally:
if isinstance(filename, PdfPages): # finish off this page
file.endStream()
else: # we opened the file above; now finish it off
file.close()
def draw(self):
self.figure.draw_without_rendering()
return super().draw()

There, they both have similar calls with self.figure.draw(renderer), but there were no errors with these backends.
I noticed that only the RendererAgg and RendererCairo classes inherit from RendererBase; a different parent is used for RendererPdf.

This is where I noticed that RendererAgg has its own draw_tex(...) method but RendererCairo does not. Furthermore, in RendererAgg, the mtext argument was not used at all, but in RendererBase (and thus RendererCairo), this argument was needed. Which was why only backend=cairo would throw an error, while backend=pdf and backend=agg were fine.

I suspect every backend that has RendererBase as a parent in its renderer class but without its own draw_tex(...) method would thus throw the same error when using LaTeX.


This is my first ever pull request to such a massive repo, so I am still figuring out how and where to write the tests for this according to the docs. Thank you for your patience and understanding! 😄

@QuLogic QuLogic added this to the v3.11.1 milestone Jun 18, 2026
@QuLogic QuLogic linked an issue Jun 19, 2026 that may be closed by this pull request
mervyzr and others added 2 commits June 26, 2026 16:01
# PR Summary
---

Missing 1 positional argument 'mtext' in `RendererBase._draw_text_as_path()` (line 509 of `backend_bases.py`).

Small fix by including `mtext=mtext` into `RendererBase._draw_text_as_path()`.

This error happens when the user is trying to use LaTeX in matplotlib with matplotlib==3.11.0.

# AI Disclosure
---

All errors and fixes were done manually.
@QuLogic

QuLogic commented Jun 26, 2026

Copy link
Copy Markdown
Member

Thanks for your work on this. Writing a test here might be a bit difficult for a newcomer, since it's not in the standard rendering path. I've gone ahead and written one here, as well as rebased, so that we can get 3.11.1 out soonish.

@timhoffm
timhoffm merged commit 996f4e8 into matplotlib:main Jun 27, 2026
39 of 41 checks passed
rcomer pushed a commit that referenced this pull request Jun 27, 2026
rcomer added a commit that referenced this pull request Jun 27, 2026
…910-on-v3.11.x

Backport PR #31910 on branch v3.11.x (FIX: Add mtext into RendererBase._draw_as_path() arguments)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: path_effects on text gives an error in matplotlib 3.11

5 participants