Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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

Open
QuLogic wants to merge15 commits intomatplotlib:text-overhaul
base:text-overhaul
Choose a base branch
Loading
fromQuLogic:colour-font
Open
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
15 commits
Select commitHold shift + click to select a range
9597991
ft2font: Add a wrapper around layouting for vector usage
QuLogicSep 24, 2025
bd17cd4
Use libraqm for text in vector outputs
QuLogicMay 24, 2025
22e5b3d
Update test images for previous libraqm-vector changes
QuLogicOct 3, 2025
b0a13fa
Drop the FT2Font intermediate buffer
anntzerMay 16, 2025
1028eaa
Expose face index when loading fonts
QuLogicJul 18, 2025
83b0144
Parse data from all fonts within a collection
QuLogicJul 18, 2025
a9abc29
Implement loading of any font in a collection
QuLogicJul 25, 2025
41cab00
pdf/ps: Support any font in a collection
QuLogicJul 25, 2025
78398f4
pgf: Support any font in a collection
QuLogicJul 25, 2025
ce91ead
DOC: Add what's new note for TTC loading
QuLogicOct 31, 2025
6521ddc
Update test images for previous changes
QuLogicSep 26, 2025
c3d6950
Merge branch '30059/ft-direct-render' into libraqm-full
QuLogicNov 3, 2025
4c802b9
Merge branch 'ttc-loading' into libraqm-full
QuLogicNov 3, 2025
219d974
Merge branch 'libraqm-vector' into libraqm-full
QuLogicNov 3, 2025
617cb43
Handle colour fonts
QuLogicJun 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletionsci/mypy-stubtest-allowlist.txt
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@ matplotlib\._.*
matplotlib\.rcsetup\._listify_validator
matplotlib\.rcsetup\._validate_linestyle
matplotlib\.ft2font\.Glyph
matplotlib\.ft2font\.LayoutItem
matplotlib\.testing\.jpl_units\..*
matplotlib\.sphinxext(\..*)?

Expand Down
18 changes: 18 additions & 0 deletionsdoc/release/next_whats_new/ttc_fonts.rst
View file
Open in desktop
Original file line numberDiff line numberDiff 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.
44 changes: 12 additions & 32 deletionslib/matplotlib/_text_helpers.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,29 +4,23 @@

from __future__ import annotations

importdataclasses
from collections.abcimportIterator

from . import _api
from .ft2font import FT2Font,GlyphIndexType, Kerning, LoadFlags
from .ft2font import FT2Font,CharacterCodeType, LayoutItem, LoadFlags


@dataclasses.dataclass(frozen=True)
class LayoutItem:
ft_object: FT2Font
char: str
glyph_index: GlyphIndexType
x: float
prev_kern: float


def warn_on_missing_glyph(codepoint, fontnames):
def warn_on_missing_glyph(codepoint: CharacterCodeType, fontnames: str):
_api.warn_external(
f"Glyph {codepoint} "
f"({chr(codepoint).encode('ascii', 'namereplace').decode('ascii')}) "
f"missing from font(s) {fontnames}.")


def layout(string, font, *, features=None, kern_mode=Kerning.DEFAULT, language=None):
def layout(string: str, font: FT2Font, *,
features: tuple[str] | None = None,
language: str | tuple[tuple[str, int, int], ...] | None = None
) -> Iterator[LayoutItem]:
"""
Render *string* with *font*.

Expand All@@ -41,8 +35,6 @@ def layout(string, font, *, features=None, kern_mode=Kerning.DEFAULT, language=N
The font.
features : tuple of str, optional
The font features to apply to the text.
kern_mode : Kerning
A FreeType kerning mode.
language : str, optional
The language of the text in a format accepted by libraqm, namely `a BCP47
language code <https://www.w3.org/International/articles/language-tags/>`_.
Expand All@@ -51,20 +43,8 @@ def layout(string, font, *, features=None, kern_mode=Kerning.DEFAULT, language=N
------
LayoutItem
"""
x = 0
prev_glyph_index = None
char_to_font = font._get_fontmap(string) # TODO: Pass in features and language.
base_font = font
for char in string:
# This has done the fallback logic
font = char_to_font.get(char, base_font)
glyph_index = font.get_char_index(ord(char))
kern = (
base_font.get_kerning(prev_glyph_index, glyph_index, kern_mode) / 64
if prev_glyph_index is not None else 0.
)
x += kern
glyph = font.load_glyph(glyph_index, flags=LoadFlags.NO_HINTING)
yield LayoutItem(font, char, glyph_index, x, kern)
x += glyph.linearHoriAdvance / 65536
prev_glyph_index = glyph_index
for raqm_item in font._layout(string, LoadFlags.NO_HINTING,
features=features, language=language):
raqm_item.ft_object.load_glyph(raqm_item.glyph_index,
flags=LoadFlags.NO_HINTING)
yield raqm_item
28 changes: 20 additions & 8 deletionslib/matplotlib/backends/_backend_pdf_ps.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,7 +42,7 @@ def get_glyphs_subset(fontfile: str, glyphs: set[GlyphIndexType]) -> TTFont:

Parameters
----------
fontfile :str
fontfile :FontPath
Path to the font file
glyphs : set[GlyphIndexType]
Set of glyph indices to include in subset.
Expand DownExpand Up@@ -80,8 +80,7 @@ def get_glyphs_subset(fontfile: str, glyphs: set[GlyphIndexType]) -> TTFont:
'xref', # The cross-reference table (some Apple font tooling information).
]
# if fontfile is a ttc, specify font number
if fontfile.endswith(".ttc"):
options.font_number = 0
options.font_number = fontfile.face_index

font = subset.load_font(fontfile, options)
subsetter = subset.Subsetter(options=options)
Expand DownExpand Up@@ -199,7 +198,10 @@ def __init__(self, subset_size: int = 0):
self.glyph_maps: dict[str, GlyphMap] = {}
self.subset_size = subset_size

def track(self, font: FT2Font, s: str) -> list[tuple[int, CharacterCodeType]]:
def track(self, font: FT2Font, s: str,
features: tuple[str, ...] | None = ...,
language: str | tuple[tuple[str, int, int], ...] | None = None
) -> list[tuple[int, CharacterCodeType]]:
"""
Record that string *s* is being typeset using font *font*.

Expand All@@ -209,6 +211,14 @@ def track(self, font: FT2Font, s: str) -> list[tuple[int, CharacterCodeType]]:
A font that is being used for the provided string.
s : str
The string that should be marked as tracked by the provided font.
features : tuple[str, ...], optional
The font feature tags to use for the font.

Available font feature tags may be found at
https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
language : str, optional
The language of the text in a format accepted by libraqm, namely `a BCP47
language code <https://www.w3.org/International/articles/language-tags/>`_.

Returns
-------
Expand All@@ -220,8 +230,9 @@ def track(self, font: FT2Font, s: str) -> list[tuple[int, CharacterCodeType]]:
and the character codes will be returned from the string unchanged.
"""
return [
self.track_glyph(f, ord(c), f.get_char_index(ord(c)))
for c, f in font._get_fontmap(s).items()
self.track_glyph(raqm_item.ft_object, raqm_item.char, raqm_item.glyph_index)
for raqm_item in font._layout(s, ft2font.LoadFlags.NO_HINTING,
features=features, language=language)
]

def track_glyph(self, font: FT2Font, chars: str | CharacterCodeType,
Expand DownExpand Up@@ -255,11 +266,12 @@ def track_glyph(self, font: FT2Font, chars: str | CharacterCodeType,
charcode = chars
chars = chr(chars)

glyph_map = self.glyph_maps.setdefault(font.fname, GlyphMap())
font_path = font_manager.FontPath(font.fname, font.face_index)
glyph_map = self.glyph_maps.setdefault(font_path, GlyphMap())
if result := glyph_map.get(chars, glyph):
return result

subset_maps = self.used.setdefault(font.fname, [{}])
subset_maps = self.used.setdefault(font_path, [{}])
use_next_charmap = (
# Multi-character glyphs always go in the non-0 subset.
len(chars) > 1 or
Expand Down
125 changes: 81 additions & 44 deletionslib/matplotlib/backends/backend_agg.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,7 @@
"""

from contextlib import nullcontext
from mathimportradians, cos, sin
importmath

import numpy as np
from PIL import features
Expand All@@ -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
Expand DownExpand Up@@ -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)

Expand DownExpand Up@@ -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`."""
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() | 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)
# 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(),
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)
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 not gc.get_antialiased():
buffer *= 0xff
if buffer.ndim == 3:
self._renderer.draw_image(
gc,
bitmap.left, bitmap.top - buffer.shape[0],
buffer[::-1, :, [2, 1, 0, 3]])
else:
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
Expand All@@ -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:
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())
Expand All@@ -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 * 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)
Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp