Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
15 changes: 9 additions & 6 deletions lib/matplotlib/_mathtext.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@

if T.TYPE_CHECKING:
from collections.abc import Iterable
from .ft2font import CharacterCodeType, Glyph
from .ft2font import CharacterCodeType, Glyph, GlyphIndexType


ParserElement.enable_packrat()
Expand Down Expand Up @@ -87,7 +87,7 @@ class VectorParse(NamedTuple):
width: float
height: float
depth: float
glyphs: list[tuple[FT2Font, float, CharacterCodeType, float, float]]
glyphs: list[tuple[FT2Font, float, CharacterCodeType, GlyphIndexType, float, float]]
rects: list[tuple[float, float, float, float]]

VectorParse.__module__ = "matplotlib.mathtext"
Expand Down Expand Up @@ -132,7 +132,8 @@ def __init__(self, box: Box):
def to_vector(self) -> VectorParse:
w, h, d = map(
np.ceil, [self.box.width, self.box.height, self.box.depth])
gs = [(info.font, info.fontsize, info.num, ox, h - oy + info.offset)
gs = [(info.font, info.fontsize, info.num, info.glyph_index,
ox, h - oy + info.offset)
for ox, oy, info in self.glyphs]
rs = [(x1, h - y2, x2 - x1, y2 - y1)
for x1, y1, x2, y2 in self.rects]
Expand Down Expand Up @@ -215,6 +216,7 @@ class FontInfo(NamedTuple):
postscript_name: str
metrics: FontMetrics
num: CharacterCodeType
glyph_index: GlyphIndexType
glyph: Glyph
offset: float

Expand Down Expand Up @@ -375,7 +377,8 @@ def _get_info(self, fontname: str, font_class: str, sym: str, fontsize: float,
dpi: float) -> FontInfo:
font, num, slanted = self._get_glyph(fontname, font_class, sym)
font.set_size(fontsize, dpi)
glyph = font.load_char(num, flags=self.load_glyph_flags)
glyph_index = font.get_char_index(num)
glyph = font.load_glyph(glyph_index, flags=self.load_glyph_flags)

xmin, ymin, xmax, ymax = (val / 64 for val in glyph.bbox)
offset = self._get_offset(font, glyph, fontsize, dpi)
Expand All @@ -398,6 +401,7 @@ def _get_info(self, fontname: str, font_class: str, sym: str, fontsize: float,
postscript_name=font.postscript_name,
metrics=metrics,
num=num,
glyph_index=glyph_index,
glyph=glyph,
offset=offset
)
Expand Down Expand Up @@ -427,8 +431,7 @@ def get_kern(self, font1: str, fontclass1: str, sym1: str, fontsize1: float,
info1 = self._get_info(font1, fontclass1, sym1, fontsize1, dpi)
info2 = self._get_info(font2, fontclass2, sym2, fontsize2, dpi)
font = info1.font
return font.get_kerning(font.get_char_index(info1.num),
font.get_char_index(info2.num),
return font.get_kerning(info1.glyph_index, info2.glyph_index,
Kerning.DEFAULT) / 64
return super().get_kern(font1, fontclass1, sym1, fontsize1,
font2, fontclass2, sym2, fontsize2, dpi)
Expand Down
16 changes: 8 additions & 8 deletions lib/matplotlib/_text_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
class LayoutItem:
ft_object: FT2Font
char: str
glyph_idx: GlyphIndexType
glyph_index: GlyphIndexType
x: float
prev_kern: float

Expand Down Expand Up @@ -47,19 +47,19 @@ def layout(string, font, *, kern_mode=Kerning.DEFAULT):
LayoutItem
"""
x = 0
prev_glyph_idx = None
prev_glyph_index = None
char_to_font = font._get_fontmap(string)
base_font = font
for char in string:
# This has done the fallback logic
font = char_to_font.get(char, base_font)
glyph_idx = font.get_char_index(ord(char))
glyph_index = font.get_char_index(ord(char))
kern = (
base_font.get_kerning(prev_glyph_idx, glyph_idx, kern_mode) / 64
if prev_glyph_idx is not None else 0.
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_idx, flags=LoadFlags.NO_HINTING)
yield LayoutItem(font, char, glyph_idx, 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_idx = glyph_idx
prev_glyph_index = glyph_index
149 changes: 128 additions & 21 deletions lib/matplotlib/backends/_backend_pdf_ps.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
Common functionality between the PDF and PS backends.
"""

from __future__ import annotations

from io import BytesIO
import functools
import logging
import typing

from fontTools import subset

Expand All @@ -14,33 +17,38 @@
from ..backend_bases import RendererBase


if typing.TYPE_CHECKING:
from .ft2font import CharacterCodeType, FT2Font, GlyphIndexType
from fontTools.ttLib import TTFont


@functools.lru_cache(50)
def _cached_get_afm_from_fname(fname):
with open(fname, "rb") as fh:
return AFM(fh)


def get_glyphs_subset(fontfile, characters):
def get_glyphs_subset(fontfile: str, glyphs: set[GlyphIndexType]) -> TTFont:
"""
Subset a TTF font
Subset a TTF font.

Reads the named fontfile and restricts the font to the characters.
Reads the named fontfile and restricts the font to the glyphs.

Parameters
----------
fontfile : str
Path to the font file
characters : str
Continuous set of characters to include in subset
glyphs : set[GlyphIndexType]
Set of glyph indices to include in subset.

Returns
-------
fontTools.ttLib.ttFont.TTFont
An open font object representing the subset, which needs to
be closed by the caller.
"""

options = subset.Options(glyph_names=True, recommended_glyphs=True)
options = subset.Options(glyph_names=True, recommended_glyphs=True,
retain_gids=True)

# Prevent subsetting extra tables.
options.drop_tables += [
Expand Down Expand Up @@ -71,7 +79,7 @@ def get_glyphs_subset(fontfile, characters):

font = subset.load_font(fontfile, options)
subsetter = subset.Subsetter(options=options)
subsetter.populate(text=characters)
subsetter.populate(gids=glyphs)
subsetter.subset(font)
return font

Expand All @@ -97,24 +105,123 @@ def font_as_file(font):

class CharacterTracker:
"""
Helper for font subsetting by the pdf and ps backends.
Helper for font subsetting by the PDF and PS backends.

Maintains a mapping of font paths to the set of character codepoints that
are being used from that font.
"""
Maintains a mapping of font paths to the set of characters and glyphs that are being
used from that font.

def __init__(self):
self.used = {}
Attributes
----------
subset_size : int
The size at which characters are grouped into subsets.
used : dict[tuple[str, int], dict[CharacterCodeType, GlyphIndexType]]
A dictionary of font files to character maps. The key is a font filename and
subset within that font. The value is a dictionary mapping a character code to a
glyph index. If *subset_size* is not set, then there will only be one subset per
font filename.
"""

def track(self, font, s):
"""Record that string *s* is being typeset using font *font*."""
def __init__(self, subset_size: int = 0):
"""
Parameters
----------
subset_size : int, optional
The maximum size that is supported for an embedded font. If provided, then
characters will be grouped into these sized subsets.
"""
self.used: dict[tuple[str, int], dict[CharacterCodeType, GlyphIndexType]] = {}
self.subset_size = subset_size

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

Parameters
----------
font : FT2Font
A font that is being used for the provided string.
s : str
The string that should be marked as tracked by the provided font.

Returns
-------
list[tuple[int, CharacterCodeType]]
A list of subset and character code pairs corresponding to the input string.
If a *subset_size* is specified on this instance, then the character code
will correspond with the given subset (and not necessarily the string as a
whole). If *subset_size* is not specified, then the subset will always be 0
and the character codes will be returned from the string unchanged.
"""
font_glyphs = []
char_to_font = font._get_fontmap(s)
for _c, _f in char_to_font.items():
self.used.setdefault(_f.fname, set()).add(ord(_c))

def track_glyph(self, font, glyph):
"""Record that codepoint *glyph* is being typeset using font *font*."""
self.used.setdefault(font.fname, set()).add(glyph)
charcode = ord(_c)
glyph_index = _f.get_char_index(charcode)
if self.subset_size != 0:
subset = charcode // self.subset_size
subset_charcode = charcode % self.subset_size
else:
subset = 0
subset_charcode = charcode
self.used.setdefault((_f.fname, subset), {})[subset_charcode] = glyph_index
font_glyphs.append((subset, subset_charcode))
return font_glyphs

def track_glyph(
self, font: FT2Font, glyph: GlyphIndexType,
charcode: CharacterCodeType | None = None) -> tuple[int, CharacterCodeType]:
"""
Record character code *charcode* at glyph index *glyph* as using font *font*.

Parameters
----------
font : FT2Font
A font that is being used for the provided string.
glyph : GlyphIndexType
The corresponding glyph index to record.
charcode : CharacterCodeType, optional
The character code to record. If not given, assume it's the same as the
glyph index.

Returns
-------
subset : int
The subset in which the returned character code resides. If *subset_size*
was not specified on this instance, then this is always 0.
subset_charcode : CharacterCodeType
The character code within the above subset. If *subset_size* was not
specified on this instance, then this is just *charcode* unmodified.
"""
if charcode is None:
# Assume we don't care, so use a correspondingly unique value.
charcode = typing.cast('CharacterCodeType', glyph)
if self.subset_size != 0:
subset = charcode // self.subset_size
subset_charcode = charcode % self.subset_size
else:
subset = 0
subset_charcode = charcode
self.used.setdefault((font.fname, subset), {})[subset_charcode] = glyph
return (subset, subset_charcode)

def subset_to_unicode(self, index: int,
charcode: CharacterCodeType) -> CharacterCodeType:
"""
Map a subset index and character code to a Unicode character code.

Parameters
----------
index : int
The subset index within a font.
charcode : CharacterCodeType
The character code within a subset to map back.

Returns
-------
CharacterCodeType
The Unicode character code corresponding to the subsetted one.
"""
return index * self.subset_size + charcode


class RendererPDFPSBase(RendererBase):
Expand Down
10 changes: 5 additions & 5 deletions lib/matplotlib/backends/backend_cairo.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import functools
import gzip
import itertools
import math

import numpy as np
Expand Down Expand Up @@ -248,13 +249,12 @@ def _draw_mathtext(self, gc, x, y, s, prop, angle):
if angle:
ctx.rotate(np.deg2rad(-angle))

for font, fontsize, idx, ox, oy in glyphs:
for (font, fontsize), font_glyphs in itertools.groupby(
glyphs, key=lambda info: (info[0], info[1])):
ctx.new_path()
ctx.move_to(ox, -oy)
ctx.select_font_face(
*_cairo_font_args_from_font_prop(ttfFontProperty(font)))
ctx.select_font_face(*_cairo_font_args_from_font_prop(ttfFontProperty(font)))
ctx.set_font_size(self.points_to_pixels(fontsize))
ctx.show_text(chr(idx))
ctx.show_glyphs([(idx, ox, -oy) for _, _, idx, ox, oy in font_glyphs])

for ox, oy, w, h in rects:
ctx.new_path()
Expand Down
Loading
Loading