FIX: Add mtext into RendererBase._draw_as_path() arguments by mervyzr · Pull Request #31910 · matplotlib/matplotlib
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! 😄