Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork8.1k
Add support for (some) colour fonts#30725
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
base:text-overhaul
Are you sure you want to change the base?
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
b0a13fadaae68ab839f876d2ae67ea80bbbe9917909c20b0cb2aa1f24df0c5bc1299cfFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| Support for loading TrueType Collection fonts | ||
| --------------------------------------------- | ||
| TrueType Collection fonts (commonly found as files with a ``.ttc`` extension) are now | ||
| supported. Namely, Matplotlib will include these file extensions in its scan for system | ||
| fonts, and will add all sub-fonts to its list of available fonts (i.e., the list from | ||
| `~.font_manager.get_font_names`). | ||
| From most high-level API, this means you should be able to specify the name of any | ||
| sub-font in a collection just as you would any other font. Note that at this time, there | ||
| is no way to specify the entire collection with any sort of automated selection of the | ||
| internal sub-fonts. | ||
| In the low-level API, to ensure backwards-compatibility while facilitating this new | ||
| support, a `.FontPath` instance (comprised of a font path and a sub-font index, with | ||
| behaviour similar to a `str`) may be passed to the font management API in place of a | ||
| simple `os.PathLike` path. Any font management API that previously returned a string path | ||
| now returns a `.FontPath` instance instead. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -22,7 +22,7 @@ | ||
| """ | ||
| from contextlib import nullcontext | ||
| importmath | ||
| 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, 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('path') | ||
| self.bbox = Bbox.from_bounds(0, 0, self.width, self.height) | ||
| @@ -173,48 +173,86 @@ 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`.""" | ||
| # 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() | LoadFlags.COLOR, | ||
| 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 + bitmap.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. | ||
| 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) | ||
| cos = math.cos(math.radians(angle)) | ||
| sin = math.sin(math.radians(angle)) | ||
| load_flags = get_hinting_flag() | LoadFlags.COLOR | LoadFlags.NO_SVG | ||
| items = font._layout( | ||
| s, flags=load_flags, | ||
| features=mtext.get_fontfeatures() if mtext is not None else None, | ||
| language=mtext.get_language() if mtext is not None else None) | ||
| for item in items: | ||
| hf = item.ft_object._hinting_factor | ||
| item.ft_object._set_transform( | ||
| [[round(0x10000 * cos / hf), round(0x10000 * -sin)], | ||
| [round(0x10000 * sin / hf), round(0x10000 * cos)]], | ||
| [round(0x40 * (x + item.x * cos - item.y * sin)), | ||
| # FreeType's y is upwards. | ||
| round(0x40 * (self.height - y + item.x * sin + item.y * cos))] | ||
| ) | ||
| bitmap = item.ft_object._render_glyph( | ||
| item.glyph_index, load_flags, | ||
| RenderMode.NORMAL if gc.get_antialiased() else RenderMode.MONO) | ||
| buffer = bitmap.buffer | ||
| if buffer.ndim == 3: | ||
| self._renderer.draw_image( | ||
| gc, | ||
| bitmap.left, bitmap.top - buffer.shape[0], | ||
| buffer[::-1, :, [2, 1, 0, 3]]) | ||
Contributor 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. I don't know if we really support big-endian archs, but at least backend_tkcairo uses | ||
| else: | ||
| 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 +262,8 @@ def get_text_width_height_descent(self, s, prop, ismath): | ||
| return super().get_text_width_height_descent(s, prop, ismath) | ||
| if ismath: | ||
| 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 +285,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 *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.
Uh oh!
There was an error while loading.Please reload this page.