Thanks to visit codestin.com
Credit goes to github.com

Skip to content

re-base of font fallback for pdf and eps output + SVG support #23559

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

Merged
merged 10 commits into from
Aug 11, 2022
Merged
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
14 changes: 10 additions & 4 deletions lib/matplotlib/_text_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


LayoutItem = dataclasses.make_dataclass(
"LayoutItem", ["char", "glyph_idx", "x", "prev_kern"])
"LayoutItem", ["ft_object", "char", "glyph_idx", "x", "prev_kern"])


def warn_on_missing_glyph(codepoint):
Expand Down Expand Up @@ -57,12 +57,18 @@ def layout(string, font, *, kern_mode=KERNING_DEFAULT):
"""
x = 0
prev_glyph_idx = 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))
kern = (font.get_kerning(prev_glyph_idx, glyph_idx, kern_mode) / 64
if prev_glyph_idx is not None else 0.)
kern = (
base_font.get_kerning(prev_glyph_idx, glyph_idx, kern_mode) / 64
if prev_glyph_idx is not None else 0.
)
x += kern
glyph = font.load_glyph(glyph_idx, flags=LOAD_NO_HINTING)
yield LayoutItem(char, glyph_idx, x, kern)
yield LayoutItem(font, char, glyph_idx, x, kern)
x += glyph.linearHoriAdvance / 65536
prev_glyph_idx = glyph_idx
8 changes: 5 additions & 3 deletions lib/matplotlib/backends/_backend_pdf_ps.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ def __init__(self):

def track(self, font, s):
"""Record that string *s* is being typeset using font *font*."""
self.used.setdefault(font.fname, set()).update(map(ord, s))
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*."""
Expand Down Expand Up @@ -135,8 +137,8 @@ def _get_font_afm(self, prop):
return _cached_get_afm_from_fname(fname)

def _get_font_ttf(self, prop):
fname = font_manager.findfont(prop)
font = font_manager.get_font(fname)
fnames = font_manager.fontManager._find_fonts_by_props(prop)
font = font_manager.get_font(fnames)
font.clear()
font.set_size(prop.get_size_in_points(), 72)
return font
70 changes: 44 additions & 26 deletions lib/matplotlib/backends/backend_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
RendererBase)
from matplotlib.backends.backend_mixed import MixedModeRenderer
from matplotlib.figure import Figure
from matplotlib.font_manager import findfont, get_font
from matplotlib.font_manager import (
findfont, get_font, fontManager as _fontManager
)
from matplotlib._afm import AFM
from matplotlib.ft2font import (FIXED_WIDTH, ITALIC, LOAD_NO_SCALE,
LOAD_NO_HINTING, KERNING_UNFITTED, FT2Font)
Expand Down Expand Up @@ -925,20 +927,28 @@ def fontName(self, fontprop):
"""

if isinstance(fontprop, str):
filename = fontprop
filenames = [fontprop]
elif mpl.rcParams['pdf.use14corefonts']:
filename = findfont(
fontprop, fontext='afm', directory=RendererPdf._afm_font_dir)
filenames = _fontManager._find_fonts_by_props(
fontprop, fontext='afm', directory=RendererPdf._afm_font_dir
)
else:
filename = findfont(fontprop)

Fx = self.fontNames.get(filename)
if Fx is None:
Fx = next(self._internal_font_seq)
self.fontNames[filename] = Fx
_log.debug('Assigning font %s = %r', Fx, filename)

return Fx
filenames = _fontManager._find_fonts_by_props(fontprop)
first_Fx = None
for fname in filenames:
Fx = self.fontNames.get(fname)
if not first_Fx:
first_Fx = Fx
if Fx is None:
Fx = next(self._internal_font_seq)
self.fontNames[fname] = Fx
_log.debug('Assigning font %s = %r', Fx, fname)
if not first_Fx:
first_Fx = Fx

# find_fontsprop's first value always adheres to
# findfont's value, so technically no behaviour change
return first_Fx

def dviFontName(self, dvifont):
"""
Expand Down Expand Up @@ -1204,7 +1214,6 @@ def get_char_width(charcode):
width = font.load_char(
s, flags=LOAD_NO_SCALE | LOAD_NO_HINTING).horiAdvance
return cvt(width)

with warnings.catch_warnings():
# Ignore 'Required glyph missing from current font' warning
# from ft2font: here we're just building the widths table, but
Expand Down Expand Up @@ -2389,22 +2398,27 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
# the regular text show command (TJ) with appropriate kerning between
# chunks, whereas multibyte characters use the XObject command (Do).
else:
# List of (start_x, [prev_kern, char, char, ...]), w/o zero kerns.
# List of (ft_object, start_x, [prev_kern, char, char, ...]),
# w/o zero kerns.
singlebyte_chunks = []
# List of (start_x, glyph_index).
# List of (ft_object, start_x, glyph_index).
multibyte_glyphs = []
prev_was_multibyte = True
prev_font = font
for item in _text_helpers.layout(
s, font, kern_mode=KERNING_UNFITTED):
if _font_supports_glyph(fonttype, ord(item.char)):
if prev_was_multibyte:
singlebyte_chunks.append((item.x, []))
if prev_was_multibyte or item.ft_object != prev_font:
singlebyte_chunks.append((item.ft_object, item.x, []))
prev_font = item.ft_object
if item.prev_kern:
singlebyte_chunks[-1][1].append(item.prev_kern)
singlebyte_chunks[-1][1].append(item.char)
singlebyte_chunks[-1][2].append(item.prev_kern)
singlebyte_chunks[-1][2].append(item.char)
prev_was_multibyte = False
else:
multibyte_glyphs.append((item.x, item.glyph_idx))
multibyte_glyphs.append(
(item.ft_object, item.x, item.glyph_idx)
)
prev_was_multibyte = True
# Do the rotation and global translation as a single matrix
# concatenation up front
Expand All @@ -2414,10 +2428,12 @@ 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,
self.file.fontName(prop), fontsize, Op.selectfont)

self.file.output(Op.begin_text)
prev_start_x = 0
for start_x, kerns_or_chars in singlebyte_chunks:
for ft_object, start_x, kerns_or_chars in singlebyte_chunks:
ft_name = self.file.fontName(ft_object.fname)
self.file.output(ft_name, fontsize, Op.selectfont)
self._setup_textpos(start_x, 0, 0, prev_start_x, 0, 0)
self.file.output(
# See pdf spec "Text space details" for the 1000/fontsize
Expand All @@ -2429,8 +2445,10 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
prev_start_x = start_x
self.file.output(Op.end_text)
# Then emit all the multibyte characters, one at a time.
for start_x, glyph_idx in multibyte_glyphs:
self._draw_xobject_glyph(font, fontsize, glyph_idx, start_x, 0)
for ft_object, start_x, glyph_idx in multibyte_glyphs:
self._draw_xobject_glyph(
ft_object, fontsize, glyph_idx, start_x, 0
)
self.file.output(Op.grestore)

def _draw_xobject_glyph(self, font, fontsize, glyph_idx, x, y):
Expand Down
35 changes: 25 additions & 10 deletions lib/matplotlib/backends/backend_ps.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
if mpl.rcParams['ps.useafm']:
font = self._get_font_afm(prop)
scale = 0.001 * prop.get_size_in_points()

stream = []
thisx = 0
last_name = None # kerns returns 0 for None.
xs_names = []
Expand All @@ -647,21 +647,36 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
thisx += kern * scale
xs_names.append((thisx, name))
thisx += width * scale
ps_name = (font.postscript_name
.encode("ascii", "replace").decode("ascii"))
stream.append((ps_name, xs_names))

else:
font = self._get_font_ttf(prop)
font.set_text(s, 0, flags=LOAD_NO_HINTING)
self._character_tracker.track(font, s)
xs_names = [(item.x, font.get_glyph_name(item.glyph_idx))
for item in _text_helpers.layout(s, font)]
stream = []
prev_font = curr_stream = None
for item in _text_helpers.layout(s, font):
ps_name = (item.ft_object.postscript_name
.encode("ascii", "replace").decode("ascii"))
if item.ft_object is not prev_font:
if curr_stream:
stream.append(curr_stream)
prev_font = item.ft_object
curr_stream = [ps_name, []]
curr_stream[1].append(
(item.x, item.ft_object.get_glyph_name(item.glyph_idx))
)
# append the last entry
stream.append(curr_stream)

self.set_color(*gc.get_rgb())
ps_name = (font.postscript_name
.encode("ascii", "replace").decode("ascii"))
self.set_font(ps_name, prop.get_size_in_points())
thetext = "\n".join(f"{x:g} 0 m /{name:s} glyphshow"
for x, name in xs_names)
self._pswriter.write(f"""\

for ps_name, xs_names in stream:
self.set_font(ps_name, prop.get_size_in_points(), False)
thetext = "\n".join(f"{x:g} 0 m /{name:s} glyphshow"
for x, name in xs_names)
self._pswriter.write(f"""\
gsave
{self._get_clip_cmd(gc)}
{x:g} {y:g} translate
Expand Down
3 changes: 2 additions & 1 deletion lib/matplotlib/backends/backend_svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -1130,7 +1130,8 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):
font_parts.append(f'{weight}')
font_parts.extend([
f'{_short_float_fmt(prop.get_size())}px',
f'{prop.get_family()[0]!r}', # ensure quoting
# ensure quoting
f'{", ".join(repr(f) for f in prop.get_family())}',
])
style['font'] = ' '.join(font_parts)
if prop.get_stretch() != 'normal':
Expand Down
1 change: 1 addition & 0 deletions lib/matplotlib/testing/_markers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import pytest

import matplotlib.testing
import matplotlib.testing.compare
from matplotlib import _get_executable_info, ExecutableNotFoundError


Expand Down
Binary file not shown.
Binary file not shown.
Loading