diff --git a/mathics/builtin/box/expression.py b/mathics/builtin/box/expression.py index 476e5156d..6180b630f 100644 --- a/mathics/builtin/box/expression.py +++ b/mathics/builtin/box/expression.py @@ -81,6 +81,10 @@ def __new__(cls, *elements, **kwargs): instance._elements = None return instance + def __init(self, *args, **kwargs): + super().__init(args, kwargs) + self.boxes = [] + def do_format(self, evaluation, format): return self diff --git a/mathics/builtin/box/graphics.py b/mathics/builtin/box/graphics.py index efd72feb3..497df615c 100644 --- a/mathics/builtin/box/graphics.py +++ b/mathics/builtin/box/graphics.py @@ -4,7 +4,7 @@ """ from abc import ABC from math import atan2, cos, degrees, pi, sin -from typing import Any, Dict, Optional, Tuple +from typing import Any, Dict, Final, List, Optional, Tuple from mathics.builtin.box.expression import BoxExpression from mathics.builtin.colors.color_directives import ( @@ -43,7 +43,7 @@ SymbolRegularPolygonBox = Symbol("RegularPolygonBox") -class _GraphicsElementBox(BoxExpression, ABC): +class GraphicsElementBox(BoxExpression, ABC): def init(self, graphics, item=None, style={}, opacity=1.0): if item is not None and not item.has_form(self.get_name(), None): raise BoxExpressionError @@ -53,7 +53,13 @@ def init(self, graphics, item=None, style={}, opacity=1.0): self.is_completely_visible = False # True for axis elements -class _Polyline(_GraphicsElementBox): +# GraphicsElementBox Builtin class that should not get added as a definition, +# and therefore not added to to external documentation. + +DOES_NOT_ADD_BUILTIN_DEFINITION: Final[List[BoxExpression]] = [GraphicsElementBox] + + +class _Polyline(GraphicsElementBox): """ A structure containing a list of line segments stored in ``self.lines`` created from @@ -109,12 +115,12 @@ def extent(self) -> list: return result -# Note: has to come before _ArcBox -class _RoundBox(_GraphicsElementBox): +# Note: has to come before ArcBox +class RoundBox(GraphicsElementBox): face_element: Optional[bool] = None def init(self, graphics, style, item): - super(_RoundBox, self).init(graphics, item, style) + super().init(graphics, item, style) if len(item.elements) not in (1, 2): raise BoxExpressionError self.edge_color, self.face_color = style.get_style( @@ -137,7 +143,7 @@ def init(self, graphics, style, item): def extent(self) -> list: """ - Compute the bounding box for _RoundBox. Note that + Compute the bounding box for RoundBox. Note that We handle ellipses here too. """ line_width = self.style.get_line_width(face_element=self.face_element) / 2 @@ -150,7 +156,7 @@ def extent(self) -> list: return [(x - rx, y - ry), (x - rx, y + ry), (x + rx, y - ry), (x + rx, y + ry)] -class _ArcBox(_RoundBox): +class ArcBox(RoundBox): def init(self, graphics, style, item): if len(item.elements) == 3: arc_expr = item.elements[2] @@ -175,7 +181,7 @@ def init(self, graphics, style, item): item = Expression(Symbol(item.get_head_name()), *item.elements[:2]) else: self.arc = None - super(_ArcBox, self).init(graphics, style, item) + super().init(graphics, style, item) def _arc_params(self): x, y = self.c.pos() @@ -214,7 +220,7 @@ def init(self, graphics, style, item=None): if not item: raise BoxExpressionError - super(ArrowBox, self).init(graphics, item, style) + super().init(graphics, item, style) elements = item.elements if len(elements) == 2: @@ -428,7 +434,7 @@ def init(self, graphics, style, item, options): self.spline_degree = spline_degree.get_int_value() -class CircleBox(_ArcBox): +class CircleBox(ArcBox): """
'CircleBox' @@ -440,7 +446,7 @@ class CircleBox(_ArcBox): summary_text = "is the symbol used in boxing 'Circle' expressions" -class DiskBox(_ArcBox): +class DiskBox(ArcBox): """
'DiskBox' @@ -471,6 +477,9 @@ def init(self, *items, **kwargs): self.background_color = None self.tooltip_text: Optional[str] = None self.evaluation = kwargs.pop("_evaluation", None) + self.boxwidth: int = -1 + self.boxheight: int = -1 + self.boxes: list = [] @property def elements(self): @@ -500,7 +509,7 @@ def boxes_to_svg(self, elements=None, **options) -> str: return svg_body -class FilledCurveBox(_GraphicsElementBox): +class FilledCurveBox(GraphicsElementBox): """
'FilledCurveBox' @@ -580,7 +589,7 @@ def extent(self): return result -class InsetBox(_GraphicsElementBox): +class InsetBox(GraphicsElementBox): # We have no documentation for this (yet). no_doc = True @@ -791,7 +800,7 @@ def process_option(self, name, value): raise BoxExpressionError -class RectangleBox(_GraphicsElementBox): +class RectangleBox(GraphicsElementBox): # We have no documentation for this (yet). no_doc = True diff --git a/mathics/builtin/box/graphics3d.py b/mathics/builtin/box/graphics3d.py index 3350513c6..6f8b69e2a 100644 --- a/mathics/builtin/box/graphics3d.py +++ b/mathics/builtin/box/graphics3d.py @@ -7,10 +7,10 @@ from mathics.builtin.box.graphics import ( ArrowBox, GraphicsBox, + GraphicsElementBox, LineBox, PointBox, PolygonBox, - _GraphicsElementBox, ) from mathics.builtin.colors.color_directives import Opacity, RGBColor, _ColorObject from mathics.builtin.drawing.graphics3d import Graphics3D, Style3D @@ -75,7 +75,7 @@ def _apply_boxscaling(self, boxscale): coords.scale(boxscale) -class Cone3DBox(_GraphicsElementBox): +class Cone3DBox(GraphicsElementBox): # """ # Internal Python class used when Boxing a 'Cone' object. # """ @@ -119,7 +119,7 @@ def _apply_boxscaling(self, boxscale): pass -class Cuboid3DBox(_GraphicsElementBox): +class Cuboid3DBox(GraphicsElementBox): # """ # Internal Python class used when Boxing a 'Cuboid' object. # """ @@ -147,7 +147,7 @@ def _apply_boxscaling(self, boxscale): pass -class Cylinder3DBox(_GraphicsElementBox): +class Cylinder3DBox(GraphicsElementBox): # """ # Internal Python class used when Boxing a 'Cylinder' object. # """ @@ -271,7 +271,7 @@ def _apply_boxscaling(self, boxscale): coords.scale(boxscale) -class Sphere3DBox(_GraphicsElementBox): +class Sphere3DBox(GraphicsElementBox): # summary_text = "box representation for a sphere" # We have no documentation for this (yet). @@ -311,7 +311,7 @@ def _apply_boxscaling(self, boxscale): pass -class Tube3DBox(_GraphicsElementBox): +class Tube3DBox(GraphicsElementBox): # summary_text = "box representation for a tube" # We have no documentation for this (yet). diff --git a/mathics/builtin/box/uniform_polyhedra.py b/mathics/builtin/box/uniform_polyhedra.py index eeaf59cb6..2644ea6b8 100644 --- a/mathics/builtin/box/uniform_polyhedra.py +++ b/mathics/builtin/box/uniform_polyhedra.py @@ -1,6 +1,6 @@ import numbers -from mathics.builtin.box.graphics import _GraphicsElementBox +from mathics.builtin.box.graphics import GraphicsElementBox from mathics.builtin.colors.color_directives import Opacity, _ColorObject from mathics.builtin.drawing.graphics3d import Coords3D from mathics.builtin.drawing.graphics_internals import GLOBALS3D @@ -11,7 +11,7 @@ no_doc = True -class UniformPolyhedron3DBox(_GraphicsElementBox): +class UniformPolyhedron3DBox(GraphicsElementBox): # Let's overwrite the default summary_text here, # to recover the spaces. summary_text = "box representation of a 3d uniform polyhedron" diff --git a/mathics/builtin/forms/base.py b/mathics/builtin/forms/base.py index d8f78e659..791f6c443 100644 --- a/mathics/builtin/forms/base.py +++ b/mathics/builtin/forms/base.py @@ -1,3 +1,5 @@ +from typing import Final, List + import mathics.core.definitions as definitions from mathics.core.builtin import Builtin from mathics.core.symbols import Symbol @@ -44,8 +46,7 @@ def __new__(cls, *args, **kwargs): return instance -# FormBaseClass is a public Builtin class that -# should not get added as a definition (and therefore not added to -# to external documentation. +# FormBaseClass is a Builtin class that should not get added as a +# definition, and therefore not added to to external documentation. -DOES_NOT_ADD_BUILTIN_DEFINITION = [FormBaseClass] +DOES_NOT_ADD_BUILTIN_DEFINITION: Final[List[Builtin]] = [FormBaseClass] diff --git a/mathics/format/render/__init__.py b/mathics/format/render/__init__.py index dc361bf7c..a66f28e18 100644 --- a/mathics/format/render/__init__.py +++ b/mathics/format/render/__init__.py @@ -1,27 +1,22 @@ -""" -Lower-level formatting routines. - -Built-in Lower-level formatting includes Asymptote, MathML, SVG, -threejs, and plain text. We hope and expect other formatting to other -kinds backend renderers like matplotlib, can be done by following the -pattern used here. - -These routines typically get called in formatting Mathics3 Box objects. +"""Rendering routines. -The higher level *Forms* (e.g. TeXForm, MathMLForm) typically cause -specific formatters to get called, (e.g. latex, mathml). However, the -two concepts and levels are a little bit different. A given From can -cause invoke of several formatters, which the front-end can influence -based on its capabilities and back-end renders available to it. +Mathics3 Built-in rendering includes renderers to Asymptote, MathML, +SVG, threejs, and plain text. We hope and expect other formatting to +other kinds backend renderers, like matplotlib, can be done by +following the pattern used here. -For example, in graphics there may be several different kinds of -renderers, SVG, or Asymptote for a particular kind of graphics Box. -The front-end needs to decides which format it better suited for it. -The Box, however, is created via a particular high-level Form. +Input to the renders come from some sort of Mathics3 Box. -As another example, front-end may decide to use MathJaX to render -TeXForm if the front-end supports this and the user so desires that. +The higher level Forms (e.g. TeXForm, MathMLForm) typically cause +specific boxing routines to get invoked. From this and the capabilites +and desires of a front end, different rendering routines will invoked +for each kind boxes created. This, in turn, produces strings in +(AMS)LaTeX, MathML, SVG, asymptote, or plain text. +For example, to process the Mathics3 builtin BezierCurve, a +BezierCurveBox will get created. Mathics3 has SVG and an Asymptote +renderers for BezierCurveBoxes. Which one is used is decided on by +the front-end's needs. """ import glob diff --git a/mathics/format/render/asy.py b/mathics/format/render/asy.py index e904eef37..2217b114a 100644 --- a/mathics/format/render/asy.py +++ b/mathics/format/render/asy.py @@ -1,21 +1,22 @@ # -*- coding: utf-8 -*- """ -Lower-level format of Mathics objects as Asymptote Vector graphics strings. +Mathics3 Graphics box rendering to Asymptote Vector graphics strings. """ import re from mathics.builtin.box.graphics import ( + ArcBox, ArrowBox, BezierCurveBox, FilledCurveBox, + GraphicsElementBox, InsetBox, LineBox, PointBox, PolygonBox, RectangleBox, - _ArcBox, - _RoundBox, + RoundBox, ) from mathics.builtin.box.graphics3d import ( Arrow3DBox, @@ -83,16 +84,16 @@ def apply(self, asy): return self._template % (" * ".join(self.transforms), asy) -def arcbox(self: _ArcBox, **options) -> str: +def arcbox(box: ArcBox, **options) -> str: """ Aysmptote formatting for an arc of a circle or an ellipse. """ - if self.arc is None: + if box.arc is None: # We have a doughnut graph and this is the inner blank hole of that. # It is an empty circle - return _roundbox(self) + return roundbox(box) - x, y, rx, ry, sx, sy, ex, ey, _ = self._arc_params() + x, y, rx, ry, sx, sy, ex, ey, _ = box._arc_params() ry = max(ry, 0.1) # Avoid division by 0 yscale = ry / rx @@ -122,39 +123,39 @@ def create_arc_path(is_closed: bool, yscale: float) -> str: return arc_path - stroke_width = self.style.get_line_width(face_element=self.face_element) - edge_opacity_value = self.edge_opacity.opacity if self.edge_opacity else None - face_opacity_value = self.face_opacity.opacity if self.face_opacity else None + stroke_width = box.style.get_line_width(face_element=box.face_element) + edge_opacity_value = box.edge_opacity.opacity if box.edge_opacity else None + face_opacity_value = box.face_opacity.opacity if box.face_opacity else None pen = asy_create_pens( - edge_color=self.edge_color, - face_color=self.face_color, + edge_color=box.edge_color, + face_color=box.face_color, edge_opacity=edge_opacity_value, face_opacity=face_opacity_value, stroke_width=stroke_width, - is_face_element=bool(self.face_element), + is_face_element=bool(box.face_element), ) - command = "filldraw" if self.face_element else "draw" - arc_path = create_arc_path(self.face_element or False, yscale) + command = "filldraw" if box.face_element else "draw" + arc_path = create_arc_path(box.face_element or False, yscale) asy = f"""// ArcBox {command}({arc_path}, {pen});""" # print("### arcbox", asy) return asy -add_conversion_fn(_ArcBox, arcbox) +add_conversion_fn(ArcBox, arcbox) -def arrow_box(self: ArrowBox, **options) -> str: - width = self.style.get_line_width(face_element=False) - edge_opacity_value = self.edge_opacity.opacity if self.edge_opacity else None +def arrow_box(box: ArrowBox, **options) -> str: + width = box.style.get_line_width(face_element=False) + edge_opacity_value = box.edge_opacity.opacity if box.edge_opacity else None pen = asy_create_pens( - edge_color=self.edge_color, stroke_width=width, edge_opacity=edge_opacity_value + edge_color=box.edge_color, stroke_width=width, edge_opacity=edge_opacity_value ) - polyline = self.curve.make_draw_asy(pen) + polyline = box.curve.make_draw_asy(pen) arrow_pen = asy_create_pens( - face_color=self.edge_color, stroke_width=width, face_opacity=edge_opacity_value + face_color=box.edge_color, stroke_width=width, face_opacity=edge_opacity_value ) def polygon(points): @@ -162,10 +163,10 @@ def polygon(points): yield "--".join(["(%.5g,%5g)" % xy for xy in points]) yield "--cycle, % s);" % arrow_pen - extent = self.graphics.view_width or 0 - default_arrow = self._default_arrow(polygon) - custom_arrow = self._custom_arrow("asy", _ASYTransform) - asy = "".join(self._draw(polyline, default_arrow, custom_arrow, extent)) + extent = box.graphics.view_width or 0 + default_arrow = box._default_arrow(polygon) + custom_arrow = box._custom_arrow("asy", _ASYTransform) + asy = "".join(box._draw(polyline, default_arrow, custom_arrow, extent)) # print("### arrowbox", asy) return asy @@ -185,26 +186,26 @@ def build_3d_pen_color(color, opacity=None): return color_str -def arrow3dbox(self, **options) -> str: +def arrow3dbox(box, **options) -> str: """ Asymptote 3D formatter for Arrow3DBox """ # Set style parameters. - edge_opacity_value = self.edge_opacity.opacity if self.edge_opacity else None + edge_opacity_value = box.edge_opacity.opacity if box.edge_opacity else None pen = asy_create_pens( - edge_color=self.edge_color, stroke_width=1, edge_opacity=edge_opacity_value + edge_color=box.edge_color, stroke_width=1, edge_opacity=edge_opacity_value ) # Draw lines between all points except the last. lines_str = "--".join( - ["({0},{1},{2})".format(*(coords.pos()[0])) for coords in self.lines[0][:-1]] + ["({0},{1},{2})".format(*(coords.pos()[0])) for coords in box.lines[0][:-1]] ) asy = f"draw({lines_str}, {pen});\n" # Draw an arrow between the penultimate and the last point. last_line_str = "--".join( - ["({0},{1},{2})".format(*(coords.pos()[0])) for coords in self.lines[0][-2:]] + ["({0},{1},{2})".format(*(coords.pos()[0])) for coords in box.lines[0][-2:]] ) asy += f"draw(({last_line_str}), {pen}, Arrow3);\n" @@ -215,22 +216,22 @@ def arrow3dbox(self, **options) -> str: add_conversion_fn(Arrow3DBox) -def bezier_curve_box(self: BezierCurveBox, **options) -> str: +def bezier_curve_box(box: BezierCurveBox, **options) -> str: """ Asymptote formatter for BezierCurveBox. """ - line_width = self.style.get_line_width(face_element=False) - edge_opacity_value = self.edge_opacity.opacity if self.edge_opacity else None + line_width = box.style.get_line_width(face_element=False) + edge_opacity_value = box.edge_opacity.opacity if box.edge_opacity else None pen = asy_create_pens( - edge_color=self.edge_color, + edge_color=box.edge_color, stroke_width=line_width, edge_opacity=edge_opacity_value, ) asy = "// BezierCurveBox\n" - asy += asy_add_graph_import(self) - asy += asy_add_bezier_fn(self) - for i, line in enumerate(self.lines): + asy += asy_add_graph_import(box) + asy += asy_add_bezier_fn(box) + for i, line in enumerate(box.lines): pts = [str(xy.pos()) for xy in line] for j in range(1, len(pts) - 1, 3): triple = ", ".join(pts[j - 1 : j + 3]) @@ -244,18 +245,18 @@ def bezier_curve_box(self: BezierCurveBox, **options) -> str: add_conversion_fn(BezierCurveBox, bezier_curve_box) -def cone3dbox(self: Cone3DBox, **options) -> str: - face_color = self.face_color.to_js() if self.face_color else (1, 1, 1) - opacity = self.face_opacity +def cone3dbox(box: Cone3DBox, **options) -> str: + face_color = box.face_color.to_js() if box.face_color else (1, 1, 1) + opacity = box.face_opacity color_str = build_3d_pen_color(face_color, opacity) asy = "// Cone3DBox\n" i = 0 - while i < len(self.points) / 2: + while i < len(box.points) / 2: try: # See https://tex.stackexchange.com/questions/736116/how-to-draw-the-base-geometrical-face-of-a-cone-surface-by-asymptote/736120#736120 - cone_center = self.points[i * 2].pos()[0] - cone_tip = self.points[i * 2 + 1].pos()[0] + cone_center = box.points[i * 2].pos()[0] + cone_tip = box.points[i * 2 + 1].pos()[0] if cone_center is None or cone_tip is None: continue @@ -270,7 +271,7 @@ def cone3dbox(self: Cone3DBox, **options) -> str: asy += f""" triple cone_center = {tuple(cone_center)}; triple cone_tip = {tuple(cone_tip)}; - real cone_radius = {self.radius}; + real cone_radius = {box.radius}; real cone_height = {cone_height}; path3 cone_circle = circle(cone_center, cone_radius, cone_tip); @@ -289,17 +290,17 @@ def cone3dbox(self: Cone3DBox, **options) -> str: add_conversion_fn(Cone3DBox) -def cuboid3dbox(self: Cuboid3DBox, **options) -> str: - face_color = self.face_color.to_js() if self.face_color else (1, 1, 1) - opacity = self.face_opacity +def cuboid3dbox(box: Cuboid3DBox, **options) -> str: + face_color = box.face_color.to_js() if box.face_color else (1, 1, 1) + opacity = box.face_opacity color_str = build_3d_pen_color(face_color, opacity) asy = "// Cuboid3DBox\n" i = 0 - while i < len(self.points) / 2: + while i < len(box.points) / 2: try: - point1 = self.points[i * 2].pos()[0] - point2 = self.points[i * 2 + 1].pos()[0] + point1 = box.points[i * 2].pos()[0] + point2 = box.points[i * 2 + 1].pos()[0] if point1 is None or point2 is None: continue @@ -327,23 +328,23 @@ def cuboid3dbox(self: Cuboid3DBox, **options) -> str: add_conversion_fn(Cuboid3DBox) -def cylinder3dbox(self: Cylinder3DBox, **options) -> str: - face_color = self.face_color.to_js() if self.face_color else (1, 1, 1) - opacity = self.face_opacity +def cylinder3dbox(box: Cylinder3DBox, **options) -> str: + face_color = box.face_color.to_js() if box.face_color else (1, 1, 1) + opacity = box.face_opacity color_str = build_3d_pen_color(face_color, opacity) asy = "// Cylinder3DBox\n" # asy += "currentprojection=orthographic(3,1,4,center=true,zoom=.9);\n" i = 0 - while i < len(self.points) / 2: + while i < len(box.points) / 2: try: - point1 = self.points[i * 2].pos()[0] - point2 = self.points[i * 2 + 1].pos()[0] + point1 = box.points[i * 2].pos()[0] + point2 = box.points[i * 2 + 1].pos()[0] if point1 is None or point2 is None: continue - asy += f"real r={self.radius};\n" + asy += f"real r={box.radius};\n" asy += f"triple A={tuple(point1)}, B={tuple(point2)};\n" asy += "real h=abs(A-B);\n" asy += "revolution cyl=cylinder(A,r,h,B-A);\n" @@ -364,11 +365,11 @@ def cylinder3dbox(self: Cylinder3DBox, **options) -> str: add_conversion_fn(Cylinder3DBox) -def filled_curve_box(self, **options) -> str: - line_width = self.style.get_line_width(face_element=False) - edge_opacity_value = self.edge_opacity.opacity if self.edge_opacity else None +def filled_curve_box(box, **options) -> str: + line_width = box.style.get_line_width(face_element=False) + edge_opacity_value = box.edge_opacity.opacity if box.edge_opacity else None pen = asy_create_pens( - edge_color=self.edge_color, + edge_color=box.edge_color, stroke_width=line_width, edge_opacity=edge_opacity_value, ) @@ -377,7 +378,7 @@ def filled_curve_box(self, **options) -> str: pen = "currentpen" def components(): - for component in self.components: + for component in box.components: transformed = [(k, [xy.pos() for xy in p]) for k, p in component] yield "fill(%s--cycle, %s);" % ("".join(asy_bezier(*transformed)), pen) @@ -387,12 +388,12 @@ def components(): add_conversion_fn(FilledCurveBox, filled_curve_box) -def graphics_elements(self, **options) -> str: +def graphics_elements(box: GraphicsElementBox, **options) -> str: """ - Asymptote formatting on a list of graphics elements. + Asymptote formatting on a GraphicsElementBox which may contain other GraphicsElementBox's. """ result = [] - for element in self.elements: + for element in box.elements: try: format_fn = lookup_method(element, "asy") except Exception: @@ -416,25 +417,25 @@ def graphics_elements(self, **options) -> str: add_conversion_fn(Graphics3DElements) -def inset_box(self, **options) -> str: +def inset_box(box: InsetBox, **options) -> str: """Asymptote formatting for boxing an Inset in a graphic.""" - x, y = self.pos.pos() + x, y = box.pos.pos() alignment = "SW" - if hasattr(self, "alignment"): - if self.alignment == "bottom": + if hasattr(box, "alignment"): + if box.alignment == "bottom": # This is typically done for labels under the x axis. alignment = "S" - elif self.alignment == "left": + elif box.alignment == "left": # This is typically done for labels to the left of the y axis. alignment = "W" - opacity_value = self.opacity.opacity if self.opacity else None - content = self.content.boxes_to_tex(evaluation=self.graphics.evaluation) + opacity_value = box.opacity.opacity if box.opacity else None + content = box.content.boxes_to_tex(evaluation=box.graphics.evaluation) # FIXME: don't hard code text_style_opts, but allow these to be adjustable. font_size = 3 pen = asy_create_pens( - edge_color=self.color, edge_opacity=opacity_value, fontsize=font_size + edge_color=box.color, edge_opacity=opacity_value, fontsize=font_size ) asy = f"""// InsetBox label("${content}$", ({x},{y}), align={alignment}, {pen});\n""" @@ -444,11 +445,11 @@ def inset_box(self, **options) -> str: add_conversion_fn(InsetBox, inset_box) -def line3dbox(self, **options) -> str: - # l = self.style.get_line_width(face_element=False) - edge_opacity_value = self.edge_opacity.opacity if self.edge_opacity else None +def line3dbox(box: Line3DBox, **options) -> str: + # l = box.style.get_line_width(face_element=False) + edge_opacity_value = box.edge_opacity.opacity if box.edge_opacity else None pen = asy_create_pens( - edge_color=self.edge_color, stroke_width=1, edge_opacity=edge_opacity_value + edge_color=box.edge_color, stroke_width=1, edge_opacity=edge_opacity_value ) return "".join( @@ -456,23 +457,23 @@ def line3dbox(self, **options) -> str: "--".join("({0},{1},{2})".format(*coords.pos()[0]) for coords in line), pen, ) - for line in self.lines + for line in box.lines ) add_conversion_fn(Line3DBox) -def line_box(self: LineBox) -> str: - line_width = self.style.get_line_width(face_element=False) - edge_opacity_value = self.edge_opacity.opacity if self.edge_opacity else None +def line_box(box: LineBox) -> str: + line_width = box.style.get_line_width(face_element=False) + edge_opacity_value = box.edge_opacity.opacity if box.edge_opacity else None pen = asy_create_pens( - edge_color=self.edge_color, + edge_color=box.edge_color, stroke_width=line_width, edge_opacity=edge_opacity_value, ) asy = "// LineBox\n" - for line in self.lines: + for line in box.lines: path = "--".join(["(%.5g,%5g)" % coords.pos() for coords in line]) asy += "draw(%s, %s);" % (path, pen) # print("### linebox", asy) @@ -482,15 +483,15 @@ def line_box(self: LineBox) -> str: add_conversion_fn(LineBox, line_box) -def point3dbox(self: Point3DBox, **options) -> str: +def point3dbox(box: Point3DBox, **options) -> str: """ Asymptote 3D formatter for Point3DBox """ - face_color = self.face_color + face_color = box.face_color face_opacity_value = face_color.to_rgba()[3] if face_opacity_value is None: - face_opacity_value = self.face_opacity.opacity + face_opacity_value = box.face_opacity.opacity # Tempoary bug fix: default Point color should be black not white if list(face_color.to_rgba()[:3]) == [1, 1, 1]: @@ -500,7 +501,7 @@ def point3dbox(self: Point3DBox, **options) -> str: face_color=face_color, is_face_element=False, face_opacity=face_opacity_value ) points = [] - for line in self.lines: + for line in box.lines: point_coords = "--".join( "(%.5g,%.5g,%.5g)" % coords.pos()[0] for coords in line ) @@ -515,24 +516,24 @@ def point3dbox(self: Point3DBox, **options) -> str: add_conversion_fn(Point3DBox) -def pointbox(self: PointBox, **options) -> str: - point_size, _ = self.style.get_style(PointSize, face_element=False) +def pointbox(box: PointBox, **options) -> str: + point_size, _ = box.style.get_style(PointSize, face_element=False) if point_size is None: - point_size = PointSize(self.graphics, value=DEFAULT_POINT_FACTOR) + point_size = PointSize(box.graphics, value=DEFAULT_POINT_FACTOR) # We'll use the heuristic that the default line width is 1 should correspond # to the DEFAULT_POINT_FACTOR dotfactor = INVERSE_POINT_FACTOR * point_size.value - face_opacity_value = self.face_opacity.opacity if self.face_opacity else None + face_opacity_value = box.face_opacity.opacity if box.face_opacity else None pen = asy_create_pens( - face_color=self.face_color, + face_color=box.face_color, is_face_element=False, dotfactor=dotfactor, face_opacity=face_opacity_value, ) asy = "// PointBox\n" - for line in self.lines: + for line in box.lines: for coords in line: asy += "dot(%s, %s);" % (coords.pos(), pen) @@ -543,21 +544,21 @@ def pointbox(self: PointBox, **options) -> str: add_conversion_fn(PointBox) -def polygon_3d_box(self: Polygon3DBox, **options) -> str: +def polygon_3d_box(box: Polygon3DBox, **options) -> str: """ Asymptote formatting of a Polygon3DBox. """ - stroke_width = self.style.get_line_width(face_element=True) - if self.vertex_colors is None: - face_color = self.face_color - face_opacity_value = self.face_opacity.opacity if self.face_opacity else None + stroke_width = box.style.get_line_width(face_element=True) + if box.vertex_colors is None: + face_color = box.face_color + face_opacity_value = box.face_opacity.opacity if box.face_opacity else None else: face_color = None face_opacity_value = None - edge_opacity_value = self.edge_opacity.opacity if self.edge_opacity else None + edge_opacity_value = box.edge_opacity.opacity if box.edge_opacity else None pen = asy_create_pens( - edge_color=self.edge_color, + edge_color=box.edge_color, face_color=face_color, edge_opacity=edge_opacity_value, face_opacity=face_opacity_value, @@ -566,7 +567,7 @@ def polygon_3d_box(self: Polygon3DBox, **options) -> str: ) asy = "// Polygon3DBox\n" - for line in self.lines: + for line in box.lines: asy += ( "path3 g=" + "--".join(["(%.5g,%.5g,%.5g)" % coords.pos()[0] for coords in line]) @@ -581,18 +582,18 @@ def polygon_3d_box(self: Polygon3DBox, **options) -> str: add_conversion_fn(Polygon3DBox, polygon_3d_box) -def polygonbox(self: PolygonBox, **options) -> str: - line_width = self.style.get_line_width(face_element=True) - if self.vertex_colors is None: - face_color = self.face_color - face_opacity_value = self.face_opacity.opacity if self.face_opacity else None +def polygonbox(box: PolygonBox, **options) -> str: + line_width = box.style.get_line_width(face_element=True) + if box.vertex_colors is None: + face_color = box.face_color + face_opacity_value = box.face_opacity.opacity if box.face_opacity else None else: face_color = None face_opacity_value = None - edge_opacity_value = self.edge_opacity.opacity if self.edge_opacity else None + edge_opacity_value = box.edge_opacity.opacity if box.edge_opacity else None pens = asy_create_pens( - edge_color=self.edge_color, + edge_color=box.edge_color, face_color=face_color, edge_opacity=edge_opacity_value, face_opacity=face_opacity_value, @@ -600,21 +601,21 @@ def polygonbox(self: PolygonBox, **options) -> str: is_face_element=True, ) asy = "// PolygonBox\n" - if self.vertex_colors is not None: + if box.vertex_colors is not None: paths = [] colors = [] edges = [] - for index, line in enumerate(self.lines): + for index, line in enumerate(box.lines): paths.append( "--".join(["(%.5g,%.5g)" % coords.pos() for coords in line]) + "--cycle" ) # ignore opacity colors.append( - ",".join([asy_color(color)[0] for color in self.vertex_colors[index]]) + ",".join([asy_color(color)[0] for color in box.vertex_colors[index]]) ) - edges.append(",".join(["0"] + ["1"] * (len(self.vertex_colors[index]) - 1))) + edges.append(",".join(["0"] + ["1"] * (len(box.vertex_colors[index]) - 1))) asy += "gouraudshade(%s, new pen[] {%s}, new int[] {%s});" % ( "^^".join(paths), @@ -622,7 +623,7 @@ def polygonbox(self: PolygonBox, **options) -> str: ",".join(edges), ) if pens and pens != "nullpen": - for line in self.lines: + for line in box.lines: path = ( "--".join(["(%.5g,%.5g)" % coords.pos() for coords in line]) + "--cycle" ) @@ -635,15 +636,15 @@ def polygonbox(self: PolygonBox, **options) -> str: add_conversion_fn(PolygonBox) -def rectanglebox(self: RectangleBox, **options) -> str: - line_width = self.style.get_line_width(face_element=True) - x1, y1 = self.p1.pos() - x2, y2 = self.p2.pos() - edge_opacity_value = self.edge_opacity.opacity if self.edge_opacity else None - face_opacity_value = self.face_opacity.opacity if self.face_opacity else None +def rectanglebox(box: RectangleBox, **options) -> str: + line_width = box.style.get_line_width(face_element=True) + x1, y1 = box.p1.pos() + x2, y2 = box.p2.pos() + edge_opacity_value = box.edge_opacity.opacity if box.edge_opacity else None + face_opacity_value = box.face_opacity.opacity if box.face_opacity else None pens = asy_create_pens( - self.edge_color, - self.face_color, + box.edge_color, + box.face_color, edge_opacity=edge_opacity_value, face_opacity=face_opacity_value, stroke_width=line_width, @@ -669,23 +670,23 @@ def rectanglebox(self: RectangleBox, **options) -> str: add_conversion_fn(RectangleBox) -def _roundbox(self: _RoundBox): - x, y = self.c.pos() - rx, ry = self.r.pos() +def roundbox(box: RoundBox): + x, y = box.c.pos() + rx, ry = box.r.pos() rx -= x ry -= y - line_width = self.style.get_line_width(face_element=self.face_element) - edge_opacity_value = self.edge_opacity.opacity if self.edge_opacity else None - face_opacity_value = self.face_opacity.opacity if self.face_opacity else None + line_width = box.style.get_line_width(face_element=box.face_element) + edge_opacity_value = box.edge_opacity.opacity if box.edge_opacity else None + face_opacity_value = box.face_opacity.opacity if box.face_opacity else None pen = asy_create_pens( - edge_color=self.edge_color, - face_color=self.face_color, + edge_color=box.edge_color, + face_color=box.face_color, edge_opacity=edge_opacity_value, face_opacity=face_opacity_value, stroke_width=line_width, - is_face_element=self.face_element, + is_face_element=box.face_element, ) - cmd = "filldraw" if self.face_element else "draw" + cmd = "filldraw" if box.face_element else "draw" return "%s(ellipse((%s,%s),%s,%s), %s);" % ( cmd, asy_number(x), @@ -696,44 +697,44 @@ def _roundbox(self: _RoundBox): ) -add_conversion_fn(_RoundBox) +add_conversion_fn(RoundBox) -def sphere3dbox(self: Sphere3DBox, **options) -> str: - # l = self.style.get_line_width(face_element=True) +def sphere3dbox(box: Sphere3DBox, **options) -> str: + # l = box.style.get_line_width(face_element=True) - face_color = self.face_color.to_js() if self.face_color else (1, 1, 1) - opacity = self.face_opacity + face_color = box.face_color.to_js() if box.face_color else (1, 1, 1) + opacity = box.face_opacity color_str = build_3d_pen_color(face_color, opacity) return "// Sphere3DBox\n" + "\n".join( "draw(surface(sphere({0}, {1})), {2});".format( - tuple(coord.pos()[0]), self.radius, color_str + tuple(coord.pos()[0]), box.radius, color_str ) - for coord in self.points + for coord in box.points ) add_conversion_fn(Sphere3DBox) -def tube_3d_box(self: Tube3DBox, **options) -> str: - # if not (hasattr(self.graphics, "tube_import_added") and self.tube_import_added): - # self.graphics.tube_import_added = True +def tube_3d_box(box: Tube3DBox, **options) -> str: + # if not (hasattr(box.graphics, "tube_import_added") and box.tube_import_added): + # box.graphics.tube_import_added = True # asy_head = "import tube;\n\n" # else: # asy_head = "" - face_color = self.face_color.to_js() if self.face_color else (1, 1, 1) - opacity = self.face_opacity + face_color = box.face_color.to_js() if box.face_color else (1, 1, 1) + opacity = box.face_opacity color_str = build_3d_pen_color(face_color, opacity) asy = ( # asy_head + "// Tube3DBox\n draw(tube({0}, scale({1})*unitcircle), {2});".format( "--".join( - "({0},{1},{2})".format(*coords.pos()[0]) for coords in self.points + "({0},{1},{2})".format(*coords.pos()[0]) for coords in box.points ), - self.radius, + box.radius, color_str, ) ) @@ -743,16 +744,16 @@ def tube_3d_box(self: Tube3DBox, **options) -> str: add_conversion_fn(Tube3DBox, tube_3d_box) -def uniform_polyhedron_3d_box(self: UniformPolyhedron3DBox, **options) -> str: - # l = self.style.get_line_width(face_element=True) +def uniform_polyhedron_3d_box(box: UniformPolyhedron3DBox, **options) -> str: + # l = box.style.get_line_width(face_element=True) - face_color = self.face_color.to_js() if self.face_color else (1, 1, 1) - opacity = self.face_opacity + face_color = box.face_color.to_js() if box.face_color else (1, 1, 1) + opacity = box.face_opacity color_str = build_3d_pen_color(face_color, opacity) - render_fn = HEDRON_NAME_MAP.get(self.sub_type, unimplimented_polygon) - return f"// {self.sub_type}\n" + "\n".join( - render_fn(tuple(coord.pos()[0]), self.edge_length, color_str) - for coord in self.points + render_fn = HEDRON_NAME_MAP.get(box.sub_type, unimplimented_polygon) + return f"// {box.sub_type}\n" + "\n".join( + render_fn(tuple(coord.pos()[0]), box.edge_length, color_str) + for coord in box.points ) diff --git a/mathics/format/render/json.py b/mathics/format/render/json.py index 5e9310535..4d1b81b8e 100644 --- a/mathics/format/render/json.py +++ b/mathics/format/render/json.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- """ -Lower-level formatter of Mathics objects as JSON data. +Mathics3 Graphics box rendering to JSON data. -Right now this happens mostly for graphics primitives. +Right now, this happens in graphics primitives. """ import json @@ -50,12 +50,12 @@ def convert_coord_collection( return data -def graphics_3D_elements(self, **options) -> list: - """Iterates over self.elements to convert each item. +def graphics_3D_elements(box: Graphics3DElements, **options) -> list: + """Iterates over box.elements to converting each item. The list of converted items is returned. """ result = [] - for element in self.elements: + for element in box.elements: format_fn = lookup_method(element, "json") result += format_fn(element) @@ -66,13 +66,13 @@ def graphics_3D_elements(self, **options) -> list: add_conversion_fn(Graphics3DElements, graphics_3D_elements) -def arrow_3d_box(self): +def arrow_3d_box(box: Arrow3DBox): """ Compact (lower-level) JSON formatting of a Arrow3DBox. """ # TODO: account for arrow widths and style - color = self.edge_color.to_rgba() - data = convert_coord_collection(self.lines, "arrow", color) + color = box.edge_color.to_rgba() + data = convert_coord_collection(box.lines, "arrow", color) # print("### json Arrow3DBox", data) return data @@ -80,18 +80,18 @@ def arrow_3d_box(self): add_conversion_fn(Arrow3DBox, arrow_3d_box) -def cone_3d_box(self): +def cone_3d_box(box: Cone3DBox): """ Compact (lower-level) JSON formatting of a Cone3DBox. """ - face_color = self.face_color + face_color = box.face_color if face_color is not None: face_color = face_color.to_js() data = convert_coord_collection( - [self.points], + [box.points], "cone", face_color, - {"radius": self.radius}, + {"radius": box.radius}, ) # print("### json Cone3DBox", data) return data @@ -100,15 +100,15 @@ def cone_3d_box(self): add_conversion_fn(Cone3DBox, cone_3d_box) -def cuboid_3d_box(self): +def cuboid_3d_box(box: Cuboid3DBox): """ Compact (lower-level) JSON formatting of a Cuboid3DBox. """ - face_color = self.face_color.to_js() - if len(face_color) < 4 and self.face_opacity: - face_color = face_color + [self.face_opacity.opacity] + face_color = box.face_color.to_js() + if len(face_color) < 4 and box.face_opacity: + face_color = face_color + [box.face_opacity.opacity] data = convert_coord_collection( - [self.points], + [box.points], "cuboid", face_color, ) @@ -119,18 +119,18 @@ def cuboid_3d_box(self): add_conversion_fn(Cuboid3DBox, cuboid_3d_box) -def cylinder_3d_box(self): +def cylinder_3d_box(box: Cylinder3DBox): """ Compact (lower-level) JSON formatting of a Cylinder3DBox. """ - face_color = self.face_color.to_js() - if len(face_color) < 4 and self.face_opacity: - face_color = face_color + [self.face_opacity.opacity] + face_color = box.face_color.to_js() + if len(face_color) < 4 and box.face_opacity: + face_color = face_color + [box.face_opacity.opacity] data = convert_coord_collection( - [self.points], + [box.points], "cylinder", face_color, - {"radius": self.radius}, + {"radius": box.radius}, ) # print("### json Cylinder3DBox", data) return data @@ -139,7 +139,7 @@ def cylinder_3d_box(self): add_conversion_fn(Cylinder3DBox, cylinder_3d_box) -def graphics3d_boxes_to_json(self, content=None, **options): +def graphics3d_boxes_to_json(box: Graphics3DBox, content=None, **options): """Turn the Graphics3DBox to into a something JSON like. This can be used to embed in something else like MathML or Javascript. @@ -154,10 +154,10 @@ def graphics3d_boxes_to_json(self, content=None, **options): ticks_style, calc_dimensions, boxscale, - ) = prepare_elements3d(self, self.content, options) + ) = prepare_elements3d(box, box.content, options) background = "rgba(100.0%, 100.0%, 100.0%, 100.0%)" - if self.background_color: - components = self.background_color.to_rgba() + if box.background_color: + components = box.background_color.to_rgba() if len(components) == 3: background = "rgb(" + ", ".join(f"{100*c}%" for c in components) + ")" else: @@ -192,8 +192,8 @@ def graphics3d_boxes_to_json(self, content=None, **options): "zmin": zmin, "zmax": zmax, }, - "lighting": self.lighting, - "viewpoint": self.viewpoint, + "lighting": box.lighting, + "viewpoint": box.viewpoint, "protocol": "1.1", } ) @@ -203,15 +203,15 @@ def graphics3d_boxes_to_json(self, content=None, **options): add_conversion_fn(Graphics3DBox, graphics3d_boxes_to_json) -def line_3d_box(self): +def line_3d_box(box: Line3DBox): """ Compact (lower-level) JSON formatting of a Line3DBox. """ # TODO: account for line widths and style - color = self.edge_color.to_rgba() - if len(color) < 4 and self.edge_opacity: - color = color + [self.edge_opacity.opacity] - data = convert_coord_collection(self.lines, "line", color) + color = box.edge_color.to_rgba() + if len(color) < 4 and box.edge_opacity: + color = color + [box.edge_opacity.opacity] + data = convert_coord_collection(box.lines, "line", color) # print("### json Line3DBox", data) return data @@ -219,21 +219,21 @@ def line_3d_box(self): add_conversion_fn(Line3DBox, line_3d_box) -def point_3d_box(self) -> list: +def point_3d_box(box: Point3DBox) -> list: """ Compact (lower-level) JSON formatting of a Point3DBox. """ # TODO: account for point size - face_color = self.face_color.to_rgba() - if len(face_color) < 4 and self.face_opacity: - face_color = face_color + [self.face_opacity.opacity] + face_color = box.face_color.to_rgba() + if len(face_color) < 4 and box.face_opacity: + face_color = face_color + [box.face_opacity.opacity] - point_size, _ = self.style.get_style(PointSize, face_element=False) + point_size, _ = box.style.get_style(PointSize, face_element=False) relative_point_size = 0.01 if point_size is None else point_size.value data = convert_coord_collection( - self.lines, + box.lines, "point", face_color, {"pointSize": relative_point_size * 0.5}, @@ -246,21 +246,21 @@ def point_3d_box(self) -> list: add_conversion_fn(Point3DBox, point_3d_box) -def polygon_3d_box(self) -> list: +def polygon_3d_box(box: Polygon3DBox) -> list: """ Compact (lower-level) JSON formatting of a Polygon3DBox. This format follows an API understood by mathics_threejs_backend. """ # TODO: account for line widths and style - if self.vertex_colors is None: - face_color = self.face_color.to_js() + if box.vertex_colors is None: + face_color = box.face_color.to_js() else: face_color = None - if face_color and len(face_color) < 4 and self.face_opacity: - face_color = face_color + [self.face_opacity.opacity] + if face_color and len(face_color) < 4 and box.face_opacity: + face_color = face_color + [box.face_opacity.opacity] data = convert_coord_collection( - self.lines, + box.lines, "polygon", face_color, ) @@ -271,15 +271,15 @@ def polygon_3d_box(self) -> list: add_conversion_fn(Polygon3DBox, polygon_3d_box) -def sphere_3d_box(self) -> list: - face_color = self.face_color.to_js() - if len(face_color) < 4 and self.face_opacity: - face_color = face_color + [self.face_opacity.opacity] +def sphere_3d_box(box: Sphere3DBox) -> list: + face_color = box.face_color.to_js() + if len(face_color) < 4 and box.face_opacity: + face_color = face_color + [box.face_opacity.opacity] data = convert_coord_collection( - [self.points], + [box.points], "sphere", face_color, - {"radius": self.radius}, + {"radius": box.radius}, ) # print("### json Sphere3DBox", data) return data @@ -288,15 +288,15 @@ def sphere_3d_box(self) -> list: add_conversion_fn(Sphere3DBox, sphere_3d_box) -def uniform_polyhedron_3d_box(self) -> list: - face_color = self.face_color.to_js() - if len(face_color) < 4 and self.edge_opacity: - face_color = face_color + [self.face_opacity.opacity] +def uniform_polyhedron_3d_box(box: UniformPolyhedron3DBox) -> list: + face_color = box.face_color.to_js() + if len(face_color) < 4 and box.edge_opacity: + face_color = face_color + [box.face_opacity.opacity] data = convert_coord_collection( - [self.points], + [box.points], "uniformPolyhedron", face_color, - {"subType": self.sub_type}, + {"subType": box.sub_type}, ) # print("### json UniformPolyhedron3DBox", data) return data @@ -305,17 +305,17 @@ def uniform_polyhedron_3d_box(self) -> list: add_conversion_fn(UniformPolyhedron3DBox, uniform_polyhedron_3d_box) -def tube_3d_box(self) -> list: - face_color = self.face_color +def tube_3d_box(box: Tube3DBox) -> list: + face_color = box.face_color if face_color is not None: face_color = face_color.to_js() - if len(face_color) < 4 and self.edge_opacity: - face_color = face_color + [self.face_opacity.opacity] + if len(face_color) < 4 and box.edge_opacity: + face_color = face_color + [box.face_opacity.opacity] data = convert_coord_collection( - [self.points], + [box.points], "tube", face_color, - {"radius": self.radius}, + {"radius": box.radius}, ) # print("### json Tube3DBox", data) return data diff --git a/mathics/format/render/latex.py b/mathics/format/render/latex.py index 93ea3b1ce..329f987fa 100644 --- a/mathics/format/render/latex.py +++ b/mathics/format/render/latex.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- -"""Lower-level formatter of Mathics objects as (AMS)LaTeX strings. +""" +Mathics3 box rendering to (AMS)LaTeX strings. + +Formatting is usually initiated in Mathics via TeXForm[]. AMS LaTeX is LaTeX with addition mathematical symbols, which we may make use of via the mathics-scanner tables. -LaTeX formatting is usually initiated in Mathics via TeXForm[]. - TeXForm in WMA is slightly vague or misleading since the output is typically LaTeX rather than Plain TeX. In Mathics3, we also assume AMS LaTeX or more specifically that we the additional AMS Mathematical @@ -14,6 +15,7 @@ import re +from mathics.builtin.box.expression import BoxExpression from mathics.builtin.box.graphics import GraphicsBox from mathics.builtin.box.graphics3d import Graphics3DBox from mathics.builtin.box.layout import ( @@ -189,9 +191,9 @@ def replace(match): return text -def string(self, **options) -> str: +def string(s: String, **options) -> str: """String to LaTeX form""" - text = self.value + text = s.value def render(format, string_, in_text=False): return format % encode_tex(string_, in_text) @@ -236,16 +238,16 @@ def render(format, string_, in_text=False): add_conversion_fn(String, string) -def interpretation_box(self, **options): - return lookup_conversion_method(self.boxes, "latex")(self.boxes, **options) +def interpretation_box(box: InterpretationBox, **options): + return lookup_conversion_method(box.boxes, "latex")(box.boxes, **options) add_conversion_fn(InterpretationBox, interpretation_box) -def pane_box(self, **options): - content = lookup_conversion_method(self.boxes, "latex")(self.boxes, **options) - options = self.box_options +def pane_box(box: PaneBox, **options): + content = lookup_conversion_method(box.boxes, "latex")(box.boxes, **options) + options = box.box_options size = options.get("System`ImageSize", SymbolAutomatic).to_python() if size == "System`Automatic": @@ -281,27 +283,27 @@ def pane_box(self, **options): add_conversion_fn(PaneBox, pane_box) -def fractionbox(self, **options) -> str: - _options = self.box_options.copy() +def fractionbox(box: FractionBox, **options) -> str: + _options = box.box_options.copy() _options.update(options) options = _options return "\\frac{%s}{%s}" % ( - lookup_conversion_method(self.num, "latex")(self.num, **options), - lookup_conversion_method(self.den, "latex")(self.den, **options), + lookup_conversion_method(box.num, "latex")(box.num, **options), + lookup_conversion_method(box.den, "latex")(box.den, **options), ) add_conversion_fn(FractionBox, fractionbox) -def gridbox(self, elements=None, **box_options) -> str: +def gridbox(box: GridBox, elements=None, **box_options) -> str: def boxes_to_tex(box, **options): return lookup_conversion_method(box, "latex")(box, **options) if not elements: - elements = self._elements + elements = box._elements evaluation = box_options.get("evaluation") - items, options = self.get_array(elements, evaluation) + items, options = box.get_array(elements, evaluation) box_options.update(options) box_options["inside_list"] = True column_alignments = box_options["System`ColumnAlignments"].get_name() @@ -338,40 +340,40 @@ def boxes_to_tex(box, **options): add_conversion_fn(GridBox, gridbox) -def sqrtbox(self, **options): - _options = self.box_options.copy() +def sqrtbox(box: SqrtBox, **options): + _options = box.box_options.copy() _options.update(options) options = _options - if self.index: + if box.index: return "\\sqrt[%s]{%s}" % ( - lookup_conversion_method(self.radicand, "latex")(self.radicand, **options), - lookup_conversion_method(self.index, "latex")(self.index, **options), + lookup_conversion_method(box.radicand, "latex")(box.radicand, **options), + lookup_conversion_method(box.index, "latex")(box.index, **options), ) - return "\\sqrt{%s}" % lookup_conversion_method(self.radicand, "latex")( - self.radicand, **options + return "\\sqrt{%s}" % lookup_conversion_method(box.radicand, "latex")( + box.radicand, **options ) add_conversion_fn(SqrtBox, sqrtbox) -def superscriptbox(self, **options): - _options = self.box_options.copy() +def superscriptbox(box: SuperscriptBox, **options): + _options = box.box_options.copy() _options.update(options) options = _options - base_to_tex = lookup_conversion_method(self.base, "latex") - tex1 = base_to_tex(self.base, **options) + base_to_tex = lookup_conversion_method(box.base, "latex") + tex1 = base_to_tex(box.base, **options) - sup_string = self.superindex.get_string_value() + sup_string = box.superindex.get_string_value() # Handle derivatives if sup_string == named_characters["Prime"]: return "%s'" % tex1 if sup_string == named_characters["Prime"] * 2: return "%s''" % tex1 - base = self.tex_block(tex1, True) - superidx_to_tex = lookup_conversion_method(self.superindex, "latex") - superindx = self.tex_block(superidx_to_tex(self.superindex, **options), True) - if len(superindx) == 1 and isinstance(self.superindex, (String, StyleBox)): + base = box.tex_block(tex1, True) + superidx_to_tex = lookup_conversion_method(box.superindex, "latex") + superindx = box.tex_block(superidx_to_tex(box.superindex, **options), True) + if len(superindx) == 1 and isinstance(box.superindex, (String, StyleBox)): return "%s^%s" % ( base, superindx, @@ -385,33 +387,33 @@ def superscriptbox(self, **options): add_conversion_fn(SuperscriptBox, superscriptbox) -def subscriptbox(self, **options): - _options = self.box_options.copy() +def subscriptbox(box: SubscriptBox, **options): + _options = box.box_options.copy() _options.update(options) options = _options - base_to_tex = lookup_conversion_method(self.base, "latex") - subidx_to_tex = lookup_conversion_method(self.subindex, "latex") + base_to_tex = lookup_conversion_method(box.base, "latex") + subidx_to_tex = lookup_conversion_method(box.subindex, "latex") return "%s_%s" % ( - self.tex_block(base_to_tex(self.base, **options), True), - self.tex_block(subidx_to_tex(self.subindex, **options)), + box.tex_block(base_to_tex(box.base, **options), True), + box.tex_block(subidx_to_tex(box.subindex, **options)), ) add_conversion_fn(SubscriptBox, subscriptbox) -def subsuperscriptbox(self, **options): - _options = self.box_options.copy() +def subsuperscriptbox(box: SubsuperscriptBox, **options): + _options = box.box_options.copy() _options.update(options) options = _options - base_to_tex = lookup_conversion_method(self.base, "latex") - subidx_to_tex = lookup_conversion_method(self.subindex, "latex") - superidx_to_tex = lookup_conversion_method(self.superindex, "latex") + base_to_tex = lookup_conversion_method(box.base, "latex") + subidx_to_tex = lookup_conversion_method(box.subindex, "latex") + superidx_to_tex = lookup_conversion_method(box.superindex, "latex") return "%s_%s^%s" % ( - self.tex_block(base_to_tex(self.base, **options), True), - self.tex_block(subidx_to_tex(self.subindex, **options)), - self.tex_block(superidx_to_tex(self.superindex, **options)), + box.tex_block(base_to_tex(box.base, **options), True), + box.tex_block(subidx_to_tex(box.subindex, **options)), + box.tex_block(superidx_to_tex(box.superindex, **options)), ) @@ -466,11 +468,11 @@ def rowbox_parenthesized(items, **options): return f'{bracket_data["latex_open"]}{contain}{bracket_data["latex_closing"]}' -def rowbox(self, **options) -> str: - _options = self.box_options.copy() +def rowbox(box: RowBox, **options) -> str: + _options = box.box_options.copy() _options.update(options) options = _options - items = self.items + items = box.items # Handle special cases if len(items) >= 3: head, *rest = items @@ -489,17 +491,17 @@ def rowbox(self, **options) -> str: add_conversion_fn(RowBox, rowbox) -def stylebox(self, **options) -> str: - _options = self.box_options.copy() +def stylebox(box: StyleBox, **options) -> str: + _options = box.box_options.copy() _options.update(options) options = _options - return lookup_conversion_method(self.boxes, "latex")(self.boxes, **options) + return lookup_conversion_method(box.boxes, "latex")(box.boxes, **options) add_conversion_fn(StyleBox, stylebox) -def graphicsbox(self, elements=None, **options) -> str: +def graphicsbox(box: GraphicsBox, elements=None, **options) -> str: """This is the top-level function that converts a Mathics Expression in to something suitable for AMSLaTeX. @@ -509,8 +511,8 @@ def graphicsbox(self, elements=None, **options) -> str: assert elements is None if not elements: - content = self.content - fields = prepare_elements2d(self, content, options, max_width=450) + content = box.content + fields = prepare_elements2d(box, content, options, max_width=450) if len(fields) == 2: elements, calc_dimensions = fields else: @@ -545,8 +547,8 @@ def graphicsbox(self, elements=None, **options) -> str: asy_number(ymax), ) - if self.background_color is not None: - color, opacity = asy_color(self.background_color) + if box.background_color is not None: + color, opacity = asy_color(box.background_color) if opacity is not None: color = color + f"+opacity({opacity})" asy_background = "filldraw(%s, %s);" % (asy_box, color) @@ -576,9 +578,9 @@ def graphicsbox(self, elements=None, **options) -> str: add_conversion_fn(GraphicsBox, graphicsbox) -def graphics3dbox(self, elements=None, **options) -> str: +def graphics3dbox(box: Graphics3DBox, elements=None, **options) -> str: assert elements is None - elements = self.content + elements = box.content ( elements, @@ -587,7 +589,7 @@ def graphics3dbox(self, elements=None, **options) -> str: ticks_style, calc_dimensions, boxscale, - ) = prepare_elements3d(self, elements, options, max_width=450) + ) = prepare_elements3d(box, elements, options, max_width=450) elements._apply_boxscaling(boxscale) @@ -614,7 +616,7 @@ def graphics3dbox(self, elements=None, **options) -> str: # Draw boundbox and axes boundbox_asy = "" - boundbox_lines = get_boundbox_lines3D(self, xmin, xmax, ymin, ymax, zmin, zmax) + boundbox_lines = get_boundbox_lines3D(box, xmin, xmax, ymin, ymax, zmin, zmax) for i, line in enumerate(boundbox_lines): if i in axes_indices: @@ -748,8 +750,8 @@ def graphics3dbox(self, elements=None, **options) -> str: height, width = (400, 400) # TODO: Proper size # Background color - if self.background_color: - bg_color, opacity = asy_color(self.background_color) + if box.background_color: + bg_color, opacity = asy_color(box.background_color) background_directive = "background=" + bg_color + ", " else: background_directive = "" @@ -769,7 +771,7 @@ def graphics3dbox(self, elements=None, **options) -> str: asy_number(width / 60), asy_number(height / 60), # Rescale viewpoint - [vp * max([xmax - xmin, ymax - ymin, zmax - zmin]) for vp in self.viewpoint], + [vp * max([xmax - xmin, ymax - ymin, zmax - zmin]) for vp in box.viewpoint], asy, boundbox_asy, background_directive, @@ -780,8 +782,8 @@ def graphics3dbox(self, elements=None, **options) -> str: add_conversion_fn(Graphics3DBox, graphics3dbox) -def tag_and_form_box(self, **options): - return lookup_conversion_method(self.boxes, "latex")(self.boxes, **options) +def tag_and_form_box(box: BoxExpression, **options): + return lookup_conversion_method(box.boxes, "latex")(box.boxes, **options) add_conversion_fn(FormBox, tag_and_form_box) diff --git a/mathics/format/render/mathml.py b/mathics/format/render/mathml.py index 683471d0d..70c641e4c 100644 --- a/mathics/format/render/mathml.py +++ b/mathics/format/render/mathml.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -Lower-level formatter of Mathics objects as MathML strings. +Mathics3 Graphics3D box rendering to MathML strings. MathML formatting is usually initiated in Mathics via MathMLForm[]. """ @@ -9,6 +9,7 @@ from mathics_scanner.tokeniser import is_symbol_name +from mathics.builtin.box.expression import BoxExpression from mathics.builtin.box.graphics import GraphicsBox from mathics.builtin.box.graphics3d import Graphics3DBox from mathics.builtin.box.layout import ( @@ -64,14 +65,14 @@ def encode_mathml(text: str) -> str: } -def string(self, **options) -> str: - text = self.value +def string(s: String, **options) -> str: + text = s.value number_as_text = options.get("number_as_text", None) show_string_characters = ( options.get("System`ShowStringCharacters", None) is SymbolTrue ) - if isinstance(self, BoxElementMixin): + if isinstance(s, BoxElementMixin): if number_as_text is None: number_as_text = SymbolFalse @@ -122,16 +123,16 @@ def render(format, string): add_conversion_fn(String, string) -def interpretation_box(self, **options): - return lookup_conversion_method(self.boxes, "mathml")(self.boxes, **options) +def interpretation_box(box: InterpretationBox, **options): + return lookup_conversion_method(box.boxes, "mathml")(box.boxes, **options) add_conversion_fn(InterpretationBox, interpretation_box) -def pane_box(self, **options): - content = lookup_conversion_method(self.boxes, "mathml")(self.boxes, **options) - options = self.box_options +def pane_box(box: PaneBox, **options): + content = lookup_conversion_method(box.boxes, "mathml")(box.boxes, **options) + options = box.box_options size = options.get("System`ImageSize", SymbolAutomatic).to_python() if size is SymbolAutomatic: width = "" @@ -167,27 +168,27 @@ def pane_box(self, **options): add_conversion_fn(PaneBox, pane_box) -def fractionbox(self, **options) -> str: - _options = self.box_options.copy() +def fractionbox(box: FractionBox, **options) -> str: + _options = box.box_options.copy() _options.update(options) options = _options return "%s %s" % ( - lookup_conversion_method(self.num, "mathml")(self.num, **options), - lookup_conversion_method(self.den, "mathml")(self.den, **options), + lookup_conversion_method(box.num, "mathml")(box.num, **options), + lookup_conversion_method(box.den, "mathml")(box.den, **options), ) add_conversion_fn(FractionBox, fractionbox) -def gridbox(self, elements=None, **box_options) -> str: +def gridbox(box: GridBox, elements=None, **box_options) -> str: def boxes_to_mathml(box, **options): return lookup_conversion_method(box, "mathml")(box, **options) if not elements: - elements = self._elements + elements = box._elements evaluation = box_options.get("evaluation") - items, options = self.get_array(elements, evaluation) + items, options = box.get_array(elements, evaluation) num_fields = max(len(item) if isinstance(item, tuple) else 1 for item in items) attrs = {} @@ -221,67 +222,67 @@ def boxes_to_mathml(box, **options): add_conversion_fn(GridBox, gridbox) -def sqrtbox(self, **options): - _options = self.box_options.copy() +def sqrtbox(box: SqrtBox, **options): + _options = box.box_options.copy() _options.update(options) options = _options - if self.index: + if box.index: return " %s %s " % ( - lookup_conversion_method(self.radicand, "mathml")(self.radicand, **options), - lookup_conversion_method(self.index, "mathml")(self.index, **options), + lookup_conversion_method(box.radicand, "mathml")(box.radicand, **options), + lookup_conversion_method(box.index, "mathml")(box.index, **options), ) - return " %s " % lookup_conversion_method(self.radicand, "mathml")( - self.radicand, **options + return " %s " % lookup_conversion_method(box.radicand, "mathml")( + box.radicand, **options ) add_conversion_fn(SqrtBox, sqrtbox) -def subscriptbox(self, **options): - _options = self.box_options.copy() +def subscriptbox(box: SubscriptBox, **options): + _options = box.box_options.copy() _options.update(options) options = _options return "%s %s" % ( - lookup_conversion_method(self.base, "mathml")(self.base, **options), - lookup_conversion_method(self.subindex, "mathml")(self.subindex, **options), + lookup_conversion_method(box.base, "mathml")(box.base, **options), + lookup_conversion_method(box.subindex, "mathml")(box.subindex, **options), ) add_conversion_fn(SubscriptBox, subscriptbox) -def superscriptbox(self, **options): - _options = self.box_options.copy() +def superscriptbox(box: SuperscriptBox, **options): + _options = box.box_options.copy() _options.update(options) options = _options return "%s %s" % ( - lookup_conversion_method(self.base, "mathml")(self.base, **options), - lookup_conversion_method(self.superindex, "mathml")(self.superindex, **options), + lookup_conversion_method(box.base, "mathml")(box.base, **options), + lookup_conversion_method(box.superindex, "mathml")(box.superindex, **options), ) add_conversion_fn(SuperscriptBox, superscriptbox) -def subsuperscriptbox(self, **options): - _options = self.box_options.copy() +def subsuperscriptbox(box: SubsuperscriptBox, **options): + _options = box.box_options.copy() _options.update(options) options = _options options["inside_row"] = True return "%s %s %s" % ( - lookup_conversion_method(self.base, "mathml")(self.base, **options), - lookup_conversion_method(self.subindex, "mathml")(self.subindex, **options), - lookup_conversion_method(self.superindex, "mathml")(self.superindex, **options), + lookup_conversion_method(box.base, "mathml")(box.base, **options), + lookup_conversion_method(box.subindex, "mathml")(box.subindex, **options), + lookup_conversion_method(box.superindex, "mathml")(box.superindex, **options), ) add_conversion_fn(SubsuperscriptBox, subsuperscriptbox) -def rowbox(self, **options) -> str: - _options = self.box_options.copy() +def rowbox(box: RowBox, **options) -> str: + _options = box.box_options.copy() _options.update(options) options = _options result = [] @@ -296,16 +297,16 @@ def is_list_interior(content): is_list_row = False if ( - len(self.items) >= 3 - and self.items[0].get_string_value() == "{" - and self.items[2].get_string_value() == "}" - and self.items[1].has_form("RowBox", 1, None) + len(box.items) >= 3 + and box.items[0].get_string_value() == "{" + and box.items[2].get_string_value() == "}" + and box.items[1].has_form("RowBox", 1, None) ): - content = self.items[1].items + content = box.items[1].items if is_list_interior(content): is_list_row = True - if not inside_row and is_list_interior(self.items): + if not inside_row and is_list_interior(box.items): is_list_row = True if is_list_row: @@ -313,7 +314,7 @@ def is_list_interior(content): else: options["inside_row"] = True - for element in self.items: + for element in box.items: result.append(lookup_conversion_method(element, "mathml")(element, **options)) # print(f"mrow: {result}") @@ -324,20 +325,20 @@ def is_list_interior(content): add_conversion_fn(RowBox, rowbox) -def stylebox(self, **options) -> str: - _options = self.box_options.copy() +def stylebox(box: StyleBox, **options) -> str: + _options = box.box_options.copy() _options.update(options) options = _options - return lookup_conversion_method(self.boxes, "mathml")(self.boxes, **options) + return lookup_conversion_method(box.boxes, "mathml")(box.boxes, **options) add_conversion_fn(StyleBox, stylebox) -def graphicsbox(self, elements=None, **options) -> str: +def graphicsbox(box: GraphicsBox, elements=None, **options) -> str: # FIXME: SVG is the only thing we can convert MathML into. # Handle other graphics formats. - svg_body = self.boxes_to_format("svg", **options) + svg_body = box.boxes_to_format("svg", **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 @@ -349,8 +350,8 @@ def graphicsbox(self, elements=None, **options) -> str: ) # print(svg_body) mathml = template % ( - int(self.boxwidth), - int(self.boxheight), + int(box.boxwidth), + int(box.boxheight), base64.b64encode(svg_body.encode("utf8")).decode("utf8"), ) # print("boxes_to_mathml", mathml) @@ -360,9 +361,9 @@ def graphicsbox(self, elements=None, **options) -> str: add_conversion_fn(GraphicsBox, graphicsbox) -def graphics3dbox(self, elements=None, **options) -> str: +def graphics3dbox(box, elements=None, **options) -> str: """Turn the Graphics3DBox into a MathML string""" - result = self.boxes_to_js(**options) + result = box.boxes_to_js(**options) result = f"{result}" return result @@ -370,8 +371,8 @@ def graphics3dbox(self, elements=None, **options) -> str: add_conversion_fn(Graphics3DBox, graphics3dbox) -def tag_and_form_box(self, **options): - return lookup_conversion_method(self.boxes, "mathml")(self.boxes, **options) +def tag_and_form_box(box: BoxExpression, **options): + return lookup_conversion_method(box.boxes, "mathml")(box.boxes, **options) add_conversion_fn(FormBox, tag_and_form_box) diff --git a/mathics/format/render/svg.py b/mathics/format/render/svg.py index 8ae10b709..bd48f4df4 100644 --- a/mathics/format/render/svg.py +++ b/mathics/format/render/svg.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- """ -Lower-level formatter of Mathics objects as SVG strings. +Mathics3 Graphics box rendering to SVG strings. """ from mathics.builtin.box.graphics import ( + ArcBox, ArrowBox, BezierCurveBox, FilledCurveBox, @@ -13,8 +14,7 @@ PointBox, PolygonBox, RectangleBox, - _ArcBox, - _RoundBox, + RoundBox, ) from mathics.builtin.drawing.graphics3d import Graphics3DElements from mathics.builtin.graphics import DEFAULT_POINT_FACTOR, PointSize, _svg_bezier @@ -92,16 +92,16 @@ def create_css( return "; ".join(css) -def arcbox(self, **options) -> str: +def arcbox(box: ArcBox, **options) -> str: """ SVG formatting for arc of a circle. """ - if self.arc is None: + if box.arc is None: # We have a doughnut graph and this is the inner blank hole of that. # It is an empty circle - return _roundbox(self) + return roundbox(box) - x, y, rx, ry, sx, sy, ex, ey, large_arc = self._arc_params() + x, y, rx, ry, sx, sy, ex, ey, large_arc = box._arc_params() def path(closed): if closed: @@ -115,40 +115,40 @@ def path(closed): if closed: yield "Z" - line_width = self.style.get_line_width(face_element=self.face_element) + line_width = box.style.get_line_width(face_element=box.face_element) style = create_css( - self.edge_color, - self.face_color, + box.edge_color, + box.face_color, stroke_width=line_width, - edge_opacity=self.edge_opacity, - face_opacity=self.face_opacity, + edge_opacity=box.edge_opacity, + face_opacity=box.face_opacity, ) - svg = f"" + svg = f"" # print("_Arcbox: ", svg) return svg -add_conversion_fn(_ArcBox, arcbox) +add_conversion_fn(ArcBox, arcbox) -def arrow_box(self, **options) -> str: - width = self.style.get_line_width(face_element=False) +def arrow_box(box: ArrowBox, **options) -> str: + width = box.style.get_line_width(face_element=False) style = create_css( - self.edge_color, stroke_width=width, edge_opacity=self.edge_opacity + box.edge_color, stroke_width=width, edge_opacity=box.edge_opacity ) - polyline = self.curve.make_draw_svg(style) + polyline = box.curve.make_draw_svg(style) - arrow_style = create_css(face_color=self.edge_color, stroke_width=width) + arrow_style = create_css(face_color=box.edge_color, stroke_width=width) def polygon(points): yield '' - extent = self.graphics.view_width or 0 - default_arrow = self._default_arrow(polygon) - custom_arrow = self._custom_arrow("svg", _SVGTransform) - svg = "\n".join(self._draw(polyline, default_arrow, custom_arrow, extent)) + extent = box.graphics.view_width or 0 + default_arrow = box._default_arrow(polygon) + custom_arrow = box._custom_arrow("svg", _SVGTransform) + svg = "\n".join(box._draw(polyline, default_arrow, custom_arrow, extent)) # print("ArrowBox: ", svg) return svg @@ -156,19 +156,19 @@ def polygon(points): add_conversion_fn(ArrowBox, arrow_box) -def bezier_curve_box(self, **options) -> str: +def bezier_curve_box(box: BezierCurveBox, **options) -> str: """ SVG formatter for BezierCurveBox. """ - line_width = self.style.get_line_width(face_element=False) + line_width = box.style.get_line_width(face_element=False) style = create_css( - edge_color=self.edge_color, + edge_color=box.edge_color, stroke_width=line_width, - edge_opacity=self.edge_opacity, + edge_opacity=box.edge_opacity, ) svg = "\n" - for line in self.lines: - s = "\n".join(_svg_bezier((self.spline_degree, [xy.pos() for xy in line]))) + for line in box.lines: + s = "\n".join(_svg_bezier((box.spline_degree, [xy.pos() for xy in line]))) svg += f'' # print("BezierCurveBox: ", svg) return svg @@ -177,7 +177,7 @@ def bezier_curve_box(self, **options) -> str: add_conversion_fn(BezierCurveBox, bezier_curve_box) -def density_plot_box(self, **options): +def density_plot_box(box, **options): """ SVG formatter for DensityPlotBox. """ @@ -202,9 +202,9 @@ def density_plot_box(self, **options): # to go from the center to each of the (square) sides. svg_data = [""] - for index, triangle_coords in enumerate(self.lines): + for index, triangle_coords in enumerate(box.lines): triangle = [coords.pos() for coords in triangle_coords] - colors = [rgb.to_js() for rgb in self.vertex_colors[index]] + colors = [rgb.to_js() for rgb in box.vertex_colors[index]] r = (colors[0][0] + colors[1][0] + colors[2][0]) / 3 g = (colors[0][1] + colors[1][1] + colors[2][1]) / 3 b = (colors[0][2] + colors[1][2] + colors[2][1]) / 3 @@ -221,21 +221,21 @@ def density_plot_box(self, **options): # No add_conversion_fn since this is a hacken-on polygonbox -def filled_curve_box(self, **options): - line_width = self.style.get_line_width(face_element=False) +def filled_curve_box(box: FilledCurveBox, **options): + line_width = box.style.get_line_width(face_element=False) style = create_css( - edge_color=self.edge_color, face_color=self.face_color, stroke_width=line_width + edge_color=box.edge_color, face_color=box.face_color, stroke_width=line_width ) style = create_css( - edge_color=self.edge_color, - face_color=self.face_color, + edge_color=box.edge_color, + face_color=box.face_color, stroke_width=line_width, - edge_opacity=self.edge_opacity, - face_opacity=self.edge_opacity, + edge_opacity=box.edge_opacity, + face_opacity=box.edge_opacity, ) def components(): - for component in self.components: + for component in box.components: transformed = [(k, [xy.pos() for xy in p]) for k, p in component] yield " ".join(_svg_bezier(*transformed)) + " Z" @@ -249,7 +249,7 @@ def components(): add_conversion_fn(FilledCurveBox, filled_curve_box) -def graphics_box(self, elements=None, **options: dict) -> str: +def graphics_box(box: GraphicsBox, elements=None, **options: dict) -> str: """ Top-level SVG routine takes ``elements`` and ``options`` and turns this into a SVG string, including the .. tag. @@ -266,7 +266,7 @@ def graphics_box(self, elements=None, **options: dict) -> str: """ assert elements is None - elements = self.content + elements = box.content data = options.get("data", None) assert data is None @@ -277,27 +277,27 @@ def graphics_box(self, elements=None, **options: dict) -> str: xmax, ymin, ymax, - self.boxwidth, - self.boxheight, + box.boxwidth, + box.boxheight, width, height, ) = data else: elements, calc_dimensions = prepare_elements2d( - self, elements, options, neg_y=True + box, elements, options, neg_y=True ) ( xmin, xmax, ymin, ymax, - self.boxwidth, - self.boxheight, + box.boxwidth, + box.boxheight, width, height, ) = calc_dimensions() - elements.view_width = self.boxwidth + elements.view_width = box.boxwidth format_fn = lookup_method(elements, "svg") if format_fn is not None: @@ -305,17 +305,26 @@ def graphics_box(self, elements=None, **options: dict) -> str: else: svg_body = elements.to_svg(**options) - self.boxwidth = options.get("width", self.boxwidth) - self.boxheight = options.get("height", self.boxheight) + boxwidth = options.get("width", box.boxwidth) + boxheight = options.get("height", box.boxheight) - tooltip_text = self.tooltip_text or "" - if self.background_color is not None: + assert isinstance( + boxwidth, (int, float) + ), f"boxwidth {boxwidth} should be 'int' or 'float'. is {type(boxwidth)}" + assert isinstance( + boxheight, (int, float) + ), f"boxwidth {boxheight} should be 'int' or 'float'. is {type(boxheight)}" + box.boxwidth = boxwidth + box.boxheight = boxheight + + tooltip_text = box.tooltip_text or "" + if box.background_color is not None: # FIXME: tests don't seem to cover this section of code. # Wrap svg_elements in a rectangle background = "rgba(100%,100%,100%,100%)" - if self.background_color: - components = self.background_color.to_rgba() + if box.background_color: + components = box.background_color.to_rgba() if len(components) == 3: background = "rgb(" + ", ".join(f"{100*c}%" for c in components) + ")" else: @@ -324,8 +333,8 @@ def graphics_box(self, elements=None, **options: dict) -> str: svg_body = f""" {tooltip_text} {svg_body} """ @@ -333,7 +342,7 @@ def graphics_box(self, elements=None, **options: dict) -> str: if options.get("noheader", False): return svg_body - svg_main = wrap_svg_body(self.boxwidth, self.boxheight, xmin, ymin, svg_body) + svg_main = wrap_svg_body(box.boxwidth, box.boxheight, xmin, ymin, svg_body) # print("svg_main", svg_main) return svg_main # , width, height @@ -341,12 +350,12 @@ def graphics_box(self, elements=None, **options: dict) -> str: add_conversion_fn(GraphicsBox, graphics_box) -def graphics_elements(self, **options) -> str: +def graphics_elements(box: GraphicsElements, **options) -> str: """ - SVG formatting on a list of graphics elements. + SVG formatting on a GraphicsElementBox which may contain other GraphicsElementBox's. """ result = [""] - for element in self.elements: + for element in box.elements: try: format_fn = lookup_method(element, "svg") except Exception: @@ -370,45 +379,45 @@ def graphics_elements(self, **options) -> str: add_conversion_fn(Graphics3DElements) -def inset_box(self, **options) -> str: +def inset_box(box: InsetBox, **options) -> str: """ SVG formatting for boxing an Inset in a graphic. """ - x, y = self.pos.pos() + x, y = box.pos.pos() offset = options.get("offset", None) if offset is not None: x = x + offset[0] y = y + offset[1] - if hasattr(self.content, "to_svg"): - content = self.content.to_svg(noheader=True, offset=(x, y)) + if hasattr(box.content, "to_svg"): + content = box.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, + font_color=box.color, + edge_color=box.color, + face_color=box.color, stroke_width=0.2, - opacity=self.opacity.opacity, + opacity=box.opacity.opacity, ) - text_pos_opts = f'x="{x}" y="{y}" ox="{self.opos[0]}" oy="{self.opos[1]}"' + text_pos_opts = f'x="{x}" y="{y}" ox="{box.opos[0]}" oy="{box.opos[1]}"' alignment = " dominant-baseline:hanging;" - if hasattr(self, "alignment"): - if self.alignment == "bottom": + if hasattr(box, "alignment"): + if box.alignment == "bottom": # This is typically done for labels under the x axis. alignment = " dominant-baseline:hanging; text-anchor:middle;" - elif self.alignment == "left": + elif box.alignment == "left": # This is typically done for labels to the left of the y axis. alignment = " dominant-baseline:middle; text-anchor:end;" # FIXME: don't hard code text_style_opts, but allow these to be adjustable. text_style_opts = alignment - content = self.content.boxes_to_text(evaluation=self.graphics.evaluation) + content = box.content.boxes_to_text(evaluation=box.graphics.evaluation) font_size = f'''font-size="{options.get("point_size", "10px")}"''' svg = f'{content}' - # content = self.content.boxes_to_mathml(evaluation=self.graphics.evaluation) - # style = create_css(font_color=self.color) + # content = box.content.boxes_to_mathml(evaluation=box.graphics.evaluation) + # style = create_css(font_color=box.color) # svg = ( # '' # "%s") @@ -420,12 +429,12 @@ def inset_box(self, **options) -> str: add_conversion_fn(InsetBox, inset_box) -def line_box(self, **options) -> str: - line_width = self.style.get_line_width(face_element=False) +def line_box(box: LineBox, **options) -> str: + line_width = box.style.get_line_width(face_element=False) style = create_css( - edge_color=self.edge_color, + edge_color=box.edge_color, stroke_width=line_width, - edge_opacity=self.edge_opacity, + edge_opacity=box.edge_opacity, ) # The following line options come from looking at SVG produced from WMA. @@ -434,7 +443,7 @@ def line_box(self, **options) -> str: style += "; stroke-linecap:square; stroke-linejoin:miter; stroke-miterlimit:3.25" svg = "\n" - for line in self.lines: + for line in box.lines: svg += '' % ( " ".join(["%f,%f" % coords.pos() for coords in line]), style, @@ -446,18 +455,18 @@ def line_box(self, **options) -> str: add_conversion_fn(LineBox, line_box) -def pointbox(self, **options) -> str: - point_size, _ = self.style.get_style(PointSize, face_element=False) +def pointbox(box: PointBox, **options) -> str: + point_size, _ = box.style.get_style(PointSize, face_element=False) if point_size is None: - point_size = PointSize(self.graphics, value=DEFAULT_POINT_FACTOR) + point_size = PointSize(box.graphics, value=DEFAULT_POINT_FACTOR) size = point_size.get_absolute_size() style = create_css( - edge_color=self.edge_color, + edge_color=box.edge_color, stroke_width=0, - face_color=self.face_color, - edge_opacity=self.edge_opacity, - face_opacity=self.face_opacity, + face_color=box.face_color, + edge_opacity=box.edge_opacity, + face_opacity=box.face_opacity, ) # The following line options come from looking at SVG produced from WMA. @@ -465,7 +474,7 @@ def pointbox(self, **options) -> str: style += "; fill-rule:even-odd" svg = "" - for line in self.lines: + for line in box.lines: for coords in line: svg += f""" str: add_conversion_fn(PointBox) -def polygonbox(self, **options): +def polygonbox(box: PolygonBox, **options): """ SVG formatter for PolygonBox """ - line_width = self.style.get_line_width(face_element=True) + line_width = box.style.get_line_width(face_element=True) # Hack alert. Currently we encode density plots as a polygon box where # each polygon is a triangle with a color. We know we have this case because - # self.vertex_colors is not empty here. - if self.vertex_colors: - return density_plot_box(self, **options) + # box.vertex_colors is not empty here. + if box.vertex_colors: + return density_plot_box(box, **options) style = create_css( - edge_color=self.edge_color, - face_color=self.face_color, + edge_color=box.edge_color, + face_color=box.face_color, stroke_width=line_width, - edge_opacity=self.edge_opacity, - face_opacity=self.face_opacity, + edge_opacity=box.edge_opacity, + face_opacity=box.face_opacity, ) svg = "\n" @@ -505,7 +514,7 @@ def polygonbox(self, **options): # Perhaps one day we will find it useful to have other fill_rules specified as an option. fill_rule = "evenodd" - for line in self.lines: + for line in box.lines: svg += f""" ' - # print("_RoundBox: ", svg) + # print("RoundBox: ", svg) return svg -add_conversion_fn(_RoundBox) +add_conversion_fn(RoundBox) def wrap_svg_body( diff --git a/mathics/format/render/text.py b/mathics/format/render/text.py index 1c2b5b9a5..a62f201cb 100644 --- a/mathics/format/render/text.py +++ b/mathics/format/render/text.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -Lower-level formatter Mathics objects as plain text. +Mathics3 box rendering to plain text. """ @@ -33,8 +33,8 @@ def boxes_to_text(boxes, **options) -> str: return lookup_method(boxes, "text")(boxes, **options) -def string(self, **options) -> str: - value = self.value +def string(s: String, **options) -> str: + value = s.value show_string_characters = ( options.get("System`ShowStringCharacters", None) is SymbolTrue ) diff --git a/test/builtin/test_forms.py b/test/builtin/test_forms.py index 7bc9e5f59..890c988c4 100644 --- a/test/builtin/test_forms.py +++ b/test/builtin/test_forms.py @@ -403,7 +403,7 @@ def test_makeboxes_form(expr, form, head, subhead): ), ], ) -def test_private_doctests_output(str_expr, msgs, str_expected, fail_msg): +def test_output(str_expr, msgs, str_expected, fail_msg): """ """ check_evaluation( str_expr,