Skip to content

Use glyph indices for font tracking in vector formats #30335

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

Draft
wants to merge 1 commit into
base: text-overhaul
Choose a base branch
from
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
16 changes: 8 additions & 8 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, GlyphIndexType, float, float]]
rects: list[tuple[float, float, float, float]]

VectorParse.__module__ = "matplotlib.mathtext"
Expand Down Expand Up @@ -132,7 +132,7 @@ 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.glyph_id, 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 @@ -214,7 +214,7 @@ class FontInfo(NamedTuple):
fontsize: float
postscript_name: str
metrics: FontMetrics
num: CharacterCodeType
glyph_id: GlyphIndexType
glyph: Glyph
offset: float

Expand Down Expand Up @@ -375,7 +375,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_id = font.get_char_index(num)
glyph = font.load_glyph(glyph_id, 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 @@ -397,7 +398,7 @@ def _get_info(self, fontname: str, font_class: str, sym: str, fontsize: float,
fontsize=fontsize,
postscript_name=font.postscript_name,
metrics=metrics,
num=num,
glyph_id=glyph_id,
glyph=glyph,
offset=offset
)
Expand Down Expand Up @@ -427,8 +428,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_id, info2.glyph_id,
Kerning.DEFAULT) / 64
return super().get_kern(font1, fontclass1, sym1, fontsize1,
font2, fontclass2, sym2, fontsize2, dpi)
Expand Down
2 changes: 1 addition & 1 deletion 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
27 changes: 14 additions & 13 deletions lib/matplotlib/backends/_backend_pdf_ps.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,27 @@ def _cached_get_afm_from_fname(fname):
return AFM(fh)


def get_glyphs_subset(fontfile, characters):
def get_glyphs_subset(fontfile, glyphs):
"""
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[int]
Set of glyph IDs 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 +71,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,10 +97,10 @@ 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 glyphs that are being used from that
font.
"""

def __init__(self):
Expand All @@ -110,10 +110,11 @@ def track(self, font, s):
"""Record that string *s* is being typeset using font *font*."""
char_to_font = font._get_fontmap(s)
for _c, _f in char_to_font.items():
self.used.setdefault(_f.fname, set()).add(ord(_c))
glyph_index = _f.get_char_index(ord(_c))
self.used.setdefault(_f.fname, set()).add(glyph_index)

def track_glyph(self, font, glyph):
"""Record that codepoint *glyph* is being typeset using font *font*."""
"""Record that glyph index *glyph* is being typeset using font *font*."""
self.used.setdefault(font.fname, set()).add(glyph)


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
78 changes: 38 additions & 40 deletions lib/matplotlib/backends/backend_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -960,9 +960,9 @@ def writeFonts(self):
else:
# a normal TrueType font
_log.debug('Writing TrueType font.')
chars = self._character_tracker.used.get(filename)
if chars:
fonts[Fx] = self.embedTTF(filename, chars)
glyphs = self._character_tracker.used.get(filename)
if glyphs:
fonts[Fx] = self.embedTTF(filename, glyphs)
self.writeObject(self.fontObject, fonts)

def _write_afm_font(self, filename):
Expand Down Expand Up @@ -1136,9 +1136,8 @@ def _get_xobject_glyph_name(self, filename, glyph_name):
end
end"""

def embedTTF(self, filename, characters):
def embedTTF(self, filename, glyphs):
"""Embed the TTF font from the named file into the document."""

font = get_font(filename)
fonttype = mpl.rcParams['pdf.fonttype']

Expand All @@ -1153,7 +1152,7 @@ def cvt(length, upe=font.units_per_EM, nearest=True):
else:
return math.ceil(value)

def embedTTFType3(font, characters, descriptor):
def embedTTFType3(font, glyphs, descriptor):
"""The Type 3-specific part of embedding a Truetype font"""
widthsObject = self.reserveObject('font widths')
fontdescObject = self.reserveObject('font descriptor')
Expand Down Expand Up @@ -1200,15 +1199,13 @@ def get_char_width(charcode):
# Make the "Differences" array, sort the ccodes < 255 from
# the multi-byte ccodes, and build the whole set of glyph ids
# that we need from this font.
glyph_ids = []
differences = []
multi_byte_chars = set()
for c in characters:
ccode = c
gind = font.get_char_index(ccode)
glyph_ids.append(gind)
charmap = {gind: ccode for ccode, gind in font.get_charmap().items()}
for gind in glyphs:
glyph_name = font.get_glyph_name(gind)
if ccode <= 255:
ccode = charmap.get(gind)
if ccode is not None and ccode <= 255:
differences.append((ccode, glyph_name))
else:
multi_byte_chars.add(glyph_name)
Expand All @@ -1222,7 +1219,7 @@ def get_char_width(charcode):
last_c = c

# Make the charprocs array.
rawcharprocs = _get_pdf_charprocs(filename, glyph_ids)
rawcharprocs = _get_pdf_charprocs(filename, glyphs)
charprocs = {}
for charname in sorted(rawcharprocs):
stream = rawcharprocs[charname]
Expand Down Expand Up @@ -1259,7 +1256,7 @@ def get_char_width(charcode):

return fontdictObject

def embedTTFType42(font, characters, descriptor):
def embedTTFType42(font, glyphs, descriptor):
"""The Type 42-specific part of embedding a Truetype font"""
fontdescObject = self.reserveObject('font descriptor')
cidFontDictObject = self.reserveObject('CID font dictionary')
Expand All @@ -1269,9 +1266,8 @@ def embedTTFType42(font, characters, descriptor):
wObject = self.reserveObject('Type 0 widths')
toUnicodeMapObject = self.reserveObject('ToUnicode map')

subset_str = "".join(chr(c) for c in characters)
_log.debug("SUBSET %s characters: %s", filename, subset_str)
with _backend_pdf_ps.get_glyphs_subset(filename, subset_str) as subset:
_log.debug("SUBSET %s characters: %s", filename, glyphs)
with _backend_pdf_ps.get_glyphs_subset(filename, glyphs) as subset:
fontdata = _backend_pdf_ps.font_as_file(subset)
_log.debug(
"SUBSET %s %d -> %d", filename,
Expand Down Expand Up @@ -1319,11 +1315,11 @@ def embedTTFType42(font, characters, descriptor):
cid_to_gid_map = ['\0'] * 65536
widths = []
max_ccode = 0
for c in characters:
ccode = c
gind = font.get_char_index(ccode)
glyph = font.load_char(ccode,
flags=LoadFlags.NO_SCALE | LoadFlags.NO_HINTING)
charmap = {gind: ccode for ccode, gind in font.get_charmap().items()}
for gind in glyphs:
glyph = font.load_glyph(gind,
flags=LoadFlags.NO_SCALE | LoadFlags.NO_HINTING)
ccode = charmap[gind]
widths.append((ccode, cvt(glyph.horiAdvance)))
if ccode < 65536:
cid_to_gid_map[ccode] = chr(gind)
Expand Down Expand Up @@ -1361,11 +1357,10 @@ def embedTTFType42(font, characters, descriptor):
(len(unicode_groups), b"\n".join(unicode_bfrange)))

# Add XObjects for unsupported chars
glyph_ids = []
for ccode in characters:
if not _font_supports_glyph(fonttype, ccode):
gind = full_font.get_char_index(ccode)
glyph_ids.append(gind)
glyph_ids = [
gind for gind in glyphs
if not _font_supports_glyph(fonttype, charmap[gind])
]

bbox = [cvt(x, nearest=False) for x in full_font.bbox]
rawcharprocs = _get_pdf_charprocs(filename, glyph_ids)
Expand Down Expand Up @@ -1450,9 +1445,9 @@ def embedTTFType42(font, characters, descriptor):
}

if fonttype == 3:
return embedTTFType3(font, characters, descriptor)
return embedTTFType3(font, glyphs, descriptor)
elif fonttype == 42:
return embedTTFType42(font, characters, descriptor)
return embedTTFType42(font, glyphs, descriptor)

def alphaState(self, alpha):
"""Return name of an ExtGState that sets alpha to the given value."""
Expand Down Expand Up @@ -2215,28 +2210,32 @@ def draw_mathtext(self, gc, x, y, s, prop, angle):
oldx, oldy = 0, 0
unsupported_chars = []

font_charmaps = {}
self.file.output(Op.begin_text)
for font, fontsize, num, ox, oy in glyphs:
self.file._character_tracker.track_glyph(font, num)
for font, fontsize, glyph_index, ox, oy in glyphs:
self.file._character_tracker.track_glyph(font, glyph_index)
fontname = font.fname
if not _font_supports_glyph(fonttype, num):
if font not in font_charmaps:
font_charmaps[font] = {gind: ccode
for ccode, gind in font.get_charmap().items()}
ccode = font_charmaps[font].get(glyph_index)
if ccode is None or not _font_supports_glyph(fonttype, ccode):
# Unsupported chars (i.e. multibyte in Type 3 or beyond BMP in
# Type 42) must be emitted separately (below).
unsupported_chars.append((font, fontsize, ox, oy, num))
unsupported_chars.append((font, fontsize, ox, oy, glyph_index))
else:
self._setup_textpos(ox, oy, 0, oldx, oldy)
oldx, oldy = ox, oy
if (fontname, fontsize) != prev_font:
self.file.output(self.file.fontName(fontname), fontsize,
Op.selectfont)
prev_font = fontname, fontsize
self.file.output(self.encode_string(chr(num), fonttype),
self.file.output(self.encode_string(chr(ccode), fonttype),
Op.show)
self.file.output(Op.end_text)

for font, fontsize, ox, oy, num in unsupported_chars:
self._draw_xobject_glyph(
font, fontsize, font.get_char_index(num), ox, oy)
for font, fontsize, ox, oy, glyph_index in unsupported_chars:
self._draw_xobject_glyph(font, fontsize, glyph_index, ox, oy)

# Draw any horizontal lines in the math layout
for ox, oy, width, height in rects:
Expand Down Expand Up @@ -2274,7 +2273,7 @@ def draw_tex(self, gc, x, y, s, prop, angle, *, mtext=None):
seq += [['font', pdfname, dvifont.size]]
oldfont = dvifont
seq += [['text', x1, y1, [bytes([glyph])], x1+width]]
self.file._character_tracker.track(dvifont, chr(glyph))
self.file._character_tracker.track_glyph(dvifont, glyph)

# Find consecutive text strings with constant y coordinate and
# combine into a sequence of strings and kerns, or just one
Expand Down Expand Up @@ -2399,7 +2398,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
singlebyte_chunks[-1][2].append(item.char)
prev_was_multibyte = False
else:
multibyte_glyphs.append((item.ft_object, item.x, item.glyph_idx))
multibyte_glyphs.append((item.ft_object, item.x, item.glyph_index))
prev_was_multibyte = True
# Do the rotation and global translation as a single matrix
# concatenation up front
Expand All @@ -2409,7 +2408,6 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
-math.sin(a), math.cos(a),
x, y, Op.concat_matrix)
# Emit all the 1-byte characters in a BT/ET group.

self.file.output(Op.begin_text)
prev_start_x = 0
for ft_object, start_x, kerns_or_chars in singlebyte_chunks:
Expand Down
Loading
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy