diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
commit | cca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch) | |
tree | 146f39ded1c938019e1ed42d30923c2ac9e86789 /share/extensions/tests/test_inkex_styles_complex.py | |
parent | Initial commit. (diff) | |
download | inkscape-upstream/1.2.2.tar.xz inkscape-upstream/1.2.2.zip |
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | share/extensions/tests/test_inkex_styles_complex.py | 465 |
1 files changed, 465 insertions, 0 deletions
diff --git a/share/extensions/tests/test_inkex_styles_complex.py b/share/extensions/tests/test_inkex_styles_complex.py new file mode 100644 index 0000000..6355228 --- /dev/null +++ b/share/extensions/tests/test_inkex_styles_complex.py @@ -0,0 +1,465 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2021 Jonathan Neuhauser, jonathan.neuhauser@outlook.com +# +# 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. +# +""" +Some more complicated styling tests, including inheritance and shorthand attributes +""" +from lxml import etree +from typing import List, Tuple +from inkex.styles import Style +from inkex.colors import Color +from inkex.tester import TestCase +from inkex.tester.svg import svg_file +from inkex import ( + SvgDocumentElement, + BaseElement, + ColorError, + BaseStyleValue, + RadialGradient, + Stop, + PathElement, +) +from inkex import SVG_PARSER + + +class StyleInheritanceTests(TestCase): + """Some test cases for css attribute handling""" + + def test_style_sheet_1(self): + """File from https://commons.wikimedia.org/wiki/File:Test_only.svg, public domain + note that Inkscape fails the same test: https://gitlab.com/inkscape/inbox/-/issues/1929""" + doc: SvgDocumentElement = svg_file( + self.data_file("svg", "style_inheritance.svg") + ) + + circles: List[BaseElement] = doc.xpath("//svg:circle") + for circle in circles: + style = circle.specified_style() + self.assertEqual(style("fill"), Color("red"), circle.getparent().get_id()) + + rects: List[BaseElement] = doc.xpath("//svg:rect") + for rect in rects: + style = rect.specified_style() + self.assertEqual(style("fill"), Color("blue")) + + def test_style_sheet_2(self): + """This is the unit test styling-css-04-f.svg from + https://www.w3.org/Graphics/SVG/Test/20061213/htmlObjectHarness/full-styling-css-04-f.html + Note that the "good" preview image attached on the site is wrong per the explanation""" + doc: SvgDocumentElement = svg_file( + self.data_file("svg", "styling-css-04-f.svg") + ) + + rects: List[BaseElement] = doc.xpath("//svg:rect") + + results = { + "A": "blue", + "B": "green", + "C": "orange", + "D": "gold", + "E": "purple", + "F": "red", + } + for rect in rects: + ident = rect.get_id() + if len(ident) != 2: + continue + result = results[ident[0]] + + style = rect.specified_style() + self.assertEqual(style("fill"), Color(result)) + self.assertEqual(style["stroke-dasharray"], "none") + + def test_current_color(self): + """This is the unit test styling-inherit-01-b.svg from + https://www.w3.org/Graphics/SVG/Test/20061213/htmlObjectHarness/full-styling-inherit-01-b.html + """ + + doc: SvgDocumentElement = svg_file( + self.data_file("svg", "styling-inherit-01-b.svg") + ) + + objects: List[BaseElement] = doc.xpath("//svg:rect|//svg:ellipse") + + for counter, obj in zip(range(3), objects[:3]): + fill = obj.specified_style()("fill") + if counter == 0: + self.assertEqual(fill, Color("yellow")) + else: + if counter == 1: + result = "green" + else: + result = "#700" + self.assertIsInstance(fill, RadialGradient) + stops = [child for child in fill if isinstance(child, Stop)] + stop = stops[1] + self.assertEqual(stop.specified_style()("stop-color"), Color(result)) + + stroke = objects[3].specified_style()("stroke") + self.assertEqual(stroke, Color("red")) + + def test_marker_style(self): + """Check if markers are read and written correctly""" + + doc: SvgDocumentElement = svg_file(self.data_file("svg", "markers.svg")) + elem = doc.getElementById("dimension") + style = elem.specified_style() + marker = style("marker-start") + self.assertEqual(marker, doc.getElementById("Arrow1Lstart")) + + # replace marker + elem.style["marker-start"] = doc.getElementById("Arrow1Lend") + marker = elem.specified_style()("marker-start") + self.assertEqual(marker, doc.getElementById("Arrow1Lend")) + + # write invalid attribute + with self.assertRaisesRegex(ValueError, "Invalid property value"): + elem.style["marker-start"] = "#url(test)" + + # write invalid attribute, second attempt + with self.assertRaisesRegex(ValueError, "invalid URL format"): + elem.style["marker-start"] = "url('test)" + + # write shorthand + elem.style["marker"] = doc.getElementById("Arrow1Lstart") + self.assertEqual(elem.style("marker-start"), doc.getElementById("Arrow1Lstart")) + self.assertEqual(elem.style("marker-mid"), doc.getElementById("Arrow1Lstart")) + self.assertEqual(elem.style("marker-end"), doc.getElementById("Arrow1Lstart")) + + # write shorthand to empty + elem.style["marker"] = "" + self.assertEqual(elem.style("marker-start"), doc.getElementById("Arrow1Lend")) + + def test_get_default(self): + """Test if the default values are returned for missing attributes""" + doc: SvgDocumentElement = svg_file(self.data_file("svg", "interp_shapes.svg")) + elem = doc.getElementById("path6") + + assert elem.style("stroke-dashoffset") == "0" + assert elem.style("font") == "" + + def parse_style_and_compare(self, tests: List[Tuple[str, dict]]): + """Parses a style and compares the output to a dictionary of attributes""" + for shorthand, result in tests: + style = Style(shorthand) + for key, value in result.items(): + self.assertEqual(str(style(key)), value) + + def test_font_shorthand(self): + """Test whether shorthand properties are applied correctly""" + tests: List[Tuple[str, dict]] = [ + ("font: ", {"font-size": "medium"}), + ( + r"font: 12px/14px sans-serif", + { + "font-size": "12px", + "line-height": "14px", + "font-family": "sans-serif", + }, + ), + ( + r"font: 80% sans-serif", + {"font-size": "80%", "font-family": "sans-serif"}, + ), + ( + r'font: x-large/110% "New Century Schoolbook", serif', + { + "font-size": "x-large", + "line-height": "110%", + "font-family": '"New Century Schoolbook", serif', + }, + ), + ( + r"font: semi-condensed bold italic large Palatino, serif", + { + "font-weight": "bold", + "font-style": "italic", + "font-size": "large", + "font-family": "Palatino, serif", + "font-stretch": "semi-condensed", + }, + ), + ( + r"font: normal small-caps 120%/120% fantasy", + { + "font-weight": "normal", + "font-style": "normal", + "font-variant": "small-caps", + "font-size": "120%", + "line-height": "120%", + "font-family": "fantasy", + }, + ), + ] + self.parse_style_and_compare(tests) + + def test_shorthand_overwrites(self): + """Test whether shorthands correctly follow precedence: only overwrite rules which are + defined before and not important""" + tests: List[Tuple[str, dict]] = [ + ( + """font-size: large; + font-family: Verdana !important; + font: bold 12px/14px sans-serif; + font-weight: normal;""", + { + "font-size": "12px", + "font-family": "Verdana", + "line-height": "14px", + "font-weight": "normal", + }, + ) + ] + self.parse_style_and_compare(tests) + + def test_gradient_parsing(self): + """Test if the style correctly outputs Gradient objects""" + doc: SvgDocumentElement = svg_file(self.data_file("svg", "interp_shapes.svg")) + elem = doc.getElementById("path6") + style = elem.style + grad = style("stroke") + self.assertEqual(grad, doc.getElementById("linearGradient855")) + + def test_attribute_set(self): + """Tests if we can set attributes with parsed values""" + doc: SvgDocumentElement = svg_file(self.data_file("svg", "interp_shapes.svg")) + + elem = doc.getElementById("path6") + style = elem.style + + tests = [ + ( + "stroke", + doc.getElementById("linearGradient847"), + "url(#linearGradient847)", + ), + ("fill", Color("red"), "red"), + ("stroke", None, "none"), + ("opacity", 0.5, "0.5"), + ("opacity", 1.2, "1"), + ("opacity", -2, "0"), + ("font-variant", "small-caps", "small-caps"), + ] + for attr, value, result in tests: + style[attr] = value + self.assertEqual(style[attr], result) + self.assertEqual(elem.specified_style()[attr], result) + + def test_opacity_clip(self): + """Test if opacity clipping works""" + style = Style() + tests = [ + ("opacity", "0.5", 0.5), + ("opacity", "1.2", 1), + ("opacity", "-2", 0), + ("opacity", "50%", 0.5), + ] + for attr, value, result in tests: + style[attr] = value + self.assertEqual(style(attr), result) + + def test_style_parsing_error(self): + """Test if bad attribute data raises an exception during parsing""" + doc: SvgDocumentElement = svg_file(self.data_file("svg", "interp_shapes.svg")) + tests: List[Tuple[str, Exception]] = [ + (r"opacity: abc", ValueError), + (r"fill: #GHI", ColorError), + (r"stroke: url(#missing)", ValueError), + (r"fill: ", ColorError), + (r"font-variant: blue", ValueError), + ] + for decl, exceptiontype in tests: + with self.assertRaises(exceptiontype): + value = BaseStyleValue.factory(declaration=decl) + _ = value.parse_value(doc) + self.assertEqual( + BaseStyleValue.factory_errorhandled(element=doc, declaration=decl), None + ) + + def test_attribute_set_invalid(self): + """Test if bad attribute data raises an exception when setting it on a style""" + doc: SvgDocumentElement = svg_file(self.data_file("svg", "interp_shapes.svg")) + + elem = doc.getElementById("path6") + tests = [ + ("fill", "nocolor", "Unknown color format"), + ("opacity", Style(), "Value must be number"), + ( + "font-variant", + "red", + "Value 'red' is invalid for the property font-variant", + ), + ("stroke", "url(#missing)", "Paint server not found"), + ] + style = elem.style + for attr, value, errormsg in tests: + with self.assertRaisesRegex(Exception, errormsg): + style[attr] = value + + def test_gradient_id_fallback(self): + """Test if the gradient fallback (color after nonexistent url) works""" + doc: SvgDocumentElement = svg_file(self.data_file("svg", "interp_shapes.svg")) + sty = Style(element=doc) + sty["stroke"] = "url(#nonexistent) red" + self.assertEqual(sty("stroke"), Color("red")) + + def test_compare_styles(self): + """Check style comparison""" + st1 = Style("fill: blue; stroke: red") + st2 = Style("fill: orange; stroke: red") + self.assertNotEqual(st1, st2) + st2["fill"] = "blue" + self.assertEqual(st1, st2) + st1["font-size"] = 1 + self.assertNotEqual(st1, st2) + + def test_basestylevalue(self): + """Create BaseStyleValue's directly and work on them""" + + val1 = BaseStyleValue.factory("fill: red;") + self.assertEqual(val1.parse_value(), Color("red")) + + # Compare the style + self.assertNotEqual(val1, "fill: red;") + + # Create a rule with an invalid declaration + with self.assertRaises(ValueError): + _ = BaseStyleValue.parse_declaration("fill=red;") + + # Try to apply a shorthand to the wrong style + val2 = BaseStyleValue.factory("font: 12pt Verdana") + style = Style("fill: context-fill;") + copy = style.copy() + + val2.apply_shorthand(style) + + self.assertEqual(style, copy) + + # Set a value to the wrong key + with self.assertRaises(ValueError): + style["stroke"] = BaseStyleValue.factory("font: 12pt Verdana") + + def test_style_bad_interfacing(self): + """Check a few ways to wrongly interface the Style class""" + style = Style("fill: red;") + + # call a missing key that is unknown and therefore has no default + with self.assertRaises(KeyError): + _ = style("favourite-test") + + # call the add_inherited method with a non-style argument + style2 = style.add_inherited("fill: blue") + self.assertEqual(style, style2) + + # set the importance on a value that doesn't exist + with self.assertRaises(KeyError): + style.set_importance("stroke", True) + + def test_style_exchange(self): + doc: SvgDocumentElement = svg_file(self.data_file("svg", "interp_shapes.svg")) + + elem = doc.getElementById("path6") + style = elem.style + + copystyle = style.copy() + self.assertIsNone(copystyle.callback) + copystyle["new-attribute"] = "test" + self.assertNotIn("new-attribute", elem.style) + + elem.style = copystyle + + copystyle["new-attribute"] = "test" + self.assertEqual(elem.style("new-attribute"), "test") + # callback is set after accessing the element + self.assertIsNotNone(elem.style.callback) + # copystyle["new-attribute2"] = "test" + # self.assertEqual(elem.style("new-attribute2"), "test") + + def test_stop_opacity_inheritance(self): + # subtest of pservers-grad-18b SVG1.1 unit test + content = """ <svg><g id="test-body-content" font-family="SVGFreeSansASCII,sans-serif" font-size="18"> + <g id="g0" stop-color="#f48" stop-opacity="0.5" color="yellow"> + <linearGradient id="MyGradient1" stop-color="inherit"> + <stop offset="0" stop-color="green" stop-opacity="1"/> + <stop offset="1" stop-color="inherit" stop-opacity="1"/> + </linearGradient> + </g></g></svg> + """ + doc = etree.fromstring(content, parser=SVG_PARSER) + grad = doc.getElementById("MyGradient1") + self.assertEqual( + grad[0].specified_style()("stop-opacity"), 1 + ) # assert that stop opacity is overwritten + self.assertEqual( + grad[1].specified_style()("stop-opacity"), 1 + ) # assert that stop opacity is not inherited by default + + def test_inheritance_second_attribute(self): + """Check that the second attribute is also correctly inherited""" + content = """<svg><g fill="red" font-size="18"><g font-size="20" id="test"></g></g></svg>""" + doc = etree.fromstring(content, parser=SVG_PARSER) + group = doc.getElementById("test") + self.assertEqual(group.specified_style()("font-size"), 20) + + def test_inherit_fallback(self): + content = """<svg><g fill="inherit" style="bla: inherit"><g fill="inherit" id="test" style="bla:inherit"/></g></svg>""" + doc = etree.fromstring(content, parser=SVG_PARSER) + group = doc.getElementById("test") + self.assertEqual(group.specified_style()("fill"), Color("black")) + self.assertEqual(group.specified_style()("bla"), None) + + def test_direct_child_and_import(self): + content = """<svg xmlns="http://www.w3.org/2000/svg"> + <style type="text/css"><![CDATA[ + @import url("test.css"); + g > ellipse + { + fill: red; + } + @import url("test.css"); + ]]></style> + <g><ellipse id="test"></ellipse></g></svg>""" + doc = etree.fromstring(content, parser=SVG_PARSER) + ellipse = doc.getElementById("test") + self.assertEqual(ellipse.specified_style()("fill"), Color("red")) + + def test_dasharray(self): + """test parsing of dasharray""" + elem = PathElement() + style = elem.style + tests = [ + ("1 2 3 4", [1, 2, 3, 4]), + ("1 2,3 4.5", [1, 2, 3, 4.5]), + ("1;2", None), + ("1.111", [1.111, 1.111]), + ("1px, 2px, 3px", [1, 2, 3, 1, 2, 3]), + ("", None), + ("1 -2", None), + (None, None), + ([1, 2, 3], [1, 2, 3, 1, 2, 3]), + ] + for value, result in tests: + style["stroke-dasharray"] = value + setvalue = style("stroke-dasharray") + if result is None: + self.assertEqual(result, setvalue, f"got {setvalue}, original: {value}") + else: + self.assertAlmostTuple( + result, setvalue, msg=f"Expected {result}, got {setvalue}" + ) |