Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
5 changes: 1 addition & 4 deletions mathics/builtin/drawing/graphics3d.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-

"""
Graphics (3D)
Three-Dimensional Graphics
"""


Expand Down Expand Up @@ -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])

Expand Down
186 changes: 16 additions & 170 deletions mathics/builtin/graphics.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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 '<rect x="%f" y="%f" width="%f" height="%f" style="%s" />' % (
xmin,
ymin,
w,
h,
style,
)

def to_asy(self):
l = self.style.get_line_width(face_element=True)
x1, y1 = self.p1.pos()
Expand Down Expand Up @@ -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 '<ellipse cx="%f" cy="%f" rx="%f" ry="%f" style="%s" />' % (
x,
y,
rx,
ry,
style,
)

def to_asy(self):
x, y = self.c.pos()
rx, ry = self.r.pos()
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 += '<circle cx="%f" cy="%f" r="%f" style="%s" />' % (
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)

Expand Down Expand Up @@ -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 += '<polyline points="%s" style="%s" />' % (
" ".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)
Expand Down Expand Up @@ -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 += '<path d="%s" style="%s"/>' % (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)
Expand Down Expand Up @@ -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 '<path d="%s" style="%s" fill-rule="evenodd"/>' % (
" ".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)
Expand Down Expand Up @@ -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 += '<meshgradient data="%s" />' % json.dumps(mesh)
for line in self.lines:
svg += '<polygon points="%s" style="%s" />' % (
" ".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:
Expand Down Expand Up @@ -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 '<polygon points="'
yield " ".join("%f,%f" % xy for xy in points)
yield '" style="%s" />' % 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)
Expand Down Expand Up @@ -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'<text {text_pos_opts} style="{text_style_opts} {css_style}">{content}</text>'

# content = self.content.boxes_to_mathml(evaluation=self.graphics.evaluation)
# style = create_css(font_color=self.color)
# svg = (
# '<foreignObject x="%f" y="%f" ox="%f" oy="%f" style="%s">'
# "<math>%s</math></foreignObject>")

return svg

def to_asy(self):
x, y = self.pos.pos()
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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 = '<rect x="%f" y="%f" width="%f" height="%f" style="fill:%s"/>%s' % (
Expand Down
2 changes: 1 addition & 1 deletion mathics/core/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
"""
Expand Down
48 changes: 48 additions & 0 deletions mathics/core/formatter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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) -> 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((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_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]
Empty file added mathics/formatter/__init__.py
Empty file.
Loading