From 5fd8ae8509338a2ebb755f82d527c28cd631f417 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 31 Dec 2020 16:48:12 +0100 Subject: [PATCH 1/3] Improve font spec for SVG font referencing. This replaces e.g. `"font-family:DejaVu Sans;font-size:12px;font-style:book;font-weight:book;"` by `"font: 400 12px 'DejaVu Sans'"`. Note that the previous font weight was plain wrong... --- lib/matplotlib/backends/backend_svg.py | 32 +++++++++++++++++++------- lib/matplotlib/tests/test_mathtext.py | 17 ++++++++------ 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py index ce6ad0c115f8..468181ae49e9 100644 --- a/lib/matplotlib/backends/backend_svg.py +++ b/lib/matplotlib/backends/backend_svg.py @@ -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 @@ -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 @@ -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 @@ -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)) diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index 8c5250ac5021..ef072c04dbbb 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -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 @@ -328,7 +330,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' @@ -342,12 +344,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): From 15f6905826839c1c1d012f1326a8fa13d516b88f Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 5 Jan 2021 07:23:22 +0100 Subject: [PATCH 2/3] Add machinery for svg-only mathtext tests. --- lib/matplotlib/testing/compare.py | 19 +++++++++++++++-- lib/matplotlib/tests/test_mathtext.py | 30 +++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/testing/compare.py b/lib/matplotlib/testing/compare.py index fb2e55880cde..a91c67ad8d81 100644 --- a/lib/matplotlib/testing/compare.py +++ b/lib/matplotlib/testing/compare.py @@ -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 @@ -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") @@ -254,6 +263,7 @@ def _update_converter(): #: extension to png format. converter = {} _update_converter() +_svg_with_matplotlib_fonts_converter = _SVGWithMatplotlibFontsConverter() def comparable_formats(): @@ -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) diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index ef072c04dbbb..35c43453ceaa 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -112,6 +112,8 @@ r'$\left(X\right)_{a}^{b}$', # github issue 7615 r'$\dfrac{\$100.00}{y}$', # github issue #1888 ] +svg_only_math_tests = [ +] digits = "0123456789" uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -167,8 +169,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): @@ -192,6 +192,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', From f9cea50f40fec605670c7cf8f4844696a5c1a711 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 5 Jan 2021 07:24:03 +0100 Subject: [PATCH 3/3] Support \sqrt[not-a-number]{...}. --- lib/matplotlib/_mathtext.py | 7 ++- .../test_mathtext/mathtext1_all_00.svg | 63 +++++++++++++++++++ lib/matplotlib/tests/test_mathtext.py | 1 + 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_all_00.svg diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index 96a69919c86b..45e51f53984d 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -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}")) ) @@ -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() diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_all_00.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_all_00.svg new file mode 100644 index 000000000000..4884f97924ed --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_all_00.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + ab + p + 123 + + + + + + + + + ab + √123 + + + + + + + + + 𝘢𝘣 + √𝟣𝟤𝟥 + + + + + + + + + ab + + 123 + + + + + + + + + ab + + 123 + + + + + + diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index 35c43453ceaa..88184e37218d 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -113,6 +113,7 @@ r'$\dfrac{\$100.00}{y}$', # github issue #1888 ] svg_only_math_tests = [ + r'$\sqrt[ab]{123}$', # github issue #8665 ] digits = "0123456789" 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