|
18 | 18 | from matplotlib.transforms import Affine2D
|
19 | 19 | import pygame
|
20 | 20 | import pygame.image
|
| 21 | +import pygame.freetype |
21 | 22 | from pygame import gfxdraw
|
22 | 23 | from matplotlib._pylab_helpers import Gcf
|
23 | 24 | from matplotlib.backend_bases import (
|
@@ -239,55 +240,142 @@ def draw_image(self, gc, x, y, im):
|
239 | 240 | )
|
240 | 241 |
|
241 | 242 | def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
|
242 |
| - |
243 |
| - logger.info( |
| 243 | + logger.debug( |
244 | 244 | f"Drawing text: {s=} at ({x=}, {y=}) with {angle=} and ismath={ismath} "
|
245 | 245 | f"{mtext=} {prop=} {gc=}"
|
246 | 246 | )
|
| 247 | + |
247 | 248 | # make sure font module is initialized
|
248 |
| - if not pygame.font.get_init(): |
249 |
| - pygame.font.init() |
| 249 | + if not pygame.freetype.get_init(): |
| 250 | + pygame.freetype.init() |
| 251 | + |
250 | 252 | # prop is the font properties
|
251 |
| - font_size = prop.get_size_in_points() * 2 |
252 |
| - myfont = pygame.font.Font(prop.get_file(), int(font_size)) |
| 253 | + # Size must be adjusted |
| 254 | + font_size = prop.get_size() * 1.42 |
| 255 | + font_file = prop.get_file() |
| 256 | + logger.debug(f"Font file: {font_file}, size: {font_size}") |
| 257 | + |
| 258 | + # Set bold |
| 259 | + # 'light', 'normal', 'regular', 'book', |
| 260 | + #' medium', 'roman', 'semibold', 'demibold', 'demi', 'bold', |
| 261 | + # 'heavy', 'extra bold', 'black' |
| 262 | + font_weight = prop.get_weight() |
| 263 | + if isinstance(font_weight, int): |
| 264 | + # If the weight is an int, we assume it is a font weight |
| 265 | + # in the range 0-1000 |
| 266 | + bold = font_weight >= 600 |
| 267 | + else: |
| 268 | + bold = font_weight in ["bold", "heavy", "black", "extra bold"] |
| 269 | + logger.debug(f"Font weight: {font_weight}, bold: {bold}") |
| 270 | + |
| 271 | + # Set italic |
| 272 | + # style can be 'normal', 'italic' or 'oblique' |
| 273 | + font_style = prop.get_style() |
| 274 | + italic = font_style in ["italic", "oblique"] |
| 275 | + |
| 276 | + if font_file is None: |
| 277 | + # Use default matplotlib font |
| 278 | + font_file = "DejaVuSans" |
| 279 | + |
| 280 | + pgfont = pygame.freetype.SysFont( |
| 281 | + font_file, int(font_size), bold=bold, italic=italic |
| 282 | + ) |
| 283 | + |
| 284 | + logger.debug(f"Font: {pgfont}") |
| 285 | + |
253 | 286 | # apply it to text on a label
|
254 |
| - font_surface = myfont.render( |
255 |
| - s, gc.get_antialiased(), [val * 255 for val in gc.get_rgb()] |
| 287 | + fg_color = [val * 255 for val in gc.get_rgb()] |
| 288 | + |
| 289 | + # Use freetype to render the text |
| 290 | + font_surface, rotated_rect = pgfont.render( |
| 291 | + s, |
| 292 | + fg_color, |
| 293 | + size=font_size, |
| 294 | + rotation=int(angle), |
256 | 295 | )
|
257 |
| - if angle: |
258 |
| - font_surface = pygame.transform.rotate(font_surface, angle) |
259 |
| - |
| 296 | + no_rotation_rect = pgfont.get_rect(s, size=font_size) |
| 297 | + |
260 | 298 | # Get the expected size of the font
|
261 |
| - width, height = myfont.size(s) |
| 299 | + # width, height = (pgfont.get_rect(s).size if USE_FREETYPE else pgfont.size(s)) |
| 300 | + width, height = rotated_rect.size |
| 301 | + |
262 | 302 | # Tuple for the position of the font
|
263 | 303 | font_surf_position = (x, self.surface.get_height() - y)
|
264 | 304 | if mtext is not None:
|
265 | 305 | # Use the alignement from mtext or default
|
266 | 306 | h_alignment = mtext.get_horizontalalignment()
|
267 | 307 | v_alignment = mtext.get_verticalalignment()
|
| 308 | + rotation_mode = mtext.get_rotation_mode() |
268 | 309 | else:
|
269 | 310 | h_alignment = "center"
|
270 | 311 | v_alignment = "center"
|
| 312 | + |
| 313 | + logger.debug( |
| 314 | + f"{h_alignment=}, {v_alignment=}, {rotation_mode=}, {width=}, {height=}" |
| 315 | + ) |
271 | 316 | # Use the alignement to know where the font should go
|
272 |
| - if h_alignment == "left": |
273 |
| - h_offset = 0 |
274 |
| - elif h_alignment == "center": |
275 |
| - h_offset = -width / 2 |
276 |
| - elif h_alignment == "right": |
277 |
| - h_offset = -width |
278 |
| - else: |
279 |
| - h_offset = 0 |
280 |
| - |
281 |
| - if v_alignment == "top": |
282 |
| - v_offset = 0 |
283 |
| - elif v_alignment == "center" or v_alignment == "center_baseline": |
284 |
| - v_offset = -height / 2 |
285 |
| - elif v_alignment == "bottom" or v_alignment == "baseline": |
286 |
| - v_offset = -height |
| 317 | + if rotation_mode == "anchor": |
| 318 | + # Anchor the text to the position |
| 319 | + sin_a = np.sin(np.radians(angle)) |
| 320 | + cos_a = np.cos(np.radians(angle)) |
| 321 | + |
| 322 | + sub_width, sub_height = no_rotation_rect.size |
| 323 | + |
| 324 | + if h_alignment == "left": |
| 325 | + if v_alignment == "top": |
| 326 | + h_offset = 0 |
| 327 | + v_offset = sub_width * sin_a |
| 328 | + elif v_alignment == "center": |
| 329 | + h_offset = sub_height * sin_a / 2 |
| 330 | + v_offset = height - sub_height * cos_a / 2 |
| 331 | + else: # "bottom" or "baseline" |
| 332 | + h_offset = sub_height * sin_a |
| 333 | + v_offset = height |
| 334 | + elif h_alignment == "center": |
| 335 | + if v_alignment == "top": |
| 336 | + h_offset = sub_width / 2 * cos_a |
| 337 | + v_offset = sub_width / 2 * sin_a |
| 338 | + elif v_alignment == "center": |
| 339 | + h_offset = width / 2 |
| 340 | + v_offset = height / 2 |
| 341 | + else: |
| 342 | + h_offset = width - (sub_width / 2 * cos_a) |
| 343 | + v_offset = height - sub_width / 2 * sin_a |
| 344 | + elif h_alignment == "right": |
| 345 | + if v_alignment == "top": |
| 346 | + h_offset = width - sub_height * sin_a |
| 347 | + v_offset = 0 |
| 348 | + elif v_alignment == "center": |
| 349 | + h_offset = width - (sub_height / 2 * sin_a) |
| 350 | + v_offset = sub_height / 2 * cos_a |
| 351 | + else: |
| 352 | + h_offset = width |
| 353 | + v_offset = cos_a * sub_height |
| 354 | + else: |
| 355 | + raise ValueError(f"Unknown {h_alignment=}") |
| 356 | + h_offset, v_offset = -h_offset, -v_offset |
| 357 | + |
287 | 358 | else:
|
288 |
| - v_offset = 0 |
289 |
| - # pygame.draw.circle(self.surface, (255, 0, 0), (x, self.surface.get_height() - y), 3) |
290 |
| - # pygame.draw.lines(self.surface, (0, 255, 0), True, ((x, self.surface.get_height() - y), (x + width, self.surface.get_height() - y), (x + width, self.surface.get_height() - y + height))) |
| 359 | + # The text box is aligned to the position |
| 360 | + |
| 361 | + if h_alignment == "left": |
| 362 | + h_offset = 0 |
| 363 | + elif h_alignment == "center": |
| 364 | + h_offset = -width / 2 |
| 365 | + elif h_alignment == "right": |
| 366 | + h_offset = -width |
| 367 | + else: |
| 368 | + h_offset = 0 |
| 369 | + |
| 370 | + if v_alignment == "top": |
| 371 | + v_offset = 0 |
| 372 | + elif v_alignment == "center" or v_alignment == "center_baseline": |
| 373 | + v_offset = -height / 2 |
| 374 | + elif v_alignment == "bottom" or v_alignment == "baseline": |
| 375 | + v_offset = -height |
| 376 | + else: |
| 377 | + v_offset = 0 |
| 378 | + |
291 | 379 | # Tuple for the position of the font
|
292 | 380 | font_surf_position = (
|
293 | 381 | x + h_offset,
|
|
0 commit comments