Skip to content

Deprecate various vector-backend-specific mathtext helpers. #18002

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 1 commit into from
Aug 2, 2020
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
11 changes: 11 additions & 0 deletions doc/api/next_api_changes/deprecations/18002-AL.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Deprecation of various mathtext helpers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``MathtextBackendPdf``, ``MathtextBackendPs``, ``MathtextBackendSvg``,
and ``MathtextBackendCairo`` classes from the :mod:`.mathtext` module, as
well as the corresponding ``.mathtext_parser`` attributes on ``RendererPdf``,
``RendererPS``, ``RendererSVG``, and ``RendererCairo``, are deprecated. The
``MathtextBackendPath`` class can be used to obtain a list of glyphs and
rectangles in a mathtext expression, and renderer-specific logic should be
directly implemented in the renderer.

``StandardPsFonts.pswriter`` is unused and deprecated.
4 changes: 4 additions & 0 deletions lib/matplotlib/afm.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,10 @@ def get_fontname(self):
"""Return the font name, e.g., 'Times-Roman'."""
return self._header[b'FontName']

@property
def postscript_name(self): # For consistency with FT2Font.
return self.get_fontname()

def get_fullname(self):
"""Return the font full name, e.g., 'Times-Roman'."""
name = self._header.get(b'FullName')
Expand Down
8 changes: 7 additions & 1 deletion lib/matplotlib/backends/_backend_pdf_ps.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def track(self, font, s):
fname = font.fname
self.used.setdefault(fname, set()).update(map(ord, s))

# Not public, can be removed when pdf/ps merge_used_characters is removed.
def merge(self, other):
"""Update self with a font path to character codepoints."""
for fname, charset in other.items():
Expand Down Expand Up @@ -87,7 +88,12 @@ def get_text_width_height_descent(self, s, prop, ismath):
s, fontsize, renderer=self)
return w, h, d
elif ismath:
parse = self.mathtext_parser.parse(s, 72, prop)
# Circular import.
from matplotlib.backends.backend_ps import RendererPS
parse = self._text2path.mathtext_parser.parse(
s, 72, prop,
_force_standard_ps_fonts=(isinstance(self, RendererPS)
and mpl.rcParams["ps.useafm"]))
return parse.width, parse.height, parse.depth
elif mpl.rcParams[self._use_afm_rc_name]:
font = self._get_font_afm(prop)
Expand Down
24 changes: 14 additions & 10 deletions lib/matplotlib/backends/backend_cairo.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,13 @@ def __init__(self, dpi):
self.gc = GraphicsContextCairo(renderer=self)
self.text_ctx = cairo.Context(
cairo.ImageSurface(cairo.FORMAT_ARGB32, 1, 1))
self.mathtext_parser = MathTextParser('Cairo')
RendererBase.__init__(self)

@cbook.deprecated("3.4")
@property
def mathtext_parser(self):
return MathTextParser('Cairo')

def set_ctx_from_surface(self, surface):
self.gc.ctx = cairo.Context(surface)
# Although it may appear natural to automatically call
Expand Down Expand Up @@ -254,26 +258,25 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):

def _draw_mathtext(self, gc, x, y, s, prop, angle):
ctx = gc.ctx
width, height, descent, glyphs, rects = self.mathtext_parser.parse(
s, self.dpi, prop)
width, height, descent, glyphs, rects = \
self._text2path.mathtext_parser.parse(s, self.dpi, prop)

ctx.save()
ctx.translate(x, y)
if angle:
ctx.rotate(np.deg2rad(-angle))

for font, fontsize, s, ox, oy in glyphs:
for font, fontsize, idx, ox, oy in glyphs:
ctx.new_path()
ctx.move_to(ox, oy)

ctx.move_to(ox, -oy)
ctx.select_font_face(
*_cairo_font_args_from_font_prop(ttfFontProperty(font)))
ctx.set_font_size(fontsize * self.dpi / 72)
ctx.show_text(s)
ctx.show_text(chr(idx))

for ox, oy, w, h in rects:
ctx.new_path()
ctx.rectangle(ox, oy, w, h)
ctx.rectangle(ox, -oy, w, -h)
ctx.set_source_rgb(0, 0, 0)
ctx.fill_preserve()

Expand All @@ -290,8 +293,9 @@ def get_text_width_height_descent(self, s, prop, ismath):
return super().get_text_width_height_descent(s, prop, ismath)

if ismath:
dims = self.mathtext_parser.parse(s, self.dpi, prop)
return dims[0:3] # return width, height, descent
width, height, descent, *_ = \
self._text2path.mathtext_parser.parse(s, self.dpi, prop)
return width, height, descent

ctx = self.text_ctx
# problem - scale remembers last setting and font can become
Expand Down
19 changes: 13 additions & 6 deletions lib/matplotlib/backends/backend_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -1772,9 +1772,13 @@ def __init__(self, file, image_dpi, height, width):
super().__init__(width, height)
self.file = file
self.gc = self.new_gc()
self.mathtext_parser = MathTextParser("Pdf")
self.image_dpi = image_dpi

@cbook.deprecated("3.4")
@property
def mathtext_parser(self):
return MathTextParser("Pdf")

def finalize(self):
self.file.output(*self.gc.finalize())

Expand Down Expand Up @@ -2020,9 +2024,8 @@ def _setup_textpos(self, x, y, angle, oldx=0, oldy=0, oldangle=0):

def draw_mathtext(self, gc, x, y, s, prop, angle):
# TODO: fix positioning and encoding
width, height, descent, glyphs, rects, used_characters = \
self.mathtext_parser.parse(s, 72, prop)
self.file._character_tracker.merge(used_characters)
width, height, descent, glyphs, rects = \
self._text2path.mathtext_parser.parse(s, 72, prop)

# When using Type 3 fonts, we can't use character codes higher
# than 255, so we use the "Do" command to render those
Expand All @@ -2040,7 +2043,9 @@ def draw_mathtext(self, gc, x, y, s, prop, angle):
self.file.output(Op.begin_text)
prev_font = None, None
oldx, oldy = 0, 0
for ox, oy, fontname, fontsize, num, symbol_name in glyphs:
for font, fontsize, num, ox, oy in glyphs:
self.file._character_tracker.track(font, chr(num))
fontname = font.fname
if is_opentype_cff_font(fontname):
fonttype = 42
else:
Expand All @@ -2060,7 +2065,8 @@ def draw_mathtext(self, gc, x, y, s, prop, angle):
# If using Type 3 fonts, render all of the multi-byte characters
# as XObjects using the 'Do' command.
if global_fonttype == 3:
for ox, oy, fontname, fontsize, num, symbol_name in glyphs:
for font, fontsize, num, ox, oy in glyphs:
fontname = font.fname
if is_opentype_cff_font(fontname):
fonttype = 42
else:
Expand All @@ -2072,6 +2078,7 @@ def draw_mathtext(self, gc, x, y, s, prop, angle):
0.001 * fontsize, 0,
0, 0.001 * fontsize,
ox, oy, Op.concat_matrix)
symbol_name = font.get_glyph_name(font.get_char_index(num))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some fallbacks; do we need to worry about those here:

if font is not None:
gid = font.get_char_index(num)
if gid != 0:
symbol_name = font.get_glyph_name(gid)
if symbol_name is None:
return self._stix_fallback._get_glyph(
fontname, font_class, sym, fontsize, math)

Copy link
Contributor Author

@anntzer anntzer Jul 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because _stix_fallback._get_glyph(...) will return a different font object (for which the glyph exists). This can be checked e.g. with

rcParams["mathtext.fontset"] = "cm"; figtext(.5, .5, "$\\perp$"); savefig("/tmp/out.pdf")

adding a print to check that fallback occurs. (Actually it looks like this should not fallback as there's a \perp sign in cmsy10.ttf? but that's a separate issue.)

name = self.file._get_xobject_symbol_name(
fontname, symbol_name)
self.file.output(Name(name), Op.use_xobject)
Expand Down
44 changes: 32 additions & 12 deletions lib/matplotlib/backends/backend_ps.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import matplotlib as mpl
from matplotlib import cbook, _path
from matplotlib import _text_layout
from matplotlib.afm import AFM
from matplotlib.backend_bases import (
_Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
GraphicsContextBase, RendererBase)
Expand Down Expand Up @@ -167,7 +168,11 @@ def __init__(self, width, height, pswriter, imagedpi=72):
self._path_collection_id = 0

self._character_tracker = _backend_pdf_ps.CharacterTracker()
self.mathtext_parser = MathTextParser("PS")

@cbook.deprecated("3.3")
@property
def mathtext_parser(self):
return MathTextParser("PS")

@cbook.deprecated("3.3")
@property
Expand Down Expand Up @@ -599,18 +604,33 @@ def draw_mathtext(self, gc, x, y, s, prop, angle):
if debugPS:
self._pswriter.write("% mathtext\n")

width, height, descent, pswriter, used_characters = \
self.mathtext_parser.parse(s, 72, prop)
self._character_tracker.merge(used_characters)
width, height, descent, glyphs, rects = \
self._text2path.mathtext_parser.parse(
s, 72, prop,
_force_standard_ps_fonts=mpl.rcParams["ps.useafm"])
self.set_color(*gc.get_rgb())
thetext = pswriter.getvalue()
self._pswriter.write(f"""\
gsave
{x:f} {y:f} translate
{angle:f} rotate
{thetext}
grestore
""")
self._pswriter.write(
f"gsave\n"
f"{x:f} {y:f} translate\n"
f"{angle:f} rotate\n")
lastfont = None
for font, fontsize, num, ox, oy in glyphs:
self._character_tracker.track(font, chr(num))
if (font.postscript_name, fontsize) != lastfont:
lastfont = font.postscript_name, fontsize
self._pswriter.write(
f"/{font.postscript_name} findfont\n"
f"{fontsize} scalefont\n"
f"setfont\n")
symbol_name = (
font.get_name_char(chr(num)) if isinstance(font, AFM) else
font.get_glyph_name(font.get_char_index(num)))
self._pswriter.write(
f"{ox:f} {oy:f} moveto\n"
f"/{symbol_name} glyphshow\n")
for ox, oy, w, h in rects:
self._pswriter.write(f"{ox} {oy} {w} {h} rectfill\n")
self._pswriter.write("grestore\n")

def draw_gouraud_triangle(self, gc, points, colors, trans):
self.draw_gouraud_triangles(gc, points.reshape((1, 3, 2)),
Expand Down
44 changes: 22 additions & 22 deletions lib/matplotlib/backends/backend_svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,6 @@ def __init__(self, width, height, svgwriter, basename=None, image_dpi=72,
self._has_gouraud = False
self._n_gradients = 0
self._fonts = OrderedDict()
self.mathtext_parser = MathTextParser('SVG')

RendererBase.__init__(self)
self._glyph_map = dict()
Expand All @@ -312,6 +311,11 @@ def __init__(self, width, height, svgwriter, basename=None, image_dpi=72,
self._write_metadata(metadata)
self._write_default_style()

@cbook.deprecated("3.4")
@property
def mathtext_parser(self):
return MathTextParser('SVG')

def finalize(self):
self._write_clips()
self._write_hatches()
Expand Down Expand Up @@ -1173,26 +1177,23 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):
else:
writer.comment(s)

width, height, descent, svg_elements, used_characters = \
self.mathtext_parser.parse(s, 72, prop)
svg_glyphs = svg_elements.svg_glyphs
svg_rects = svg_elements.svg_rects

attrib = {}
attrib['style'] = generate_css(style)
attrib['transform'] = generate_transform([
('translate', (x, y)),
('rotate', (-angle,))])
width, height, descent, glyphs, rects = \
self._text2path.mathtext_parser.parse(s, 72, prop)

# Apply attributes to 'g', not 'text', because we likely have some
# rectangles as well with the same style and transformation.
writer.start('g', attrib=attrib)
writer.start('g',
style=generate_css(style),
transform=generate_transform([
('translate', (x, y)),
('rotate', (-angle,))]),
)

writer.start('text')

# Sort the characters by font, and output one tspan for each.
spans = OrderedDict()
for font, fontsize, thetext, new_x, new_y, metrics in svg_glyphs:
for font, fontsize, thetext, new_x, new_y in glyphs:
style = generate_css({
'font-size': short_float_fmt(fontsize) + 'px',
'font-family': font.family_name,
Expand Down Expand Up @@ -1223,15 +1224,14 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):

writer.end('text')

if len(svg_rects):
for x, y, width, height in svg_rects:
writer.element(
'rect',
x=short_float_fmt(x),
y=short_float_fmt(-y + height),
width=short_float_fmt(width),
height=short_float_fmt(height)
)
for x, y, width, height in rects:
writer.element(
'rect',
x=short_float_fmt(x),
y=short_float_fmt(-y-1),
width=short_float_fmt(width),
height=short_float_fmt(height)
)

writer.end('g')

Expand Down
Loading