summaryrefslogtreecommitdiffstats
path: root/share/extensions/tests/test_inkex_svg.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--share/extensions/tests/test_inkex_svg.py461
1 files changed, 461 insertions, 0 deletions
diff --git a/share/extensions/tests/test_inkex_svg.py b/share/extensions/tests/test_inkex_svg.py
new file mode 100644
index 0000000..4f1e8b7
--- /dev/null
+++ b/share/extensions/tests/test_inkex_svg.py
@@ -0,0 +1,461 @@
+#!/usr/bin/env python
+# coding=utf-8
+#
+# Copyright (C) 2018 Martin Owens
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA.
+#
+"""
+Test the svg interface for inkscape extensions.
+"""
+from inkex.transforms import Vector2d
+from inkex.utils import addNS
+from inkex import Guide
+from inkex.tester import TestCase
+from inkex.tester.svg import svg, svg_file, uu_svg
+
+class BasicSvgTest(TestCase):
+ """Basic svg tests"""
+
+ def test_svg_load(self):
+ """Test loading an svg with the right parser"""
+ self.assertEqual(type(svg()).__name__, 'SvgDocumentElement')
+
+ def test_add_ns(self):
+ """Test adding a namespace to a tag"""
+ self.assertEqual(addNS('g', 'svg'), '{http://www.w3.org/2000/svg}g')
+ self.assertEqual(addNS('h', 'inkscape'), '{http://www.inkscape.org/namespaces/inkscape}h')
+ self.assertEqual(addNS('i', 'sodipodi'),
+ '{http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}i')
+ self.assertEqual(addNS('{p}j'), '{p}j')
+
+ def test_svg_ids(self):
+ """Test a list of ids from an svg document"""
+ self.assertEqual(svg('id="apples"').get_ids(), {'apples'})
+
+ def test_svg_new_id(self):
+ """Test generatign a new id for a given tag"""
+ doc = svg('id="apples"')
+ usedids = set(['apples'])
+ for prefix in ['apples'] * 3:
+ newid = doc.get_unique_id(prefix)
+ self.assertTrue(newid.startswith(prefix))
+ self.assertTrue(newid not in usedids)
+ usedids.add(newid)
+
+ def test_svg_select_id(self):
+ """Select an id from the document"""
+ doc = svg('id="bananas"')
+ doc.selection.set('bananas')
+ self.assertEqual(doc.selection['bananas'], doc)
+ self.assertEqual(doc.selection.first(), doc)
+ doc = svg('id="apples"')
+ doc.selected.set(doc.getElementById('apples'))
+ self.assertEqual(doc.selection['apples'], doc)
+ self.assertEqual(doc.selection.first(), doc)
+
+ def test_svg_by_class(self):
+ """Select elements by class"""
+ doc = svg_file(self.data_file('svg', 'multilayered-test.svg'))
+ elems = doc.getElementsByClass('frog')
+ self.assertEqual([elem.get_id() for elem in elems],
+ ['path3902', 'text3926', 'path3900', 'rect3898'])
+ elems = doc.getElementsByClass('apple')
+ self.assertEqual([elem.get_id() for elem in elems], ['text3926', 'rect3898'])
+
+ def test_svg_by_href(self):
+ """Select element by xlink href"""
+ doc = svg_file(self.data_file('svg', 'multilayered-test.svg'))
+ elem = doc.getElementsByHref('path3900')[0]
+ self.assertEqual(elem.TAG, 'textPath')
+ self.assertEqual(elem.get_id(), 'textPath3923')
+ elem = doc.getElementsByHref('path3904')[0]
+ self.assertEqual(elem.TAG, 'textPath')
+ self.assertEqual(elem.get_id(), 'textPath3906')
+ self.assertEqual(doc.getElementsByHref('not-an-id'), [])
+
+ def test_svg_by_url_link(self):
+ """Select element by urls in styles"""
+ doc = svg_file(self.data_file('svg', 'markers.svg'))
+ elem = doc.getElementsByStyleUrl('Arrow1Lend')[0]
+ self.assertEqual(elem.get_id(), 'dimension')
+ elem = doc.getElementsByStyleUrl('Arrow1Lstart')[0]
+ self.assertEqual(elem.get_id(), 'dimension')
+ self.assertEqual(doc.getElementsByStyleUrl('not-an-id'), [])
+
+ def test_selected_bbox(self):
+ """Can we get a bounding box from the selected items"""
+ doc = svg_file(self.data_file('svg', 'multilayered-test.svg'))
+ doc.selected.set('path3904', 'path3902')
+ from inkex.transforms import BoundingBox
+ x, y, w, h = 199.544, 156.412, 377.489, 199.972 # from inkscape --query-all
+ expected_3904 = BoundingBox((x, x + w), (y, y + h))
+ x, y, w, h = 145.358, 478.373, 439.135, 419.142 # from inkscape --query-all
+ expected_3902 = BoundingBox((x, x + w), (y, y + h))
+ expected = list(expected_3902 + expected_3904)
+
+ for x, y in zip(expected, doc.selection.bounding_box()):
+ self.assertDeepAlmostEqual(tuple(x), tuple(y), delta=1e-3)
+
+
+ def test_svg_name(self):
+ """Can get the sodipodi name attribute"""
+ doc = svg_file(self.data_file('svg', 'multilayered-test.svg'))
+ self.assertEqual(doc.name, 'Nouveau document 1')
+
+ def test_svg_nameview(self):
+ """Can get the sodipodi nameview element"""
+ doc = svg()
+ self.assertEqual(doc.namedview.center.x, 0)
+ self.assertEqual(type(doc.namedview).__name__, 'NamedView')
+
+ def test_svg_layers(self):
+ """Selected layer is selected"""
+ doc = svg_file(self.data_file('svg', 'multilayered-test.svg'))
+ self.assertEqual(doc.get_current_layer().get('id'), 'layer3')
+ doc = svg('id="empty"')
+ self.assertEqual(doc.get_current_layer(), doc)
+
+ def test_svg_center_position(self):
+ """SVG with namedview has a center position"""
+ doc = svg_file(self.data_file('svg', 'multilayered-test.svg'))
+ self.assertTrue(doc.namedview.center.is_close((30.714286, 520.0)))
+ self.assertTrue(svg().namedview.center.is_close(Vector2d()))
+
+ def test_defs(self):
+ """Can get the defs from an svg file"""
+ doc = svg_file(self.data_file('svg', 'markers.svg'))
+ self.assertEqual(len(doc.defs), 2)
+ doc = svg('id="empty"')
+ self.assertEqual(len(doc.defs), 0)
+
+ def test_scale(self):
+ """Scale of a document"""
+ doc = svg('id="empty" viewBox="0 0 100 100" width="200" height="200"')
+ self.assertEqual(doc.width, 200.0)
+ self.assertEqual(doc.get_viewbox()[2], 100.0)
+ self.assertEqual(doc.scale, 2.0)
+ doc = svg('id="empty" viewBox="0 0 0 0" width="200" height="200"')
+ self.assertEqual(doc.scale, 1.0)
+
+class NamedViewTest(TestCase):
+ """Tests for the named view functionality"""
+
+ def test_create_guide(self):
+ """Test creating guides"""
+ doc = svg_file(self.data_file('svg', 'multilayered-test.svg'))
+ namedview = doc.namedview
+ self.assertEqual(len(namedview.get_guides()), 0)
+
+ namedview.add(Guide().move_to(50, 50, 45))
+ self.assertEqual(len(namedview.get_guides()), 1)
+ guide, = namedview.get_guides()
+ self.assertEqual(guide.get('position'), '50,50')
+ self.assertEqual(guide.get('orientation'), '0.707107,-0.707107')
+
+
+class GetDocumentWidthTest(TestCase):
+ """Tests for Effect.width."""
+
+ def test_no_dimensions(self):
+ """An empty width value should be default zero width"""
+ self.assertEqual(svg().width, 0)
+
+ def test_empty_width(self):
+ """An empty width value should be the same as a missing width."""
+ self.assertEqual(svg('width=""').width, 0)
+
+ def test_empty_viewbox(self):
+ """An empty viewBox value should be the same as a missing viewBox."""
+ self.assertEqual(svg('viewBox=""').width, 0)
+
+ def test_empty_width_and_viewbox(self):
+ """Empty values for both should be the same as both missing."""
+ self.assertEqual(svg('width="" viewBox=""').width, 0)
+
+ def test_width_only(self):
+ """Test a fixed width"""
+ self.assertAlmostEqual(svg('width="120mm"').width, 453.5433071)
+
+ def test_width_and_viewbox(self):
+ """If both are present, width overrides viewBox."""
+ self.assertAlmostEqual(svg('width="120mm" viewBox="0 0 22 99"').width, 453.5433071)
+
+ def test_viewbox_only(self):
+ """IF only the viewBox is present"""
+ self.assertEqual(svg('viewBox="0 0 22 99"').width, 22.0)
+
+ def test_only_valid_viewbox(self):
+ """An empty width value should be the same as a missing width."""
+ self.assertEqual(svg('width="" viewBox="0 0 22 99"').width, 22.0)
+
+ def test_non_zero_viewbox_x(self):
+ """Demonstrate that a non-zero x value (viewbox[0]) does not affect the width value."""
+ self.assertEqual(svg('width="" viewBox="5 7 22 99"').width, 22.0)
+
+
+class GetDocumentHeightTest(TestCase):
+ """Tests for Effect.height."""
+
+ def test_no_dimensions(self):
+ """Test height from blank svg"""
+ self.assertEqual(svg().height, 0)
+
+ def test_empty_height(self):
+ """An empty height value should be the same as a missing height."""
+ self.assertEqual(svg('height=""').height, 0)
+
+ def test_empty_viewbox(self):
+ """An empty viewBox value should be the same as a missing viewBox."""
+ self.assertEqual(svg('viewBox=""').height, 0)
+
+ def test_empty_height_viewbox(self):
+ """Empty values for both should be the same as both missing."""
+ self.assertEqual(svg('height="" viewBox=""').height, 0)
+
+ def test_height_only(self):
+ """A simple height only in px"""
+ self.assertEqual(svg('height="330px"').height, 330)
+
+ def test_height_and_viewbox(self):
+ """If both are present, height overrides viewBox."""
+ self.assertEqual(svg('height="330px" viewBox="0 0 22 99"').height, 330)
+
+ def test_viewbox_only(self):
+ """Height from viewBox only"""
+ self.assertEqual(svg('viewBox="0 0 22 99"').height, 99.0)
+
+ def test_no_height_valid_viewbox(self):
+ """An empty height value should be the same as a missing height."""
+ self.assertEqual(svg('height="" viewBox="0 0 22 99"').height, 99.0)
+
+ def test_non_zero_viewbox_y(self):
+ """Demonstrate that a non-zero y value (viewbox[1]) does not affect the height value."""
+ self.assertEqual(svg('height="" viewBox="5 7 22 99"').height, 99.0)
+
+
+class GetDocumentUnitTest(TestCase):
+ """Tests for Effect.unit."""
+
+ def test_no_dimensions(self):
+ """Default units with no arguments"""
+ self.assertEqual(svg().unit, 'px')
+
+ def test_width_only(self):
+ """"Units from document width only"""
+ # TODO: Determine whether returning 'px' in this case is the
+ # intended behavior.
+ self.assertEqual(svg('width="100m"').unit, 'px')
+
+ def test_height_only(self):
+ """Units from document height only"""
+ # TODO: Determine whether returning 'px' in this case is the
+ # intended behavior.
+ self.assertEqual(svg('height="100m"').unit, 'px')
+
+ def test_viewbox_only(self):
+ """Test viewbox only document units"""
+ self.assertEqual(svg('viewBox="0 0 377 565"').unit, 'px')
+
+ # Unit-ratio tests. Don't exhaustively test every unit conversion, just
+ # demonstrate that the logic works.
+
+ def test_width_and_viewbox_px(self):
+ """100mm is ~377px, so unit should be 'px'."""
+ self.assertEqual(svg('width="100mm" viewBox="0 0 377 565"').unit, 'px')
+
+ def test_width_and_viewbox_in(self):
+ """100mm is ~3.94in, so unit should be 'in'."""
+ self.assertEqual(svg('width="100mm" viewBox="0 0 3.94 5.90"').unit, 'in')
+
+ def test_unitless_width_and_viewbox(self):
+ """Unitless width should be treated as 'px'."""
+ # 3779px is ~1m, so unit should be 'm'.
+ self.assertEqual(svg('width="3779" viewBox="0 0 1 1.5"').unit, 'm')
+
+ def test_height_with_viewbox(self):
+ """150mm is ~5.90in, so unit should be 'in', but height is ignored"""
+ # TODO: Determine whether returning 'px' in this case is the intended
+ # behavior.
+ self.assertEqual(svg('height="150mm" viewBox="0 0 3.94 5.90"').unit, 'px')
+
+ def test_height_width_and_viewbox(self):
+ """100mm is ~23.6pc, so unit should be 'pc'."""
+ doc = svg('width="100mm" height="150mm" viewBox="0 0 23.6 35.4"')
+ self.assertEqual(doc.unit, 'pc')
+
+ def test_large_error_reverts_to_px(self):
+ """'px' instead of using the closest match 'pc'."""
+ # 100mm is ~23.6pc; 24.1 is ~2% off from that, so unit should fall back
+ self.assertEqual(svg('width="100mm" viewBox="0 0 24.1 35.4"').unit, 'px')
+
+ # TODO: Demonstrate that unknown width units are treated as px while
+ # determining the ratio.
+
+ # 100px fallback tests. Although that value is an arbitrary default, it's
+ # possible for users of inkex.Effect to depend on this behavior.
+ # NOTE: Do not treat the existence of these tests as a reason to preserve
+ # the 100px fallback logic.
+
+ def test_bad_width_number(self):
+ """Fallback test: Bad numbers default to 100"""
+ # First, demonstrate that 1in is 2.54cm, so unit should be 'cm'.
+ self.assertEqual(svg('width="1in" viewBox="0 0 2.54 1"').unit, 'cm')
+
+ # Corrupt the width to contain an invalid number component; note that
+ # the units change to 'px'. This is because the corrupt number part is
+ # replaced with 100px, producing a width of "100px";
+ self.assertEqual(svg('width="ABCDin" viewBox="0 0 2.54 1"').unit, 'px')
+
+ def test_bad_viewbox_entry(self):
+ """Fallback test: Bad viewBox default to 100"""
+ # First, demonstrate that 3779px is 1m, so unit should be 'm'.
+ self.assertEqual(svg('width="3779px" viewBox="0 0 1 1"').unit, 'm')
+
+ # Corrupt the viewBox to include a non-float value; will default to 'px'
+ self.assertEqual(svg('width="3779px" viewBox="x 0 1 1"').unit, 'px')
+
+
+class UserUnitTest(TestCase):
+ """Tests for methods that are based on the value of unit."""
+
+ def assertToUserUnit(self, user_unit, test_value, expected): # pylint: disable=invalid-name
+ """Checks a user unit and a test_value against the expected result"""
+ doc = uu_svg(user_unit)
+ self.assertEqual(doc.unit, user_unit, msg=svg)
+ self.assertAlmostEqual(doc.unittouu(test_value), expected)
+
+ def assertFromUserUnit(self, user_unit, value, unit, expected): # pylint: disable=invalid-name
+ """Check converting from a user unity for the test_value"""
+ self.assertAlmostEqual(uu_svg(user_unit).uutounit(value, unit), expected)
+
+ # Unit-ratio tests. Don't exhaustively test every unit conversion, just
+ # demonstrate that the logic works.
+
+ def test_unittouu_in_to_cm(self):
+ """1in is ~2.54cm"""
+ self.assertToUserUnit('cm', '1in', 2.54)
+
+ def test_unittouu_yd_to_m(self):
+ """1yd is ~0.9144m"""
+ self.assertToUserUnit('m', '1yd', 0.9144)
+
+ def test_unittouu_identity(self):
+ """If the input and output units are the same, the input and output
+ values should exactly be the same, too."""
+ self.assertToUserUnit('pc', '9.87654321pc', 9.87654321)
+
+ def test_unittouu_unitless_input(self):
+ """Passing a unitless value to unittouu() should treat the units as 'px'."""
+ self.assertToUserUnit('in', '96', 1) # 1in == 96px
+
+ def test_unittouu_empty_input(self):
+ """Passing an empty string to unittouu() should treat the value as zero."""
+ self.assertToUserUnit('in', '', 0)
+
+ def test_unittouu_parsing(self):
+ """Test user unit parsing forms"""
+ for value in (
+ '100pc',
+ '100 pc',
+ ' 100pc',
+ '100pc ',
+ '+100pc',
+ '100.0pc',
+ '100.0e0pc',
+ '10.0e1pc',
+ '10.0e+1pc',
+ '1000.0e-1pc',
+ '.1e+3pc',
+ '+.1e+3pc',
+ ):
+ # 100pc is ~3.937in
+ self.assertToUserUnit('px', value, 1600)
+
+ def test_unittouu_bad_input_number(self):
+ """Bad input number"""
+ self.assertToUserUnit('cm', '1in', 2.54)
+ # Demonstrate that 1in is ~2.54cm.
+
+ # Corrupt the input to contain an invalid number component; note that
+ # the result changes to zero.
+ self.assertToUserUnit('cm', 'ABCDin', 0)
+
+ def test_unittouu_bad_input_unit(self):
+ """Bad input unit"""
+ # Demonstrate that 1.0in passes through without change.
+ self.assertToUserUnit('in', '1.0in', 1.0)
+
+ # Corrupt the input to contain an invalid unit component; note that the
+ # result changes to 0.0, because corrupt parsing is zero px.
+ # it used to be the ratio between inches and pixels. This was
+ # because unittouu() treats unknown units as 'px'.
+ self.assertToUserUnit('in', '1.0ABCD', 0)
+
+ # Unit-ratio tests. Don't exhaustively test every unit conversion, just
+ # demonstrate that the logic works.
+
+ def test_uutounit_cm_to_in(self):
+ """Convert 1 user unit ('in') to 'cm'."""
+ self.assertFromUserUnit('in', 1, 'cm', 2.54) # 1in is ~2.54cm
+
+ def test_uutounit_m_to_yd(self):
+ """Convert 1 user unit ('yd') to 'm'."""
+ self.assertFromUserUnit('yd', 1, 'm', 0.9144) # 1yd is ~0.9144m
+
+ def test_uutounit_identity(self):
+ """If the input and output units are the same, the input and output
+ values should exactly be the same, too."""
+ self.assertFromUserUnit('pc', 9.87654321, 'pc', 9.87654)
+
+ def test_uutounit_unknown_unit(self):
+ """Demonstrate that passing an unknown unit string to uutounit()"""
+ self.assertEqual(uu_svg('in').uutounit(1, 'px'), 96.0)
+
+ def test_adddocumentunit_common(self):
+ """Test common add_unit results"""
+ # For valid float inputs, the output should be the input with the user unit appended.
+ doc = uu_svg('pt')
+ cases = (
+ # Input, expected output
+ (100, '100pt'),
+ ('100', '100pt'),
+ ('+100', '100pt'),
+ ('-100', '-100pt'),
+ ('100.0', '100pt'),
+ ('100.0e0', '100pt'),
+ ('10.0e1', '100pt'),
+ ('10.0e+1', '100pt'),
+ ('1000.0e-1', '100pt'),
+ ('.1e+3', '100pt'),
+ ('+.1e+3', '100pt'),
+ (' 100', '100pt'),
+ ('100 ', '100pt'),
+ (' 100 ', '100pt'),
+ )
+ for input_value, expected in cases:
+ self.assertEqual(doc.add_unit(input_value), expected)
+
+ def test_adddocumentunit_non_float(self):
+ """Strings that are invalid floats should pass through unchanged."""
+ doc = uu_svg('pt')
+ inputs = (
+ '',
+ 'ABCD',
+ '.',
+ ' ',
+ )
+ for value in inputs:
+ self.assertEqual(doc.add_unit(value), '')