From b96d5ecb62c434c72eb48530ab48ab0d8b33dc1b Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 16 May 2021 08:41:38 -0400 Subject: [PATCH 1/6] Start to split off formatting routines.. from builtin --- mathics/builtin/drawing/graphics3d.py | 5 +- mathics/builtin/graphics.py | 186 ++------------ mathics/core/definitions.py | 2 +- mathics/core/formatter.py | 10 + mathics/formatter/__init__.py | 0 mathics/formatter/svg.py | 333 ++++++++++++++++++++++++++ setup.py | 1 + 7 files changed, 362 insertions(+), 175 deletions(-) create mode 100644 mathics/core/formatter.py create mode 100644 mathics/formatter/__init__.py create mode 100644 mathics/formatter/svg.py diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index f7e9df4477..efbf2c4cbf 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ -Graphics (3D) +Three-Dimensional Graphics """ @@ -778,9 +778,6 @@ def __init__(self, content, evaluation, neg_y=False): def extent(self, completely_visible_only=False): return total_extent_3d([element.extent() for element in self.elements]) - def to_svg(self): - return "\n".join(element.to_svg() for element in self.elements) - def to_asy(self): return "\n".join([element.to_asy() for element in self.elements]) diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index fc0c9a0302..a4c546d5b9 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -7,7 +7,6 @@ from math import floor, ceil, log10, sin, cos, pi, sqrt, atan2, degrees, radians, exp -import json import base64 from itertools import chain @@ -33,6 +32,8 @@ system_symbols_dict, from_python, ) +from mathics.core.formatter import lookup_method + from mathics.builtin.drawing.colors import convert as convert_color from mathics.core.numbers import machine_epsilon @@ -323,7 +324,11 @@ def _extract_graphics(graphics, format, evaluation): if format == "asy": code = "\n".join(element.to_asy() for element in elements.elements) elif format == "svg": - code = elements.to_svg() + format_fn = lookup_method(elements, "svg") + if format_fn is not None: + code = format_fn(elements) + else: + code = elements.to_svg() else: raise NotImplementedError @@ -1231,26 +1236,6 @@ def extent(self): ) return result - def to_svg(self, offset=None): - l = self.style.get_line_width(face_element=True) - x1, y1 = self.p1.pos() - x2, y2 = self.p2.pos() - xmin = min(x1, x2) - ymin = min(y1, y2) - w = max(x1, x2) - xmin - h = max(y1, y2) - ymin - if offset: - x1, x2 = x1 + offset[0], x2 + offset[0] - y1, y2 = y1 + offset[1], y2 + offset[1] - style = create_css(self.edge_color, self.face_color, l) - return '' % ( - xmin, - ymin, - w, - h, - style, - ) - def to_asy(self): l = self.style.get_line_width(face_element=True) x1, y1 = self.p1.pos() @@ -1302,21 +1287,6 @@ def extent(self): ry += l return [(x - rx, y - ry), (x - rx, y + ry), (x + rx, y - ry), (x + rx, y + ry)] - def to_svg(self, offset=None): - x, y = self.c.pos() - rx, ry = self.r.pos() - rx -= x - ry = y - ry - l = self.style.get_line_width(face_element=self.face_element) - style = create_css(self.edge_color, self.face_color, stroke_width=l) - return '' % ( - x, - y, - rx, - ry, - style, - ) - def to_asy(self): x, y = self.c.pos() rx, ry = self.r.pos() @@ -1390,11 +1360,15 @@ def _arc_params(self): return x, y, abs(rx), abs(ry), sx, sy, ex, ey, large_arc def to_svg(self, offset=None): + # FIXME: figure out how to put in svg.py if self.arc is None: return super(_ArcBox, self).to_svg(offset) x, y, rx, ry, sx, sy, ex, ey, large_arc = self._arc_params() + format_fn = lookup_method(self, "svg") + if format_fn is not None: + return format_fn(self, offset) def path(closed): if closed: yield "M %f,%f" % (x, y) @@ -1522,26 +1496,6 @@ def init(self, graphics, style, item=None): else: raise BoxConstructError - def to_svg(self, offset=None): - point_size, _ = self.style.get_style(PointSize, face_element=False) - if point_size is None: - point_size = PointSize(self.graphics, value=0.005) - size = point_size.get_size() - - style = create_css( - edge_color=self.edge_color, stroke_width=0, face_color=self.face_color - ) - svg = "" - for line in self.lines: - for coords in line: - svg += '' % ( - coords.pos()[0], - coords.pos()[1], - size, - style, - ) - return svg - def to_asy(self): pen = create_pens(face_color=self.face_color, is_face_element=False) @@ -1586,17 +1540,6 @@ def init(self, graphics, style, item=None, lines=None): else: raise BoxConstructError - def to_svg(self, offset=None): - l = self.style.get_line_width(face_element=False) - style = create_css(edge_color=self.edge_color, stroke_width=l) - svg = "" - for line in self.lines: - svg += '' % ( - " ".join(["%f,%f" % coords.pos() for coords in line]), - style, - ) - return svg - def to_asy(self): l = self.style.get_line_width(face_element=False) pen = create_pens(edge_color=self.edge_color, stroke_width=l) @@ -1741,16 +1684,6 @@ def init(self, graphics, style, item, options): raise BoxConstructError self.spline_degree = spline_degree.get_int_value() - def to_svg(self, offset=None): - l = self.style.get_line_width(face_element=False) - style = create_css(edge_color=self.edge_color, stroke_width=l) - - svg = "" - for line in self.lines: - s = " ".join(_svg_bezier((self.spline_degree, [xy.pos() for xy in line]))) - svg += '' % (s, style) - return svg - def to_asy(self): l = self.style.get_line_width(face_element=False) pen = create_pens(edge_color=self.edge_color, stroke_width=l) @@ -1828,22 +1761,6 @@ def parse_component(segments): else: raise BoxConstructError - def to_svg(self, offset=None): - l = self.style.get_line_width(face_element=False) - style = create_css( - edge_color=self.edge_color, face_color=self.face_color, stroke_width=l - ) - - def components(): - for component in self.components: - transformed = [(k, [xy.pos() for xy in p]) for k, p in component] - yield " ".join(_svg_bezier(*transformed)) + " Z" - - return '' % ( - " ".join(components()), - style, - ) - def to_asy(self): l = self.style.get_line_width(face_element=False) pen = create_pens(edge_color=self.edge_color, stroke_width=l) @@ -1933,32 +1850,6 @@ def process_option(self, name, value): else: raise BoxConstructError - def to_svg(self, offset=None): - l = self.style.get_line_width(face_element=True) - if self.vertex_colors is None: - face_color = self.face_color - else: - face_color = None - style = create_css( - edge_color=self.edge_color, face_color=face_color, stroke_width=l - ) - svg = "" - if self.vertex_colors is not None: - mesh = [] - for index, line in enumerate(self.lines): - data = [ - [coords.pos(), color.to_js()] - for coords, color in zip(line, self.vertex_colors[index]) - ] - mesh.append(data) - svg += '' % json.dumps(mesh) - for line in self.lines: - svg += '' % ( - " ".join("%f,%f" % coords.pos() for coords in line), - style, - ) - return svg - def to_asy(self): l = self.style.get_line_width(face_element=True) if self.vertex_colors is None: @@ -2514,23 +2405,6 @@ def draw(px, py, vx, vy, t1, s): return make - def to_svg(self, offset=None): - width = self.style.get_line_width(face_element=False) - style = create_css(edge_color=self.edge_color, stroke_width=width) - polyline = self.curve.make_draw_svg(style) - - arrow_style = create_css(face_color=self.edge_color, stroke_width=width) - - def polygon(points): - yield '' % arrow_style - - extent = self.graphics.view_width or 0 - default_arrow = self._default_arrow(polygon) - custom_arrow = self._custom_arrow("svg", _SVGTransform) - return "".join(self._draw(polyline, default_arrow, custom_arrow, extent)) - def to_asy(self): width = self.style.get_line_width(face_element=False) pen = create_pens(edge_color=self.edge_color, stroke_width=width) @@ -2617,35 +2491,6 @@ def extent(self): y = p[1] - h / 2.0 + opos[1] * h / 2.0 return [(x, y), (x + w, y + h)] - def to_svg(self, offset=None): - x, y = self.pos.pos() - if offset: - x = x + offset[0] - y = y + offset[1] - - if hasattr(self.content, "to_svg"): - content = self.content.to_svg(noheader=True, offset=(x, y)) - svg = "\n" + content + "\n" - else: - css_style = create_css( - font_color=self.color, - edge_color=self.color, - face_color=self.color, - opacity=self.opacity, - ) - text_pos_opts = f'x="{x}" y="{y}" ox="{self.opos[0]}" oy="{self.opos[1]}"' - # FIXME: don't hard code text_style_opts, but allow these to be adjustable. - text_style_opts = "text-anchor:middle; dominant-baseline:middle;" - content = self.content.boxes_to_text(evaluation=self.graphics.evaluation) - svg = f'{content}' - - # content = self.content.boxes_to_mathml(evaluation=self.graphics.evaluation) - # style = create_css(font_color=self.color) - # svg = ( - # '' - # "%s") - - return svg def to_asy(self): x, y = self.pos.pos() @@ -2960,9 +2805,6 @@ def extent(self, completely_visible_only=False): ymax *= 2 return xmin, xmax, ymin, ymax - def to_svg(self, offset=None): - return "\n".join(element.to_svg(offset) for element in self.elements) - def to_asy(self): return "\n".join(element.to_asy() for element in self.elements) @@ -3265,7 +3107,11 @@ def to_svg(self, leaves=None, **options): elements.view_width = w - svg = elements.to_svg(offset=options.get("offset", None)) + format_fn = lookup_method(elements, "svg") + if format_fn is not None: + svg = format_fn(elements, offset=options.get("offset", None)) + else: + svg = elements.to_svg(offset=options.get("offset", None)) if self.background_color is not None: svg = '%s' % ( diff --git a/mathics/core/definitions.py b/mathics/core/definitions.py index 19f47fb9da..57d0228be8 100644 --- a/mathics/core/definitions.py +++ b/mathics/core/definitions.py @@ -84,7 +84,6 @@ def __init__( if add_builtin: from mathics.builtin import modules, contribute - from mathics.core.evaluation import Evaluation from mathics.settings import ROOT_DIR loaded = False @@ -126,6 +125,7 @@ def __init__( self.builtin.update(self.user) self.user = {} self.clear_cache() + import mathics.formatter.svg def load_pymathics_module(self, module, remove_on_quit=True): """ diff --git a/mathics/core/formatter.py b/mathics/core/formatter.py new file mode 100644 index 0000000000..1302bdf87f --- /dev/null +++ b/mathics/core/formatter.py @@ -0,0 +1,10 @@ + # key is str: to_xxx name, value is formatter function to call +format2fn = {} +import inspect + +def lookup_method(self, format: str): + for cls in inspect.getmro(type(self)): + format_fn = format2fn.get(("svg", cls), None) + if format_fn is not None: + return format_fn + raise RuntimeError(f"Can't find formatter {format} for {type(self)}") diff --git a/mathics/formatter/__init__.py b/mathics/formatter/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/mathics/formatter/svg.py b/mathics/formatter/svg.py new file mode 100644 index 0000000000..f7aa1f35d9 --- /dev/null +++ b/mathics/formatter/svg.py @@ -0,0 +1,333 @@ +# -*- coding: utf-8 -*- +import json + +""" +Format a Mathics object as an SVG string +""" + +from mathics.builtin.drawing.graphics3d import Graphics3DElements +from mathics.builtin.graphics import ( + # _ArcBox, + ArrowBox, + BezierCurveBox, + FilledCurveBox, + # GraphicsBox, + GraphicsElements, + InsetBox, + LineBox, + PointBox, + PointSize, + PolygonBox, + RectangleBox, + _RoundBox, + _svg_bezier, + _SVGTransform, +) + +from mathics.core.formatter import format2fn, lookup_method + + +def create_css( + edge_color=None, face_color=None, stroke_width=None, font_color=None, opacity=1.0 +): + css = [] + if edge_color is not None: + color, stroke_opacity = edge_color.to_css() + css.append("stroke: %s" % color) + css.append("stroke-opacity: %s" % stroke_opacity) + else: + css.append("stroke: none") + if stroke_width is not None: + css.append("stroke-width: %fpx" % stroke_width) + if face_color is not None: + color, fill_opacity = face_color.to_css() + css.append("fill: %s" % color) + css.append("fill-opacity: %s" % fill_opacity) + else: + css.append("fill: none") + if font_color is not None: + color, _ = font_color.to_css() + css.append("color: %s" % color) + css.append("opacity: %s" % opacity) + return "; ".join(css) + + +# def ArcBox_svg(self, offset=None): +# if self.arc is None: +# raise RuntimeError(f"{self}.arc should not be none") + +# x, y, rx, ry, sx, sy, ex, ey, large_arc = self._arc_params() + +# def path(closed): +# if closed: +# yield "M %f,%f" % (x, y) +# yield "L %f,%f" % (sx, sy) +# else: +# yield "M %f,%f" % (sx, sy) + +# yield "A %f,%f,0,%d,0,%f,%f" % (rx, ry, large_arc, ex, ey) + +# if closed: +# yield "Z" + +# l = self.style.get_line_width(face_element=self.face_element) +# style = create_css(self.edge_color, self.face_color, stroke_width=l) +# return '' % (" ".join(path(self.face_element)), style) + + +def ArrowBox_svg(self, offset=None): + width = self.style.get_line_width(face_element=False) + style = create_css(edge_color=self.edge_color, stroke_width=width) + polyline = self.curve.make_draw_svg(style) + + arrow_style = create_css(face_color=self.edge_color, stroke_width=width) + + def polygon(points): + yield '' % arrow_style + + extent = self.graphics.view_width or 0 + default_arrow = self._default_arrow(polygon) + custom_arrow = self._custom_arrow("svg", _SVGTransform) + return "".join(self._draw(polyline, default_arrow, custom_arrow, extent)) + +def BezierCurveBox_svg(self, offset=None): + l = self.style.get_line_width(face_element=False) + style = create_css(edge_color=self.edge_color, stroke_width=l) + + svg = "" + for line in self.lines: + s = " ".join(_svg_bezier((self.spline_degree, [xy.pos() for xy in line]))) + svg += '' % (s, style) + # print("XXX bezier", svg) + return svg + +def FilledCurveBox_svg(self, offset=None): + l = self.style.get_line_width(face_element=False) + style = create_css( + edge_color=self.edge_color, face_color=self.face_color, stroke_width=l + ) + + def components(): + for component in self.components: + transformed = [(k, [xy.pos() for xy in p]) for k, p in component] + yield " ".join(_svg_bezier(*transformed)) + " Z" + + # print("XXX FilledcurveBox", components) + return '' % ( + " ".join(components()), + style, + ) + +# def GraphicsBox_svg(self, leaves=None, **options) -> str: +# if not leaves: +# leaves = self._leaves + +# data = options.get("data", None) +# if data: +# elements, xmin, xmax, ymin, ymax, w, h, width, height = data +# else: +# elements, calc_dimensions = self._prepare_elements( +# leaves, options, neg_y=True +# ) +# xmin, xmax, ymin, ymax, w, h, width, height = calc_dimensions() + +# elements.view_width = w + +# format_fn = lookup_method(elements, "svg") +# if format_fn is not None: +# svg = format_fn(elements) +# else: +# svg = elements.to_svg(offset=options.get("offset", None)) + +# if self.background_color is not None: +# svg = '%s' % ( +# xmin, +# ymin, +# w, +# h, +# self.background_color.to_css()[0], +# svg, +# ) + +# xmin -= 1 +# ymin -= 1 +# w += 2 +# h += 2 + +# if options.get("noheader", False): +# return svg +# svg = """ +# +# %s +# +# """ % ( +# " ".join("%f" % t for t in (xmin, ymin, w, h)), +# svg, +# ) +# return svg # , width, height + +def GraphicsElements_svg(self, offset=None): + result = [] + for element in self.elements: + format_fn = lookup_method(element, "svg") + if format_fn is not None: + result.append(format_fn(element, offset)) + else: + result.append(element.to_svg(offset)) + + return "\n".join(result) + +Graphic3DElements_svg = GraphicsElements_svg + +def InsetBox_svg(self, offset=None): + x, y = self.pos.pos() + if offset: + x = x + offset[0] + y = y + offset[1] + + if hasattr(self.content, "to_svg"): + content = self.content.to_svg(noheader=True, offset=(x, y)) + svg = "\n" + content + "\n" + else: + css_style = create_css( + font_color=self.color, + edge_color=self.color, + face_color=self.color, + opacity=self.opacity, + ) + text_pos_opts = f'x="{x}" y="{y}" ox="{self.opos[0]}" oy="{self.opos[1]}"' + # FIXME: don't hard code text_style_opts, but allow these to be adjustable. + text_style_opts = "text-anchor:middle; dominant-baseline:middle;" + content = self.content.boxes_to_text(evaluation=self.graphics.evaluation) + svg = f'{content}' + + # content = self.content.boxes_to_mathml(evaluation=self.graphics.evaluation) + # style = create_css(font_color=self.color) + # svg = ( + # '' + # "%s") + + return svg + +def LineBox_svg(self, offset=None): + l = self.style.get_line_width(face_element=False) + style = create_css(edge_color=self.edge_color, stroke_width=l) + svg = "" + for line in self.lines: + svg += '' % ( + " ".join(["%f,%f" % coords.pos() for coords in line]), + style, + ) + # print("XXX linebox", svg) + return svg + + +def PointBox_svg(self, offset=None): + point_size, _ = self.style.get_style(PointSize, face_element=False) + if point_size is None: + point_size = PointSize(self.graphics, value=0.005) + size = point_size.get_size() + + style = create_css( + edge_color=self.edge_color, stroke_width=0, face_color=self.face_color + ) + svg = "" + for line in self.lines: + for coords in line: + svg += '' % ( + coords.pos()[0], + coords.pos()[1], + size, + style, + ) + # print("XXX PointBox", svg) + return svg + +def PolygonBox_svg(self, offset=None): + l = self.style.get_line_width(face_element=True) + if self.vertex_colors is None: + face_color = self.face_color + else: + face_color = None + style = create_css( + edge_color=self.edge_color, face_color=face_color, stroke_width=l + ) + svg = "" + if self.vertex_colors is not None: + mesh = [] + for index, line in enumerate(self.lines): + data = [ + [coords.pos(), color.to_js()] + for coords, color in zip(line, self.vertex_colors[index]) + ] + mesh.append(data) + svg += '' % json.dumps(mesh) + for line in self.lines: + svg += '' % ( + " ".join("%f,%f" % coords.pos() for coords in line), + style, + ) + print("XXX PolygonBox", svg) + return svg + + +def RectangleBox_svg(self, offset=None): + l = self.style.get_line_width(face_element=True) + x1, y1 = self.p1.pos() + x2, y2 = self.p2.pos() + xmin = min(x1, x2) + ymin = min(y1, y2) + w = max(x1, x2) - xmin + h = max(y1, y2) - ymin + if offset: + x1, x2 = x1 + offset[0], x2 + offset[0] + y1, y2 = y1 + offset[1], y2 + offset[1] + style = create_css(self.edge_color, self.face_color, l) + return '' % ( + xmin, + ymin, + w, + h, + style, + ) + "\n".join(element.to_svg() for element in self.elements) + + +def RoundBox_svg(self, offset=None): + x, y = self.c.pos() + rx, ry = self.r.pos() + rx -= x + ry = y - ry + l = self.style.get_line_width(face_element=self.face_element) + style = create_css(self.edge_color, self.face_color, stroke_width=l) + return '' % ( + x, + y, + rx, + ry, + style, + ) + + +format2fn.update( + { + ("svg", ArrowBox): ArrowBox_svg, + ("svg", BezierCurveBox): BezierCurveBox_svg, + ("svg", FilledCurveBox): FilledCurveBox_svg, + # ("svg", GraphicsBox): GraphicsBox_svg, + ("svg", Graphics3DElements): Graphic3DElements_svg, + ("svg", GraphicsElements): GraphicsElements_svg, + ("svg", InsetBox): InsetBox_svg, + ("svg", LineBox): LineBox_svg, + ("svg", PointBox): PointBox_svg, + ("svg", PolygonBox): PolygonBox_svg, + ("svg", RectangleBox): RectangleBox_svg, + # ("svg", _ArcBox): ArcBox_svg, + ("svg", _RoundBox): RoundBox_svg, + } +) diff --git a/setup.py b/setup.py index 6dfc62621e..bd5c73e12b 100644 --- a/setup.py +++ b/setup.py @@ -134,6 +134,7 @@ def subdirs(root, file="*.*", depth=10): "mathics.builtin.pympler", "mathics.builtin.specialfns", "mathics.doc", + "mathics.formatter", ], install_requires=INSTALL_REQUIRES, dependency_links=DEPENDENCY_LINKS, From 495a84bfdbb232079963f1f4246566b6d0f07330 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 16 May 2021 20:44:18 -0400 Subject: [PATCH 2/6] DRY code a little --- mathics/core/formatter.py | 48 +++++++++++++++++++++++++++---- mathics/formatter/svg.py | 60 +++++++++++++++++---------------------- 2 files changed, 69 insertions(+), 39 deletions(-) diff --git a/mathics/core/formatter.py b/mathics/core/formatter.py index 1302bdf87f..c39b72f69d 100644 --- a/mathics/core/formatter.py +++ b/mathics/core/formatter.py @@ -1,10 +1,48 @@ - # key is str: to_xxx name, value is formatter function to call -format2fn = {} import inspect +from typing import Callable + +# key is str: (to_xxx name, value) is formatter function to call +format2fn = {} + -def lookup_method(self, format: str): +def lookup_method(self, format: str) -> Callable: + """ + Find a conversion method for `format` in self's class method resolution order. + """ for cls in inspect.getmro(type(self)): - format_fn = format2fn.get(("svg", cls), None) + format_fn = format2fn.get((format, cls), None) if format_fn is not None: + # print(f"format function: {format_fn.__name__} for {type(self).__name__}") return format_fn - raise RuntimeError(f"Can't find formatter {format} for {type(self)}") + raise RuntimeError( + f"Can't find formatter {format_fn.__name__} for {type(self).__name__}" + ) + + +def add_conversion_fn(cls) -> None: + """Add to `format2fn` a mapping from a conversion type and builtin-class + to a conversion method. + + The conversion type is determined form the module name. + For example, in module mathics.formatter.svg the conversion + type is "svg". + + The conversion method is assumed to be a method in the caller's + module, and is derived from lowercasing `cls`. + + For example function arrowbox in module mathics.formatter.svg would be + the SVG conversion routine for class ArrowBox. + + We use frame introspection to get all of this done. + """ + fr = inspect.currentframe().f_back + module_dict = fr.f_globals + + # The last part of the module name is expected to be the conversion routine. + conversion_type = module_dict["__name__"].split(".")[-1] + + # Derive the conversion function from the passed-in class argument. + module_fn_name = cls.__name__.lower() + + # Finally register the mapping: (Builtin-class, conversion name) -> conversion_function. + format2fn[(conversion_type, cls)] = module_dict[module_fn_name] diff --git a/mathics/formatter/svg.py b/mathics/formatter/svg.py index f7aa1f35d9..82f46f0abe 100644 --- a/mathics/formatter/svg.py +++ b/mathics/formatter/svg.py @@ -24,7 +24,7 @@ _SVGTransform, ) -from mathics.core.formatter import format2fn, lookup_method +from mathics.core.formatter import lookup_method, add_conversion_fn def create_css( @@ -52,7 +52,7 @@ def create_css( return "; ".join(css) -# def ArcBox_svg(self, offset=None): +# def arcbox(self, offset=None): # if self.arc is None: # raise RuntimeError(f"{self}.arc should not be none") @@ -75,7 +75,7 @@ def create_css( # return '' % (" ".join(path(self.face_element)), style) -def ArrowBox_svg(self, offset=None): +def arrowbox(self, offset=None): width = self.style.get_line_width(face_element=False) style = create_css(edge_color=self.edge_color, stroke_width=width) polyline = self.curve.make_draw_svg(style) @@ -91,8 +91,9 @@ def polygon(points): default_arrow = self._default_arrow(polygon) custom_arrow = self._custom_arrow("svg", _SVGTransform) return "".join(self._draw(polyline, default_arrow, custom_arrow, extent)) +add_conversion_fn(ArrowBox) -def BezierCurveBox_svg(self, offset=None): +def beziercurvebox(self, offset=None): l = self.style.get_line_width(face_element=False) style = create_css(edge_color=self.edge_color, stroke_width=l) @@ -102,8 +103,9 @@ def BezierCurveBox_svg(self, offset=None): svg += '' % (s, style) # print("XXX bezier", svg) return svg +add_conversion_fn(BezierCurveBox) -def FilledCurveBox_svg(self, offset=None): +def filledcurvebox(self, offset=None): l = self.style.get_line_width(face_element=False) style = create_css( edge_color=self.edge_color, face_color=self.face_color, stroke_width=l @@ -119,8 +121,9 @@ def components(): " ".join(components()), style, ) +add_conversion_fn(FilledCurveBox) -# def GraphicsBox_svg(self, leaves=None, **options) -> str: +# def graphicsbox(self, leaves=None, **options) -> str: # if not leaves: # leaves = self._leaves @@ -171,7 +174,7 @@ def components(): # ) # return svg # , width, height -def GraphicsElements_svg(self, offset=None): +def graphicselements(self, offset=None): result = [] for element in self.elements: format_fn = lookup_method(element, "svg") @@ -181,10 +184,12 @@ def GraphicsElements_svg(self, offset=None): result.append(element.to_svg(offset)) return "\n".join(result) +add_conversion_fn(GraphicsElements) +graphics3delements = graphicselements -Graphic3DElements_svg = GraphicsElements_svg +add_conversion_fn(Graphics3DElements) -def InsetBox_svg(self, offset=None): +def insetbox(self, offset=None): x, y = self.pos.pos() if offset: x = x + offset[0] @@ -213,8 +218,9 @@ def InsetBox_svg(self, offset=None): # "%s") return svg +add_conversion_fn(InsetBox) -def LineBox_svg(self, offset=None): +def linebox(self, offset=None): l = self.style.get_line_width(face_element=False) style = create_css(edge_color=self.edge_color, stroke_width=l) svg = "" @@ -225,9 +231,10 @@ def LineBox_svg(self, offset=None): ) # print("XXX linebox", svg) return svg +add_conversion_fn(LineBox) -def PointBox_svg(self, offset=None): +def pointbox(self, offset=None): point_size, _ = self.style.get_style(PointSize, face_element=False) if point_size is None: point_size = PointSize(self.graphics, value=0.005) @@ -247,8 +254,9 @@ def PointBox_svg(self, offset=None): ) # print("XXX PointBox", svg) return svg +add_conversion_fn(PointBox) -def PolygonBox_svg(self, offset=None): +def polygonbox(self, offset=None): l = self.style.get_line_width(face_element=True) if self.vertex_colors is None: face_color = self.face_color @@ -272,11 +280,12 @@ def PolygonBox_svg(self, offset=None): " ".join("%f,%f" % coords.pos() for coords in line), style, ) - print("XXX PolygonBox", svg) + # print("XXX PolygonBox", svg) return svg +add_conversion_fn(PolygonBox) -def RectangleBox_svg(self, offset=None): +def rectanglebox(self, offset=None): l = self.style.get_line_width(face_element=True) x1, y1 = self.p1.pos() x2, y2 = self.p2.pos() @@ -296,9 +305,10 @@ def RectangleBox_svg(self, offset=None): style, ) "\n".join(element.to_svg() for element in self.elements) +add_conversion_fn(RectangleBox) -def RoundBox_svg(self, offset=None): +def _roundbox(self, offset=None): x, y = self.c.pos() rx, ry = self.r.pos() rx -= x @@ -312,22 +322,4 @@ def RoundBox_svg(self, offset=None): ry, style, ) - - -format2fn.update( - { - ("svg", ArrowBox): ArrowBox_svg, - ("svg", BezierCurveBox): BezierCurveBox_svg, - ("svg", FilledCurveBox): FilledCurveBox_svg, - # ("svg", GraphicsBox): GraphicsBox_svg, - ("svg", Graphics3DElements): Graphic3DElements_svg, - ("svg", GraphicsElements): GraphicsElements_svg, - ("svg", InsetBox): InsetBox_svg, - ("svg", LineBox): LineBox_svg, - ("svg", PointBox): PointBox_svg, - ("svg", PolygonBox): PolygonBox_svg, - ("svg", RectangleBox): RectangleBox_svg, - # ("svg", _ArcBox): ArcBox_svg, - ("svg", _RoundBox): RoundBox_svg, - } -) +add_conversion_fn(_RoundBox) From ee8e6d3376c2aa997690ca04cec8507724338495 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 16 May 2021 23:11:41 -0400 Subject: [PATCH 3/6] to_svg doesn't seem needed for _ArcBox --- mathics/builtin/graphics.py | 66 +++++++++++++++++++++---------------- mathics/formatter/svg.py | 61 ++++++++++++++++++---------------- 2 files changed, 70 insertions(+), 57 deletions(-) diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index a4c546d5b9..31901d2ddf 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -1176,20 +1176,27 @@ class Disk(Builtin): class Circle(Builtin): """
-
'Circle[{$cx$, $cy$}, $r$]' -
draws a circle with center '($cx$, $cy$)' and radius $r$. -
'Circle[{$cx$, $cy$}, {$rx$, $ry$}]' -
draws an ellipse. -
'Circle[{$cx$, $cy$}]' -
chooses radius 1. -
'Circle[]' -
chooses center '(0, 0)' and radius 1. +
'Circle[{$cx$, $cy$}, $r$]' +
draws a circle with center '($cx$, $cy$)' and radius $r$. + +
'Circle[{$cx$, $cy$}, {$rx$, $ry$}]' +
draws an ellipse. + +
'Circle[{$cx$, $cy$}]' +
chooses radius 1. + +
'Circle[]' +
chooses center '(0, 0)' and radius 1.
>> Graphics[{Red, Circle[{0, 0}, {2, 1}]}] = -Graphics- >> Graphics[{Circle[], Disk[{0, 0}, {1, 1}, {0, 2.1}]}] = -Graphics- + + Target practice: + >> Graphics[Circle[], Axes-> True] + = -Graphics- """ rules = {"Circle[]": "Circle[{0, 0}]"} @@ -1359,31 +1366,33 @@ def _arc_params(self): return x, y, abs(rx), abs(ry), sx, sy, ex, ey, large_arc - def to_svg(self, offset=None): - # FIXME: figure out how to put in svg.py - if self.arc is None: - return super(_ArcBox, self).to_svg(offset) + # FIXME: Why do we need this? If so, + # figure out how to put in svg.py + # -------------------------------- + # def to_svg(self, offset=None): + # if self.arc is None: + # return super(_ArcBox, self).to_svg(offset) - x, y, rx, ry, sx, sy, ex, ey, large_arc = self._arc_params() + # x, y, rx, ry, sx, sy, ex, ey, large_arc = self._arc_params() - format_fn = lookup_method(self, "svg") - if format_fn is not None: - return format_fn(self, offset) - def path(closed): - if closed: - yield "M %f,%f" % (x, y) - yield "L %f,%f" % (sx, sy) - else: - yield "M %f,%f" % (sx, sy) + # format_fn = lookup_method(self, "svg") + # if format_fn is not None: + # return format_fn(self, offset) + # def path(closed): + # if closed: + # yield "M %f,%f" % (x, y) + # yield "L %f,%f" % (sx, sy) + # else: + # yield "M %f,%f" % (sx, sy) - yield "A %f,%f,0,%d,0,%f,%f" % (rx, ry, large_arc, ex, ey) + # yield "A %f,%f,0,%d,0,%f,%f" % (rx, ry, large_arc, ex, ey) - if closed: - yield "Z" + # if closed: + # yield "Z" - l = self.style.get_line_width(face_element=self.face_element) - style = create_css(self.edge_color, self.face_color, stroke_width=l) - return '' % (" ".join(path(self.face_element)), style) + # l = self.style.get_line_width(face_element=self.face_element) + # style = create_css(self.edge_color, self.face_color, stroke_width=l) + # return '' % (" ".join(path(self.face_element)), style) def to_asy(self): if self.arc is None: @@ -2491,7 +2500,6 @@ def extent(self): y = p[1] - h / 2.0 + opos[1] * h / 2.0 return [(x, y), (x + w, y + h)] - def to_asy(self): x, y = self.pos.pos() content = self.content.boxes_to_tex(evaluation=self.graphics.evaluation) diff --git a/mathics/formatter/svg.py b/mathics/formatter/svg.py index 82f46f0abe..fd2c81386f 100644 --- a/mathics/formatter/svg.py +++ b/mathics/formatter/svg.py @@ -7,7 +7,6 @@ from mathics.builtin.drawing.graphics3d import Graphics3DElements from mathics.builtin.graphics import ( - # _ArcBox, ArrowBox, BezierCurveBox, FilledCurveBox, @@ -29,7 +28,10 @@ def create_css( edge_color=None, face_color=None, stroke_width=None, font_color=None, opacity=1.0 -): +) -> str: + """ + Return a string suitable for CSS inclusion setting the various parameters passed. + """ css = [] if edge_color is not None: color, stroke_opacity = edge_color.to_css() @@ -52,29 +54,6 @@ def create_css( return "; ".join(css) -# def arcbox(self, offset=None): -# if self.arc is None: -# raise RuntimeError(f"{self}.arc should not be none") - -# x, y, rx, ry, sx, sy, ex, ey, large_arc = self._arc_params() - -# def path(closed): -# if closed: -# yield "M %f,%f" % (x, y) -# yield "L %f,%f" % (sx, sy) -# else: -# yield "M %f,%f" % (sx, sy) - -# yield "A %f,%f,0,%d,0,%f,%f" % (rx, ry, large_arc, ex, ey) - -# if closed: -# yield "Z" - -# l = self.style.get_line_width(face_element=self.face_element) -# style = create_css(self.edge_color, self.face_color, stroke_width=l) -# return '' % (" ".join(path(self.face_element)), style) - - def arrowbox(self, offset=None): width = self.style.get_line_width(face_element=False) style = create_css(edge_color=self.edge_color, stroke_width=width) @@ -91,8 +70,11 @@ def polygon(points): default_arrow = self._default_arrow(polygon) custom_arrow = self._custom_arrow("svg", _SVGTransform) return "".join(self._draw(polyline, default_arrow, custom_arrow, extent)) + + add_conversion_fn(ArrowBox) + def beziercurvebox(self, offset=None): l = self.style.get_line_width(face_element=False) style = create_css(edge_color=self.edge_color, stroke_width=l) @@ -103,8 +85,11 @@ def beziercurvebox(self, offset=None): svg += '' % (s, style) # print("XXX bezier", svg) return svg + + add_conversion_fn(BezierCurveBox) + def filledcurvebox(self, offset=None): l = self.style.get_line_width(face_element=False) style = create_css( @@ -121,6 +106,8 @@ def components(): " ".join(components()), style, ) + + add_conversion_fn(FilledCurveBox) # def graphicsbox(self, leaves=None, **options) -> str: @@ -174,21 +161,25 @@ def components(): # ) # return svg # , width, height + def graphicselements(self, offset=None): result = [] for element in self.elements: format_fn = lookup_method(element, "svg") - if format_fn is not None: - result.append(format_fn(element, offset)) - else: + if format_fn is None: result.append(element.to_svg(offset)) + else: + result.append(format_fn(element, offset)) return "\n".join(result) + + add_conversion_fn(GraphicsElements) graphics3delements = graphicselements add_conversion_fn(Graphics3DElements) + def insetbox(self, offset=None): x, y = self.pos.pos() if offset: @@ -218,8 +209,11 @@ def insetbox(self, offset=None): # "%s") return svg + + add_conversion_fn(InsetBox) + def linebox(self, offset=None): l = self.style.get_line_width(face_element=False) style = create_css(edge_color=self.edge_color, stroke_width=l) @@ -231,6 +225,8 @@ def linebox(self, offset=None): ) # print("XXX linebox", svg) return svg + + add_conversion_fn(LineBox) @@ -254,8 +250,11 @@ def pointbox(self, offset=None): ) # print("XXX PointBox", svg) return svg + + add_conversion_fn(PointBox) + def polygonbox(self, offset=None): l = self.style.get_line_width(face_element=True) if self.vertex_colors is None: @@ -282,6 +281,8 @@ def polygonbox(self, offset=None): ) # print("XXX PolygonBox", svg) return svg + + add_conversion_fn(PolygonBox) @@ -305,6 +306,8 @@ def rectanglebox(self, offset=None): style, ) "\n".join(element.to_svg() for element in self.elements) + + add_conversion_fn(RectangleBox) @@ -322,4 +325,6 @@ def _roundbox(self, offset=None): ry, style, ) + + add_conversion_fn(_RoundBox) From 33fc83ab62a3d4a5bb05aa35bf84279856d65641 Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 17 May 2021 06:46:51 -0400 Subject: [PATCH 4/6] Mark what's needed and tidy --- mathics/builtin/graphics.py | 27 ++++++---- mathics/formatter/svg.py | 101 +++++++++++++++++++----------------- 2 files changed, 69 insertions(+), 59 deletions(-) diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index 31901d2ddf..e15c9e00ad 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -3100,6 +3100,7 @@ def boxes_to_tex(self, leaves=None, **options): return tex + # FIXME: figure out how to move this to the SVG formatter def to_svg(self, leaves=None, **options): if not leaves: leaves = self._leaves @@ -3117,18 +3118,19 @@ def to_svg(self, leaves=None, **options): format_fn = lookup_method(elements, "svg") if format_fn is not None: - svg = format_fn(elements, offset=options.get("offset", None)) + svg_body = format_fn(elements, offset=options.get("offset", None)) else: - svg = elements.to_svg(offset=options.get("offset", None)) + svg_body = elements.to_svg(offset=options.get("offset", None)) if self.background_color is not None: - svg = '%s' % ( + # Wrap svg_elements in a rectangle + svg_body = '%s' % ( xmin, ymin, w, h, self.background_color.to_css()[0], - svg, + svg_body, ) xmin -= 1 @@ -3137,8 +3139,8 @@ def to_svg(self, leaves=None, **options): h += 2 if options.get("noheader", False): - return svg - svg_xml = """ + return svg_body + svg_main = """ """ % ( " ".join("%f" % t for t in (xmin, ymin, w, h)), - svg, + svg_body, ) - return svg_xml # , width, height + return svg_main # , width, height - def boxes_to_mathml(self, leaves=None, **options): + # fixme: figure out how to move the svg-specific portions to the SVG formatter. + def boxes_to_mathml(self, leaves=None, **options) -> str: if not leaves: leaves = self._leaves @@ -3159,7 +3162,8 @@ def boxes_to_mathml(self, leaves=None, **options): xmin, xmax, ymin, ymax, w, h, width, height = calc_dimensions() data = (elements, xmin, xmax, ymin, ymax, w, h, width, height) - svg_xml = self.to_svg(leaves, data=data, **options) + svg_main = self.to_svg(leaves, data=data, **options) + # mglyph, which is what we have been using, is bad because MathML standard changed. # metext does not work because the way in which we produce the svg images is also based on this outdated mglyph behaviour. # template = '' @@ -3172,9 +3176,10 @@ def boxes_to_mathml(self, leaves=None, **options): # int(height), int(width), int(height), - base64.b64encode(svg_xml.encode("utf8")).decode("utf8"), + base64.b64encode(svg_main.encode("utf8")).decode("utf8"), ) + # FIXME: this isn't always properly align with overlaid SVG plots def axis_ticks(self, xmin, xmax): def round_to_zero(value): if value == 0: diff --git a/mathics/formatter/svg.py b/mathics/formatter/svg.py index fd2c81386f..39d280f4f9 100644 --- a/mathics/formatter/svg.py +++ b/mathics/formatter/svg.py @@ -110,56 +110,61 @@ def components(): add_conversion_fn(FilledCurveBox) +# FIXME figure out how we can add this. # def graphicsbox(self, leaves=None, **options) -> str: -# if not leaves: -# leaves = self._leaves - -# data = options.get("data", None) -# if data: -# elements, xmin, xmax, ymin, ymax, w, h, width, height = data -# else: -# elements, calc_dimensions = self._prepare_elements( -# leaves, options, neg_y=True +# if not leaves: +# leaves = self._leaves +# +# data = options.get("data", None) +# if data: +# elements, xmin, xmax, ymin, ymax, w, h, width, height = data +# else: +# elements, calc_dimensions = self._prepare_elements( +# leaves, options, neg_y=True +# ) +# xmin, xmax, ymin, ymax, w, h, width, height = calc_dimensions() +# +# elements.view_width = w +# +# format_fn = lookup_method(elements, "svg") +# if format_fn is not None: +# svg_body = format_fn(elements, offset=options.get("offset", None)) +# else: +# svg_body = elements.to_svg(offset=options.get("offset", None)) +# +# if self.background_color is not None: +# # Wrap svg_elements in a rectangle +# svg_body = '%s' % ( +# xmin, +# ymin, +# w, +# h, +# self.background_color.to_css()[0], +# svg_body, +# ) +# +# xmin -= 1 +# ymin -= 1 +# w += 2 +# h += 2 +# +# if options.get("noheader", False): +# return svg_body +# svg_main = """ +# +# %s +# +# """ % ( +# " ".join("%f" % t for t in (xmin, ymin, w, h)), +# svg_body, # ) -# xmin, xmax, ymin, ymax, w, h, width, height = calc_dimensions() - -# elements.view_width = w - -# format_fn = lookup_method(elements, "svg") -# if format_fn is not None: -# svg = format_fn(elements) -# else: -# svg = elements.to_svg(offset=options.get("offset", None)) - -# if self.background_color is not None: -# svg = '%s' % ( -# xmin, -# ymin, -# w, -# h, -# self.background_color.to_css()[0], -# svg, -# ) - -# xmin -= 1 -# ymin -= 1 -# w += 2 -# h += 2 - -# if options.get("noheader", False): -# return svg -# svg = """ -# -# %s -# -# """ % ( -# " ".join("%f" % t for t in (xmin, ymin, w, h)), -# svg, -# ) -# return svg # , width, height +# return svg_main # , width, height +# +# +# add_conversion_fn(GraphicsBox) def graphicselements(self, offset=None): From 346b19f86b17eb208b8556a4e70d8b855f2375e2 Mon Sep 17 00:00:00 2001 From: rocky Date: Tue, 18 May 2021 04:58:44 -0400 Subject: [PATCH 5/6] Relocate one more SVG to formatter space --- mathics/builtin/graphics.py | 59 ++----------------- mathics/formatter/svg.py | 110 ++++++++++++++++++------------------ 2 files changed, 59 insertions(+), 110 deletions(-) diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index e15c9e00ad..f5b0125923 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -3100,60 +3100,6 @@ def boxes_to_tex(self, leaves=None, **options): return tex - # FIXME: figure out how to move this to the SVG formatter - def to_svg(self, leaves=None, **options): - if not leaves: - leaves = self._leaves - - data = options.get("data", None) - if data: - elements, xmin, xmax, ymin, ymax, w, h, width, height = data - else: - elements, calc_dimensions = self._prepare_elements( - leaves, options, neg_y=True - ) - xmin, xmax, ymin, ymax, w, h, width, height = calc_dimensions() - - elements.view_width = w - - format_fn = lookup_method(elements, "svg") - if format_fn is not None: - svg_body = format_fn(elements, offset=options.get("offset", None)) - else: - svg_body = elements.to_svg(offset=options.get("offset", None)) - - if self.background_color is not None: - # Wrap svg_elements in a rectangle - svg_body = '%s' % ( - xmin, - ymin, - w, - h, - self.background_color.to_css()[0], - svg_body, - ) - - xmin -= 1 - ymin -= 1 - w += 2 - h += 2 - - if options.get("noheader", False): - return svg_body - svg_main = """ - - %s - - """ % ( - " ".join("%f" % t for t in (xmin, ymin, w, h)), - svg_body, - ) - return svg_main # , width, height - - # fixme: figure out how to move the svg-specific portions to the SVG formatter. def boxes_to_mathml(self, leaves=None, **options) -> str: if not leaves: leaves = self._leaves @@ -3162,7 +3108,10 @@ def boxes_to_mathml(self, leaves=None, **options) -> str: xmin, xmax, ymin, ymax, w, h, width, height = calc_dimensions() data = (elements, xmin, xmax, ymin, ymax, w, h, width, height) - svg_main = self.to_svg(leaves, data=data, **options) + # FIXME: SVG is the only thing we can convert MathML into. + # Handle other graphics formats. + format_fn = lookup_method(self, "svg") + svg_main = format_fn(self, leaves, data=data, **options) # mglyph, which is what we have been using, is bad because MathML standard changed. # metext does not work because the way in which we produce the svg images is also based on this outdated mglyph behaviour. diff --git a/mathics/formatter/svg.py b/mathics/formatter/svg.py index 39d280f4f9..395c2cae74 100644 --- a/mathics/formatter/svg.py +++ b/mathics/formatter/svg.py @@ -10,7 +10,7 @@ ArrowBox, BezierCurveBox, FilledCurveBox, - # GraphicsBox, + GraphicsBox, GraphicsElements, InsetBox, LineBox, @@ -111,60 +111,60 @@ def components(): add_conversion_fn(FilledCurveBox) # FIXME figure out how we can add this. -# def graphicsbox(self, leaves=None, **options) -> str: -# if not leaves: -# leaves = self._leaves -# -# data = options.get("data", None) -# if data: -# elements, xmin, xmax, ymin, ymax, w, h, width, height = data -# else: -# elements, calc_dimensions = self._prepare_elements( -# leaves, options, neg_y=True -# ) -# xmin, xmax, ymin, ymax, w, h, width, height = calc_dimensions() -# -# elements.view_width = w -# -# format_fn = lookup_method(elements, "svg") -# if format_fn is not None: -# svg_body = format_fn(elements, offset=options.get("offset", None)) -# else: -# svg_body = elements.to_svg(offset=options.get("offset", None)) -# -# if self.background_color is not None: -# # Wrap svg_elements in a rectangle -# svg_body = '%s' % ( -# xmin, -# ymin, -# w, -# h, -# self.background_color.to_css()[0], -# svg_body, -# ) -# -# xmin -= 1 -# ymin -= 1 -# w += 2 -# h += 2 -# -# if options.get("noheader", False): -# return svg_body -# svg_main = """ -# -# %s -# -# """ % ( -# " ".join("%f" % t for t in (xmin, ymin, w, h)), -# svg_body, -# ) -# return svg_main # , width, height -# -# -# add_conversion_fn(GraphicsBox) +def graphicsbox(self, leaves=None, **options) -> str: + if not leaves: + leaves = self._leaves + + data = options.get("data", None) + if data: + elements, xmin, xmax, ymin, ymax, w, h, width, height = data + else: + elements, calc_dimensions = self._prepare_elements( + leaves, options, neg_y=True + ) + xmin, xmax, ymin, ymax, w, h, width, height = calc_dimensions() + + elements.view_width = w + + format_fn = lookup_method(elements, "svg") + if format_fn is not None: + svg_body = format_fn(elements, offset=options.get("offset", None)) + else: + svg_body = elements.to_svg(offset=options.get("offset", None)) + + if self.background_color is not None: + # Wrap svg_elements in a rectangle + svg_body = '%s' % ( + xmin, + ymin, + w, + h, + self.background_color.to_css()[0], + svg_body, + ) + + xmin -= 1 + ymin -= 1 + w += 2 + h += 2 + + if options.get("noheader", False): + return svg_body + svg_main = """ + + %s + + """ % ( + " ".join("%f" % t for t in (xmin, ymin, w, h)), + svg_body, + ) + return svg_main # , width, height + + +add_conversion_fn(GraphicsBox) def graphicselements(self, offset=None): From faf052869a36047d58f554956e351b5afe8a6185 Mon Sep 17 00:00:00 2001 From: rocky Date: Tue, 18 May 2021 09:03:34 -0400 Subject: [PATCH 6/6] Test Point and Ellipse --- mathics/builtin/inout.py | 12 +++--- test/test_formatter.py | 86 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 test/test_formatter.py diff --git a/mathics/builtin/inout.py b/mathics/builtin/inout.py index 9930eacb70..adf4b2ce6d 100644 --- a/mathics/builtin/inout.py +++ b/mathics/builtin/inout.py @@ -2063,25 +2063,25 @@ def apply_mathml(self, expr, evaluation) -> Expression: boxes = MakeBoxes(expr).evaluate(evaluation) try: - xml = boxes.boxes_to_mathml(evaluation=evaluation) + mathml = boxes.boxes_to_mathml(evaluation=evaluation) except BoxError: evaluation.message( "General", "notboxes", Expression("FullForm", boxes).evaluate(evaluation), ) - xml = "" - is_a_picture = xml[:6] == "', inner_svg + ) + assert matches + assert matches.group(1) == matches.group(2) == matches.group(3) + +def test_svg_point(): + expression = Expression( + GraphicsSymbol, + Expression("Point", Expression(ListSymbol, Integer0, Integer0)), + ) + + svg = get_svg(expression) + inner_svg = extract_svg_body(svg) + + # Circles are implemented as ellipses with equal major and minor axes. + # Check for that. + print(inner_svg) + matches = re.match( + r'^', inner_svg + ) + assert matches + assert matches.group(1) == matches.group(2) + + +if __name__ == "__main__": + test_svg_point()