diff --git a/features/reportlab.feature b/features/reportlab.feature new file mode 100644 index 00000000..3544ad44 --- /dev/null +++ b/features/reportlab.feature @@ -0,0 +1,5 @@ +Feature: ReportLab renderer + Scenario: SVG cubic path commands are translated to ReportLab path calls + Given an SVG path with move, line, cubic, and close commands + When the SVG path is rendered with the ReportLab backend + Then the ReportLab path should receive the expected drawing commands diff --git a/features/steps/reportlab_steps.py b/features/steps/reportlab_steps.py new file mode 100644 index 00000000..c257231f --- /dev/null +++ b/features/steps/reportlab_steps.py @@ -0,0 +1,57 @@ +# Copyright 2014, Sandia Corporation. Under the terms of Contract +# DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains certain +# rights in this software. + +from behave import * + +import unittest.mock +import xml.etree.ElementTree as xml + +import test +import toyplot.reportlab + + +@given(u'an SVG path with move, line, cubic, and close commands') +def step_impl(context): + context.svg = xml.Element( + "svg", + width="10px", + height="10px", + style="border-style:none", + ) + xml.SubElement( + context.svg, + "path", + d="M 0 0 L 1 1 C 2 3 4 5 6 7 Z", + style="fill:none;stroke:black;stroke-width:1", + ) + + +@when(u'the SVG path is rendered with the ReportLab backend') +def step_impl(context): + context.path = unittest.mock.Mock() + context.canvas = unittest.mock.Mock() + context.canvas.beginPath.return_value = context.path + toyplot.reportlab.render(context.svg, context.canvas) + + +@then(u'the ReportLab path should receive the expected drawing commands') +def step_impl(context): + test.assert_equal(context.canvas.beginPath.call_count, 1) + test.assert_sequence_equal( + context.path.moveTo.call_args_list, + [unittest.mock.call(0.0, 0.0)], + ) + test.assert_sequence_equal( + context.path.lineTo.call_args_list, + [unittest.mock.call(1.0, 1.0)], + ) + test.assert_sequence_equal( + context.path.curveTo.call_args_list, + [unittest.mock.call(2.0, 3.0, 4.0, 5.0, 6.0, 7.0)], + ) + test.assert_equal(context.path.close.call_count, 1) + test.assert_sequence_equal( + context.canvas.drawPath.call_args_list, + [unittest.mock.call(context.path)], + ) diff --git a/toyplot/reportlab/__init__.py b/toyplot/reportlab/__init__.py index ffab7558..48d7a362 100644 --- a/toyplot/reportlab/__init__.py +++ b/toyplot/reportlab/__init__.py @@ -131,6 +131,23 @@ def set_stroke_color(canvas, color): canvas.setStrokeColorRGB(color["r"], color["g"], color["b"]) canvas.setStrokeAlpha(color["a"].item()) + def draw_svg_path(path, commands): + while commands: + command = commands.pop(0) + if command == "L": + path.lineTo( + as_float(commands.pop(0)), as_float(commands.pop(0))) + elif command == "M": + path.moveTo( + as_float(commands.pop(0)), as_float(commands.pop(0))) + elif command == "C": + path.curveTo( + as_float(commands.pop(0)), as_float(commands.pop(0)), + as_float(commands.pop(0)), as_float(commands.pop(0)), + as_float(commands.pop(0)), as_float(commands.pop(0))) + elif command in ["Z", "z"]: + path.close() + def render_element(root, element, canvas, styles): canvas.saveState() @@ -243,15 +260,7 @@ def render_element(root, element, canvas, styles): set_stroke_color(canvas, stroke) canvas.setLineCap(get_line_cap(current_style)) path = canvas.beginPath() - commands = element.get("d").split() - while commands: - command = commands.pop(0) - if command == "L": - path.lineTo( - as_float(commands.pop(0)), as_float(commands.pop(0))) - elif command == "M": - path.moveTo( - as_float(commands.pop(0)), as_float(commands.pop(0))) + draw_svg_path(path, element.get("d").split()) canvas.drawPath(path) elif element.tag == "polygon": fill, fill_gradient = get_fill(root, current_style)