Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
48 changes: 43 additions & 5 deletions mathics/core/formatter.py
Original file line number Diff line number Diff line change
@@ -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]
60 changes: 26 additions & 34 deletions mathics/formatter/svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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")

Expand All @@ -75,7 +75,7 @@ def create_css(
# return '<path d="%s" style="%s" />' % (" ".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)
Expand All @@ -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)

Expand All @@ -102,8 +103,9 @@ def BezierCurveBox_svg(self, offset=None):
svg += '<path d="%s" style="%s"/>' % (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
Expand All @@ -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

Expand Down Expand Up @@ -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")
Expand All @@ -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]
Expand Down Expand Up @@ -213,8 +218,9 @@ def InsetBox_svg(self, offset=None):
# "<math>%s</math></foreignObject>")

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 = ""
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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()
Expand All @@ -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
Expand All @@ -312,22 +322,4 @@ def RoundBox_svg(self, offset=None):
ry,
style,
)


format2fn.update(
Copy link
Copy Markdown
Member Author

@rocky rocky May 17, 2021

Choose a reason for hiding this comment

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

@mmatera In this comment I start to DRY to code. As with a number of other places where we DRY code, it does it by assuming some sort of convention. Conventions and abbreviations abound in Physics for example, and if you don't understand the convention, then things are more complex.

Therefore for experimental kinds of things I find it useful to start out with the long form and then figure out how to shorten it.

In proofs, some mathematicians and physicists are able to go immediately to the additional abbreviations and skip steps in proofs. I can't and find it helpful to start out with the long form and then compress from there.

There still are two routines that need to get moved out of the Builtin code. And I would like to start to work up a test for just the conversion routines.

One other thing I love about this is that I now see the possibility of being able to write a utility function which I can use to write SVG. With this I can use this to experiment with SVG's in a way that is in less heavy form than going through Mathics. And we can do that for Asymptote and the next thing and the next thing...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If this is a temporary way to disentangle the code, I think it is OK. Also if at the end, we only expose to the core a function that convert an expression into an svg.

{
("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)