44
55import math
66import numpy as np
7+ from functools import lru_cache
78from matplotlib .backend_bases import (
89 FigureCanvasBase ,
910 FigureManagerBase ,
1011 RendererBase ,
1112 GraphicsContextBase ,
1213 _Backend ,
1314)
14- from matplotlib .cbook import maxdict
15+ from matplotlib ._enums import CapStyle
1516from matplotlib .font_manager import findfont
1617from matplotlib .ft2font import LOAD_NO_HINTING , FT2Font
1718from matplotlib .mathtext import MathTextParser
@@ -94,12 +95,20 @@ def restore(self):
9495 self .renderer .ctx .restore ()
9596
9697 def set_capstyle (self , cs ):
98+ if isinstance (cs , str ):
99+ cs = CapStyle (cs )
100+ # Convert the JoinStyle enum to its name if needed
101+ if hasattr (cs , "name" ):
102+ cs = cs .name .lower ()
97103 if cs in ["butt" , "round" , "projecting" ]:
98104 self ._capstyle = cs
99105 self .renderer .ctx .lineCap = _capstyle_d [cs ]
100106 else :
101107 raise ValueError (f"Unrecognized cap style. Found { cs } " )
102108
109+ def get_capstyle (self ):
110+ return self ._capstyle
111+
103112 def set_clip_rectangle (self , rectangle ):
104113 self .renderer .ctx .save ()
105114 if not rectangle :
@@ -153,8 +162,8 @@ def __init__(self, ctx, width, height, dpi, fig):
153162 self .ctx .width = self .width
154163 self .ctx .height = self .height
155164 self .dpi = dpi
156- self .fontd = maxdict ( 50 )
157- self .mathtext_parser = MathTextParser ( "bitmap" )
165+ self .mathtext_parser = MathTextParser ( "path" )
166+ self ._get_font_helper = lru_cache ( maxsize = 50 )( self . _get_font_helper )
158167
159168 # Keep the state of fontfaces that are loading
160169 self .fonts_loading = {}
@@ -189,14 +198,44 @@ def _matplotlib_color_to_CSS(self, color, alpha, alpha_overrides, is_RGB=True):
189198
190199 return CSS_color
191200
201+ def _draw_math_text (self , gc , x , y , s , prop , angle ):
202+ width , height , depth , glyphs , rects = self .mathtext_parser .parse (
203+ s , dpi = self .dpi , prop = prop
204+ )
205+ self .ctx .save ()
206+ self .ctx .translate (x , self .height + y )
207+ if angle != 0 :
208+ self .ctx .rotate (- math .radians (angle ))
209+ self .ctx .fillStyle = self ._matplotlib_color_to_CSS (
210+ gc .get_rgb (), gc .get_alpha (), gc .get_forced_alpha ()
211+ )
212+ for font , fontsize , c , ox , oy in glyphs :
213+ self .ctx .save ()
214+ self .ctx .translate (ox , - oy )
215+ font .set_size (fontsize , self .dpi )
216+ font .load_char (c )
217+ verts , codes = font .get_path ()
218+ path = Path (verts , codes )
219+ transform = Affine2D ().scale (1.0 , - 1.0 )
220+ self ._path_helper (self .ctx , path , transform )
221+ self .ctx .fill ()
222+ self .ctx .restore ()
223+ for x1 , y1 , x2 , y2 in rects :
224+ self .ctx .fillRect (x1 , - y2 , x2 - x1 , y2 - y1 )
225+ self .ctx .restore ()
226+
192227 def _set_style (self , gc , rgbFace = None ):
193228 if rgbFace is not None :
194229 self .ctx .fillStyle = self ._matplotlib_color_to_CSS (
195230 rgbFace , gc .get_alpha (), gc .get_forced_alpha ()
196231 )
197232
198- if gc .get_capstyle ():
199- self .ctx .lineCap = _capstyle_d [gc .get_capstyle ()]
233+ capstyle = gc .get_capstyle ()
234+ if capstyle :
235+ # Get the string name if it's an enum
236+ if hasattr (capstyle , "name" ):
237+ capstyle = capstyle .name .lower ()
238+ self .ctx .lineCap = _capstyle_d [capstyle ]
200239
201240 self .ctx .strokeStyle = self ._matplotlib_color_to_CSS (
202241 gc .get_rgb (), gc .get_alpha (), gc .get_forced_alpha ()
@@ -238,30 +277,29 @@ def draw_path(self, gc, path, transform, rgbFace=None):
238277 def draw_markers (self , gc , marker_path , marker_trans , path , trans , rgbFace = None ):
239278 super ().draw_markers (gc , marker_path , marker_trans , path , trans , rgbFace )
240279
280+ def _get_font_helper (self , prop ):
281+ fname = findfont (prop )
282+ font = FT2Font (str (fname ))
283+ font_file_name = fname .rpartition ("/" )[- 1 ]
284+ return (font , font_file_name )
285+
241286 def _get_font (self , prop ):
242- key = hash (prop )
243- font_value = self .fontd .get (key )
244- if font_value is None :
245- fname = findfont (prop )
246- font_value = self .fontd .get (fname )
247- if font_value is None :
248- font = FT2Font (str (fname ))
249- font_file_name = fname [fname .rfind ("/" ) + 1 :]
250- font_value = font , font_file_name
251- self .fontd [fname ] = font_value
252- self .fontd [key ] = font_value
253- font , font_file_name = font_value
287+ result = self ._get_font_helper (prop )
288+ font = result [0 ]
254289 font .clear ()
255290 font .set_size (prop .get_size_in_points (), self .dpi )
256- return font , font_file_name
291+ return result
257292
258293 def get_text_width_height_descent (self , s , prop , ismath ):
259294 w : float
260295 h : float
296+ d : float
261297 if ismath :
262- image , d = self .mathtext_parser .parse (s , self .dpi , prop )
263- image_arr = np .asarray (image )
264- h , w = image_arr .shape
298+ # Use the path parser to get exact metrics
299+ width , height , depth , _ , _ = self .mathtext_parser .parse (
300+ s , dpi = 72 , prop = prop
301+ )
302+ return width , height , depth
265303 else :
266304 font , _ = self ._get_font (prop )
267305 font .set_text (s , 0.0 , flags = LOAD_NO_HINTING )
@@ -271,21 +309,6 @@ def get_text_width_height_descent(self, s, prop, ismath):
271309 d = font .get_descent () / 64.0
272310 return w , h , d
273311
274- def _draw_math_text (self , gc , x , y , s , prop , angle ):
275- rgba , descent = self .mathtext_parser .to_rgba (
276- s , gc .get_rgb (), self .dpi , prop .get_size_in_points ()
277- )
278- height , width , _ = rgba .shape
279- angle = math .radians (angle )
280- if angle != 0 :
281- self .ctx .save ()
282- self .ctx .translate (x , y )
283- self .ctx .rotate (- angle )
284- self .ctx .translate (- x , - y )
285- self .draw_image (gc , x , - y - descent , np .flipud (rgba ))
286- if angle != 0 :
287- self .ctx .restore ()
288-
289312 def draw_image (self , gc , x , y , im , transform = None ):
290313 import numpy as np
291314 im = np .flipud (im )
0 commit comments