-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Drop the FT2Font intermediate buffer. #30059
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,7 +22,7 @@ | |
| """ | ||
|
|
||
| from contextlib import nullcontext | ||
| from math import radians, cos, sin | ||
| import math | ||
|
|
||
| import numpy as np | ||
| from PIL import features | ||
|
|
@@ -32,7 +32,7 @@ | |
| from matplotlib.backend_bases import ( | ||
| _Backend, FigureCanvasBase, FigureManagerBase, RendererBase) | ||
| from matplotlib.font_manager import fontManager as _fontManager, get_font | ||
| from matplotlib.ft2font import LoadFlags | ||
| from matplotlib.ft2font import LoadFlags, RenderMode | ||
| from matplotlib.mathtext import MathTextParser | ||
| from matplotlib.path import Path | ||
| from matplotlib.transforms import Bbox, BboxBase | ||
|
|
@@ -71,7 +71,7 @@ def __init__(self, width, height, dpi): | |
| self._filter_renderers = [] | ||
|
|
||
| self._update_methods() | ||
| self.mathtext_parser = MathTextParser('agg') | ||
| self.mathtext_parser = MathTextParser('path') | ||
|
|
||
| self.bbox = Bbox.from_bounds(0, 0, self.width, self.height) | ||
|
|
||
|
|
@@ -173,48 +173,75 @@ def draw_path(self, gc, path, transform, rgbFace=None): | |
|
|
||
| def draw_mathtext(self, gc, x, y, s, prop, angle): | ||
| """Draw mathtext using :mod:`matplotlib.mathtext`.""" | ||
| ox, oy, width, height, descent, font_image = \ | ||
| self.mathtext_parser.parse(s, self.dpi, prop, | ||
| antialiased=gc.get_antialiased()) | ||
|
|
||
| xd = descent * sin(radians(angle)) | ||
| yd = descent * cos(radians(angle)) | ||
| x = round(x + ox + xd) | ||
| y = round(y - oy + yd) | ||
| self._renderer.draw_text_image(font_image, x, y + 1, angle, gc) | ||
| # y is downwards. | ||
| parse = self.mathtext_parser.parse( | ||
| s, self.dpi, prop, antialiased=gc.get_antialiased()) | ||
| cos = math.cos(math.radians(angle)) | ||
| sin = math.sin(math.radians(angle)) | ||
| for font, size, _char, glyph_index, dx, dy in parse.glyphs: # dy is upwards. | ||
| font.set_size(size, self.dpi) | ||
| hf = font._hinting_factor | ||
| font._set_transform( | ||
| [[round(0x10000 * cos / hf), round(0x10000 * -sin)], | ||
| [round(0x10000 * sin / hf), round(0x10000 * cos)]], | ||
| [round(0x40 * (x + dx * cos - dy * sin)), | ||
| # FreeType's y is upwards. | ||
| round(0x40 * (self.height - y + dx * sin + dy * cos))] | ||
| ) | ||
| bitmap = font._render_glyph( | ||
| glyph_index, get_hinting_flag(), | ||
| RenderMode.NORMAL if gc.get_antialiased() else RenderMode.MONO) | ||
| buffer = np.asarray(bitmap.buffer) | ||
| if not gc.get_antialiased(): | ||
| buffer *= 0xff | ||
| # draw_text_image's y is downwards & the bitmap bottom side. | ||
| self._renderer.draw_text_image( | ||
| buffer, | ||
| bitmap.left, int(self.height) - bitmap.top + buffer.shape[0], | ||
| 0, gc) | ||
| rgba = gc.get_rgb() | ||
| if len(rgba) == 3 or gc.get_forced_alpha(): | ||
| rgba = rgba[:3] + (gc.get_alpha(),) | ||
| gc1 = self.new_gc() | ||
| gc1.set_linewidth(0) | ||
| gc1.set_snap(gc.get_snap()) | ||
| for dx, dy, w, h in parse.rects: # dy is upwards & the rect top side. | ||
| if gc1.get_snap() in [None, True]: | ||
| # Prevent thin bars from disappearing by growing symmetrically. | ||
| if w < 1: | ||
| dx -= (1 - w) / 2 | ||
| w = 1 | ||
| if h < 1: | ||
| dy -= (1 - h) / 2 | ||
| h = 1 | ||
| path = Path._create_closed( | ||
| [(dx, dy), (dx + w, dy), (dx + w, dy + h), (dx, dy + h)]) | ||
| self._renderer.draw_path( | ||
| gc1, path, | ||
| mpl.transforms.Affine2D() | ||
| .rotate_deg(angle).translate(x, self.height - y), | ||
| rgba) | ||
| gc1.restore() | ||
|
|
||
| def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): | ||
| # docstring inherited | ||
| if ismath: | ||
| return self.draw_mathtext(gc, x, y, s, prop, angle) | ||
| font = self._prepare_font(prop) | ||
| # We pass '0' for angle here, since it will be rotated (in raster | ||
| # space) in the following call to draw_text_image). | ||
| font.set_text(s, 0, flags=get_hinting_flag(), | ||
| font.set_text(s, angle, flags=get_hinting_flag(), | ||
| features=mtext.get_fontfeatures() if mtext is not None else None, | ||
| language=mtext.get_language() if mtext is not None else None) | ||
| font.draw_glyphs_to_bitmap( | ||
| antialiased=gc.get_antialiased()) | ||
| d = font.get_descent() / 64.0 | ||
| # The descent needs to be adjusted for the angle. | ||
| xo, yo = font.get_bitmap_offset() | ||
| xo /= 64.0 | ||
| yo /= 64.0 | ||
|
|
||
| rad = radians(angle) | ||
| xd = d * sin(rad) | ||
| yd = d * cos(rad) | ||
| # Rotating the offset vector ensures text rotates around the anchor point. | ||
| # Without this, rotated text offsets incorrectly, causing a horizontal shift. | ||
| # Applying the 2D rotation matrix. | ||
| rotated_xo = xo * cos(rad) - yo * sin(rad) | ||
| rotated_yo = xo * sin(rad) + yo * cos(rad) | ||
| # Subtract rotated_yo to account for the inverted y-axis in computer graphics, | ||
| # compared to the mathematical convention. | ||
| x = round(x + rotated_xo + xd) | ||
| y = round(y - rotated_yo + yd) | ||
|
|
||
| self._renderer.draw_text_image(font, x, y + 1, angle, gc) | ||
| for bitmap in font._render_glyphs( | ||
| x, self.height - y, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this were trimmed to integers (
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's the intent, at least. Looks like something similar to #30059 (comment) was happening as well? |
||
| RenderMode.NORMAL if gc.get_antialiased() else RenderMode.MONO, | ||
| ): | ||
| buffer = bitmap.buffer | ||
| if not gc.get_antialiased(): | ||
| buffer *= 0xff | ||
| self._renderer.draw_text_image( | ||
| buffer, | ||
| bitmap.left, int(self.height) - bitmap.top + buffer.shape[0], | ||
| 0, gc) | ||
|
|
||
| def get_text_width_height_descent(self, s, prop, ismath): | ||
| # docstring inherited | ||
|
|
@@ -224,9 +251,8 @@ def get_text_width_height_descent(self, s, prop, ismath): | |
| return super().get_text_width_height_descent(s, prop, ismath) | ||
|
|
||
| if ismath: | ||
| ox, oy, width, height, descent, font_image = \ | ||
| self.mathtext_parser.parse(s, self.dpi, prop) | ||
| return width, height, descent | ||
| parse = self.mathtext_parser.parse(s, self.dpi, prop) | ||
| return parse.width, parse.height, parse.depth | ||
|
|
||
| font = self._prepare_font(prop) | ||
| font.set_text(s, 0.0, flags=get_hinting_flag()) | ||
|
|
@@ -248,8 +274,8 @@ def draw_tex(self, gc, x, y, s, prop, angle, *, mtext=None): | |
| Z = np.array(Z * 255.0, np.uint8) | ||
|
|
||
| w, h, d = self.get_text_width_height_descent(s, prop, ismath="TeX") | ||
| xd = d * sin(radians(angle)) | ||
| yd = d * cos(radians(angle)) | ||
| xd = d * math.sin(math.radians(angle)) | ||
| yd = d * math.cos(math.radians(angle)) | ||
| x = round(x + xd) | ||
| y = round(y + yd) | ||
| self._renderer.draw_text_image(Z, x, y, angle, gc) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.