22
22
"""
23
23
24
24
from contextlib import nullcontext
25
- from math import radians , cos , sin
25
+ import math
26
26
27
27
import numpy as np
28
28
from PIL import features
@@ -71,7 +71,7 @@ def __init__(self, width, height, dpi):
71
71
self ._filter_renderers = []
72
72
73
73
self ._update_methods ()
74
- self .mathtext_parser = MathTextParser ('agg ' )
74
+ self .mathtext_parser = MathTextParser ('path ' )
75
75
76
76
self .bbox = Bbox .from_bounds (0 , 0 , self .width , self .height )
77
77
@@ -173,48 +173,64 @@ def draw_path(self, gc, path, transform, rgbFace=None):
173
173
174
174
def draw_mathtext (self , gc , x , y , s , prop , angle ):
175
175
"""Draw mathtext using :mod:`matplotlib.mathtext`."""
176
- ox , oy , width , height , descent , font_image = \
177
- self .mathtext_parser .parse (s , self .dpi , prop ,
178
- antialiased = gc .get_antialiased ())
179
-
180
- xd = descent * sin (radians (angle ))
181
- yd = descent * cos (radians (angle ))
182
- x = round (x + ox + xd )
183
- y = round (y - oy + yd )
184
- self ._renderer .draw_text_image (font_image , x , y + 1 , angle , gc )
176
+ # y is downwards.
177
+ parse = self .mathtext_parser .parse (
178
+ s , self .dpi , prop , antialiased = gc .get_antialiased ())
179
+ cos = math .cos (math .radians (angle ))
180
+ sin = math .sin (math .radians (angle ))
181
+ for font , size , char , glyph_index , dx , dy in parse .glyphs : # dy is upwards.
182
+ font .set_size (size , self .dpi )
183
+ hf = font ._hinting_factor
184
+ font ._set_transform (
185
+ [[round (0x10000 * cos / hf ), round (0x10000 * - sin )],
186
+ [round (0x10000 * sin / hf ), round (0x10000 * cos )]],
187
+ [round (0x40 * (x + dx * cos - dy * sin )),
188
+ # FreeType's y is upwards.
189
+ round (0x40 * (self .height - y + dx * sin + dy * cos ))]
190
+ )
191
+ bitmap = font ._render_glyph (glyph_index , get_hinting_flag ())
192
+ # draw_text_image's y is downwards & the bitmap bottom side.
193
+ self ._renderer .draw_text_image (
194
+ bitmap ["buffer" ],
195
+ bitmap ["left" ],
196
+ int (self .height ) - bitmap ["top" ] + bitmap ["buffer" ].shape [0 ],
197
+ 0 , gc )
198
+ if not angle :
199
+ for dx , dy , w , h in parse .rects : # dy is upwards & the rect top side.
200
+ self ._renderer .draw_text_image (
201
+ np .full ((round (h ), round (w )), np .uint8 (0xff )),
202
+ round (x + dx ), round (y - dy - h ),
203
+ 0 , gc )
204
+ else :
205
+ rgba = gc .get_rgb ()
206
+ if len (rgba ) == 3 or gc .get_forced_alpha ():
207
+ rgba = rgba [:3 ] + (gc .get_alpha (),)
208
+ gc1 = self .new_gc ()
209
+ gc1 .set_linewidth (0 )
210
+ for dx , dy , w , h in parse .rects : # dy is upwards & the rect top side.
211
+ path = Path ._create_closed (
212
+ [(dx , dy ), (dx + w , dy ), (dx + w , dy + h ), (dx , dy + h )])
213
+ self ._renderer .draw_path (
214
+ gc1 , path ,
215
+ mpl .transforms .Affine2D ()
216
+ .rotate_deg (angle ).translate (x , self .height - y ),
217
+ rgba )
218
+ gc1 .restore ()
185
219
186
220
def draw_text (self , gc , x , y , s , prop , angle , ismath = False , mtext = None ):
187
221
# docstring inherited
188
222
if ismath :
189
223
return self .draw_mathtext (gc , x , y , s , prop , angle )
190
224
font = self ._prepare_font (prop )
191
- # We pass '0' for angle here, since it will be rotated (in raster
192
- # space) in the following call to draw_text_image).
193
- font .set_text (s , 0 , flags = get_hinting_flag (),
225
+ font .set_text (s , angle , flags = get_hinting_flag (),
194
226
features = mtext .get_fontfeatures () if mtext is not None else None ,
195
227
language = mtext .get_language () if mtext is not None else None )
196
- font .draw_glyphs_to_bitmap (
197
- antialiased = gc .get_antialiased ())
198
- d = font .get_descent () / 64.0
199
- # The descent needs to be adjusted for the angle.
200
- xo , yo = font .get_bitmap_offset ()
201
- xo /= 64.0
202
- yo /= 64.0
203
-
204
- rad = radians (angle )
205
- xd = d * sin (rad )
206
- yd = d * cos (rad )
207
- # Rotating the offset vector ensures text rotates around the anchor point.
208
- # Without this, rotated text offsets incorrectly, causing a horizontal shift.
209
- # Applying the 2D rotation matrix.
210
- rotated_xo = xo * cos (rad ) - yo * sin (rad )
211
- rotated_yo = xo * sin (rad ) + yo * cos (rad )
212
- # Subtract rotated_yo to account for the inverted y-axis in computer graphics,
213
- # compared to the mathematical convention.
214
- x = round (x + rotated_xo + xd )
215
- y = round (y - rotated_yo + yd )
216
-
217
- self ._renderer .draw_text_image (font , x , y + 1 , angle , gc )
228
+ for bitmap in font ._render_glyphs (x , self .height - y ):
229
+ self ._renderer .draw_text_image (
230
+ bitmap ["buffer" ],
231
+ bitmap ["left" ],
232
+ int (self .height ) - bitmap ["top" ] + bitmap ["buffer" ].shape [0 ],
233
+ 0 , gc )
218
234
219
235
def get_text_width_height_descent (self , s , prop , ismath ):
220
236
# docstring inherited
@@ -224,9 +240,8 @@ def get_text_width_height_descent(self, s, prop, ismath):
224
240
return super ().get_text_width_height_descent (s , prop , ismath )
225
241
226
242
if ismath :
227
- ox , oy , width , height , descent , font_image = \
228
- self .mathtext_parser .parse (s , self .dpi , prop )
229
- return width , height , descent
243
+ parse = self .mathtext_parser .parse (s , self .dpi , prop )
244
+ return parse .width , parse .height , parse .depth
230
245
231
246
font = self ._prepare_font (prop )
232
247
font .set_text (s , 0.0 , flags = get_hinting_flag ())
@@ -248,8 +263,8 @@ def draw_tex(self, gc, x, y, s, prop, angle, *, mtext=None):
248
263
Z = np .array (Z * 255.0 , np .uint8 )
249
264
250
265
w , h , d = self .get_text_width_height_descent (s , prop , ismath = "TeX" )
251
- xd = d * sin (radians (angle ))
252
- yd = d * cos (radians (angle ))
266
+ xd = d * math . sin (math . radians (angle ))
267
+ yd = d * math . cos (math . radians (angle ))
253
268
x = round (x + xd )
254
269
y = round (y + yd )
255
270
self ._renderer .draw_text_image (Z , x , y , angle , gc )
0 commit comments