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

Skip to content

Create tiny mathtext baseline images using svg with non-embedded fonts. #19201

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

Closed
wants to merge 3 commits into from
Closed
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
7 changes: 4 additions & 3 deletions lib/matplotlib/_mathtext.py
Original file line number Diff line number Diff line change
Expand Up @@ -2158,7 +2158,8 @@ def __init__(self):

p.sqrt <<= Group(
Suppress(Literal(r"\sqrt"))
- ((Optional(p.lbracket + p.int_literal + p.rbracket, default=None)
- ((Group(Optional(
p.lbracket + OneOrMore(~p.rbracket + p.token) + p.rbracket))
+ p.required_group)
| Error("Expected \\sqrt{value}"))
)
Expand Down Expand Up @@ -2864,10 +2865,10 @@ def sqrt(self, s, loc, toks):

# Add the root and shift it upward so it is above the tick.
# The value of 0.6 is a hard-coded hack ;)
if root is None:
if not root:
root = Box(check.width * 0.5, 0., 0.)
else:
root = Hlist([Char(x, state) for x in root])
root = Hlist(root)
root.shrink()
root.shrink()

Expand Down
32 changes: 24 additions & 8 deletions lib/matplotlib/backends/backend_svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from matplotlib.backends.backend_mixed import MixedModeRenderer
from matplotlib.colors import rgb2hex
from matplotlib.dates import UTC
from matplotlib.font_manager import findfont, get_font
from matplotlib.font_manager import findfont, get_font, ttfFontProperty
from matplotlib.ft2font import LOAD_NO_HINTING
from matplotlib.mathtext import MathTextParser
from matplotlib.path import Path
Expand Down Expand Up @@ -94,6 +94,12 @@ def escape_attrib(s):
return s


def _quote_escape_attrib(s):
return ('"' + escape_cdata(s) + '"' if '"' not in s else
"'" + escape_cdata(s) + "'" if "'" not in s else
'"' + escape_attrib(s) + '"')


def short_float_fmt(x):
"""
Create a short string representation of a float, which is %f
Expand Down Expand Up @@ -159,8 +165,8 @@ def start(self, tag, attrib={}, **extra):
for k, v in sorted({**attrib, **extra}.items()):
if v:
k = escape_cdata(k)
v = escape_attrib(v)
self.__write(' %s="%s"' % (k, v))
v = _quote_escape_attrib(v)
self.__write(' %s=%s' % (k, v))
self.__open = 1
return len(self.__tags) - 1

Expand Down Expand Up @@ -1197,11 +1203,21 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):
# Sort the characters by font, and output one tspan for each.
spans = OrderedDict()
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,
'font-style': font.style_name.lower(),
'font-weight': font.style_name.lower()})
entry = ttfFontProperty(font)
font_parts = ['font:']
if entry.style != 'normal':
font_parts.append(entry.style)
if entry.variant != 'normal':
font_parts.append(entry.variant)
if entry.weight != 400:
font_parts.append(f'{entry.weight}')
font_parts.extend([
f'{short_float_fmt(fontsize)}px',
f'{entry.name!r}', # ensure quoting
])
if entry.stretch != 'normal':
font_parts.extend(['; font-stretch:', entry.stretch])
style = ' '.join(font_parts)
if thetext == 32:
thetext = 0xa0 # non-breaking space
spans.setdefault(style, []).append((new_x, -new_y, thetext))
Expand Down
19 changes: 17 additions & 2 deletions lib/matplotlib/testing/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def __call__(self, orig, dest):
# just be reported as a regular exception below).
"DISPLAY": "",
# Do not load any user options.
"INKSCAPE_PROFILE_DIR": os.devnull,
"INKSCAPE_PROFILE_DIR": self._tmpdir.name,
}
# Old versions of Inkscape (e.g. 0.48.3.1) seem to sometimes
# deadlock when stderr is redirected to a pipe, so we redirect it
Expand Down Expand Up @@ -233,6 +233,15 @@ def __del__(self):
self._tmpdir.cleanup()


class _SVGWithMatplotlibFontsConverter(_SVGConverter):
def __call__(self, orig, dest):
if not hasattr(self, "_tmpdir"):
self._tmpdir = TemporaryDirectory()
shutil.copytree(cbook._get_data_path("fonts/ttf"),
Path(self._tmpdir.name, "fonts"))
return super().__call__(orig, dest)


def _update_converter():
try:
mpl._get_executable_info("gs")
Expand All @@ -254,6 +263,7 @@ def _update_converter():
#: extension to png format.
converter = {}
_update_converter()
_svg_with_matplotlib_fonts_converter = _SVGWithMatplotlibFontsConverter()


def comparable_formats():
Expand Down Expand Up @@ -303,7 +313,12 @@ def convert(filename, cache):
return str(newpath)

_log.debug("For %s: converting to png.", filename)
converter[path.suffix[1:]](path, newpath)
convert = converter[path.suffix[1:]]
if path.suffix == ".svg":
contents = path.read_text()
if 'style="font:' in contents: # for svg.fonttype = none.
convert = _svg_with_matplotlib_fonts_converter
convert(path, newpath)

if cache_dir is not None:
_log.debug("For %s: caching conversion result.", filename)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 39 additions & 9 deletions lib/matplotlib/tests/test_mathtext.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import io
import os
from pathlib import Path
import re
import shlex
from xml.etree import ElementTree as ET

import numpy as np
import pytest
Expand Down Expand Up @@ -110,6 +112,9 @@
r'$\left(X\right)_{a}^{b}$', # github issue 7615
r'$\dfrac{\$100.00}{y}$', # github issue #1888
]
svg_only_math_tests = [
r'$\sqrt[ab]{123}$', # github issue #8665
]

digits = "0123456789"
uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Expand Down Expand Up @@ -165,8 +170,6 @@
for set in chars:
font_tests.append(wrapper % set)

font_tests = list(filter(lambda x: x[1] is not None, enumerate(font_tests)))


@pytest.fixture
def baseline_images(request, fontset, index):
Expand All @@ -190,6 +193,32 @@ def test_mathtext_rendering(baseline_images, fontset, index, test):
horizontalalignment='center', verticalalignment='center')


cur_svg_only_math_tests = list(
filter(lambda x: x[1] is not None, enumerate(svg_only_math_tests)))


@pytest.mark.parametrize('index, test', cur_svg_only_math_tests,
ids=[str(idx) for idx, _ in cur_svg_only_math_tests])
@pytest.mark.parametrize('fontset', ['all'])
@pytest.mark.parametrize('baseline_images', ['mathtext1'], indirect=True)
@image_comparison(
baseline_images=None, extensions=['svg'],
savefig_kwarg={
'metadata': { # Minimize svg size.
'Creator': None, 'Date': None, 'Format': None, 'Type': None}})
def test_mathtext_rendering_svg_only(baseline_images, fontset, index, test):
mpl.rcParams['svg.fonttype'] = 'none'
fig = plt.figure(figsize=(5.25, 5.25))
fig.patch.set_visible(False) # Minimize svg size.
fontsets = ['cm', 'stix', 'stixsans', 'dejavusans', 'dejavuserif']
for i, fontset in enumerate(fontsets):
fig.text(0.5, (i + .5) / len(fontsets), test, math_fontfamily=fontset,
horizontalalignment='center', verticalalignment='center')


font_tests = list(filter(lambda x: x[1] is not None, enumerate(font_tests)))


@pytest.mark.parametrize('index, test', font_tests,
ids=[str(index) for index, _ in font_tests])
@pytest.mark.parametrize('fontset',
Expand Down Expand Up @@ -328,7 +357,7 @@ def test_mathtext_fallback_to_cm_invalid():
("stix", ['DejaVu Sans', 'mpltest', 'STIXGeneral'])])
def test_mathtext_fallback(fallback, fontlist):
mpl.font_manager.fontManager.addfont(
os.path.join((os.path.dirname(os.path.realpath(__file__))), 'mpltest.ttf'))
str(Path(__file__).resolve().parent / 'mpltest.ttf'))
mpl.rcParams["svg.fonttype"] = 'none'
mpl.rcParams['mathtext.fontset'] = 'custom'
mpl.rcParams['mathtext.rm'] = 'mpltest'
Expand All @@ -342,12 +371,13 @@ def test_mathtext_fallback(fallback, fontlist):
fig, ax = plt.subplots()
fig.text(.5, .5, test_str, fontsize=40, ha='center')
fig.savefig(buff, format="svg")
char_fonts = [
line.split("font-family:")[-1].split(";")[0]
for line in str(buff.getvalue()).split(r"\n") if "tspan" in line
]
tspans = (ET.fromstring(buff.getvalue())
.findall(".//{http://www.w3.org/2000/svg}tspan[@style]"))
# Getting the last element of the style attrib is a close enough
# approximation for parsing the font property.
char_fonts = [shlex.split(tspan.attrib["style"])[-1] for tspan in tspans]
assert char_fonts == fontlist
mpl.font_manager.fontManager.ttflist = mpl.font_manager.fontManager.ttflist[:-1]
mpl.font_manager.fontManager.ttflist.pop()


def test_math_to_image(tmpdir):
Expand Down