|
3 | 3 | import math
|
4 | 4 | from functools import lru_cache
|
5 | 5 |
|
| 6 | +import matplotlib.pyplot as plt |
6 | 7 | import numpy as np
|
7 |
| -from matplotlib import __version__, interactive |
| 8 | +from matplotlib import __version__, figure, interactive |
8 | 9 | from matplotlib.backend_bases import (
|
9 | 10 | FigureManagerBase,
|
10 | 11 | GraphicsContextBase,
|
11 | 12 | RendererBase,
|
12 | 13 | _Backend,
|
13 | 14 | )
|
| 15 | +from matplotlib.backends import backend_agg |
14 | 16 | from matplotlib.colors import colorConverter, rgb2hex
|
15 | 17 | from matplotlib.font_manager import findfont
|
16 | 18 | from matplotlib.ft2font import LOAD_NO_HINTING, FT2Font
|
@@ -204,7 +206,11 @@ def __init__(self, ctx, width, height, dpi, fig):
|
204 | 206 | self.ctx.width = self.width
|
205 | 207 | self.ctx.height = self.height
|
206 | 208 | self.dpi = dpi
|
207 |
| - self.mathtext_parser = MathTextParser("bitmap") |
| 209 | + |
| 210 | + # Create path-based math text parser; as the bitmap parser |
| 211 | + # was deprecated in 3.4 and removed after 3.5 |
| 212 | + self.mathtext_parser = MathTextParser("path") |
| 213 | + |
208 | 214 | self._get_font_helper = lru_cache(maxsize=50)(self._get_font_helper)
|
209 | 215 |
|
210 | 216 | # Keep the state of fontfaces that are loading
|
@@ -240,6 +246,51 @@ def _matplotlib_color_to_CSS(self, color, alpha, alpha_overrides, is_RGB=True):
|
240 | 246 |
|
241 | 247 | return CSS_color
|
242 | 248 |
|
| 249 | + def _math_to_rgba(self, s, prop, rgb): |
| 250 | + """Convert math text to an RGBA array using path parser and figure""" |
| 251 | + from io import BytesIO |
| 252 | + |
| 253 | + # Get the text dimensions |
| 254 | + width, height, depth, _, _ = self.mathtext_parser.parse(s, dpi=72, prop=prop) |
| 255 | + |
| 256 | + # Create a figure of the right size |
| 257 | + fig = figure.Figure(figsize=(width / 72, height / 72)) |
| 258 | + |
| 259 | + # Add text to the figure |
| 260 | + # Note: depth/height gives us the baseline position |
| 261 | + fig.text(0, depth / height, s, fontproperties=prop, color=rgb) |
| 262 | + |
| 263 | + # Set up the Agg backend |
| 264 | + backend_agg.FigureCanvasAgg(fig) |
| 265 | + |
| 266 | + # Render to PNG |
| 267 | + buf = BytesIO() |
| 268 | + fig.savefig(buf, dpi=self.dpi, format="png", transparent=True) |
| 269 | + buf.seek(0) |
| 270 | + |
| 271 | + # Read back the image |
| 272 | + rgba = plt.imread(buf) |
| 273 | + return rgba, depth |
| 274 | + |
| 275 | + def _draw_math_text(self, gc, x, y, s, prop, angle): |
| 276 | + # Get color from graphics context |
| 277 | + rgb = gc.get_rgb() |
| 278 | + |
| 279 | + # Get RGBA array using the new method |
| 280 | + rgba, depth = self._math_to_rgba(s, prop, rgb) |
| 281 | + |
| 282 | + angle = math.radians(angle) |
| 283 | + if angle != 0: |
| 284 | + self.ctx.save() |
| 285 | + self.ctx.translate(x, y) |
| 286 | + self.ctx.rotate(-angle) |
| 287 | + self.ctx.translate(-x, -y) |
| 288 | + |
| 289 | + self.draw_image(gc, x, -y - depth, np.flipud(rgba)) |
| 290 | + |
| 291 | + if angle != 0: |
| 292 | + self.ctx.restore() |
| 293 | + |
243 | 294 | def _set_style(self, gc, rgbFace=None):
|
244 | 295 | if rgbFace is not None:
|
245 | 296 | self.ctx.fillStyle = self._matplotlib_color_to_CSS(
|
@@ -330,41 +381,19 @@ def get_text_width_height_descent(self, s, prop, ismath):
|
330 | 381 | w: float
|
331 | 382 | h: float
|
332 | 383 | if ismath:
|
333 |
| - image, d = self.mathtext_parser.parse(s, self.dpi, prop) |
334 |
| - image_arr = np.asarray(image) |
335 |
| - h, w = image_arr.shape |
| 384 | + # Use the path parser to get exact metrics |
| 385 | + width, height, depth, _, _ = self.mathtext_parser.parse( |
| 386 | + s, dpi=72, prop=prop |
| 387 | + ) |
| 388 | + return width, height, depth |
336 | 389 | else:
|
337 | 390 | font, _ = self._get_font(prop)
|
338 | 391 | font.set_text(s, 0.0, flags=LOAD_NO_HINTING)
|
339 | 392 | w, h = font.get_width_height()
|
340 | 393 | w /= 64.0
|
341 | 394 | h /= 64.0
|
342 | 395 | d = font.get_descent() / 64.0
|
343 |
| - return w, h, d |
344 |
| - |
345 |
| - def _draw_math_text(self, gc, x, y, s, prop, angle): |
346 |
| - rgba, descent = self.mathtext_parser.to_rgba( |
347 |
| - s, gc.get_rgb(), self.dpi, prop.get_size_in_points() |
348 |
| - ) |
349 |
| - height, width, _ = rgba.shape |
350 |
| - angle = math.radians(angle) |
351 |
| - if angle != 0: |
352 |
| - self.ctx.save() |
353 |
| - self.ctx.translate(x, y) |
354 |
| - self.ctx.rotate(-angle) |
355 |
| - self.ctx.translate(-x, -y) |
356 |
| - self.draw_image(gc, x, -y - descent, np.flipud(rgba)) |
357 |
| - if angle != 0: |
358 |
| - self.ctx.restore() |
359 |
| - |
360 |
| - def load_font_into_web(self, loaded_face, font_url): |
361 |
| - fontface = loaded_face.result() |
362 |
| - document.fonts.add(fontface) |
363 |
| - self.fonts_loading.pop(font_url, None) |
364 |
| - |
365 |
| - # Redraw figure after font has loaded |
366 |
| - self.fig.draw() |
367 |
| - return fontface |
| 396 | + return w, h, d |
368 | 397 |
|
369 | 398 | def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
|
370 | 399 | if ismath:
|
@@ -421,6 +450,15 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
|
421 | 450 | if angle != 0:
|
422 | 451 | self.ctx.restore()
|
423 | 452 |
|
| 453 | + def load_font_into_web(self, loaded_face, font_url): |
| 454 | + fontface = loaded_face.result() |
| 455 | + document.fonts.add(fontface) |
| 456 | + self.fonts_loading.pop(font_url, None) |
| 457 | + |
| 458 | + # Redraw figure after font has loaded |
| 459 | + self.fig.draw() |
| 460 | + return fontface |
| 461 | + |
424 | 462 |
|
425 | 463 | class FigureManagerHTMLCanvas(FigureManagerBase):
|
426 | 464 | def __init__(self, canvas, num):
|
|
0 commit comments