Skip to content

Commit f0abdef

Browse files
authored
Merge pull request #1407 from mathics/svg-formatter-size-adjust
Handle PointSize in PointBox (roughly)
2 parents ebe952b + 029d06a commit f0abdef

4 files changed

Lines changed: 80 additions & 42 deletions

File tree

mathics/builtin/graphics.py

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,7 +1279,6 @@ class Point(Builtin):
12791279
<dd>represents a number of point primitives.
12801280
</dl>
12811281
1282-
>> Graphics[Point[{0,0}]]
12831282
= -Graphics-
12841283
12851284
>> Graphics[Point[Table[{Sin[t], Cos[t]}, {t, 0, 2. Pi, Pi / 15.}]]]
@@ -1292,10 +1291,29 @@ class Point(Builtin):
12921291
pass
12931292

12941293

1294+
# FIXME: We model points as line segments which
1295+
# is kind of wrong.
12951296
class PointBox(_Polyline):
1297+
"""
1298+
A Bounding box for a list of points.
1299+
object attributes:
1300+
edge_color: _Color
1301+
point_radius: radius of each point
1302+
"""
12961303
def init(self, graphics, style, item=None):
12971304
super(PointBox, self).init(graphics, item, style)
12981305
self.edge_color, self.face_color = style.get_style(_Color, face_element=True)
1306+
1307+
# Handle PointSize in a hacky way for now.
1308+
point_size, _ = style.get_style(PointSize, face_element=False)
1309+
if point_size is None:
1310+
point_size = PointSize(self.graphics, value=0.005)
1311+
1312+
# FIXME: we don't have graphics options. Until we do, we'll
1313+
# just assume an image width of 400
1314+
image_width = 400
1315+
self.point_radius = (image_width * point_size.value)
1316+
12991317
if item is not None:
13001318
if len(item.leaves) != 1:
13011319
raise BoxConstructError
@@ -1307,6 +1325,19 @@ def init(self, graphics, style, item=None):
13071325
else:
13081326
raise BoxConstructError
13091327

1328+
def extent(self):
1329+
"""Returns a list of bounding-box coordinates each point in the PointBox"""
1330+
l = self.point_radius
1331+
result = []
1332+
for line in self.lines:
1333+
for c in line:
1334+
x, y = c.pos()
1335+
result.extend(
1336+
[(x - l, y - l), (x - l, y + l), (x + l, y - l), (x + l, y + l)]
1337+
)
1338+
return result
1339+
1340+
13101341

13111342
class Line(Builtin):
13121343
"""
@@ -2382,6 +2413,7 @@ def __init__(self, content, evaluation, neg_y=False):
23822413
self.xmin = self.ymin = self.pixel_width = None
23832414
self.pixel_height = self.extent_width = self.extent_height = None
23842415
self.view_width = None
2416+
self.content = content
23852417

23862418
def translate(self, coords):
23872419
if self.pixel_width is not None:
@@ -2512,8 +2544,8 @@ def _get_image_size(self, options, graphics_options, max_width):
25122544
def _prepare_elements(self, leaves, options, neg_y=False, max_width=None):
25132545
if not leaves:
25142546
raise BoxConstructError
2515-
graphics_options = self.get_option_values(leaves[1:], **options)
2516-
background = graphics_options["System`Background"]
2547+
self.graphics_options = self.get_option_values(leaves[1:], **options)
2548+
background = self.graphics_options["System`Background"]
25172549
if (
25182550
isinstance(background, Symbol)
25192551
and background.get_name() == "System`Automatic"
@@ -2523,10 +2555,10 @@ def _prepare_elements(self, leaves, options, neg_y=False, max_width=None):
25232555
self.background_color = _Color.create(background)
25242556

25252557
base_width, base_height, size_multiplier, size_aspect = self._get_image_size(
2526-
options, graphics_options, max_width
2558+
options, self.graphics_options, max_width
25272559
)
25282560

2529-
plot_range = graphics_options["System`PlotRange"].to_python()
2561+
plot_range = self.graphics_options["System`PlotRange"].to_python()
25302562
if plot_range == "System`Automatic":
25312563
plot_range = ["System`Automatic", "System`Automatic"]
25322564

@@ -2658,7 +2690,7 @@ def get_range(min, max):
26582690
ymax += h * 0.02
26592691

26602692
axes.extend(
2661-
self.create_axes(elements, graphics_options, xmin, xmax, ymin, ymax)
2693+
self.create_axes(elements, self.graphics_options, xmin, xmax, ymin, ymax)
26622694
)
26632695

26642696
return elements, calc_dimensions
@@ -2725,6 +2757,7 @@ def boxes_to_mathml(self, leaves=None, **options) -> str:
27252757
elements, calc_dimensions = self._prepare_elements(leaves, options, neg_y=True)
27262758
xmin, xmax, ymin, ymax, w, h, width, height = calc_dimensions()
27272759
data = (elements, xmin, xmax, ymin, ymax, w, h, width, height)
2760+
elements.view_width = w
27282761

27292762
# FIXME: SVG is the only thing we can convert MathML into.
27302763
# Handle other graphics formats.

mathics/doc/tex/Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ doc_tex_data.pcl:
1616
mathics.pdf: mathics.tex documentation.tex logo-text-nodrop.pdf logo-heptatom.pdf doc_tex_data.pcl
1717
$(LATEXMK) -f -pdf -pdflatex="$(XETEX) -halt-on-error" mathics
1818

19+
#: Build test PDF
20+
mathics-test.pdf: mathics-test.tex
21+
$(LATEXMK) -f -pdf -pdflatex="$(XETEX) -halt-on-error" mathics-test
22+
23+
1924
#: Generate logos used in the titlepage
2025
logo-heptatom.pdf logo-text-nodrop.pdf:
2126
(cd .. && $(BASH) ./images.sh)
@@ -34,3 +39,4 @@ clean:
3439
rm -f mathics_*.* || true
3540
rm -f mathics-*.* documentation.tex || true
3641
rm -f mathics.pdf mathics.dvi data_tex_data.pcl || true
42+
rm -f mathics-test.pdf mathics-test.dvi || true

mathics/formatter/svg.py

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,19 @@ def matrix(self, a, b, c, d, e, f):
3333
# a c e
3434
# b d f
3535
# 0 0 1
36-
self.transforms.append("matrix(%f, %f, %f, %f, %f, %f)" % (a, b, c, d, e, f))
36+
self.transforms.append(f"matrix({a:f}, {b:f}, {c:f}, {d:f}, {e:f}, {e:f})")
3737

3838
def translate(self, x, y):
39-
self.transforms.append("translate(%f, %f)" % (x, y))
39+
self.transforms.append(f"translate({x:f}, {y:f})")
4040

4141
def scale(self, x, y):
42-
self.transforms.append("scale(%f, %f)" % (x, y))
42+
self.transforms.append(f"scale({x:f}, {y:f})")
4343

4444
def rotate(self, x):
45-
self.transforms.append("rotate(%f)" % x)
45+
self.transforms.append(f"rotate({x:f})")
4646

4747
def apply(self, svg):
48-
return '<g transform="%s">%s</g>' % (" ".join(self.transforms), svg)
48+
return f"""<g transform="{' '.join(self.transforms)}">{svg}</g>"""
4949

5050

5151
def create_css(
@@ -57,22 +57,22 @@ def create_css(
5757
css = []
5858
if edge_color is not None:
5959
color, stroke_opacity = edge_color.to_css()
60-
css.append("stroke: %s" % color)
61-
css.append("stroke-opacity: %s" % stroke_opacity)
60+
css.append(f"stroke: {color}")
61+
css.append(f"stroke-opacity: {stroke_opacity}")
6262
else:
6363
css.append("stroke: none")
6464
if stroke_width is not None:
65-
css.append("stroke-width: %fpx" % stroke_width)
65+
css.append(f"stroke-width: {stroke_width:f}px")
6666
if face_color is not None:
6767
color, fill_opacity = face_color.to_css()
68-
css.append("fill: %s" % color)
69-
css.append("fill-opacity: %s" % fill_opacity)
68+
css.append(f"fill: {color}")
69+
css.append(f"fill-opacity: {fill_opacity}")
7070
else:
7171
css.append("fill: none")
7272
if font_color is not None:
7373
color, _ = font_color.to_css()
74-
css.append("color: %s" % color)
75-
css.append("opacity: %s" % opacity)
74+
css.append(f"color: {color}")
75+
css.append(f"opacity: {opacity}")
7676
return "; ".join(css)
7777

7878

@@ -86,7 +86,7 @@ def arrow_box(self, **options):
8686
def polygon(points):
8787
yield '<polygon points="'
8888
yield " ".join("%f,%f" % xy for xy in points)
89-
yield '" style="%s" />' % arrow_style
89+
yield f'" style="{arrow_style}" />'
9090

9191
extent = self.graphics.view_width or 0
9292
default_arrow = self._default_arrow(polygon)
@@ -104,7 +104,7 @@ def beziercurvebox(self, **options):
104104
svg = ""
105105
for line in self.lines:
106106
s = " ".join(_svg_bezier((self.spline_degree, [xy.pos() for xy in line])))
107-
svg += '<path d="%s" style="%s"/>' % (s, style)
107+
svg += f'<path d="{s}" style="{style}"/>'
108108
# print("XXX bezier", svg)
109109
return svg
110110

@@ -133,6 +133,7 @@ def components():
133133
add_conversion_fn(FilledCurveBox, filled_curve_box)
134134

135135
def graphics_box(self, leaves=None, **options) -> str:
136+
136137
if not leaves:
137138
leaves = self._leaves
138139

@@ -164,24 +165,20 @@ def graphics_box(self, leaves=None, **options) -> str:
164165
svg_body,
165166
)
166167

167-
xmin -= 1
168-
ymin -= 1
169-
w += 2
170-
h += 2
171-
172168
if options.get("noheader", False):
173169
return svg_body
174170
svg_main = """
175-
<svg xmlns:svg="http://www.w3.org/2000/svg"
171+
<svg xmlns:svg="http://www.w3.org/2000/svg"
176172
xmlns="http://www.w3.org/2000/svg"
177173
version="1.1"
178174
viewBox="%s">
179175
%s
180-
</svg>
181-
""" % (
176+
</svg>
177+
""" % (
182178
" ".join("%f" % t for t in (xmin, ymin, w, h)),
183179
svg_body,
184180
)
181+
# print("svg_main", svg_main)
185182
return svg_main # , width, height
186183

187184

@@ -200,7 +197,9 @@ def graphics_elements(self, **options)->str:
200197
else:
201198
result.append(format_fn(element, **options))
202199

203-
return "\n".join(result)
200+
svg = "\n".join(result)
201+
# print("graphics_elements", svg)
202+
return svg
204203

205204

206205
add_conversion_fn(GraphicsElements, graphics_elements)
@@ -256,7 +255,7 @@ def line_box(self, **options)->str:
256255
" ".join(["%f,%f" % coords.pos() for coords in line]),
257256
style,
258257
)
259-
# print("XXX linebox", svg)
258+
# print("LineBox", svg)
260259
return svg
261260

262261

@@ -275,13 +274,10 @@ def pointbox(self, **options)->str:
275274
svg = ""
276275
for line in self.lines:
277276
for coords in line:
278-
svg += '<circle cx="%f" cy="%f" r="%f" style="%s" />' % (
279-
coords.pos()[0],
280-
coords.pos()[1],
281-
size,
282-
style,
283-
)
284-
# print("XXX PointBox", svg)
277+
svg += f"""
278+
<circle cx="{coords.pos()[0]:f}" cy="{coords.pos()[1]:f}"
279+
r="{size:f}" style="{style}"/>"""
280+
# print("PointBox", svg)
285281
return svg
286282

287283

@@ -312,7 +308,7 @@ def polygonbox(self, **options):
312308
" ".join("%f,%f" % coords.pos() for coords in line),
313309
style,
314310
)
315-
# print("XXX PolygonBox", svg)
311+
print("XXX PolygonBox", svg)
316312
return svg
317313

318314

@@ -331,14 +327,15 @@ def rectanglebox(self, **options):
331327
x1, x2 = x1 + offset[0], x2 + offset[0]
332328
y1, y2 = y1 + offset[1], y2 + offset[1]
333329
style = create_css(self.edge_color, self.face_color, line_width)
334-
return '<rect x="%f" y="%f" width="%f" height="%f" style="%s" />' % (
330+
svg = '<rect x="%f" y="%f" width="%f" height="%f" style="%s" />' % (
335331
xmin,
336332
ymin,
337333
w,
338334
h,
339335
style,
340336
)
341-
"\n".join(element.to_svg() for element in self.elements)
337+
# print("RectangleBox", svg)
338+
return svg
342339

343340

344341
add_conversion_fn(RectangleBox)
@@ -351,13 +348,15 @@ def _roundbox(self, **options):
351348
ry = y - ry
352349
line_width = self.style.get_line_width(face_element=self.face_element)
353350
style = create_css(self.edge_color, self.face_color, stroke_width=line_width)
354-
return '<ellipse cx="%f" cy="%f" rx="%f" ry="%f" style="%s" />' % (
351+
svg = '<ellipse cx="%f" cy="%f" rx="%f" ry="%f" style="%s" />' % (
355352
x,
356353
y,
357354
rx,
358355
ry,
359356
style,
360357
)
358+
# print("_RoundBox", svg)
359+
return svg
361360

362361

363362
add_conversion_fn(_RoundBox)

test/test_formatter/test_svg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def test_svg_point():
7676
# Circles are implemented as ellipses with equal major and minor axes.
7777
# Check for that.
7878
print(inner_svg)
79-
matches = re.match(r'^<circle cx="(\S+)" cy="(\S+)" r="(\S+)" .*/>', inner_svg)
79+
matches = re.match(r'^<circle cx="(\S+)" cy="(\S+)"', inner_svg)
8080
assert matches
8181
assert matches.group(1) == matches.group(2)
8282

0 commit comments

Comments
 (0)