Skip to content
This repository was archived by the owner on Jul 28, 2025. It is now read-only.

Commit 2e93101

Browse files
Use path rendering for drawing math text
1 parent bf8f1f1 commit 2e93101

File tree

1 file changed

+100
-11
lines changed

1 file changed

+100
-11
lines changed

matplotlib_pyodide/html5_canvas_backend.py

Lines changed: 100 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -292,25 +292,113 @@ def _math_to_rgba(self, s, prop, rgb):
292292
rgba = plt.imread(buf)
293293
return rgba, depth
294294

295-
def _draw_math_text(self, gc, x, y, s, prop, angle):
296-
# Get color from graphics context
297-
rgb = gc.get_rgb()
295+
def _draw_math_text_path(self, gc, x, y, s, prop, angle):
296+
"""Draw mathematical text using paths directly on the canvas.
298297
299-
# Get RGBA array using the new method
300-
rgba, depth = self._math_to_rgba(s, prop, rgb)
298+
This method renders math text by drawing the actual glyph paths
299+
onto the canvas, rather than creating a temporary image.
301300
302-
angle = math.radians(angle)
301+
Parameters
302+
----------
303+
gc : GraphicsContextHTMLCanvas
304+
The graphics context to use for drawing
305+
x, y : float
306+
The position of the text baseline in pixels
307+
s : str
308+
The text string to render
309+
prop : FontProperties
310+
The font properties to use for rendering
311+
angle : float
312+
The rotation angle in degrees
313+
"""
314+
# Parse the math text to get paths and metrics
315+
width, height, depth, glyphs, rects = self.mathtext_parser.parse(
316+
s, dpi=self.dpi, prop=prop
317+
)
318+
319+
# Save the canvas state
320+
self.ctx.save()
321+
322+
# Move to text position and apply rotation if needed
323+
self.ctx.translate(x, self.height - y)
303324
if angle != 0:
325+
self.ctx.rotate(-math.radians(angle))
326+
327+
# Set up text rendering style
328+
self.ctx.fillStyle = self._matplotlib_color_to_CSS(
329+
gc.get_rgb(), gc.get_alpha(), gc.get_forced_alpha()
330+
)
331+
332+
# Draw each glyph in the mathematical expression
333+
for font, fontsize, _, ox, oy in glyphs:
334+
# Move to glyph position
304335
self.ctx.save()
305-
self.ctx.translate(x, y)
306-
self.ctx.rotate(-angle)
307-
self.ctx.translate(-x, -y)
336+
self.ctx.translate(ox, -oy)
308337

309-
self.draw_image(gc, x, -y - depth, np.flipud(rgba))
338+
# Get the glyph's path data
339+
font.set_size(fontsize, self.dpi)
340+
verts, codes = font.get_path()
341+
342+
verts = verts * fontsize / font.units_per_EM
343+
344+
# Convert the glyph to a Path object
345+
path = Path(verts, codes)
346+
347+
# Draw the path
348+
transform = Affine2D().scale(1.0, -1.0)
349+
self._path_helper(self.ctx, path, transform)
350+
self.ctx.fill()
310351

311-
if angle != 0:
312352
self.ctx.restore()
313353

354+
# Draw rectangles (fraction bars, roots, etc.)
355+
for x1, y1, x2, y2 in rects:
356+
self.ctx.fillRect(x1, -y2, x2 - x1, y2 - y1)
357+
358+
# Restore the canvas state
359+
self.ctx.restore()
360+
361+
def _draw_math_text(self, gc, x, y, s, prop, angle):
362+
"""Draw mathematical text using the most appropriate method.
363+
364+
This method tries direct path rendering first, and falls back to
365+
the image-based approach if needed.
366+
367+
Parameters
368+
----------
369+
gc : GraphicsContextHTMLCanvas
370+
The graphics context to use for drawing
371+
x, y : float
372+
The position of the text baseline in pixels
373+
s : str
374+
The text string to render
375+
prop : FontProperties
376+
The font properties to use for rendering
377+
angle : float
378+
The rotation angle in degrees
379+
"""
380+
try:
381+
# Try rendering directly with paths first
382+
self._draw_math_text_path(gc, x, y, s, prop, angle)
383+
except Exception as e:
384+
# If path rendering fails, fall back to image-based approach
385+
print(f"Path rendering failed, falling back to image: {str(e)}")
386+
387+
# Get RGBA array using the existing image-based method
388+
rgba, depth = self._math_to_rgba(s, prop, gc.get_rgb())
389+
390+
angle = math.radians(angle)
391+
if angle != 0:
392+
self.ctx.save()
393+
self.ctx.translate(x, y)
394+
self.ctx.rotate(-angle)
395+
self.ctx.translate(-x, -y)
396+
397+
self.draw_image(gc, x, -y - depth, np.flipud(rgba))
398+
399+
if angle != 0:
400+
self.ctx.restore()
401+
314402
def _set_style(self, gc, rgbFace=None):
315403
if rgbFace is not None:
316404
self.ctx.fillStyle = self._matplotlib_color_to_CSS(
@@ -404,6 +492,7 @@ def _get_font(self, prop):
404492
def get_text_width_height_descent(self, s, prop, ismath):
405493
w: float
406494
h: float
495+
d: float
407496
if ismath:
408497
# Use the path parser to get exact metrics
409498
width, height, depth, _, _ = self.mathtext_parser.parse(

0 commit comments

Comments
 (0)