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 merge10 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
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
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.
10 changes: 5 additions & 5 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@@ -267,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 buffer.ndim == 3:
self._renderer.draw_image(
gc,
bitmap.left, bitmap.top - buffer.shape[0],
buffer[::-1, :, [2, 1, 0, 3]])
Copy link
Contributor

Choose a reason for hiding this comment

The 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(2, 1, 0, 3) if sys.byteorder == "little" else (1, 2, 3, 0).

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
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
29 changes: 17 additions & 12 deletionslib/matplotlib/backends/backend_pdf.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,7 +32,7 @@
RendererBase)
from matplotlib.backends.backend_mixed import MixedModeRenderer
from matplotlib.figure import Figure
from matplotlib.font_manager import get_font, fontManager as _fontManager
from matplotlib.font_manager importFontPath,get_font, fontManager as _fontManager
from matplotlib._afm import AFM
from matplotlib.ft2font import FT2Font, FaceFlags, LoadFlags, StyleFlags
from matplotlib.transforms import Affine2D, BboxBase
Expand DownExpand Up@@ -894,8 +894,10 @@ def fontName(self, fontprop, subset=0):
as the filename of the font.
"""

if isinstance(fontprop,str):
if isinstance(fontprop,FontPath):
filenames = [fontprop]
elif isinstance(fontprop, str):
filenames = [FontPath(fontprop, 0)]
elif mpl.rcParams['pdf.use14corefonts']:
filenames = _fontManager._find_fonts_by_props(
fontprop, fontext='afm', directory=RendererPdf._afm_font_dir
Expand DownExpand Up@@ -935,7 +937,7 @@ def writeFonts(self):
_log.debug('Embedding Type-1 font %s from dvi.', dvifont.texname)
fonts[pdfname] = self._embedTeXFont(dvifont)
for (filename, subset), Fx in sorted(self._fontNames.items()):
_log.debug('Embedding font %s:%d.', filename, subset)
_log.debug('Embedding font %r:%d.', filename, subset)
if filename.endswith('.afm'):
# from pdf.use14corefonts
_log.debug('Writing AFM font.')
Expand DownExpand Up@@ -986,10 +988,11 @@ def _embedTeXFont(self, dvifont):

# Reduce the font to only the glyphs used in the document, get the encoding
# for that subset, and compute various properties based on the encoding.
charmap = self._character_tracker.used[dvifont.fname][0]
font_path = FontPath(dvifont.fname, dvifont.face_index)
charmap = self._character_tracker.used[font_path][0]
chars = {
# DVI type 1 fonts always map single glyph to single character.
ord(self._character_tracker.subset_to_unicode(dvifont.fname, 0, ccode))
ord(self._character_tracker.subset_to_unicode(font_path, 0, ccode))
for ccode in charmap
}
t1font = t1font.subset(chars, self._get_subset_prefix(charmap.values()))
Expand DownExpand Up@@ -1241,12 +1244,12 @@ def embedTTFType42(font, subset_index, charmap, descriptor):
wObject = self.reserveObject('Type 0 widths')
toUnicodeMapObject = self.reserveObject('ToUnicode map')

_log.debug("SUBSET %s:%d characters: %s", filename, subset_index, charmap)
_log.debug("SUBSET %r:%d characters: %s", filename, subset_index, charmap)
with _backend_pdf_ps.get_glyphs_subset(filename,
charmap.values()) as subset:
fontdata = _backend_pdf_ps.font_as_file(subset)
_log.debug(
"SUBSET %s:%d %d -> %d", filename, subset_index,
"SUBSET %r:%d %d -> %d", filename, subset_index,
os.stat(filename).st_size, fontdata.getbuffer().nbytes
)

Expand DownExpand Up@@ -2137,13 +2140,13 @@ def draw_mathtext(self, gc, x, y, s, prop, angle):
for font, fontsize, ccode, glyph_index, ox, oy in glyphs:
subset_index, subset_charcode = self.file._character_tracker.track_glyph(
font, ccode, glyph_index)
fontname = font.fname
font_path =FontPath(font.fname, font.face_index)
self._setup_textpos(ox, oy, 0, oldx, oldy)
oldx, oldy = ox, oy
if (fontname, subset_index, fontsize) != prev_font:
self.file.output(self.file.fontName(fontname, subset_index), fontsize,
if (font_path, subset_index, fontsize) != prev_font:
self.file.output(self.file.fontName(font_path, subset_index), fontsize,
Op.selectfont)
prev_font =fontname, subset_index, fontsize
prev_font =font_path, subset_index, fontsize
self.file.output(self._encode_glyphs([subset_charcode], fonttype),
Op.show)
self.file.output(Op.end_text)
Expand DownExpand Up@@ -2338,7 +2341,9 @@ def output_singlebyte_chunk(kerns_or_chars):
item.ft_object, item.char, item.glyph_index)
if (item.ft_object, subset) != prev_font:
output_singlebyte_chunk(singlebyte_chunk)
ft_name = self.file.fontName(item.ft_object.fname, subset)
font_path = FontPath(item.ft_object.fname,
item.ft_object.face_index)
ft_name = self.file.fontName(font_path, subset)
self.file.output(ft_name, fontsize, Op.selectfont)
self._setup_textpos(item.x, 0, 0, prev_start_x, 0, 0)
prev_font = (item.ft_object, subset)
Expand Down
25 changes: 13 additions & 12 deletionslib/matplotlib/backends/backend_pgf.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,9 +38,17 @@

def _get_preamble():
"""Prepare a LaTeX preamble based on the rcParams configuration."""
font_size_pt = FontProperties(
size=mpl.rcParams["font.size"]
).get_size_in_points()
def _to_fontspec():
for command, family in [("setmainfont", "serif"),
("setsansfont", "sans\\-serif"),
("setmonofont", "monospace")]:
font_path = fm.findfont(family)
path = pathlib.Path(font_path)
yield r" \%s{%s}[Path=\detokenize{%s/}%s]" % (
command, path.name, path.parent.as_posix(),
f',FontIndex={font_path.face_index:d}' if path.suffix == '.ttc' else '')

font_size_pt = FontProperties(size=mpl.rcParams["font.size"]).get_size_in_points()
return "\n".join([
# Remove Matplotlib's custom command \mathdefault. (Not using
# \mathnormal instead since this looks odd with Computer Modern.)
Expand All@@ -63,15 +71,8 @@ def _get_preamble():
*([
r"\ifdefined\pdftexversion\else % non-pdftex case.",
r" \usepackage{fontspec}",
] + [
r" \%s{%s}[Path=\detokenize{%s/}]"
% (command, path.name, path.parent.as_posix())
for command, path in zip(
["setmainfont", "setsansfont", "setmonofont"],
[pathlib.Path(fm.findfont(family))
for family in ["serif", "sans\\-serif", "monospace"]]
)
] + [r"\fi"] if mpl.rcParams["pgf.rcfonts"] else []),
*_to_fontspec(),
r"\fi"] if mpl.rcParams["pgf.rcfonts"] else []),
# Documented as "must come last".
mpl.texmanager._usepackage_if_not_loaded("underscore", option="strings"),
])
Expand Down
12 changes: 4 additions & 8 deletionslib/matplotlib/backends/backend_ps.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -94,7 +94,7 @@ def _font_to_ps_type3(font_path, subset_index, glyph_indices):

Parameters
----------
font_path :path-like
font_path :FontPath
Path to the font to be subsetted.
subset_index : int
The subset of the above font being created.
Expand DownExpand Up@@ -176,7 +176,7 @@ def _font_to_ps_type42(font_path, subset_index, glyph_indices, fh):

Parameters
----------
font_path :path-like
font_path :FontPath
Path to the font to be subsetted.
subset_index : int
The subset of the above font being created.
Expand All@@ -187,12 +187,8 @@ def _font_to_ps_type42(font_path, subset_index, glyph_indices, fh):
"""
_log.debug("SUBSET %s:%d characters: %s", font_path, subset_index, glyph_indices)
try:
kw = {}
# fix this once we support loading more fonts from a collection
# https://github.com/matplotlib/matplotlib/issues/3135#issuecomment-571085541
if font_path.endswith('.ttc'):
kw['fontNumber'] = 0
with (fontTools.ttLib.TTFont(font_path, **kw) as font,
with (fontTools.ttLib.TTFont(font_path.path,
fontNumber=font_path.face_index) as font,
_backend_pdf_ps.get_glyphs_subset(font_path, glyph_indices) as subset):
fontdata = _backend_pdf_ps.font_as_file(subset).getvalue()
_log.debug(
Expand Down
4 changes: 4 additions & 0 deletionslib/matplotlib/dviread.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -719,6 +719,10 @@ def fname(self):
"""A fake filename"""
returnself.texname.decode('latin-1')

@property
defface_index(self):# For compatibility with FT2Font.
return0

def_get_fontmap(self,string):
"""Get the mapping from characters to the font that includes them.
Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp