summaryrefslogtreecommitdiffstats
path: root/testfiles/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
commitcca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch)
tree146f39ded1c938019e1ed42d30923c2ac9e86789 /testfiles/src
parentInitial commit. (diff)
downloadinkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.tar.xz
inkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.zip
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testfiles/src')
-rw-r--r--testfiles/src/2geom-characterization-test.cpp31
-rw-r--r--testfiles/src/attributes-test.cpp659
-rw-r--r--testfiles/src/cairo-utils-test.cpp51
-rw-r--r--testfiles/src/color-profile-test.cpp127
-rw-r--r--testfiles/src/curve-test.cpp292
-rw-r--r--testfiles/src/cxxtests-to-migrate/marker-test.h42
-rw-r--r--testfiles/src/cxxtests-to-migrate/mod360-test.h65
-rw-r--r--testfiles/src/cxxtests-to-migrate/preferences-test.h139
-rw-r--r--testfiles/src/cxxtests-to-migrate/sp-style-elem-test.h166
-rw-r--r--testfiles/src/cxxtests-to-migrate/test-helpers.h71
-rw-r--r--testfiles/src/cxxtests-to-migrate/verbs-test.h95
-rw-r--r--testfiles/src/dir-util-test.cpp64
-rw-r--r--testfiles/src/drag-and-drop-svgz.cpp70
-rw-r--r--testfiles/src/extract-uri-test.cpp75
-rw-r--r--testfiles/src/lpe-test.cpp165
-rw-r--r--testfiles/src/lpe64-test.cpp54
-rw-r--r--testfiles/src/object-set-test.cpp706
-rw-r--r--testfiles/src/object-style-test.cpp199
-rw-r--r--testfiles/src/object-test.cpp204
-rw-r--r--testfiles/src/path-boolop-test.cpp86
-rw-r--r--testfiles/src/rebase-hrefs-test.cpp135
-rw-r--r--testfiles/src/sp-glyph-kerning-test.cpp26
-rw-r--r--testfiles/src/sp-gradient-test.cpp130
-rw-r--r--testfiles/src/sp-item-group-test.cpp46
-rw-r--r--testfiles/src/sp-object-test.cpp122
-rw-r--r--testfiles/src/style-elem-test.cpp71
-rw-r--r--testfiles/src/style-internal-test.cpp44
-rw-r--r--testfiles/src/style-test.cpp604
-rw-r--r--testfiles/src/svg-affine-test.cpp226
-rw-r--r--testfiles/src/svg-color-test.cpp112
-rw-r--r--testfiles/src/svg-extension-test.cpp80
-rw-r--r--testfiles/src/svg-length-test.cpp187
-rw-r--r--testfiles/src/svg-path-geom-test.cpp499
-rw-r--r--testfiles/src/svg-stringstream-test.cpp156
-rw-r--r--testfiles/src/uri-test.cpp304
-rw-r--r--testfiles/src/util-test.cpp82
-rw-r--r--testfiles/src/xml-test.cpp84
37 files changed, 6269 insertions, 0 deletions
diff --git a/testfiles/src/2geom-characterization-test.cpp b/testfiles/src/2geom-characterization-test.cpp
new file mode 100644
index 0000000..d3f099b
--- /dev/null
+++ b/testfiles/src/2geom-characterization-test.cpp
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * 2Geom Lib characterization tests
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2020 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <gtest/gtest.h>
+
+#include <2geom/path.h>
+
+TEST(Characterization2Geom, retrievingBackElementOfAnEmptyClosedPathFails)
+{
+ Geom::Path path(Geom::Point(3, 5));
+ path.close();
+ ASSERT_TRUE(path.closed());
+ ASSERT_EQ(path.size_closed(), 0u);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/attributes-test.cpp b/testfiles/src/attributes-test.cpp
new file mode 100644
index 0000000..b1cb1fb
--- /dev/null
+++ b/testfiles/src/attributes-test.cpp
@@ -0,0 +1,659 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/*
+ * Unit tests for attributes.
+ *
+ * Author:
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2015 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+#include "attributes.h"
+
+namespace {
+
+static const unsigned int FIRST_VALID_ID = 1;
+
+class AttributeInfo
+{
+public:
+ AttributeInfo(std::string attr, bool supported) :
+ attr(std::move(attr)),
+ supported(supported)
+ {
+ }
+
+ std::string attr;
+ bool supported;
+};
+
+typedef std::vector<AttributeInfo>::iterator AttrItr;
+
+std::vector<AttributeInfo> getKnownAttrs()
+{
+/* Originally extracted mechanically from
+ http://www.w3.org/TR/SVG11/attindex.html:
+
+ tidy -wrap 999 -asxml < attindex.html 2>/dev/null |
+ tr -d \\n |
+ sed 's,<tr>,@,g' |
+ tr @ \\n |
+ sed 's,</td>.*,,;s,^<td>,,;1,/^%/d;/^%/d;s,^, {",;s/$/", false},/' |
+ uniq
+
+ attindex.html lacks attributeName, begin, additive, font, marker;
+ I've added these manually.
+
+ SVG 2: white-space, shape-inside, shape-subtrace, shape-padding, shape-margin
+*/
+ AttributeInfo all_attrs[] = {
+ AttributeInfo("attributeName", true),
+ AttributeInfo("begin", true),
+ AttributeInfo("additive", true),
+ AttributeInfo("font", true),
+ AttributeInfo("-inkscape-font-specification", true), // TODO look into this attribute's name
+ AttributeInfo("marker", true),
+ AttributeInfo("line-height", true),
+
+ AttributeInfo("accent-height", true),
+ AttributeInfo("accumulate", true),
+ AttributeInfo("alignment-baseline", true),
+ AttributeInfo("alphabetic", true),
+ AttributeInfo("amplitude", true),
+ AttributeInfo("animate", false),
+ AttributeInfo("arabic-form", true),
+ AttributeInfo("ascent", true),
+ AttributeInfo("attributeType", true),
+ AttributeInfo("azimuth", true),
+ AttributeInfo("baseFrequency", true),
+ AttributeInfo("baseline-shift", true),
+ AttributeInfo("baseProfile", false),
+ AttributeInfo("bbox", true),
+ AttributeInfo("bias", true),
+ AttributeInfo("by", true),
+ AttributeInfo("calcMode", true),
+ AttributeInfo("cap-height", true),
+ AttributeInfo("class", false),
+ AttributeInfo("clip", true),
+ AttributeInfo("clip-path", true),
+ AttributeInfo("clip-rule", true),
+ AttributeInfo("clipPathUnits", true),
+ AttributeInfo("color", true),
+ AttributeInfo("color-interpolation", true),
+ AttributeInfo("color-interpolation-filters", true),
+ AttributeInfo("color-profile", true),
+ AttributeInfo("color-rendering", true),
+ AttributeInfo("contentScriptType", false),
+ AttributeInfo("contentStyleType", false),
+ AttributeInfo("cursor", true),
+ AttributeInfo("cx", true),
+ AttributeInfo("cy", true),
+ AttributeInfo("d", true),
+ AttributeInfo("descent", true),
+ AttributeInfo("diffuseConstant", true),
+ AttributeInfo("direction", true),
+ AttributeInfo("display", true),
+ AttributeInfo("divisor", true),
+ AttributeInfo("dominant-baseline", true),
+ AttributeInfo("dur", true),
+ AttributeInfo("dx", true),
+ AttributeInfo("dy", true),
+ AttributeInfo("edgeMode", true),
+ AttributeInfo("elevation", true),
+ AttributeInfo("enable-background", true),
+ AttributeInfo("end", true),
+ AttributeInfo("exponent", true),
+ AttributeInfo("externalResourcesRequired", false),
+ AttributeInfo("feBlend", false),
+ AttributeInfo("feColorMatrix", false),
+ AttributeInfo("feComponentTransfer", false),
+ AttributeInfo("feComposite", false),
+ AttributeInfo("feConvolveMatrix", false),
+ AttributeInfo("feDiffuseLighting", false),
+ AttributeInfo("feDisplacementMap", false),
+ AttributeInfo("feFlood", false),
+ AttributeInfo("feGaussianBlur", false),
+ AttributeInfo("feImage", false),
+ AttributeInfo("feMerge", false),
+ AttributeInfo("feMorphology", false),
+ AttributeInfo("feOffset", false),
+ AttributeInfo("feSpecularLighting", false),
+ AttributeInfo("feTile", false),
+ AttributeInfo("fill", true),
+ AttributeInfo("fill-opacity", true),
+ AttributeInfo("fill-rule", true),
+ AttributeInfo("filter", true),
+ AttributeInfo("filterRes", true),
+ AttributeInfo("filterUnits", true),
+ AttributeInfo("flood-color", true),
+ AttributeInfo("flood-opacity", true),
+ AttributeInfo("font-family", true),
+ AttributeInfo("font-feature-settings", true),
+ AttributeInfo("font-size", true),
+ AttributeInfo("font-size-adjust", true),
+ AttributeInfo("font-stretch", true),
+ AttributeInfo("font-style", true),
+ AttributeInfo("font-variant", true),
+ AttributeInfo("font-variant-ligatures", true),
+ AttributeInfo("font-variant-position", true),
+ AttributeInfo("font-variant-caps", true),
+ AttributeInfo("font-variant-numeric", true),
+ AttributeInfo("font-variant-east-asian", true),
+ AttributeInfo("font-variant-alternates", true),
+ AttributeInfo("font-variation-settings", true),
+ AttributeInfo("font-weight", true),
+ AttributeInfo("format", false),
+ AttributeInfo("from", true),
+ AttributeInfo("fx", true),
+ AttributeInfo("fr", true),
+ AttributeInfo("fy", true),
+ AttributeInfo("g1", true),
+ AttributeInfo("g2", true),
+ AttributeInfo("glyph-name", true),
+ AttributeInfo("glyph-orientation-horizontal", true),
+ AttributeInfo("glyph-orientation-vertical", true),
+ AttributeInfo("glyphRef", false),
+ AttributeInfo("gradientTransform", true),
+ AttributeInfo("gradientUnits", true),
+ AttributeInfo("hanging", true),
+ AttributeInfo("hatchContentUnits", true), // SVG 2.0
+ AttributeInfo("hatchTransform", true), // SVG 2.0 TODO renamed to transform
+ AttributeInfo("hatchUnits", true), // SVG 2.0
+ AttributeInfo("height", true),
+ AttributeInfo("horiz-adv-x", true),
+ AttributeInfo("horiz-origin-x", true),
+ AttributeInfo("horiz-origin-y", true),
+ AttributeInfo("ideographic", true),
+ AttributeInfo("image-rendering", true),
+ AttributeInfo("in", true),
+ AttributeInfo("in2", true),
+ AttributeInfo("inline-size", true),
+ AttributeInfo("intercept", true),
+ AttributeInfo("isolation", true),
+ AttributeInfo("k", true),
+ AttributeInfo("k1", true),
+ AttributeInfo("k2", true),
+ AttributeInfo("k3", true),
+ AttributeInfo("k4", true),
+ AttributeInfo("kernelMatrix", true),
+ AttributeInfo("kernelUnitLength", true),
+ AttributeInfo("kerning", true),
+ AttributeInfo("keyPoints", false),
+ AttributeInfo("keySplines", true),
+ AttributeInfo("keyTimes", true),
+ AttributeInfo("lang", true),
+ AttributeInfo("lengthAdjust", true),
+ AttributeInfo("letter-spacing", true),
+ AttributeInfo("lighting-color", true),
+ AttributeInfo("inkscape:auto-region", true),
+ AttributeInfo("limitingConeAngle", true),
+ AttributeInfo("local", true),
+ AttributeInfo("marker-end", true),
+ AttributeInfo("marker-mid", true),
+ AttributeInfo("marker-start", true),
+ AttributeInfo("markerHeight", true),
+ AttributeInfo("markerUnits", true),
+ AttributeInfo("markerWidth", true),
+ AttributeInfo("mask", true),
+ AttributeInfo("maskContentUnits", true),
+ AttributeInfo("maskUnits", true),
+ AttributeInfo("mathematical", true),
+ AttributeInfo("max", true),
+ AttributeInfo("media", false),
+ AttributeInfo("method", false),
+ AttributeInfo("min", true),
+ AttributeInfo("mix-blend-mode", true),
+ AttributeInfo("mode", true),
+ AttributeInfo("name", true),
+ AttributeInfo("numOctaves", true),
+ AttributeInfo("offset", true),
+ AttributeInfo("onabort", false),
+ AttributeInfo("onactivate", false),
+ AttributeInfo("onbegin", false),
+ AttributeInfo("onclick", false),
+ AttributeInfo("onend", false),
+ AttributeInfo("onerror", false),
+ AttributeInfo("onfocusin", false),
+ AttributeInfo("onfocusout", false),
+ AttributeInfo("onload", true),
+ AttributeInfo("onmousedown", false),
+ AttributeInfo("onmousemove", false),
+ AttributeInfo("onmouseout", false),
+ AttributeInfo("onmouseover", false),
+ AttributeInfo("onmouseup", false),
+ AttributeInfo("onrepeat", false),
+ AttributeInfo("onresize", false),
+ AttributeInfo("onscroll", false),
+ AttributeInfo("onunload", false),
+ AttributeInfo("onzoom", false),
+ AttributeInfo("opacity", true),
+ AttributeInfo("operator", true),
+ AttributeInfo("order", true),
+ AttributeInfo("orient", true),
+ AttributeInfo("orientation", true),
+ AttributeInfo("origin", false),
+ AttributeInfo("overflow", true),
+ AttributeInfo("overline-position", true),
+ AttributeInfo("overline-thickness", true),
+ AttributeInfo("paint-order", true),
+ AttributeInfo("panose-1", true),
+ AttributeInfo("path", true),
+ AttributeInfo("pathLength", false),
+ AttributeInfo("patternContentUnits", true),
+ AttributeInfo("patternTransform", true),
+ AttributeInfo("patternUnits", true),
+ AttributeInfo("pitch", true), // SVG 2.-
+ AttributeInfo("pointer-events", true),
+ AttributeInfo("points", true),
+ AttributeInfo("pointsAtX", true),
+ AttributeInfo("pointsAtY", true),
+ AttributeInfo("pointsAtZ", true),
+ AttributeInfo("preserveAlpha", true),
+ AttributeInfo("preserveAspectRatio", true),
+ AttributeInfo("primitiveUnits", true),
+ AttributeInfo("r", true),
+ AttributeInfo("radius", true),
+ AttributeInfo("refX", true),
+ AttributeInfo("refY", true),
+ AttributeInfo("rendering-intent", true),
+ AttributeInfo("repeatCount", true),
+ AttributeInfo("repeatDur", true),
+ AttributeInfo("requiredFeatures", true),
+ AttributeInfo("requiredExtensions", true),
+ AttributeInfo("restart", true),
+ AttributeInfo("result", true),
+ AttributeInfo("rotate", true),
+ AttributeInfo("rx", true),
+ AttributeInfo("ry", true),
+ AttributeInfo("scale", true),
+ AttributeInfo("seed", true),
+ AttributeInfo("shape-inside", true),
+ AttributeInfo("shape-margin", true),
+ AttributeInfo("shape-subtract", true),
+ AttributeInfo("shape-padding", true),
+ AttributeInfo("shape-rendering", true),
+ AttributeInfo("side", true),
+ AttributeInfo("slope", true),
+ AttributeInfo("solid-color", true), // SVG 2.0
+ AttributeInfo("solid-opacity", true), // SVG 2.0
+ AttributeInfo("spacing", false),
+ AttributeInfo("specularConstant", true),
+ AttributeInfo("specularExponent", true),
+ AttributeInfo("spreadMethod", true),
+ AttributeInfo("startOffset", true),
+ AttributeInfo("stdDeviation", true),
+ AttributeInfo("stemh", true),
+ AttributeInfo("stemv", true),
+ AttributeInfo("stitchTiles", true),
+ AttributeInfo("stop-color", true),
+ AttributeInfo("stop-opacity", true),
+ AttributeInfo("strikethrough-position", true),
+ AttributeInfo("strikethrough-thickness", true),
+ AttributeInfo("stroke", true),
+ AttributeInfo("stroke-dasharray", true),
+ AttributeInfo("stroke-dashoffset", true),
+ AttributeInfo("stroke-linecap", true),
+ AttributeInfo("stroke-linejoin", true),
+ AttributeInfo("stroke-miterlimit", true),
+ AttributeInfo("stroke-opacity", true),
+ AttributeInfo("stroke-width", true),
+ AttributeInfo("style", true),
+ AttributeInfo("surfaceScale", true),
+ AttributeInfo("systemLanguage", true),
+ AttributeInfo("tableValues", true),
+ AttributeInfo("target", true),
+ AttributeInfo("targetX", true),
+ AttributeInfo("targetY", true),
+ AttributeInfo("text-align", true),
+ AttributeInfo("text-anchor", true),
+ AttributeInfo("text-decoration", true),
+ AttributeInfo("text-decoration-color", true),
+ AttributeInfo("text-decoration-fill", true),
+ AttributeInfo("text-decoration-line", true),
+ AttributeInfo("text-decoration-stroke", true),
+ AttributeInfo("text-decoration-style", true),
+ AttributeInfo("text-indent", true),
+ AttributeInfo("text-orientation", true),
+ AttributeInfo("text-rendering", true),
+ AttributeInfo("text-transform", true),
+ AttributeInfo("textLength", true),
+ AttributeInfo("title", false),
+ AttributeInfo("to", true),
+ AttributeInfo("transform", true),
+ AttributeInfo("type", true),
+ AttributeInfo("u1", true),
+ AttributeInfo("u2", true),
+ AttributeInfo("underline-position", true),
+ AttributeInfo("underline-thickness", true),
+ AttributeInfo("unicode", true),
+ AttributeInfo("unicode-bidi", true),
+ AttributeInfo("unicode-range", true),
+ AttributeInfo("units-per-em", true),
+ AttributeInfo("v-alphabetic", true),
+ AttributeInfo("v-hanging", true),
+ AttributeInfo("v-ideographic", true),
+ AttributeInfo("v-mathematical", true),
+ AttributeInfo("values", true),
+ AttributeInfo("vector-effect", true),
+ AttributeInfo("version", true),
+ AttributeInfo("vert-adv-y", true),
+ AttributeInfo("vert-origin-x", true),
+ AttributeInfo("vert-origin-y", true),
+ AttributeInfo("viewBox", true),
+ AttributeInfo("viewTarget", false),
+ AttributeInfo("visibility", true),
+ AttributeInfo("white-space", true),
+ AttributeInfo("width", true),
+ AttributeInfo("widths", true),
+ AttributeInfo("word-spacing", true),
+ AttributeInfo("writing-mode", true),
+ AttributeInfo("x", true),
+ AttributeInfo("x-height", true),
+ AttributeInfo("x1", true),
+ AttributeInfo("x2", true),
+ AttributeInfo("xChannelSelector", true),
+ AttributeInfo("xlink:actuate", true),
+ AttributeInfo("xlink:arcrole", true),
+ AttributeInfo("xlink:href", true),
+ AttributeInfo("xlink:role", true),
+ AttributeInfo("xlink:show", true),
+ AttributeInfo("xlink:title", true),
+ AttributeInfo("xlink:type", true),
+ AttributeInfo("xml:base", false),
+ AttributeInfo("xml:lang", true),
+ AttributeInfo("xml:space", true),
+ AttributeInfo("xmlns", false),
+ AttributeInfo("xmlns:xlink", false),
+ AttributeInfo("y", true),
+ AttributeInfo("y1", true),
+ AttributeInfo("y2", true),
+ AttributeInfo("yChannelSelector", true),
+ AttributeInfo("z", true),
+ AttributeInfo("zoomAndPan", false),
+
+ // Extra attributes.
+ AttributeInfo("-inkscape-stroke", true),
+ AttributeInfo("id", true),
+ // AttributeInfo("inkscape:bbox-nodes", true),
+ // AttributeInfo("inkscape:bbox-paths", true),
+ AttributeInfo("inkscape:deskcolor", true),
+ AttributeInfo("inkscape:deskopacity", true),
+ AttributeInfo("inkscape:box3dsidetype", true),
+ AttributeInfo("inkscape:collect", true),
+ AttributeInfo("inkscape:color", true),
+ AttributeInfo("inkscape:connection-end", true),
+ AttributeInfo("inkscape:connection-end-point", true),
+ AttributeInfo("inkscape:connection-points", true),
+ AttributeInfo("inkscape:connection-start", true),
+ AttributeInfo("inkscape:connection-start-point", true),
+ AttributeInfo("inkscape:connector-avoid", true),
+ AttributeInfo("inkscape:connector-curvature", true),
+ AttributeInfo("inkscape:connector-spacing", true),
+ AttributeInfo("inkscape:connector-type", true),
+ AttributeInfo("inkscape:corner0", true),
+ AttributeInfo("inkscape:corner7", true),
+ AttributeInfo("inkscape:current-layer", true),
+ AttributeInfo("inkscape:cx", true),
+ AttributeInfo("inkscape:cy", true),
+ AttributeInfo("inkscape:rotation", true),
+ AttributeInfo("inkscape:document-units", true),
+ AttributeInfo("inkscape:dstBox", true),
+ AttributeInfo("inkscape:dstColumn", true),
+ AttributeInfo("inkscape:dstPath", true),
+ AttributeInfo("inkscape:dstShape", true),
+ AttributeInfo("inkscape:excludeShape", true),
+ AttributeInfo("inkscape:expanded", true),
+ AttributeInfo("inkscape:flatsided", true),
+ AttributeInfo("inkscape:groupmode", true),
+ AttributeInfo("inkscape:highlight-color", true),
+ AttributeInfo("inkscape:href", true),
+ AttributeInfo("inkscape:label", true),
+ AttributeInfo("inkscape:layoutOptions", true),
+ AttributeInfo("inkscape:lockguides", true),
+ AttributeInfo("inkscape:locked", true),
+ // AttributeInfo("inkscape:object-nodes", true),
+ // AttributeInfo("inkscape:object-paths", true),
+ AttributeInfo("inkscape:original", true),
+ AttributeInfo("inkscape:original-d", true),
+ AttributeInfo("inkscape:pagecheckerboard", true),
+ AttributeInfo("inkscape:pageopacity", true),
+ AttributeInfo("inkscape:pageshadow", true),
+ AttributeInfo("inkscape:path-effect", true),
+ AttributeInfo("inkscape:persp3d", true),
+ AttributeInfo("inkscape:persp3d-origin", true),
+ AttributeInfo("inkscape:perspectiveID", true),
+ AttributeInfo("inkscape:radius", true),
+ AttributeInfo("inkscape:randomized", true),
+ AttributeInfo("inkscape:rounded", true),
+ // AttributeInfo("inkscape:snap-alignment", true),
+ // AttributeInfo("inkscape:snap-alignment-self", true),
+ // AttributeInfo("inkscape:snap-distribution", true),
+ // AttributeInfo("inkscape:snap-bbox", true),
+ // AttributeInfo("inkscape:snap-bbox-edge-midpoints", true),
+ // AttributeInfo("inkscape:snap-bbox-midpoints", true),
+ // AttributeInfo("inkscape:snap-center", true),
+ // AttributeInfo("inkscape:snap-global", true),
+ // AttributeInfo("inkscape:snap-grids", true),
+ // AttributeInfo("inkscape:snap-intersection-paths", true),
+ // AttributeInfo("inkscape:snap-midpoints", true),
+ // AttributeInfo("inkscape:snap-nodes", true),
+ // AttributeInfo("inkscape:snap-object-midpoints", true),
+ // AttributeInfo("inkscape:snap-others", true),
+ // AttributeInfo("inkscape:snap-from-guide", true),
+ // AttributeInfo("inkscape:snap-page", true),
+ // AttributeInfo("inkscape:snap-path-clip", true),
+ // AttributeInfo("inkscape:snap-path-mask", true),
+ // AttributeInfo("inkscape:snap-perpendicular", true),
+ // AttributeInfo("inkscape:snap-smooth-nodes", true),
+ // AttributeInfo("inkscape:snap-tangential", true),
+ // AttributeInfo("inkscape:snap-text-baseline", true),
+ // AttributeInfo("inkscape:snap-to-guides", true),
+ AttributeInfo("inkscape:spray-origin", true),
+ AttributeInfo("inkscape:srcNoMarkup", true),
+ AttributeInfo("inkscape:srcPango", true),
+ AttributeInfo("inkscape:transform-center-x", true),
+ AttributeInfo("inkscape:transform-center-y", true),
+ AttributeInfo("inkscape:version", true),
+ AttributeInfo("inkscape:vp_x", true),
+ AttributeInfo("inkscape:vp_y", true),
+ AttributeInfo("inkscape:vp_z", true),
+ AttributeInfo("inkscape:window-height", true),
+ AttributeInfo("inkscape:window-maximized", true),
+ AttributeInfo("inkscape:window-width", true),
+ AttributeInfo("inkscape:window-x", true),
+ AttributeInfo("inkscape:window-y", true),
+ AttributeInfo("inkscape:zoom", true),
+ AttributeInfo("inkscape:svg-dpi", true),
+ AttributeInfo("inkscape:swatch", true),
+ AttributeInfo("sodipodi:arc-type", true),
+ AttributeInfo("sodipodi:arg1", true),
+ AttributeInfo("sodipodi:arg2", true),
+ AttributeInfo("sodipodi:argument", true),
+ AttributeInfo("sodipodi:cx", true),
+ AttributeInfo("sodipodi:cy", true),
+ AttributeInfo("sodipodi:docname", true),
+ AttributeInfo("sodipodi:end", true),
+ AttributeInfo("sodipodi:expansion", true),
+ AttributeInfo("sodipodi:insensitive", true),
+ AttributeInfo("sodipodi:linespacing", true),
+ AttributeInfo("sodipodi:open", true),
+ AttributeInfo("sodipodi:original", true),
+ AttributeInfo("sodipodi:r1", true),
+ AttributeInfo("sodipodi:r2", true),
+ AttributeInfo("sodipodi:radius", true),
+ AttributeInfo("sodipodi:revolution", true),
+ AttributeInfo("sodipodi:role", true),
+ AttributeInfo("sodipodi:rx", true),
+ AttributeInfo("sodipodi:ry", true),
+ AttributeInfo("sodipodi:sides", true),
+ AttributeInfo("sodipodi:start", true),
+ AttributeInfo("sodipodi:t0", true),
+ AttributeInfo("sodipodi:type", true),
+ AttributeInfo("sodipodi:version", false),
+
+ // SPMeshPatch
+ AttributeInfo("tensor", true),
+
+ // SPNamedView
+ AttributeInfo("fit-margin-top", true),
+ AttributeInfo("fit-margin-left", true),
+ AttributeInfo("fit-margin-right", true),
+ AttributeInfo("fit-margin-bottom", true),
+ AttributeInfo("units", true),
+ AttributeInfo("viewonly", true),
+ AttributeInfo("showgrid", true),
+// AttributeInfo("gridtype", true),
+ AttributeInfo("showguides", true),
+ AttributeInfo("gridtolerance", true),
+ AttributeInfo("guidetolerance", true),
+ AttributeInfo("objecttolerance", true),
+ AttributeInfo("alignmenttolerance", true),
+ AttributeInfo("distributiontolerance", true),
+/* AttributeInfo("gridoriginx", true),
+ AttributeInfo("gridoriginy", true),
+ AttributeInfo("gridspacingx", true),
+ AttributeInfo("gridspacingy", true),
+ AttributeInfo("gridanglex", true),
+ AttributeInfo("gridanglez", true),
+ AttributeInfo("gridcolor", true),
+ AttributeInfo("gridopacity", true),
+ AttributeInfo("gridempcolor", true),
+ AttributeInfo("gridempopacity", true),
+ AttributeInfo("gridempspacing", true), */
+ AttributeInfo("guidecolor", true),
+ AttributeInfo("guideopacity", true),
+ AttributeInfo("guidehicolor", true),
+ AttributeInfo("guidehiopacity", true),
+ AttributeInfo("showborder", true),
+ AttributeInfo("inkscape:showpageshadow", true),
+ AttributeInfo("borderlayer", true),
+ AttributeInfo("bordercolor", true),
+ AttributeInfo("borderopacity", true),
+ AttributeInfo("pagecolor", true),
+
+ // SPGuide
+ AttributeInfo("position", true),
+
+ // don't know what that is
+ AttributeInfo("effect", true)
+ };
+
+ size_t count = sizeof(all_attrs) / sizeof(all_attrs[0]);
+ std::vector<AttributeInfo> vect(all_attrs, all_attrs + count);
+ EXPECT_GT(vect.size(), size_t(100)); // should be more than
+ return vect;
+}
+
+/**
+ * Returns a vector with counts for all IDs up to the highest known value.
+ *
+ * The index is the ID, and the value is the number of times that ID is seen.
+ */
+std::vector<size_t> getIdIds()
+{
+ std::vector<size_t> ids;
+ std::vector<AttributeInfo> all_attrs = getKnownAttrs();
+ ids.reserve(all_attrs.size()); // minimize memory thrashing
+ for (auto & all_attr : all_attrs) {
+ auto id = sp_attribute_lookup(all_attr.attr.c_str());
+ if ((int)id >= ids.size()) {
+ ids.resize((int)id + 1);
+ }
+ ids[(int)id]++;
+ }
+
+ return ids;
+}
+
+// Ensure 'supported' value for each known attribute is correct.
+TEST(AttributesTest, SupportedKnown)
+{
+ std::vector<AttributeInfo> all_attrs = getKnownAttrs();
+ for (AttrItr it(all_attrs.begin()); it != all_attrs.end(); ++it) {
+ auto id = sp_attribute_lookup(it->attr.c_str());
+ EXPECT_EQ(it->supported, id != SPAttr::INVALID) << "Matching for attribute '" << it->attr << "'";
+ }
+}
+
+// Ensure names of known attributes are preserved when converted to id and back.
+TEST(AttributesTest, NameRoundTrip)
+{
+ std::vector<AttributeInfo> all_attrs = getKnownAttrs();
+ for (AttrItr it(all_attrs.begin()); it != all_attrs.end(); ++it) {
+ if (it->supported) {
+ auto id = sp_attribute_lookup(it->attr.c_str());
+ char const *redoneName = sp_attribute_name(id);
+ EXPECT_TRUE(redoneName != NULL) << "For attribute '" << it->attr << "'";
+ if (redoneName) {
+ EXPECT_EQ(it->attr, redoneName);
+ }
+ }
+ }
+}
+
+/* Test for any attributes that this test program doesn't know about.
+ *
+ * If any are found, then:
+ *
+ * If it is in the `inkscape:' namespace then simply add it to all_attrs with
+ * `true' as the second field (`supported').
+ *
+ * If it is in the `sodipodi:' namespace then check the spelling against sodipodi
+ * sources. If you don't have sodipodi sources, then don't add it: leave to someone
+ * else.
+ *
+ * Otherwise, it's probably a bug: ~all SVG 1.1 attributes should already be
+ * in the all_attrs table. However, the comment above all_attrs does mention
+ * some things missing from attindex.html, so there may be more. Check the SVG
+ * spec. Another possibility is that the attribute is new in SVG 1.2. In this case,
+ * check the spelling against the [draft] SVG 1.2 spec before adding to all_attrs.
+ * (If you can't be bothered checking the spec, then don't update all_attrs.)
+ *
+ * If the attribute isn't in either SVG 1.1 or 1.2 then it's probably a mistake
+ * for it not to be in the inkscape namespace. (Not sure about attributes used only
+ * on elements in the inkscape namespace though.)
+ *
+ * In any case, make sure that the attribute's source is documented accordingly.
+ */
+TEST(AttributesTest, ValuesAreKnown)
+{
+ std::vector<size_t> ids = getIdIds();
+ for (size_t i = FIRST_VALID_ID; i < ids.size(); ++i) {
+ if (!ids[i]) {
+ char const *name = sp_attribute_name((SPAttr)i);
+ EXPECT_TRUE(ids[i] > 0) << "Attribute string with enum " << i << " {" << name << "} not handled";
+ }
+ }
+}
+
+// Ensure two different names aren't mapped to the same enum value.
+TEST(AttributesTest, ValuesUnique)
+{
+ std::vector<size_t> ids = getIdIds();
+ for (size_t i = FIRST_VALID_ID; i < ids.size(); ++i) {
+ EXPECT_LE(ids[i], size_t(1)) << "Attribute enum " << i << " used for multiple strings"
+ << " including {" << sp_attribute_name((SPAttr)i) << "}";
+ }
+}
+
+} // namespace
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/testfiles/src/cairo-utils-test.cpp b/testfiles/src/cairo-utils-test.cpp
new file mode 100644
index 0000000..3412c4a
--- /dev/null
+++ b/testfiles/src/cairo-utils-test.cpp
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Tests for classes like Pixbuf from cairo-utils
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2020 Authors
+ *
+ * Released under GNU GPL version 2 or later, read the file 'COPYING' for more information
+ */
+
+#include <gtest/gtest.h>
+#include <src/display/cairo-utils.h>
+#include <src/inkscape.h>
+
+
+class PixbufTest : public ::testing::Test {
+ public:
+ static std::string base64of(const std::string &s)
+ {
+ gchar *encoded = g_base64_encode(reinterpret_cast<guchar const *>(s.c_str()), s.size());
+ std::string r(encoded);
+ g_free(encoded);
+ return r;
+ }
+
+ protected:
+ void SetUp() override
+ {
+ // setup hidden dependency
+ Inkscape::Application::create(false);
+ }
+};
+
+TEST_F(PixbufTest, creatingFromSvgBufferWithoutViewboxOrWidthAndHeightReturnsNull)
+{
+ std::string svg_buffer(
+ "<svg><path d=\"M 71.527648,186.14229 A 740.48715,740.48715 0 0 0 696.31258,625.8041 Z\"/></svg>");
+ double default_dpi = 96.0;
+ std::string filename_with_svg_extension("malformed.svg");
+
+ ASSERT_EQ(Inkscape::Pixbuf::create_from_buffer(svg_buffer, default_dpi, filename_with_svg_extension), nullptr);
+}
+
+TEST_F(PixbufTest, creatingFromSvgUriWithoutViewboxOrWidthAndHeightReturnsNull)
+{
+ std::string uri_data = "image/svg+xml;base64," + base64of("<svg><path d=\"M 71.527648,186.14229 A 740.48715,740.48715 0 0 0 696.31258,625.8041 Z\"/></svg>");
+ double default_dpi = 96.0;
+
+ ASSERT_EQ(Inkscape::Pixbuf::create_from_data_uri(uri_data.c_str(), default_dpi), nullptr);
+} \ No newline at end of file
diff --git a/testfiles/src/color-profile-test.cpp b/testfiles/src/color-profile-test.cpp
new file mode 100644
index 0000000..b140b3c
--- /dev/null
+++ b/testfiles/src/color-profile-test.cpp
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Unit tests for color profile.
+ *
+ * Author:
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2015 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "gtest/gtest.h"
+
+#include "attributes.h"
+#include "cms-system.h"
+#include "object/color-profile.h"
+#include "doc-per-case-test.h"
+
+namespace {
+
+/**
+ * Test fixture to inherit a shared doc and create a color profile instance per test.
+ */
+class ProfTest : public DocPerCaseTest
+{
+public:
+ ProfTest() :
+ DocPerCaseTest(),
+ _prof(0)
+ {
+ }
+
+protected:
+ void SetUp() override
+ {
+ DocPerCaseTest::SetUp();
+ _prof = new Inkscape::ColorProfile();
+ ASSERT_TRUE( _prof != NULL );
+ _prof->document = _doc.get();
+ }
+
+ void TearDown() override
+ {
+ if (_prof) {
+ delete _prof;
+ _prof = NULL;
+ }
+ DocPerCaseTest::TearDown();
+ }
+
+ Inkscape::ColorProfile *_prof;
+};
+
+typedef ProfTest ColorProfileTest;
+
+TEST_F(ColorProfileTest, SetRenderingIntent)
+{
+ struct {
+ gchar const *attr;
+ guint intVal;
+ }
+ const cases[] = {
+ {"auto", (guint)Inkscape::RENDERING_INTENT_AUTO},
+ {"perceptual", (guint)Inkscape::RENDERING_INTENT_PERCEPTUAL},
+ {"relative-colorimetric", (guint)Inkscape::RENDERING_INTENT_RELATIVE_COLORIMETRIC},
+ {"saturation", (guint)Inkscape::RENDERING_INTENT_SATURATION},
+ {"absolute-colorimetric", (guint)Inkscape::RENDERING_INTENT_ABSOLUTE_COLORIMETRIC},
+ {"something-else", (guint)Inkscape::RENDERING_INTENT_UNKNOWN},
+ {"auto2", (guint)Inkscape::RENDERING_INTENT_UNKNOWN},
+ };
+
+ for (auto i : cases) {
+ _prof->setKeyValue( SPAttr::RENDERING_INTENT, i.attr);
+ ASSERT_EQ( (guint)i.intVal, _prof->rendering_intent ) << i.attr;
+ }
+}
+
+TEST_F(ColorProfileTest, SetLocal)
+{
+ gchar const* cases[] = {
+ "local",
+ "something",
+ };
+
+ for (auto & i : cases) {
+ _prof->setKeyValue( SPAttr::LOCAL, i);
+ ASSERT_TRUE( _prof->local != NULL );
+ if ( _prof->local ) {
+ ASSERT_EQ( std::string(i), _prof->local );
+ }
+ }
+ _prof->setKeyValue( SPAttr::LOCAL, NULL);
+ ASSERT_EQ( (gchar*)0, _prof->local );
+}
+
+TEST_F(ColorProfileTest, SetName)
+{
+ gchar const* cases[] = {
+ "name",
+ "something",
+ };
+
+ for (auto & i : cases) {
+ _prof->setKeyValue( SPAttr::NAME, i);
+ ASSERT_TRUE( _prof->name != NULL );
+ if ( _prof->name ) {
+ ASSERT_EQ( std::string(i), _prof->name );
+ }
+ }
+ _prof->setKeyValue( SPAttr::NAME, NULL );
+ ASSERT_EQ( (gchar*)0, _prof->name );
+}
+
+
+} // namespace
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/testfiles/src/curve-test.cpp b/testfiles/src/curve-test.cpp
new file mode 100644
index 0000000..75c7b98
--- /dev/null
+++ b/testfiles/src/curve-test.cpp
@@ -0,0 +1,292 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Curve test
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2020 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <gtest/gtest.h>
+
+#include "display/curve.h"
+#include <2geom/curves.h>
+#include <2geom/path.h>
+#include <2geom/pathvector.h>
+
+class CurveTest : public ::testing::Test {
+ public:
+ Geom::Path path1;
+ Geom::Path path2;
+ Geom::Path path3;
+ Geom::Path path4;
+
+ static size_t get_refcount(SPCurve const *curve) { return curve->_refcount; }
+
+ protected:
+ CurveTest()
+ : path4(Geom::Point(3, 5)) // Just a moveto
+ {
+ // Closed path
+ path1.append(Geom::LineSegment(Geom::Point(0, 0), Geom::Point(1, 0)));
+ path1.append(Geom::LineSegment(Geom::Point(1, 0), Geom::Point(1, 1)));
+ path1.close();
+ // Closed path (ClosingSegment is zero length)
+ path2.append(Geom::LineSegment(Geom::Point(2, 0), Geom::Point(3, 0)));
+ path2.append(Geom::CubicBezier(Geom::Point(3, 0), Geom::Point(2, 1), Geom::Point(1, 1), Geom::Point(2, 0)));
+ path2.close();
+ // Open path
+ path3.setStitching(true);
+ path3.append(Geom::EllipticalArc(Geom::Point(4, 0), 1, 2, M_PI, false, false, Geom::Point(5, 1)));
+ path3.append(Geom::LineSegment(Geom::Point(5, 1), Geom::Point(5, 2)));
+ path3.append(Geom::LineSegment(Geom::Point(6, 4), Geom::Point(2, 4)));
+ }
+};
+
+TEST_F(CurveTest, testMoveSemantics)
+{
+ SPCurve c1;
+ c1.moveto(2, 3);
+ c1.lineto(4, 5);
+
+ // move construction
+ SPCurve c2(std::move(c1));
+
+ ASSERT_EQ(c1.get_segment_count(), 0);
+ ASSERT_EQ(c2.get_segment_count(), 1);
+
+ // move assignment
+ c1 = std::move(c2);
+
+ ASSERT_EQ(c1.get_segment_count(), 1);
+ ASSERT_EQ(c2.get_segment_count(), 0);
+}
+
+TEST_F(CurveTest, testRefCount)
+{
+ auto c1 = std::make_unique<SPCurve>();
+ ASSERT_EQ(get_refcount(c1.get()), 1);
+ {
+ auto c2 = c1->ref();
+ ASSERT_EQ(c2.get(), c1.get());
+ ASSERT_EQ(get_refcount(c1.get()), 2);
+ ASSERT_EQ(get_refcount(c2.get()), 2);
+ }
+ ASSERT_EQ(get_refcount(c1.get()), 1);
+}
+
+TEST_F(CurveTest, testGetSegmentCount)
+{
+ { // Zero segments
+ Geom::PathVector pv;
+ SPCurve curve(pv);
+ ASSERT_EQ(curve.get_segment_count(), 0u);
+ }
+ { // Zero segments
+ Geom::PathVector pv;
+ pv.push_back(Geom::Path());
+ SPCurve curve(pv);
+ ASSERT_EQ(curve.get_segment_count(), 0u);
+ }
+ { // Individual paths
+ Geom::PathVector pv((Geom::Path()));
+ pv[0] = path1;
+ ASSERT_EQ(SPCurve(pv).get_segment_count(), 3u);
+ pv[0] = path2;
+ ASSERT_EQ(SPCurve(pv).get_segment_count(), 2u);
+ pv[0] = path3;
+ ASSERT_EQ(SPCurve(pv).get_segment_count(), 4u);
+ pv[0] = path4;
+ ASSERT_EQ(SPCurve(pv).get_segment_count(), 0u);
+ pv[0].close();
+ ASSERT_EQ(SPCurve(pv).get_segment_count(), 0u);
+ }
+ { // Combination
+ Geom::PathVector pv;
+ pv.push_back(path1);
+ pv.push_back(path2);
+ pv.push_back(path3);
+ pv.push_back(path4);
+ SPCurve curve(pv);
+ ASSERT_EQ(curve.get_segment_count(), 9u);
+ }
+}
+
+TEST_F(CurveTest, testNodesInPathForZeroSegments)
+{
+ { // Zero segments
+ Geom::PathVector pv;
+ SPCurve curve(pv);
+ ASSERT_EQ(curve.nodes_in_path(), 0u);
+ }
+ { // Zero segments
+ Geom::PathVector pv;
+ pv.push_back(Geom::Path());
+ SPCurve curve(pv);
+ ASSERT_EQ(curve.nodes_in_path(), 1u);
+ }
+}
+
+TEST_F(CurveTest, testNodesInPathForIndividualPaths)
+{
+ Geom::PathVector pv((Geom::Path()));
+ pv[0] = path1;
+ ASSERT_EQ(SPCurve(pv).nodes_in_path(), 3u);
+ pv[0] = path2;
+ ASSERT_EQ(SPCurve(pv).nodes_in_path(), 2u); // zero length closing segments do not increase the nodecount.
+ pv[0] = path3;
+ ASSERT_EQ(SPCurve(pv).nodes_in_path(), 5u);
+ pv[0] = path4;
+ ASSERT_EQ(SPCurve(pv).nodes_in_path(), 1u);
+}
+
+TEST_F(CurveTest, testNodesInPathForNakedMoveToClosedPath)
+{
+ Geom::PathVector pv((Geom::Path()));
+ pv[0] = path4; // just a MoveTo
+ pv[0].close();
+ ASSERT_EQ(SPCurve(pv).nodes_in_path(), 1u);
+}
+
+/*
+TEST_F(CurveTest, testNodesInPathForPathsCombination)
+{
+ Geom::PathVector pv;
+ pv.push_back(path1);
+ pv.push_back(path2);
+ pv.push_back(path3);
+ pv.push_back(path4);
+ SPCurve curve(pv);
+ ASSERT_EQ(curve.nodes_in_path(), 12u);
+}
+*/
+
+TEST_F(CurveTest, testIsEmpty)
+{
+ ASSERT_TRUE(SPCurve(Geom::PathVector()).is_empty());
+ ASSERT_FALSE(SPCurve(path1).is_empty());
+ ASSERT_FALSE(SPCurve(path2).is_empty());
+ ASSERT_FALSE(SPCurve(path3).is_empty());
+ ASSERT_FALSE(SPCurve(path4).is_empty());
+}
+
+TEST_F(CurveTest, testIsClosed)
+{
+ ASSERT_FALSE(SPCurve(Geom::PathVector()).is_closed());
+ Geom::PathVector pv((Geom::Path()));
+ ASSERT_FALSE(SPCurve(pv).is_closed());
+ pv[0].close();
+ ASSERT_TRUE(SPCurve(pv).is_closed());
+ ASSERT_TRUE(SPCurve(path1).is_closed());
+ ASSERT_TRUE(SPCurve(path2).is_closed());
+ ASSERT_FALSE(SPCurve(path3).is_closed());
+ ASSERT_FALSE(SPCurve(path4).is_closed());
+}
+
+/*
+TEST_F(CurveTest, testLastFirstSegment)
+{
+ Geom::PathVector pv(path4);
+ ASSERT_EQ(SPCurve(pv).first_segment(), (void *)0);
+ ASSERT_EQ(SPCurve(pv).last_segment(), (void *)0);
+ pv[0].close();
+ ASSERT_NE(SPCurve(pv).first_segment(), (void *)0);
+ ASSERT_NE(SPCurve(pv).last_segment(), (void *)0);
+}
+*/
+
+TEST_F(CurveTest, testLastFirstPath)
+{
+ Geom::PathVector pv;
+ ASSERT_EQ(SPCurve(pv).first_path(), (void *)0);
+ ASSERT_EQ(SPCurve(pv).last_path(), (void *)0);
+ pv.push_back(path1);
+ ASSERT_EQ(*SPCurve(pv).first_path(), pv[0]);
+ ASSERT_EQ(*SPCurve(pv).last_path(), pv[0]);
+ pv.push_back(path2);
+ ASSERT_EQ(*SPCurve(pv).first_path(), pv[0]);
+ ASSERT_EQ(*SPCurve(pv).last_path(), pv[1]);
+ pv.push_back(path3);
+ ASSERT_EQ(*SPCurve(pv).first_path(), pv[0]);
+ ASSERT_EQ(*SPCurve(pv).last_path(), pv[2]);
+ pv.push_back(path4);
+ ASSERT_EQ(*SPCurve(pv).first_path(), pv[0]);
+ ASSERT_EQ(*SPCurve(pv).last_path(), pv[3]);
+}
+
+TEST_F(CurveTest, testFirstPoint)
+{
+ ASSERT_EQ(*(SPCurve(path1).first_point()), Geom::Point(0, 0));
+ ASSERT_EQ(*(SPCurve(path2).first_point()), Geom::Point(2, 0));
+ ASSERT_EQ(*(SPCurve(path3).first_point()), Geom::Point(4, 0));
+ ASSERT_EQ(*(SPCurve(path4).first_point()), Geom::Point(3, 5));
+ Geom::PathVector pv;
+ ASSERT_FALSE(SPCurve(pv).first_point());
+ pv.push_back(path1);
+ pv.push_back(path2);
+ pv.push_back(path3);
+ ASSERT_EQ(*(SPCurve(pv).first_point()), Geom::Point(0, 0));
+ pv.insert(pv.begin(), path4);
+ ASSERT_EQ(*(SPCurve(pv).first_point()), Geom::Point(3, 5));
+}
+
+/*
+TEST_F(CurveTest, testLastPoint)
+{
+ ASSERT_EQ(*(SPCurve(path1).last_point()), Geom::Point(0, 0));
+ ASSERT_EQ(*(SPCurve(path2).last_point()), Geom::Point(2, 0));
+ ASSERT_EQ(*(SPCurve(path3).last_point()), Geom::Point(8, 4));
+ ASSERT_EQ(*(SPCurve(path4).last_point()), Geom::Point(3, 5));
+ Geom::PathVector pv;
+ ASSERT_FALSE(SPCurve(pv).last_point());
+ pv.push_back(path1);
+ pv.push_back(path2);
+ pv.push_back(path3);
+ ASSERT_EQ(*(SPCurve(pv).last_point()), Geom::Point(8, 4));
+ pv.push_back(path4);
+ ASSERT_EQ(*(SPCurve(pv).last_point()), Geom::Point(3, 5));
+}
+*/
+
+TEST_F(CurveTest, testSecondPoint)
+{
+ ASSERT_EQ(*(SPCurve(path1).second_point()), Geom::Point(1, 0));
+ ASSERT_EQ(*(SPCurve(path2).second_point()), Geom::Point(3, 0));
+ ASSERT_EQ(*(SPCurve(path3).second_point()), Geom::Point(5, 1));
+ ASSERT_EQ(*(SPCurve(path4).second_point()), Geom::Point(3, 5));
+ Geom::PathVector pv;
+ pv.push_back(path1);
+ pv.push_back(path2);
+ pv.push_back(path3);
+ ASSERT_EQ(*(SPCurve(pv).second_point()), Geom::Point(1, 0));
+ pv.insert(pv.begin(), path4);
+ ASSERT_EQ(*SPCurve(pv).second_point(), Geom::Point(0, 0));
+}
+
+/*
+TEST_F(CurveTest, testPenultimatePoint)
+{
+ ASSERT_EQ(*(SPCurve(Geom::PathVector(path1)).penultimate_point()), Geom::Point(1, 1));
+ ASSERT_EQ(*(SPCurve(Geom::PathVector(path2)).penultimate_point()), Geom::Point(3, 0));
+ ASSERT_EQ(*(SPCurve(Geom::PathVector(path3)).penultimate_point()), Geom::Point(6, 4));
+ ASSERT_EQ(*(SPCurve(Geom::PathVector(path4)).penultimate_point()), Geom::Point(3, 5));
+ Geom::PathVector pv;
+ pv.push_back(path1);
+ pv.push_back(path2);
+ pv.push_back(path3);
+ ASSERT_EQ(*(SPCurve(pv).penultimate_point()), Geom::Point(6, 4));
+ pv.push_back(path4);
+ ASSERT_EQ(*(SPCurve(pv).penultimate_point()), Geom::Point(8, 4));
+}
+*/
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/cxxtests-to-migrate/marker-test.h b/testfiles/src/cxxtests-to-migrate/marker-test.h
new file mode 100644
index 0000000..1a77aff
--- /dev/null
+++ b/testfiles/src/cxxtests-to-migrate/marker-test.h
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief Unit tests for SVG marker handling
+ *//*
+ * Authors:
+ * see git history
+ * Johan Engelen <goejendaagh@zonnet.nl>
+ *
+ * Copyright (C) 2016 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cxxtest/TestSuite.h>
+
+#include "sp-marker-loc.h"
+
+class MarkerTest : public CxxTest::TestSuite
+{
+public:
+
+ void testMarkerLoc()
+ {
+ // code depends on these *exact* values, so check them here.
+ TS_ASSERT_EQUALS(SP_MARKER_LOC, 0);
+ TS_ASSERT_EQUALS(SP_MARKER_LOC_START, 1);
+ TS_ASSERT_EQUALS(SP_MARKER_LOC_MID, 2);
+ TS_ASSERT_EQUALS(SP_MARKER_LOC_END, 3);
+ TS_ASSERT_EQUALS(SP_MARKER_LOC_QTY, 4);
+ }
+
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/cxxtests-to-migrate/mod360-test.h b/testfiles/src/cxxtests-to-migrate/mod360-test.h
new file mode 100644
index 0000000..12ee994
--- /dev/null
+++ b/testfiles/src/cxxtests-to-migrate/mod360-test.h
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2016 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_MOD_360_TEST_H
+#define SEEN_MOD_360_TEST_H
+
+#include <cxxtest/TestSuite.h>
+#include <2geom/math-utils.h>
+#include "mod360.h"
+
+
+class Mod360Test : public CxxTest::TestSuite
+{
+public:
+ static double inf() { return INFINITY; }
+ static double nan() { return ((double)INFINITY) - ((double)INFINITY); }
+
+ void testMod360()
+ {
+ double cases[][2] = {
+ {0, 0},
+ {10, 10},
+ {360, 0},
+ {361, 1},
+ {-1, 359},
+ {-359, 1},
+ {-360, -0},
+ {-361, 359},
+ {inf(), 0},
+ {-inf(), 0},
+ {nan(), 0},
+ {720, 0},
+ {-721, 359},
+ {-1000, 80}
+ };
+
+ for ( unsigned i = 0; i < G_N_ELEMENTS(cases); i++ ) {
+ double result = mod360( cases[i][0] );
+ TS_ASSERT_EQUALS( cases[i][1], result );
+ }
+ }
+
+};
+
+
+#endif // SEEN_MOD_360_TEST_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
+
diff --git a/testfiles/src/cxxtests-to-migrate/preferences-test.h b/testfiles/src/cxxtests-to-migrate/preferences-test.h
new file mode 100644
index 0000000..0dc04b8
--- /dev/null
+++ b/testfiles/src/cxxtests-to-migrate/preferences-test.h
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief Unit tests for the Preferences object
+ *//*
+ * Authors:
+ * see git history
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2016 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cxxtest/TestSuite.h>
+#include "preferences.h"
+
+#include <glibmm/ustring.h>
+
+// test observer
+class TestObserver : public Inkscape::Preferences::Observer {
+public:
+ TestObserver(Glib::ustring const &path) :
+ Inkscape::Preferences::Observer(path),
+ value(0) {}
+
+ virtual void notify(Inkscape::Preferences::Entry const &val)
+ {
+ value = val.getInt();
+ }
+ int value;
+};
+
+class PreferencesTest : public CxxTest::TestSuite {
+public:
+ void setUp() {
+ prefs = Inkscape::Preferences::get();
+ }
+ void tearDown() {
+ prefs = NULL;
+ Inkscape::Preferences::unload();
+ }
+
+ void testStartingState()
+ {
+ TS_ASSERT_DIFFERS(prefs, static_cast<void*>(0));
+ TS_ASSERT_EQUALS(prefs->isWritable(), true);
+ }
+
+ void testOverwrite()
+ {
+ prefs->setInt("/test/intvalue", 123);
+ prefs->setInt("/test/intvalue", 321);
+ TS_ASSERT_EQUALS(prefs->getInt("/test/intvalue"), 321);
+ }
+
+ void testDefaultReturn()
+ {
+ TS_ASSERT_EQUALS(prefs->getInt("/this/path/does/not/exist", 123), 123);
+ }
+
+ void testLimitedReturn()
+ {
+ prefs->setInt("/test/intvalue", 1000);
+
+ // simple case
+ TS_ASSERT_EQUALS(prefs->getIntLimited("/test/intvalue", 123, 0, 500), 123);
+ // the below may seem quirky but this behaviour is intended
+ TS_ASSERT_EQUALS(prefs->getIntLimited("/test/intvalue", 123, 1001, 5000), 123);
+ // corner cases
+ TS_ASSERT_EQUALS(prefs->getIntLimited("/test/intvalue", 123, 0, 1000), 1000);
+ TS_ASSERT_EQUALS(prefs->getIntLimited("/test/intvalue", 123, 1000, 5000), 1000);
+ }
+
+ void testKeyObserverNotification()
+ {
+ Glib::ustring const path = "/some/random/path";
+ TestObserver obs("/some/random");
+ obs.value = 1;
+ prefs->setInt(path, 5);
+ TS_ASSERT_EQUALS(obs.value, 1); // no notifications sent before adding
+
+ prefs->addObserver(obs);
+ prefs->setInt(path, 10);
+ TS_ASSERT_EQUALS(obs.value, 10);
+ prefs->setInt("/some/other/random/path", 10);
+ TS_ASSERT_EQUALS(obs.value, 10); // value should not change
+
+ prefs->removeObserver(obs);
+ prefs->setInt(path, 15);
+ TS_ASSERT_EQUALS(obs.value, 10); // no notifications sent after removal
+ }
+
+ void testEntryObserverNotification()
+ {
+ Glib::ustring const path = "/some/random/path";
+ TestObserver obs(path);
+ obs.value = 1;
+ prefs->setInt(path, 5);
+ TS_ASSERT_EQUALS(obs.value, 1); // no notifications sent before adding
+
+ prefs->addObserver(obs);
+ prefs->setInt(path, 10);
+ TS_ASSERT_EQUALS(obs.value, 10);
+
+ // test that filtering works properly
+ prefs->setInt("/some/random/value", 1234);
+ TS_ASSERT_EQUALS(obs.value, 10);
+ prefs->setInt("/some/randomvalue", 1234);
+ TS_ASSERT_EQUALS(obs.value, 10);
+ prefs->setInt("/some/random/path2", 1234);
+ TS_ASSERT_EQUALS(obs.value, 10);
+
+ prefs->removeObserver(obs);
+ prefs->setInt(path, 15);
+ TS_ASSERT_EQUALS(obs.value, 10); // no notifications sent after removal
+ }
+
+ void testPreferencesEntryMethods()
+ {
+ prefs->setInt("/test/prefentry", 100);
+ Inkscape::Preferences::Entry val = prefs->getEntry("/test/prefentry");
+ TS_ASSERT(val.isValid());
+ TS_ASSERT_EQUALS(val.getPath(), "/test/prefentry");
+ TS_ASSERT_EQUALS(val.getEntryName(), "prefentry");
+ TS_ASSERT_EQUALS(val.getInt(), 100);
+ }
+private:
+ Inkscape::Preferences *prefs;
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/cxxtests-to-migrate/sp-style-elem-test.h b/testfiles/src/cxxtests-to-migrate/sp-style-elem-test.h
new file mode 100644
index 0000000..7846360
--- /dev/null
+++ b/testfiles/src/cxxtests-to-migrate/sp-style-elem-test.h
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2016 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_SP_STYLE_ELEM_TEST_H
+#define SEEN_SP_STYLE_ELEM_TEST_H
+
+#include <cxxtest/TestSuite.h>
+
+#include "test-helpers.h"
+
+#include "sp-style-elem.h"
+#include "xml/repr.h"
+
+class SPStyleElemTest : public CxxTest::TestSuite
+{
+public:
+ std::unique_ptr<SPDocument> _doc;
+
+ SPStyleElemTest() = default;
+
+ virtual ~SPStyleElemTest() = default;
+
+ static void createSuiteSubclass( SPStyleElemTest *& dst )
+ {
+ SPStyleElem *style_elem = new SPStyleElem();
+
+ if ( style_elem ) {
+ TS_ASSERT(!style_elem->is_css);
+ TS_ASSERT(style_elem->media.print);
+ TS_ASSERT(style_elem->media.screen);
+ delete style_elem;
+
+ dst = new SPStyleElemTest();
+ }
+ }
+
+ static SPStyleElemTest *createSuite()
+ {
+ return Inkscape::createSuiteAndDocument<SPStyleElemTest>( createSuiteSubclass );
+ }
+
+ static void destroySuite( SPStyleElemTest *suite ) { delete suite; }
+
+// -------------------------------------------------------------------------
+// -------------------------------------------------------------------------
+
+
+ void testSetType()
+ {
+ SPStyleElem *style_elem = new SPStyleElem();
+ SP_OBJECT(style_elem)->document = _doc.get();
+
+ SP_OBJECT(style_elem)->setKeyValue( SPAttr::TYPE, "something unrecognized");
+ TS_ASSERT( !style_elem->is_css );
+
+ SP_OBJECT(style_elem)->setKeyValue( SPAttr::TYPE, "text/css");
+ TS_ASSERT( style_elem->is_css );
+
+ SP_OBJECT(style_elem)->setKeyValue( SPAttr::TYPE, "atext/css");
+ TS_ASSERT( !style_elem->is_css );
+
+ SP_OBJECT(style_elem)->setKeyValue( SPAttr::TYPE, "text/cssx");
+ TS_ASSERT( !style_elem->is_css );
+
+ delete style_elem;
+ }
+
+ void testWrite()
+ {
+ TS_ASSERT( _doc );
+ TS_ASSERT( _doc->getReprDoc() );
+ if ( !_doc->getReprDoc() ) {
+ return; // evil early return
+ }
+
+ SPStyleElem *style_elem = new SPStyleElem();
+ SP_OBJECT(style_elem)->document = _doc.get();
+
+ SP_OBJECT(style_elem)->setKeyValue( SPAttr::TYPE, "text/css");
+ Inkscape::XML::Node *repr = _doc->getReprDoc()->createElement("svg:style");
+ SP_OBJECT(style_elem)->updateRepr(_doc->getReprDoc(), repr, SP_OBJECT_WRITE_ALL);
+ {
+ gchar const *typ = repr->attribute("type");
+ TS_ASSERT( typ != NULL );
+ if ( typ )
+ {
+ TS_ASSERT_EQUALS( std::string(typ), std::string("text/css") );
+ }
+ }
+
+ delete style_elem;
+ }
+
+ void testBuild()
+ {
+ TS_ASSERT( _doc );
+ TS_ASSERT( _doc->getReprDoc() );
+ if ( !_doc->getReprDoc() ) {
+ return; // evil early return
+ }
+
+ SPStyleElem *style_elem = new SPStyleElem();
+ Inkscape::XML::Node *const repr = _doc->getReprDoc()->createElement("svg:style");
+ repr->setAttribute("type", "text/css");
+ style_elem->invoke_build( _doc.get(), repr, false);
+ TS_ASSERT( style_elem->is_css );
+ TS_ASSERT( style_elem->media.print );
+ TS_ASSERT( style_elem->media.screen );
+
+ /* Some checks relevant to the read_content test below. */
+ {
+ g_assert(_doc->style_cascade);
+ CRStyleSheet const *const stylesheet = cr_cascade_get_sheet(_doc->style_cascade, ORIGIN_AUTHOR);
+ g_assert(stylesheet);
+ g_assert(stylesheet->statements == NULL);
+ }
+
+ delete style_elem;
+ Inkscape::GC::release(repr);
+ }
+
+ void testReadContent()
+ {
+ TS_ASSERT( _doc );
+ TS_ASSERT( _doc->getReprDoc() );
+ if ( !_doc->getReprDoc() ) {
+ return; // evil early return
+ }
+
+ SPStyleElem *style_elem = new SPStyleElem();
+ Inkscape::XML::Node *const repr = _doc->getReprDoc()->createElement("svg:style");
+ repr->setAttribute("type", "text/css");
+ Inkscape::XML::Node *const content_repr = _doc->getReprDoc()->createTextNode(".myclass { }");
+ repr->addChild(content_repr, NULL);
+ style_elem->invoke_build(_doc.get(), repr, false);
+ TS_ASSERT( style_elem->is_css );
+ TS_ASSERT( _doc->style_cascade );
+ CRStyleSheet const *const stylesheet = cr_cascade_get_sheet(_doc->style_cascade, ORIGIN_AUTHOR);
+ TS_ASSERT(stylesheet != NULL);
+ TS_ASSERT(stylesheet->statements != NULL);
+
+ delete style_elem;
+ Inkscape::GC::release(repr);
+ }
+
+};
+
+
+#endif // SEEN_SP_STYLE_ELEM_TEST_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/cxxtests-to-migrate/test-helpers.h b/testfiles/src/cxxtests-to-migrate/test-helpers.h
new file mode 100644
index 0000000..a6e49e5
--- /dev/null
+++ b/testfiles/src/cxxtests-to-migrate/test-helpers.h
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2016 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_TEST_HELPERS_H
+#define SEEN_TEST_HELPERS_H
+
+
+#include <cxxtest/TestSuite.h>
+
+#include "document.h"
+#include "inkscape.h"
+
+
+// Dummy functions to keep linker happy
+#if !defined(DUMMY_MAIN_TEST_CALLS_SEEN)
+#define DUMMY_MAIN_TEST_CALLS_SEEN
+int sp_main_gui (int, char const**) { return 0; }
+int sp_main_console (int, char const**) { return 0; }
+#endif // DUMMY_MAIN_TEST_CALLS_SEEN
+
+namespace Inkscape
+{
+
+template <class T>
+T* createSuiteAndDocument( void (*fun)(T*&) )
+{
+ T* suite = 0;
+
+#if !GLIB_CHECK_VERSION(2,36,0)
+ g_type_init();
+#endif
+
+ Inkscape::GC::init();
+ if ( !Inkscape::Application::exists() )
+ {
+ // Create the global inkscape object.
+ Inkscape::Application::create(false);
+ }
+
+ auto tmp = std::unique_ptr<SPDocument>(SPDocument::createNewDoc(NULL, TRUE, true));
+ if ( tmp ) {
+ fun( suite );
+ if ( suite )
+ {
+ suite->_doc = std::move(tmp);
+ }
+ }
+
+ return suite;
+}
+
+} // namespace Inkscape
+
+#endif // SEEN_TEST_HELPERS_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/cxxtests-to-migrate/verbs-test.h b/testfiles/src/cxxtests-to-migrate/verbs-test.h
new file mode 100644
index 0000000..b8fd299
--- /dev/null
+++ b/testfiles/src/cxxtests-to-migrate/verbs-test.h
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2016 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include <cxxtest/TestSuite.h>
+
+#include "verbs.h"
+
+class VerbsTest : public CxxTest::TestSuite
+{
+public:
+
+ class TestHook : public Inkscape::Verb {
+ public:
+ static int getInternalTableSize() { return _getBaseListSize(); }
+
+ private:
+ TestHook();
+ };
+
+ void testEnumLength()
+ {
+ TS_ASSERT_DIFFERS( 0, static_cast<int>(SP_VERB_LAST) );
+ TS_ASSERT_EQUALS( static_cast<int>(SP_VERB_LAST) + 1, TestHook::getInternalTableSize() );
+ }
+
+ void testEnumFixed()
+ {
+ TS_ASSERT_EQUALS( 0, static_cast<int>(SP_VERB_INVALID) );
+ TS_ASSERT_EQUALS( 1, static_cast<int>(SP_VERB_NONE) );
+
+ TS_ASSERT_DIFFERS( 0, static_cast<int>(SP_VERB_LAST) );
+ TS_ASSERT_DIFFERS( 1, static_cast<int>(SP_VERB_LAST) );
+ }
+
+ void testFetch()
+ {
+ for ( int i = 0; i < static_cast<int>(SP_VERB_LAST); i++ )
+ {
+ char tmp[16];
+ snprintf( tmp, sizeof(tmp), "Verb# %d", i );
+ tmp[sizeof(tmp)-1] = 0;
+ std::string descr(tmp);
+
+ Inkscape::Verb* verb = Inkscape::Verb::get(i);
+ TSM_ASSERT( descr, verb );
+ if ( verb )
+ {
+ TSM_ASSERT_EQUALS( descr, verb->get_code(), static_cast<unsigned int>(i) );
+
+ if ( i != static_cast<int>(SP_VERB_INVALID) )
+ {
+ TSM_ASSERT( descr, verb->get_id() );
+ TSM_ASSERT( descr, verb->get_name() );
+
+ Inkscape::Verb* bounced = verb->getbyid( verb->get_id() );
+ // TODO - put this back once verbs are fixed
+ //TSM_ASSERT( descr, bounced );
+ if ( bounced )
+ {
+ TSM_ASSERT_EQUALS( descr, bounced->get_code(), static_cast<unsigned int>(i) );
+ }
+ else
+ {
+ TS_FAIL( std::string("Unable to getbyid() for ") + descr + std::string(" ID: '") + std::string(verb->get_id()) + std::string("'") );
+ }
+ }
+ else
+ {
+ TSM_ASSERT( std::string("SP_VERB_INVALID"), !verb->get_id() );
+ TSM_ASSERT( std::string("SP_VERB_INVALID"), !verb->get_name() );
+ }
+ }
+ }
+ }
+
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/dir-util-test.cpp b/testfiles/src/dir-util-test.cpp
new file mode 100644
index 0000000..bac5e3c
--- /dev/null
+++ b/testfiles/src/dir-util-test.cpp
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Unit tests for dir utils.
+ *
+ * Author:
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2015 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "gtest/gtest.h"
+
+#include <glib.h>
+
+#include "io/dir-util.h"
+
+namespace {
+
+
+TEST(DirUtilTest, Base)
+{
+ char const* cases[][3] = {
+#if defined(WIN32) || defined(__WIN32__)
+ {"\\foo\\bar", "\\foo", "bar"},
+ {"\\foo\\barney", "\\foo\\bar", "\\foo\\barney"},
+ {"\\foo\\bar\\baz", "\\foo\\", "bar\\baz"},
+ {"\\foo\\bar\\baz", "\\", "foo\\bar\\baz"},
+ {"\\foo\\bar\\baz", "\\foo\\qux", "\\foo\\bar\\baz"},
+#else
+ {"/foo/bar", "/foo", "bar"},
+ {"/foo/barney", "/foo/bar", "/foo/barney"},
+ {"/foo/bar/baz", "/foo/", "bar/baz"},
+ {"/foo/bar/baz", "/", "foo/bar/baz"},
+ {"/foo/bar/baz", "/foo/qux", "/foo/bar/baz"},
+#endif
+ };
+
+ for (auto & i : cases)
+ {
+ if ( i[0] && i[1] ) { // std::string can't use null.
+ std::string result = sp_relative_path_from_path( i[0], i[1] );
+ ASSERT_FALSE( result.empty() );
+ if ( !result.empty() )
+ {
+ ASSERT_EQ( std::string(i[2]), result );
+ }
+ }
+ }
+}
+
+} // namespace
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/testfiles/src/drag-and-drop-svgz.cpp b/testfiles/src/drag-and-drop-svgz.cpp
new file mode 100644
index 0000000..faa3e0f
--- /dev/null
+++ b/testfiles/src/drag-and-drop-svgz.cpp
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+/**
+ * @file
+ * Test that svgz (= compressed SVG) import/drag-and-drop
+ * is working: https://gitlab.com/inkscape/inkscape/-/issues/906 .
+ *
+ */
+/*
+ * Authors:
+ * Shlomi Fish
+ *
+ * Copyright (C) 2020 Authors
+ */
+
+#include "doc-per-case-test.h"
+#include <glibmm.h>
+
+#include "extension/init.h"
+#include "extension/db.h"
+#include "extension/find_extension_by_mime.h"
+#include "extension/internal/svgz.h"
+#include "io/resource.h"
+#include "path-prefix.h"
+#include "preferences.h"
+
+#include "gtest/gtest.h"
+
+#include <iostream>
+class SvgzImportTest : public DocPerCaseTest {
+ public:
+ void TestBody() override
+ {
+ Inkscape::Extension::init();
+ ASSERT_TRUE(_doc != nullptr);
+ ASSERT_TRUE(_doc->getRoot() != nullptr);
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool("/dialogs/import/ask_svg", true);
+ prefs->setBool("/options/onimport", true);
+ auto ext = Inkscape::Extension::find_by_mime("image/svg+xml-compressed");
+ if (!ext) {
+ std::cerr << "SvgzImportTest: Failed to find mime type!" << std::endl;
+ }
+ ext->set_gui(true);
+
+ using namespace Inkscape::IO::Resource;
+ auto fn = get_path_string(SYSTEM, EXAMPLES, "tiger.svgz");
+
+ auto imod = dynamic_cast<Inkscape::Extension::Input *>(ext);
+ auto svg_mod = (new Inkscape::Extension::Internal::Svg);
+ ASSERT_TRUE(svg_mod->open(imod, fn.c_str()) != nullptr);
+ }
+ ~SvgzImportTest() override {}
+};
+
+TEST_F(SvgzImportTest, Eq)
+{
+ SvgzImportTest foo;
+ foo.TestBody();
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/extract-uri-test.cpp b/testfiles/src/extract-uri-test.cpp
new file mode 100644
index 0000000..acff966
--- /dev/null
+++ b/testfiles/src/extract-uri-test.cpp
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Test extract_uri
+ */
+/*
+ * Authors:
+ * Thomas Holder
+ *
+ * Copyright (C) 2018 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extract-uri.h"
+#include "gtest/gtest.h"
+
+TEST(ExtractUriTest, valid)
+{
+ ASSERT_EQ(extract_uri("url(#foo)"), "#foo");
+ ASSERT_EQ(extract_uri("url( \t #foo \t )"), "#foo");
+ ASSERT_EQ(extract_uri("url( '#foo' )"), "#foo");
+ ASSERT_EQ(extract_uri("url('url(foo)')"), "url(foo)");
+ ASSERT_EQ(extract_uri("url(\"foo(url)\")"), "foo(url)");
+ ASSERT_EQ(extract_uri("url()bar"), "");
+ ASSERT_EQ(extract_uri("url( )bar"), "");
+ ASSERT_EQ(extract_uri("url(a b)"), "a b");
+}
+
+TEST(ExtractUriTest, legacy)
+{
+ ASSERT_EQ(extract_uri("url (foo)"), "foo");
+}
+
+TEST(ExtractUriTest, invalid)
+{
+ ASSERT_EQ(extract_uri("#foo"), "");
+ ASSERT_EQ(extract_uri(" url(foo)"), "");
+ ASSERT_EQ(extract_uri("url(#foo"), "");
+ ASSERT_EQ(extract_uri("url('#foo'"), "");
+ ASSERT_EQ(extract_uri("url('#foo)"), "");
+ ASSERT_EQ(extract_uri("url #foo)"), "");
+}
+
+static char const *extract_end(char const *s)
+{
+ char const *end = nullptr;
+ extract_uri(s, &end);
+ return end;
+}
+
+TEST(ExtractUriTest, endptr)
+{
+ ASSERT_STREQ(extract_end(""), nullptr);
+ ASSERT_STREQ(extract_end("url(invalid"), nullptr);
+ ASSERT_STREQ(extract_end("url('invalid)"), nullptr);
+ ASSERT_STREQ(extract_end("url(valid)"), "");
+ ASSERT_STREQ(extract_end("url(valid)foo"), "foo");
+ ASSERT_STREQ(extract_end("url('valid')bar"), "bar");
+ ASSERT_STREQ(extract_end("url( 'valid' )bar"), "bar");
+ ASSERT_STREQ(extract_end("url( valid ) bar "), " bar ");
+ ASSERT_STREQ(extract_end("url()bar"), "bar");
+ ASSERT_STREQ(extract_end("url( )bar"), "bar");
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/lpe-test.cpp b/testfiles/src/lpe-test.cpp
new file mode 100644
index 0000000..87d263f
--- /dev/null
+++ b/testfiles/src/lpe-test.cpp
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * LPE tests
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2020 Authors
+ *
+ * Released under GNU GPL version 2 or later, read the file 'COPYING' for more information
+ */
+
+#include <gtest/gtest.h>
+#include <testfiles/lpespaths-test.h>
+#include <src/document.h>
+#include <src/inkscape.h>
+#include <src/live_effects/lpe-bool.h>
+#include <src/object/sp-ellipse.h>
+#include <src/object/sp-lpe-item.h>
+
+using namespace Inkscape;
+using namespace Inkscape::LivePathEffect;
+
+class LPETest : public LPESPathsTest {
+public:
+ void run() {
+ testDoc(svg);
+ }
+};
+
+// A) FILE BASED TESTS
+// TEST_F(LPETest, Bool_multi_px_1_1) { run(); }
+// TEST_F(LPETest, Bool_multi_mm_1_1) { run(); }
+TEST_F(LPETest, AttachPath_0_92_5_mixed) { run(); }
+TEST_F(LPETest, AttachPath_mm_1_0_2) { run(); }
+TEST_F(LPETest, AttachPath_px_1_0_2) { run(); }
+TEST_F(LPETest, BoundingBox_mixed_0_92_5) { run(); }
+TEST_F(LPETest, BoundingBox_mm_1_0_2) { run(); }
+TEST_F(LPETest, BoundingBox_px_1_0_2) { run(); }
+TEST_F(LPETest, CloneOriginal_mixed_0_92_5) { run(); }
+// linked item is broken in 1.0.2 because group cliboard items, use same version of 1.1 but resaved in 1.2 to get comapat in 1.0.1 or before the group clipboard is added
+TEST_F(LPETest, CloneOriginal_boken_1_0_2) { run(); }
+TEST_F(LPETest, CloneOriginal_mixed_px_1_1) { run(); }
+TEST_F(LPETest, CloneOriginal_mixed_mm_1_1) { run(); }
+TEST_F(LPETest, ConstructGrid_mixed_0_92_5) { run(); }
+TEST_F(LPETest, ConstructGrid_mm_1_0_2) { run(); }
+TEST_F(LPETest, ConstructGrid_px_1_0_2) { run(); }
+TEST_F(LPETest, Transform2Points_path_0_92_5) { run(); }
+TEST_F(LPETest, Transform2Points_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, Transform2Points_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, VonCoch_path_0_92_5) { run(); }
+TEST_F(LPETest, VonCoch_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, VonCoch_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, StitchSubPaths_path_0_92_5) { run(); }
+TEST_F(LPETest, StitchSubPaths_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, StitchSubPaths_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, Spiro_mixed_0_92_5) { run(); }
+TEST_F(LPETest, Spiro_mm_1_0_2) { run(); }
+TEST_F(LPETest, Spiro_px_1_0_2) { run(); }
+TEST_F(LPETest, Slice_multi_px_1_1) { run(); }
+TEST_F(LPETest, Slice_multi_mm_1_1) { run(); }
+TEST_F(LPETest, Simplify_path_0_92_5) { run(); }
+TEST_F(LPETest, Simplify_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, Simplify_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, ShowHandles_path_0_92_5) { run(); }
+TEST_F(LPETest, ShowHandles_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, ShowHandles_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, Ruler_path_0_92_5) { run(); }
+TEST_F(LPETest, Ruler_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, Ruler_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, RoughHatches_path_0_92_5) { run(); }
+TEST_F(LPETest, RoughHatches_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, RoughHatches_multi_mm_1_0_2) { run(); }
+// Rougen Test till 1.1 fail because wrong implementation of rand on the LPE
+TEST_F(LPETest, Roughen_path_1_1) { run(); }
+TEST_F(LPETest, EllipseFromPoints_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, EllipseFromPoints_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, PowerMask_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, PowerMask_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, PowerClip_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, PowerClip_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, PerspectiveEnvelope_mixed_0_92_5) { run(); }
+TEST_F(LPETest, PerspectiveEnvelope_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, PerspectiveEnvelope_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, Offset_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, Offset_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, Offset_multi_px_1_1) { run(); }
+TEST_F(LPETest, MirrorSymmetry_path_0_92_5) { run(); }
+TEST_F(LPETest, MirrorSymmetry_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, MirrorSymmetry_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, MeasureSegments_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, MeasureSegments_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, Lattice2_path_0_92_5) { run(); }
+TEST_F(LPETest, Lattice2_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, Lattice2_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, Knot_path_0_92_5) { run(); }
+TEST_F(LPETest, Knot_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, Knot_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, JoinType_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, JoinType_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, Interpolate_path_0_92_5) { run(); }
+TEST_F(LPETest, Interpolate_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, Interpolate_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, InterpolatePoints_path_0_92_5) { run(); }
+TEST_F(LPETest, InterpolatePoints_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, InterpolatePoints_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, Gears_path_0_92_5) { run(); }
+TEST_F(LPETest, Gears_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, Gears_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, FilletChamfer_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, FilletChamfer_multi_mm_1_0_2) { run(); }
+// NEED to test on 0.92 no working one here (gnome 40)
+// TEST_F(LPETest, FillBetweenStrokes_path_0_92_5) { run(); }
+TEST_F(LPETest, FillBetweenStrokes_path_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, FillBetweenStrokes_path_multi_mm_1_0_2) { run(); }
+// NEED to test on 0.92 no working one here (gnome 40)
+// TEST_F(LPETest, FillBetweenMany_multi_0_92_5) { run(); }
+TEST_F(LPETest, FillBetweenMany_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, FillBetweenMany_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, Ellipse5pts_path_0_92_5) { run(); }
+TEST_F(LPETest, Ellipse5pts_ellipse_px_1_0_2) { run(); }
+TEST_F(LPETest, Ellipse5pts_ellipse_mm_1_0_2) { run(); }
+TEST_F(LPETest, DashedStroke_multi_px_1_0_2) { run(); }
+TEST_F(LPETest, DashedStroke_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, RotateCopies_multi_mm_1_0_2) { run(); }
+TEST_F(LPETest, RotateCopies_multi_px_1_0_2) { run(); }
+// B) CUSTOM TESTS
+// BOOL LPE
+TEST_F(LPETest, Bool_canBeApplyedToNonSiblingPaths)
+{
+ std::string svg("\
+<svg width='100' height='100'\
+ xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd'\
+ xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape'>\
+ <defs>\
+ <inkscape:path-effect\
+ id='path-effect1'\
+ effect='bool_op'\
+ operation='diff'\
+ operand-path='#circle1'\
+ lpeversion='1'\
+ hide-linked='true' />\
+ </defs>\
+ <path id='rect1'\
+ inkscape:path-effect='#path-effect1'\
+ sodipodi:type='rect'\
+ width='100' height='100' fill='#ff0000' />\
+ <g id='group1'>\
+ <circle id='circle1'\
+ r='40' cy='50' cx='50' fill='#ffffff' style='display:inline'/>\
+ </g>\
+</svg>");
+
+ SPDocument *doc = SPDocument::createNewDocFromMem(svg.c_str(), svg.size(), true);
+ doc->ensureUpToDate();
+
+ auto lpe_item = dynamic_cast<SPLPEItem *>(doc->getObjectById("rect1"));
+ ASSERT_TRUE(lpe_item != nullptr);
+
+ auto lpe_bool_op_effect = dynamic_cast<LPEBool *>(lpe_item->getFirstPathEffectOfType(EffectType::BOOL_OP));
+ ASSERT_TRUE(lpe_bool_op_effect != nullptr);
+
+ auto operand_path = lpe_bool_op_effect->getParameter("operand-path")->param_getSVGValue();
+ auto circle = dynamic_cast<SPGenericEllipse *>(doc->getObjectById(operand_path.substr(1)));
+ ASSERT_TRUE(circle != nullptr);
+} \ No newline at end of file
diff --git a/testfiles/src/lpe64-test.cpp b/testfiles/src/lpe64-test.cpp
new file mode 100644
index 0000000..91bf1c7
--- /dev/null
+++ b/testfiles/src/lpe64-test.cpp
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * LPE 64B tests
+ * Because some issues rounding in 32B windows we move this tests only to 64B
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2020 Authors
+ *
+ * Released under GNU GPL version 2 or later, read the file 'COPYING' for more information
+ */
+
+#include <gtest/gtest.h>
+#include <testfiles/lpespaths-test.h>
+#include <src/inkscape.h>
+
+using namespace Inkscape;
+using namespace Inkscape::LivePathEffect;
+
+class LPE64Test : public LPESPathsTest {
+public:
+ void run() {
+ testDoc(svg);
+ }
+};
+
+
+// A) FILE BASED TESTS
+TEST_F(LPE64Test, Bendpath_mixed_0_92_5) { run(); }
+TEST_F(LPE64Test, Bendpath_shape_1_0_2) { run(); }
+TEST_F(LPE64Test, Bendpath_shapeClipPath_1_0_2) { run(); }
+TEST_F(LPE64Test, Bendpath_multiGroup_1_0_2) { run(); }
+TEST_F(LPE64Test, Bendpath_stackNested_mm_1_0_2) { run(); }
+TEST_F(LPE64Test, Bendpath_stackNested_px_1_0_2) { run(); }
+TEST_F(LPE64Test, BSpline_mixed_0_92_5) { run(); }
+TEST_F(LPE64Test, BSpline_mm_1_0_2) { run(); }
+TEST_F(LPE64Test, BSpline_px_1_0_2) { run(); }
+TEST_F(LPE64Test, TaperStroke_multi_px_1_0_2) { run(); }
+TEST_F(LPE64Test, TaperStroke_multi_mm_1_0_2) { run(); }
+TEST_F(LPE64Test, Sketch_path_0_92_5) { run(); }
+TEST_F(LPE64Test, Sketch_multi_px_1_0_2) { run(); }
+TEST_F(LPE64Test, Sketch_multi_mm_1_0_2) { run(); }
+TEST_F(LPE64Test, PowerStroke_multi_mm_1_0_2) { run(); }
+TEST_F(LPE64Test, PowerStroke_multi_px_1_0_2) { run(); }
+TEST_F(LPE64Test, PatternAlongPath_mixed_0_92_5) { run(); }
+TEST_F(LPE64Test, PatternAlongPath_shape_1_0_2) { run(); }
+TEST_F(LPE64Test, PatternAlongPath_path_1_0_2) { run(); }
+TEST_F(LPE64Test, PatternAlongPath_multiple_mm_1_0_2) { run(); }
+TEST_F(LPE64Test, PatternAlongPath_multiple_px_1_0_2) { run(); }
+TEST_F(LPE64Test, EnvelopeDeformation_multi_0_92_5) { run(); }
+TEST_F(LPE64Test, EnvelopeDeformation_multi_px_1_0_2) { run(); }
+TEST_F(LPE64Test, EnvelopeDeformation_multi_mm_1_0_2) { run(); }
+
+// B) CUSTOM TESTS \ No newline at end of file
diff --git a/testfiles/src/object-set-test.cpp b/testfiles/src/object-set-test.cpp
new file mode 100644
index 0000000..2112937
--- /dev/null
+++ b/testfiles/src/object-set-test.cpp
@@ -0,0 +1,706 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Multiindex container for selection
+ *
+ * Authors:
+ * Adrian Boguszewski
+ *
+ * Copyright (C) 2016 Adrian Boguszewski
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <gtest/gtest.h>
+#include <doc-per-case-test.h>
+#include <src/object/sp-factory.h>
+#include <src/object/sp-marker.h>
+#include <src/object/sp-rect.h>
+#include <src/object/sp-path.h>
+#include <src/object/sp-use.h>
+#include <src/object/sp-root.h>
+#include <src/object/object-set.h>
+#include <xml/node.h>
+#include <src/xml/text-node.h>
+#include <src/xml/simple-document.h>
+//#include <unistd.h>
+#include <2geom/transforms.h>
+using namespace Inkscape;
+using namespace Inkscape::XML;
+
+class ObjectSetTest: public DocPerCaseTest {
+public:
+ ObjectSetTest() {
+ auto *const _doc = this->_doc.get();
+ N = _doc->getRoot()->children.size();
+
+ A = new SPObject();
+ B = new SPObject();
+ C = new SPObject();
+ D = new SPObject();
+ E = new SPObject();
+ F = new SPObject();
+ G = new SPObject();
+ H = new SPObject();
+ X = new SPObject();
+ set = new ObjectSet(_doc);
+ set2 = new ObjectSet(_doc);
+ auto sd = _doc->getReprDoc();
+ auto xt = new TextNode(Util::share_string("x"), sd);
+ auto ht = new TextNode(Util::share_string("h"), sd);
+ auto gt = new TextNode(Util::share_string("g"), sd);
+ auto ft = new TextNode(Util::share_string("f"), sd);
+ auto et = new TextNode(Util::share_string("e"), sd);
+ auto dt = new TextNode(Util::share_string("d"), sd);
+ auto ct = new TextNode(Util::share_string("c"), sd);
+ auto bt = new TextNode(Util::share_string("b"), sd);
+ auto at = new TextNode(Util::share_string("a"), sd);
+ X->invoke_build(_doc, xt, 0);
+ H->invoke_build(_doc, ht, 0);
+ G->invoke_build(_doc, gt, 0);
+ F->invoke_build(_doc, ft, 0);
+ E->invoke_build(_doc, et, 0);
+ D->invoke_build(_doc, dt, 0);
+ C->invoke_build(_doc, ct, 0);
+ B->invoke_build(_doc, bt, 0);
+ A->invoke_build(_doc, at, 0);
+
+ //create 3 rects at root of document
+ Inkscape::XML::Node *repr = _doc->getReprDoc()->createElement("svg:rect");
+ _doc->getRoot()->appendChild(repr);
+ r1.reset(dynamic_cast<SPRect*>(_doc->getObjectByRepr(repr)));
+ repr = _doc->getReprDoc()->createElement("svg:rect");
+ _doc->getRoot()->appendChild(repr);
+ r2.reset(dynamic_cast<SPRect*>(_doc->getObjectByRepr(repr)));
+ repr = _doc->getReprDoc()->createElement("svg:rect");
+ _doc->getRoot()->appendChild(repr);
+ r3.reset(dynamic_cast<SPRect*>(_doc->getObjectByRepr(repr)));
+ EXPECT_EQ(N + 3, _doc->getRoot()->children.size());// defs, namedview, and those three rects.
+ r1->x = r1->y = r2->x = r2->y = r3->x = r3->y = 0;
+ r1->width = r1->height = r2->width = r2->height = r3->width = r3->height = 10;
+ r1->set_shape();
+ r2->set_shape();
+ r3->set_shape();
+
+ }
+ ~ObjectSetTest() override {
+ delete set;
+ delete set2;
+ delete X;
+ delete H;
+ delete G;
+ delete F;
+ delete E;
+ delete D;
+ delete C;
+ delete B;
+ delete A;
+ }
+ SPObject* A;
+ SPObject* B;
+ SPObject* C;
+ SPObject* D;
+ SPObject* E;
+ SPObject* F;
+ SPObject* G;
+ SPObject* H;
+ SPObject* X;
+ std::unique_ptr<SPRect> r1;
+ std::unique_ptr<SPRect> r2;
+ std::unique_ptr<SPRect> r3;
+ ObjectSet* set;
+ ObjectSet* set2;
+ int N; //!< Number of root children in default document
+};
+
+#define SP_IS_CLONE(obj) (dynamic_cast<const SPUse*>(obj) != NULL)
+
+bool containsClone(ObjectSet* set) {
+ for (auto it : set->items()) {
+ if (SP_IS_CLONE(it)) {
+ return true;
+ }
+ if (SP_IS_GROUP(it)) {
+ ObjectSet tmp_set(set->document());
+ std::vector<SPObject*> c = it->childList(false);
+ tmp_set.setList(c);
+ if (containsClone(&tmp_set)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+TEST_F(ObjectSetTest, Basics) {
+ EXPECT_EQ(0, set->size());
+ set->add(A);
+ EXPECT_EQ(1, set->size());
+ EXPECT_TRUE(set->includes(A));
+ set->add(B);
+ set->add(C);
+ EXPECT_EQ(3, set->size());
+ EXPECT_TRUE(set->includes(B));
+ EXPECT_TRUE(set->includes(C));
+ EXPECT_FALSE(set->includes(D));
+ EXPECT_FALSE(set->includes(X));
+ EXPECT_FALSE(set->includes(nullptr));
+ set->remove(A);
+ EXPECT_EQ(2, set->size());
+ EXPECT_FALSE(set->includes(A));
+ set->clear();
+ EXPECT_EQ(0, set->size());
+ bool resultNull = set->add((SPObject*)nullptr);
+ EXPECT_FALSE(resultNull);
+ EXPECT_EQ(0, set->size());
+ bool resultNull2 = set->remove(nullptr);
+ EXPECT_FALSE(resultNull2);
+}
+
+TEST_F(ObjectSetTest, Advanced) {
+ set->add(A);
+ set->add(B);
+ set->add(C);
+ EXPECT_TRUE(set->includes(C));
+ set->toggle(C);
+ EXPECT_EQ(2, set->size());
+ EXPECT_FALSE(set->includes(C));
+ set->toggle(D);
+ EXPECT_EQ(3, set->size());
+ EXPECT_TRUE(set->includes(D));
+ set->toggle(D);
+ EXPECT_EQ(2, set->size());
+ EXPECT_FALSE(set->includes(D));
+ EXPECT_EQ(nullptr, set->single());
+ set->set(X);
+ EXPECT_EQ(1, set->size());
+ EXPECT_TRUE(set->includes(X));
+ EXPECT_EQ(X, set->single());
+ EXPECT_FALSE(set->isEmpty());
+ set->clear();
+ EXPECT_TRUE(set->isEmpty());
+ std::vector<SPObject*> list1 {A, B, C, D};
+ std::vector<SPObject*> list2 {E, F};
+ set->addList(list1);
+ EXPECT_EQ(4, set->size());
+ set->addList(list2);
+ EXPECT_EQ(6, set->size());
+ EXPECT_TRUE(set->includes(A));
+ EXPECT_TRUE(set->includes(B));
+ EXPECT_TRUE(set->includes(C));
+ EXPECT_TRUE(set->includes(D));
+ EXPECT_TRUE(set->includes(E));
+ EXPECT_TRUE(set->includes(F));
+ set->setList(list2);
+ EXPECT_EQ(2, set->size());
+ EXPECT_TRUE(set->includes(E));
+ EXPECT_TRUE(set->includes(F));
+}
+
+TEST_F(ObjectSetTest, Items) {
+ // cannot test smallestItem and largestItem functions due to too many dependencies
+ // uncomment if the problem is fixed
+
+ SPRect* rect10x100 = &*r1;
+ rect10x100->x = rect10x100->x = 0;
+ rect10x100->width = 10;
+ rect10x100->height = 100;
+ rect10x100->set_shape();
+
+ SPRect* rect20x40 = &*r2;
+ rect20x40->x = rect20x40->x = 0;
+ rect20x40->width = 20;
+ rect20x40->height = 40;
+ rect20x40->set_shape();
+
+ SPRect* rect30x30 = &*r3;
+ rect30x30->x = rect30x30->x = 0;
+ rect30x30->width = 30;
+ rect30x30->height = 30;
+ rect30x30->set_shape();
+
+
+ set->add(rect10x100);
+ EXPECT_EQ(rect10x100, set->singleItem());
+ EXPECT_EQ(rect10x100->getRepr(), set->singleRepr());
+ set->add(rect20x40);
+ EXPECT_EQ(nullptr, set->singleItem());
+ EXPECT_EQ(nullptr, set->singleRepr());
+ set->add(rect30x30);
+ EXPECT_EQ(3, set->size());
+ EXPECT_EQ(rect10x100, set->smallestItem(ObjectSet::CompareSize::HORIZONTAL));
+ EXPECT_EQ(rect30x30, set->smallestItem(ObjectSet::CompareSize::VERTICAL));
+ EXPECT_EQ(rect20x40, set->smallestItem(ObjectSet::CompareSize::AREA));
+ EXPECT_EQ(rect30x30, set->largestItem(ObjectSet::CompareSize::HORIZONTAL));
+ EXPECT_EQ(rect10x100, set->largestItem(ObjectSet::CompareSize::VERTICAL));
+ EXPECT_EQ(rect10x100, set->largestItem(ObjectSet::CompareSize::AREA));
+}
+
+TEST_F(ObjectSetTest, Ranges) {
+ std::vector<SPObject*> objs {A, D, B, E, C, F};
+ set->add(objs.begin() + 1, objs.end() - 1);
+ EXPECT_EQ(4, set->size());
+ auto it = set->objects().begin();
+ EXPECT_EQ(D, *it++);
+ EXPECT_EQ(B, *it++);
+ EXPECT_EQ(E, *it++);
+ EXPECT_EQ(C, *it++);
+ EXPECT_EQ(set->objects().end(), it);
+ SPObject* rect1 = SPFactory::createObject("svg:rect");
+ SPObject* rect2 = SPFactory::createObject("svg:rect");
+ SPObject* rect3 = SPFactory::createObject("svg:rect");
+ set->add(rect1);
+ set->add(rect2);
+ set->add(rect3);
+ EXPECT_EQ(7, set->size());
+ auto xmlNode = set->xmlNodes().begin();
+ EXPECT_EQ(3, boost::distance(set->xmlNodes()));
+ EXPECT_EQ(rect1->getRepr(), *xmlNode++);
+ EXPECT_EQ(rect2->getRepr(), *xmlNode++);
+ EXPECT_EQ(rect3->getRepr(), *xmlNode++);
+ EXPECT_EQ(set->xmlNodes().end(), xmlNode);
+ auto item = set->items().begin();
+ EXPECT_EQ(3, boost::distance(set->items()));
+ EXPECT_EQ(rect1, *item++);
+ EXPECT_EQ(rect2, *item++);
+ EXPECT_EQ(rect3, *item++);
+ EXPECT_EQ(set->items().end(), item);
+}
+
+TEST_F(ObjectSetTest, Autoremoving) {
+ set->add(A);
+ EXPECT_TRUE(set->includes(A));
+ EXPECT_EQ(1, set->size());
+ A->releaseReferences();
+ EXPECT_EQ(0, set->size());
+}
+
+TEST_F(ObjectSetTest, BasicDescendants) {
+ A->attach(B, nullptr);
+ B->attach(C, nullptr);
+ A->attach(D, nullptr);
+ bool resultB = set->add(B);
+ bool resultB2 = set->add(B);
+ EXPECT_TRUE(resultB);
+ EXPECT_FALSE(resultB2);
+ EXPECT_TRUE(set->includes(B));
+ bool resultC = set->add(C);
+ EXPECT_FALSE(resultC);
+ EXPECT_FALSE(set->includes(C));
+ EXPECT_EQ(1, set->size());
+ bool resultA = set->add(A);
+ EXPECT_TRUE(resultA);
+ EXPECT_EQ(1, set->size());
+ EXPECT_TRUE(set->includes(A));
+ EXPECT_FALSE(set->includes(B));
+}
+
+TEST_F(ObjectSetTest, AdvancedDescendants) {
+ A->attach(B, nullptr);
+ A->attach(C, nullptr);
+ A->attach(X, nullptr);
+ B->attach(D, nullptr);
+ B->attach(E, nullptr);
+ C->attach(F, nullptr);
+ C->attach(G, nullptr);
+ C->attach(H, nullptr);
+ set->add(A);
+ bool resultF = set->remove(F);
+ EXPECT_TRUE(resultF);
+ EXPECT_EQ(4, set->size());
+ EXPECT_FALSE(set->includes(F));
+ EXPECT_TRUE(set->includes(B));
+ EXPECT_TRUE(set->includes(G));
+ EXPECT_TRUE(set->includes(H));
+ EXPECT_TRUE(set->includes(X));
+ bool resultF2 = set->add(F);
+ EXPECT_TRUE(resultF2);
+ EXPECT_EQ(5, set->size());
+ EXPECT_TRUE(set->includes(F));
+}
+
+TEST_F(ObjectSetTest, Removing) {
+ A->attach(B, nullptr);
+ A->attach(C, nullptr);
+ A->attach(X, nullptr);
+ B->attach(D, nullptr);
+ B->attach(E, nullptr);
+ C->attach(F, nullptr);
+ C->attach(G, nullptr);
+ C->attach(H, nullptr);
+ bool removeH = set->remove(H);
+ EXPECT_FALSE(removeH);
+ set->add(A);
+ bool removeX = set->remove(X);
+ EXPECT_TRUE(removeX);
+ EXPECT_EQ(2, set->size());
+ EXPECT_TRUE(set->includes(B));
+ EXPECT_TRUE(set->includes(C));
+ EXPECT_FALSE(set->includes(X));
+ EXPECT_FALSE(set->includes(A));
+ bool removeX2 = set->remove(X);
+ EXPECT_FALSE(removeX2);
+ EXPECT_EQ(2, set->size());
+ bool removeA = set->remove(A);
+ EXPECT_FALSE(removeA);
+ EXPECT_EQ(2, set->size());
+ bool removeC = set->remove(C);
+ EXPECT_TRUE(removeC);
+ EXPECT_EQ(1, set->size());
+ EXPECT_TRUE(set->includes(B));
+ EXPECT_FALSE(set->includes(C));
+}
+
+TEST_F(ObjectSetTest, TwoSets) {
+ A->attach(B, nullptr);
+ A->attach(C, nullptr);
+ set->add(A);
+ set2->add(A);
+ EXPECT_EQ(1, set->size());
+ EXPECT_EQ(1, set2->size());
+ set->remove(B);
+ EXPECT_EQ(1, set->size());
+ EXPECT_TRUE(set->includes(C));
+ EXPECT_EQ(1, set2->size());
+ EXPECT_TRUE(set2->includes(A));
+ C->releaseReferences();
+ EXPECT_EQ(0, set->size());
+ EXPECT_EQ(1, set2->size());
+ EXPECT_TRUE(set2->includes(A));
+}
+
+TEST_F(ObjectSetTest, SetRemoving) {
+ ObjectSet *objectSet = new ObjectSet(_doc.get());
+ A->attach(B, nullptr);
+ objectSet->add(A);
+ objectSet->add(C);
+ EXPECT_EQ(2, objectSet->size());
+ delete objectSet;
+ EXPECT_STREQ(nullptr, A->getId());
+ EXPECT_STREQ(nullptr, C->getId());
+}
+
+TEST_F(ObjectSetTest, Delete) {
+ //we cannot use the same item as in other tests since it will be freed at the test destructor
+
+ EXPECT_EQ(_doc->getRoot(), r1->parent);
+ set->add(r1.get());
+ set->deleteItems();
+ r1.release();
+ EXPECT_EQ(0, set->size());
+ //EXPECT_EQ(nullptr, r1->parent);
+}
+
+TEST_F(ObjectSetTest, Ops) {
+ set->add(r1.get());
+ set->add(r2.get());
+ set->add(r3.get());
+ set->duplicate();
+ EXPECT_EQ(N + 6, _doc->getRoot()->children.size());// defs, namedview, and those 3x2 rects.
+ EXPECT_EQ(3, set->size());
+ EXPECT_FALSE(set->includes(r1.get()));
+ set->deleteItems();
+ EXPECT_TRUE(set->isEmpty());
+ set->add(r1.get());
+ set->add(r2.get());
+ set->add(r3.get());
+ set->group();//r1-3 are now invalid (grouping makes copies)
+ r1.release();
+ r2.release();
+ r3.release();
+ EXPECT_EQ(N + 1, _doc->getRoot()->children.size());
+ EXPECT_EQ(1, set->size());
+ set->ungroup();
+ EXPECT_EQ(N + 3, _doc->getRoot()->children.size());
+ EXPECT_EQ(3, set->size());
+ /* Uncomment this when toNextLayer is made desktop-independent
+ set->group();
+ set2->add(set->singleItem()->childList(false)[0]);
+ EXPECT_EQ(3, set->singleItem()->children.size());
+ EXPECT_EQ(4, _doc->getRoot()->children.size());
+ set2->popFromGroup();
+ EXPECT_EQ(2, set->singleItem()->children.size());
+ EXPECT_EQ(5, _doc->getRoot()->children.size());
+ set->ungroup();
+ set->add(set2->singleItem());
+ */
+ set->clone();
+ EXPECT_EQ(N + 6, _doc->getRoot()->children.size());
+ EXPECT_EQ(3, set->size());
+ EXPECT_NE(nullptr,dynamic_cast<SPUse*>(*(set->items().begin())));
+ EXPECT_EQ(nullptr,dynamic_cast<SPRect*>(*(set->items().begin())));
+ set->unlink();
+ EXPECT_EQ(N + 6, _doc->getRoot()->children.size());
+ EXPECT_EQ(3, set->size());
+ EXPECT_EQ(nullptr,dynamic_cast<SPUse*>(*(set->items().begin())));
+ EXPECT_NE(nullptr,dynamic_cast<SPRect*>(*(set->items().begin())));
+ set->clone(); //creates 3 clones
+ set->clone(); //creates 3 clones of clones
+ EXPECT_EQ(N + 12, _doc->getRoot()->children.size());
+ EXPECT_EQ(3, set->size());
+ EXPECT_NE(nullptr,dynamic_cast<SPUse*>( ((SPUse*)(*(set->items().begin())))->get_original()));//"original is a Use"
+ set->unlink(); //clone of clone of rect -> rect
+ EXPECT_EQ(nullptr,dynamic_cast<SPUse*>(*(set->items().begin())));
+ EXPECT_NE(nullptr,dynamic_cast<SPRect*>(*(set->items().begin())));
+ set->clone();
+ set->set(*(set->items().begin()));
+ set->cloneOriginal();//get clone original
+ EXPECT_EQ(N + 15, _doc->getRoot()->children.size());
+ EXPECT_EQ(1, set->size());
+ EXPECT_NE(nullptr,dynamic_cast<SPRect*>(*(set->items().begin())));
+ //let's stop here.
+ // TODO: write a hundred more tests to check clone (non-)displacement when grouping, ungrouping and unlinking...
+ TearDownTestCase();
+ SetUpTestCase();
+}
+
+TEST_F(ObjectSetTest, unlinkRecursiveBasic) {
+ // This is the same as the test (ObjectSetTest, Ops), but with unlinkRecursive instead of unlink.
+ set->set(r1.get());
+ set->add(r2.get());
+ set->add(r3.get());
+ EXPECT_FALSE(containsClone(set));
+ set->duplicate();
+ EXPECT_FALSE(containsClone(set));
+ EXPECT_EQ(N + 6, _doc->getRoot()->children.size());// defs, namedview, and those 3x2 rects.
+ EXPECT_EQ(3, set->size());
+ EXPECT_FALSE(set->includes(r1.get()));
+ set->deleteItems();
+ EXPECT_FALSE(containsClone(set));
+ EXPECT_TRUE(set->isEmpty());
+ set->add(r1.get());
+ set->add(r2.get());
+ set->add(r3.get());
+ EXPECT_FALSE(containsClone(set));
+ set->group();//r1-3 are now invalid (grouping makes copies)
+ r1.release();
+ r2.release();
+ r3.release();
+ EXPECT_FALSE(containsClone(set));
+ EXPECT_EQ(N + 1, _doc->getRoot()->children.size());
+ EXPECT_EQ(1, set->size());
+ set->ungroup();
+ EXPECT_FALSE(containsClone(set));
+ EXPECT_EQ(N + 3, _doc->getRoot()->children.size());
+ EXPECT_EQ(3, set->size());
+ /* Uncomment this when toNextLayer is made desktop-independent
+ set->group();
+ set2->add(set->singleItem()->childList(false)[0]);
+ EXPECT_EQ(3, set->singleItem()->children.size());
+ EXPECT_EQ(4, _doc->getRoot()->children.size());
+ set2->popFromGroup();
+ EXPECT_EQ(2, set->singleItem()->children.size());
+ EXPECT_EQ(5, _doc->getRoot()->children.size());
+ set->ungroup();
+ set->add(set2->singleItem());
+ */
+ set->clone();
+ EXPECT_TRUE(containsClone(set));
+ EXPECT_EQ(N + 6, _doc->getRoot()->children.size());
+ EXPECT_EQ(3, set->size());
+ EXPECT_NE(nullptr, dynamic_cast<SPUse*>(*(set->items().begin())));
+ EXPECT_EQ(nullptr, dynamic_cast<SPRect*>(*(set->items().begin())));
+ set->unlinkRecursive(false, true);
+ EXPECT_FALSE(containsClone(set));
+ EXPECT_EQ(N + 6, _doc->getRoot()->children.size());
+ EXPECT_EQ(3, set->size());
+ EXPECT_EQ(nullptr, dynamic_cast<SPUse*>(*(set->items().begin())));
+ EXPECT_NE(nullptr, dynamic_cast<SPRect*>(*(set->items().begin())));
+ set->clone(); //creates 3 clones
+ EXPECT_TRUE(containsClone(set));
+ set->clone(); //creates 3 clones of clones
+ EXPECT_TRUE(containsClone(set));
+ EXPECT_EQ(N + 12, _doc->getRoot()->children.size());
+ EXPECT_EQ(3, set->size());
+ EXPECT_NE(nullptr, dynamic_cast<SPUse*>( ((SPUse*)(*(set->items().begin())))->get_original()));//"original is a Use"
+ set->unlinkRecursive(false, true); //clone of clone of rect -> rect
+ EXPECT_FALSE(containsClone(set));
+ EXPECT_EQ(nullptr, dynamic_cast<SPUse*>(*(set->items().begin())));
+ EXPECT_NE(nullptr, dynamic_cast<SPRect*>(*(set->items().begin())));
+ set->clone();
+ EXPECT_TRUE(containsClone(set));
+ set->set(*(set->items().begin()));
+ set->cloneOriginal();//get clone original
+ EXPECT_EQ(N + 15, _doc->getRoot()->children.size());
+ EXPECT_EQ(1, set->size());
+ EXPECT_NE(nullptr, dynamic_cast<SPRect*>(*(set->items().begin())));
+ TearDownTestCase();
+ SetUpTestCase();
+}
+
+TEST_F(ObjectSetTest, unlinkRecursiveAdvanced) {
+ set->set(r1.get());
+ set->add(r2.get());
+ set->add(r3.get());
+ set->group();//r1-3 are now invalid (grouping makes copies)
+ r1.release();
+ r2.release();
+ r3.release();
+ EXPECT_FALSE(containsClone(set));
+ EXPECT_EQ(1, set->size());
+ SPItem* original = set->singleItem();
+ set->clone();
+ EXPECT_TRUE(containsClone(set));
+ EXPECT_EQ(1, set->size());
+ set->add(original);
+ EXPECT_TRUE(containsClone(set));
+ EXPECT_EQ(2, set->size());
+ set->group();
+ EXPECT_TRUE(containsClone(set));
+ EXPECT_EQ(1, set->size());
+ original = set->singleItem();
+ set->clone();
+ EXPECT_TRUE(containsClone(set));
+ EXPECT_EQ(1, set->size());
+ set->add(original);
+ EXPECT_TRUE(containsClone(set));
+ EXPECT_EQ(2, set->size());
+ set->group();
+ EXPECT_TRUE(containsClone(set));
+ EXPECT_EQ(1, set->size());
+ original = set->singleItem();
+ set->clone();
+ EXPECT_TRUE(containsClone(set));
+ EXPECT_EQ(1, set->size());
+ set->add(original);
+ EXPECT_TRUE(containsClone(set));
+ EXPECT_EQ(2, set->size());
+ set->unlinkRecursive(false, true);
+ EXPECT_FALSE(containsClone(set));
+ EXPECT_EQ(2, set->size());
+
+ TearDownTestCase();
+ SetUpTestCase();
+}
+
+TEST_F(ObjectSetTest, ZOrder) {
+ //sp_object_compare_position_bool == true iff "r1<r2" iff r1 is "before" r2 in the file, ie r1 is lower than r2
+ EXPECT_TRUE(sp_object_compare_position_bool(r1.get(),r2.get()));
+ EXPECT_TRUE(sp_object_compare_position_bool(r2.get(),r3.get()));
+ EXPECT_TRUE(sp_object_compare_position_bool(r1.get(),r3.get()));
+ EXPECT_FALSE(sp_object_compare_position_bool(r2.get(),r1.get()));
+ EXPECT_FALSE(sp_object_compare_position_bool(r3.get(),r1.get()));
+ EXPECT_FALSE(sp_object_compare_position_bool(r3.get(),r2.get()));
+ //1 2 3
+ set->set(r2.get());
+ set->raise();
+ //1 3 2
+ EXPECT_TRUE(sp_object_compare_position_bool(r1.get(),r3.get()));
+ EXPECT_TRUE(sp_object_compare_position_bool(r3.get(),r2.get()));//!
+ set->set(r3.get());
+ set->lower();
+ //3 1 2
+ EXPECT_TRUE(sp_object_compare_position_bool(r3.get(),r1.get()));
+ EXPECT_TRUE(sp_object_compare_position_bool(r1.get(),r2.get()));
+ set->raiseToTop();
+ //1 2 3
+ EXPECT_TRUE(sp_object_compare_position_bool(r1.get(),r2.get()));
+ EXPECT_TRUE(sp_object_compare_position_bool(r2.get(),r3.get()));
+ set->lowerToBottom();
+ //3 1 2
+ EXPECT_TRUE(sp_object_compare_position_bool(r3.get(),r1.get()));
+ EXPECT_TRUE(sp_object_compare_position_bool(r1.get(),r2.get()));
+}
+
+TEST_F(ObjectSetTest, Combine) {
+ set->add(r1.get());
+ set->add(r2.get());
+ set->combine();
+ r1.release();
+ r2.release();
+ EXPECT_EQ(1, set->size());
+ EXPECT_EQ(N + 2, _doc->getRoot()->children.size());
+ set->breakApart();
+ EXPECT_EQ(2, set->size());
+ EXPECT_EQ(N + 3, _doc->getRoot()->children.size());
+ set->deleteItems();
+ set->set(r3.get());
+ set->toCurves();
+ r3.release();
+ auto x = set->singleItem();
+ EXPECT_NE(nullptr,dynamic_cast<SPPath*>(x));
+ EXPECT_EQ(nullptr,dynamic_cast<SPRect*>(x));
+ set->deleteItems();
+}
+
+TEST_F(ObjectSetTest, Moves) {
+ set->add(r1.get());
+ set->moveRelative(15,15);
+ EXPECT_EQ(15,r1->x.value);
+ Geom::Point p(20,20);
+ Geom::Scale s(2);
+ set->setScaleRelative(p,s);
+ EXPECT_EQ(10,r1->x.value);
+ EXPECT_EQ(20,r1->width.value);
+ set->toCurves();
+ r1.release();
+ auto x = set->singleItem();
+ EXPECT_EQ(20,(*(x->documentVisualBounds()))[0].extent());
+ set->rotate90(true);
+ set->rotate90(true);
+ EXPECT_EQ(20,(*(x->documentVisualBounds()))[0].extent());
+ set->deleteItems();
+}
+
+TEST_F(ObjectSetTest, toMarker) {
+ r1->x = 12;
+ r1->y = 34;
+ r1->width = 56;
+ r1->height = 78;
+ r1->set_shape();
+ r1->updateRepr();
+
+ r2->x = 6;
+ r2->y = 7;
+ r2->width = 8;
+ r2->height = 9;
+ r2->set_shape();
+ r2->updateRepr();
+
+ r3->x = 10;
+ r3->y = 10;
+ r3->width = 10;
+ r3->height = 10;
+ r3->set_shape();
+ r3->updateRepr();
+
+ // add rects to set in different order than they appear in the document,
+ // to verify selection order independence.
+ set->set(r1.get());
+ set->add(r3.get());
+ set->add(r2.get());
+ set->toMarker();
+
+ // original items got deleted
+ r1.release();
+ r2.release();
+ r3.release();
+
+ auto markers = _doc->getObjectsByElement("marker");
+ ASSERT_EQ(markers.size(), 1);
+
+ auto marker = dynamic_cast<SPMarker *>(markers[0]);
+ ASSERT_NE(marker, nullptr);
+
+ EXPECT_FLOAT_EQ(marker->refX.computed, 31);
+ EXPECT_FLOAT_EQ(marker->refY.computed, 52.5);
+ EXPECT_FLOAT_EQ(marker->markerWidth.computed, 62);
+ EXPECT_FLOAT_EQ(marker->markerHeight.computed, 105);
+
+ auto markerchildren = marker->childList(false);
+ ASSERT_EQ(markerchildren.size(), 3);
+
+ auto *markerrect1 = dynamic_cast<SPRect *>(markerchildren[0]);
+ auto *markerrect2 = dynamic_cast<SPRect *>(markerchildren[1]);
+
+ ASSERT_NE(markerrect1, nullptr);
+ ASSERT_NE(markerrect2, nullptr);
+
+ EXPECT_FLOAT_EQ(markerrect1->x.value, 6);
+ EXPECT_FLOAT_EQ(markerrect1->y.value, 27);
+ EXPECT_FLOAT_EQ(markerrect1->width.value, 56);
+ EXPECT_FLOAT_EQ(markerrect1->height.value, 78);
+
+ EXPECT_FLOAT_EQ(markerrect2->x.value, 0);
+ EXPECT_FLOAT_EQ(markerrect2->y.value, 0);
+ EXPECT_FLOAT_EQ(markerrect2->width.value, 8);
+ EXPECT_FLOAT_EQ(markerrect2->height.value, 9);
+}
diff --git a/testfiles/src/object-style-test.cpp b/testfiles/src/object-style-test.cpp
new file mode 100644
index 0000000..ff2118b
--- /dev/null
+++ b/testfiles/src/object-style-test.cpp
@@ -0,0 +1,199 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Combination style and object testing for cascading and flags.
+ *//*
+ *
+ * Authors:
+ * Martin Owens
+ *
+ * Copyright (C) 2018 Authors
+ *
+ * Released under GNU GPL version 2 or later, read the file 'COPYING' for more information
+ */
+
+#include <gtest/gtest.h>
+#include <doc-per-case-test.h>
+
+#include <src/style.h>
+#include <src/object/sp-root.h>
+#include <src/object/sp-rect.h>
+
+using namespace Inkscape;
+using namespace Inkscape::XML;
+
+class ObjectTest: public DocPerCaseTest {
+public:
+ ObjectTest() {
+ char const *docString = "\
+<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>\
+<style>\
+rect { fill: #808080; opacity:0.5; }\
+.extra { opacity:1.0; }\
+.overload { fill: #d0d0d0 !important; stroke: #c0c0c0 !important; }\
+.font { font: italic bold 12px/30px Georgia, serif; }\
+.exsize { stroke-width: 1ex; }\
+.fosize { font-size: 15px; }\
+</style>\
+<g style='fill:blue; stroke-width:2px;font-size: 14px;'>\
+ <rect id='one' style='fill:red; stroke:green;'/>\
+ <rect id='two' style='stroke:green; stroke-width:4px;'/>\
+ <rect id='three' class='extra' style='fill: #cccccc;'/>\
+ <rect id='four' class='overload' style='fill:green;stroke:red !important;'/>\
+ <rect id='five' class='font' style='font: 15px arial, sans-serif;'/>/\
+ <rect id='six' style='stroke-width:1em;'/>\
+ <rect id='seven' class='exsize'/>\
+ <rect id='eight' class='fosize' style='stroke-width: 50%;'/>\
+</g>\
+</svg>";
+ doc.reset(SPDocument::createNewDocFromMem(docString, static_cast<int>(strlen(docString)), false));
+ doc->ensureUpToDate();
+ }
+
+ ~ObjectTest() override = default;
+
+ std::unique_ptr<SPDocument> doc;
+};
+
+/*
+ * Test basic cascade values, that they are set correctly as we'd want to see them.
+ */
+TEST_F(ObjectTest, Styles) {
+ ASSERT_TRUE(doc != nullptr);
+ ASSERT_TRUE(doc->getRoot() != nullptr);
+
+ SPRoot *root = doc->getRoot();
+ ASSERT_TRUE(root->getRepr() != nullptr);
+ ASSERT_TRUE(root->hasChildren());
+
+ SPRect *one = dynamic_cast<SPRect *>(doc->getObjectById("one"));
+ ASSERT_TRUE(one != nullptr);
+
+ // TODO: Fix when Inkscape preserves colour names (i.e. 'red')
+ EXPECT_EQ(one->style->fill.get_value(), Glib::ustring("#ff0000"));
+ EXPECT_EQ(one->style->stroke.get_value(), Glib::ustring("#008000"));
+ EXPECT_EQ(one->style->opacity.get_value(), Glib::ustring("0.5"));
+ EXPECT_EQ(one->style->stroke_width.get_value(), Glib::ustring("2px"));
+
+ SPRect *two = dynamic_cast<SPRect *>(doc->getObjectById("two"));
+ ASSERT_TRUE(two != nullptr);
+
+ EXPECT_EQ(two->style->fill.get_value(), Glib::ustring("#808080"));
+ EXPECT_EQ(two->style->stroke.get_value(), Glib::ustring("#008000"));
+ EXPECT_EQ(two->style->opacity.get_value(), Glib::ustring("0.5"));
+ EXPECT_EQ(two->style->stroke_width.get_value(), Glib::ustring("4px"));
+
+ SPRect *three = dynamic_cast<SPRect *>(doc->getObjectById("three"));
+ ASSERT_TRUE(three != nullptr);
+
+ EXPECT_EQ(three->style->fill.get_value(), Glib::ustring("#cccccc"));
+ EXPECT_EQ(three->style->stroke.get_value(), Glib::ustring(""));
+ EXPECT_EQ(three->style->opacity.get_value(), Glib::ustring("1"));
+ EXPECT_EQ(three->style->stroke_width.get_value(), Glib::ustring("2px"));
+
+ SPRect *four = dynamic_cast<SPRect *>(doc->getObjectById("four"));
+ ASSERT_TRUE(four != nullptr);
+
+ EXPECT_EQ(four->style->fill.get_value(), Glib::ustring("#d0d0d0"));
+ EXPECT_EQ(four->style->stroke.get_value(), Glib::ustring("#ff0000"));
+ EXPECT_EQ(four->style->opacity.get_value(), Glib::ustring("0.5"));
+ EXPECT_EQ(four->style->stroke_width.get_value(), Glib::ustring("2px"));
+}
+
+/*
+ * Test the origin flag for each of the values, should indicate where it came from.
+ */
+TEST_F(ObjectTest, StyleSource) {
+ ASSERT_TRUE(doc != nullptr);
+ ASSERT_TRUE(doc->getRoot() != nullptr);
+
+ SPRoot *root = doc->getRoot();
+ ASSERT_TRUE(root->getRepr() != nullptr);
+ ASSERT_TRUE(root->hasChildren());
+
+ SPRect *one = dynamic_cast<SPRect *>(doc->getObjectById("one"));
+ ASSERT_TRUE(one != nullptr);
+
+ EXPECT_EQ(one->style->fill.style_src, SPStyleSrc::STYLE_PROP);
+ EXPECT_EQ(one->style->stroke.style_src, SPStyleSrc::STYLE_PROP);
+ EXPECT_EQ(one->style->opacity.style_src, SPStyleSrc::STYLE_SHEET);
+ EXPECT_EQ(one->style->stroke_width.style_src, SPStyleSrc::STYLE_PROP);
+
+ SPRect *two = dynamic_cast<SPRect *>(doc->getObjectById("two"));
+ ASSERT_TRUE(two != nullptr);
+
+ EXPECT_EQ(two->style->fill.style_src, SPStyleSrc::STYLE_SHEET);
+ EXPECT_EQ(two->style->stroke.style_src, SPStyleSrc::STYLE_PROP);
+ EXPECT_EQ(two->style->opacity.style_src, SPStyleSrc::STYLE_SHEET);
+ EXPECT_EQ(two->style->stroke_width.style_src, SPStyleSrc::STYLE_PROP);
+
+ SPRect *three = dynamic_cast<SPRect *>(doc->getObjectById("three"));
+ ASSERT_TRUE(three != nullptr);
+
+ EXPECT_EQ(three->style->fill.style_src, SPStyleSrc::STYLE_PROP);
+ EXPECT_EQ(three->style->stroke.style_src, SPStyleSrc::STYLE_PROP);
+ EXPECT_EQ(three->style->opacity.style_src, SPStyleSrc::STYLE_SHEET);
+ EXPECT_EQ(three->style->stroke_width.style_src, SPStyleSrc::STYLE_PROP);
+
+ SPRect *four = dynamic_cast<SPRect *>(doc->getObjectById("four"));
+ ASSERT_TRUE(four != nullptr);
+
+ EXPECT_EQ(four->style->fill.style_src, SPStyleSrc::STYLE_SHEET);
+ EXPECT_EQ(four->style->stroke.style_src, SPStyleSrc::STYLE_PROP);
+ EXPECT_EQ(four->style->opacity.style_src, SPStyleSrc::STYLE_SHEET);
+ EXPECT_EQ(four->style->stroke_width.style_src, SPStyleSrc::STYLE_PROP);
+}
+
+/*
+ * Test the breaking up of the font property and recreation into separate properties.
+ */
+TEST_F(ObjectTest, StyleFont) {
+ ASSERT_TRUE(doc != nullptr);
+ ASSERT_TRUE(doc->getRoot() != nullptr);
+
+ SPRoot *root = doc->getRoot();
+ ASSERT_TRUE(root->getRepr() != nullptr);
+ ASSERT_TRUE(root->hasChildren());
+
+ SPRect *five = dynamic_cast<SPRect *>(doc->getObjectById("five"));
+ ASSERT_TRUE(five != nullptr);
+
+ // Font property is ALWAYS unset as it's converted into specific font css properties
+ EXPECT_EQ(five->style->font.get_value(), Glib::ustring(""));
+ EXPECT_EQ(five->style->font_size.get_value(), Glib::ustring("12px"));
+ EXPECT_EQ(five->style->font_weight.get_value(), Glib::ustring("bold"));
+ EXPECT_EQ(five->style->font_style.get_value(), Glib::ustring("italic"));
+ EXPECT_EQ(five->style->font_family.get_value(), Glib::ustring("arial, sans-serif"));
+}
+
+/*
+ * Test the consumption of font dependent lengths in SPILength, e.g. EM, EX and % units
+ */
+TEST_F(ObjectTest, StyleFontSizes) {
+ ASSERT_TRUE(doc != nullptr);
+ ASSERT_TRUE(doc->getRoot() != nullptr);
+
+ SPRoot *root = doc->getRoot();
+ ASSERT_TRUE(root->getRepr() != nullptr);
+ ASSERT_TRUE(root->hasChildren());
+
+ SPRect *six = dynamic_cast<SPRect *>(doc->getObjectById("six"));
+ ASSERT_TRUE(six != nullptr);
+
+ EXPECT_EQ(six->style->stroke_width.get_value(), Glib::ustring("1em"));
+ EXPECT_EQ(six->style->stroke_width.computed, 14);
+
+ SPRect *seven = dynamic_cast<SPRect *>(doc->getObjectById("seven"));
+ ASSERT_TRUE(seven != nullptr);
+
+ EXPECT_EQ(seven->style->stroke_width.get_value(), Glib::ustring("1ex"));
+ EXPECT_EQ(seven->style->stroke_width.computed, 7);
+
+ SPRect *eight = dynamic_cast<SPRect *>(doc->getObjectById("eight"));
+ ASSERT_TRUE(eight != nullptr);
+
+ EXPECT_EQ(eight->style->stroke_width.get_value(), Glib::ustring("50%"));
+
+ // stroke-width in percent is relative to viewport size, which is 300x150 in this example.
+ // 50% is 118.59 == ((300^2 + 150^2) / 2)^0.5 * 0.5
+ EXPECT_FLOAT_EQ(eight->style->stroke_width.computed, 118.58541);
+}
diff --git a/testfiles/src/object-test.cpp b/testfiles/src/object-test.cpp
new file mode 100644
index 0000000..ded67f1
--- /dev/null
+++ b/testfiles/src/object-test.cpp
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Unit tests migrated from cxxtest
+ *
+ * Authors:
+ * Adrian Boguszewski
+ *
+ * Copyright (C) 2018 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtest/gtest.h>
+#include <doc-per-case-test.h>
+#include <src/object/sp-root.h>
+#include <src/object/sp-path.h>
+
+using namespace Inkscape;
+using namespace Inkscape::XML;
+
+class ObjectTest: public DocPerCaseTest {
+public:
+ ObjectTest() {
+ // Sample document
+ // svg:svg
+ // svg:defs
+ // svg:path
+ // svg:linearGradient
+ // svg:stop
+ // svg:filter
+ // svg:feGaussianBlur (feel free to implement for other filters)
+ // svg:clipPath
+ // svg:rect
+ // svg:g
+ // svg:use
+ // svg:circle
+ // svg:ellipse
+ // svg:text
+ // svg:polygon
+ // svg:polyline
+ // svg:image
+ // svg:line
+ char const *docString = R"A(
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- just a comment -->
+ <title id="title">SVG test</title>
+ <defs>
+ <path id="P" d="M -21,-4 -5,0 -18,12 -3,4 -4,21 0,5 12,17 4,2 21,3 5,-1 17,-12 2,-4 3,-21 -1,-5 -12,-18 -4,-3z"/>
+ <linearGradient id="LG" x1="0%" y1="0%" x2="100%" y2="0%">
+ <stop offset="0%" style="stop-color:#ffff00;stop-opacity:1"/>
+ <stop offset="100%" style="stop-color:red;stop-opacity:1"/>
+ </linearGradient>
+ <clipPath id="clip" clipPathUnits="userSpaceOnUse">
+ <rect x="10" y="10" width="100" height="100"/>
+ </clipPath>
+ <filter style="color-interpolation-filters:sRGB" id="filter" x="-0.15" width="1.34" y="0" height="1">
+ <feGaussianBlur stdDeviation="4.26"/>
+ </filter>
+ </defs>
+
+ <g id="G" transform="skewX(10.5) translate(9,5)">
+ <use id="U" xlink:href="#P" opacity="0.5" fill="#1dace3" transform="rotate(4)"/>
+ <circle id="C" cx="45.5" cy="67" r="23" fill="#000"/>
+ <ellipse id="E" cx="200" cy="70" rx="85" ry="55" fill="url(#LG)"/>
+ <text id="T" fill="#fff" style="font-size:45;font-family:Verdana" x="150" y="86">TEST</text>
+ <polygon id="PG" points="60,20 100,40 100,80 60,100 20,80 20,40" clip-path="url(#clip)" filter="url(#filter)"/>
+ <polyline id="PL" points="0,40 40,40 40,80 80,80 80,120 120,120 120,160" style="fill:none;stroke:red;stroke-width:4"/>
+ <image id="I" xlink:href="data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE4MCIgd2lkdGg9IjUwMCI+PHBhdGggZD0iTTAsNDAgNDAsNDAgNDAsODAgODAsODAgODAsMTIwIDEyMCwxMjAgMTIwLDE2MCIgc3R5bGU9ImZpbGw6d2hpdGU7c3Ryb2tlOnJlZDtzdHJva2Utd2lkdGg6NCIvPjwvc3ZnPgo="/>
+ <line id="L" x1="20" y1="100" x2="100" y2="20" stroke="black" stroke-width="2"/>
+ </g>
+</svg>
+ )A";
+ doc.reset(SPDocument::createNewDocFromMem(docString, static_cast<int>(strlen(docString)), false));
+ }
+
+ ~ObjectTest() override = default;
+
+ std::unique_ptr<SPDocument> doc;
+};
+
+TEST_F(ObjectTest, Clones) {
+ ASSERT_TRUE(doc != nullptr);
+ ASSERT_TRUE(doc->getRoot() != nullptr);
+
+ SPRoot *root = doc->getRoot();
+ ASSERT_TRUE(root->getRepr() != nullptr);
+ ASSERT_TRUE(root->hasChildren());
+
+ SPPath *path = dynamic_cast<SPPath *>(doc->getObjectById("P"));
+ ASSERT_TRUE(path != nullptr);
+
+ Node *node = path->getRepr();
+ ASSERT_TRUE(node != nullptr);
+
+ Document *xml_doc = node->document();
+ ASSERT_TRUE(xml_doc != nullptr);
+
+ Node *parent = node->parent();
+ ASSERT_TRUE(parent != nullptr);
+
+ const size_t num_clones = 1000;
+ std::string href = std::string("#") + std::string(path->getId());
+ std::vector<Node *> clones(num_clones, nullptr);
+
+ // Create num_clones clones of this path and stick them in the document
+ for (size_t i = 0; i < num_clones; ++i) {
+ Node *clone = xml_doc->createElement("svg:use");
+ Inkscape::GC::release(clone);
+ clone->setAttribute("xlink:href", href);
+ parent->addChild(clone, node);
+ clones[i] = clone;
+ }
+
+ // Remove those clones
+ for (size_t i = 0; i < num_clones; ++i) {
+ parent->removeChild(clones[i]);
+ }
+}
+
+TEST_F(ObjectTest, Grouping) {
+ ASSERT_TRUE(doc != nullptr);
+ ASSERT_TRUE(doc->getRoot() != nullptr);
+
+ SPRoot *root = doc->getRoot();
+ ASSERT_TRUE(root->getRepr() != nullptr);
+ ASSERT_TRUE(root->hasChildren());
+
+ SPGroup *group = dynamic_cast<SPGroup *>(doc->getObjectById("G"));
+
+ ASSERT_TRUE(group != nullptr);
+
+ Node *node = group->getRepr();
+ ASSERT_TRUE(node != nullptr);
+
+ Document *xml_doc = node->document();
+ ASSERT_TRUE(xml_doc != nullptr);
+
+ const size_t num_elements = 1000;
+
+ Node *new_group = xml_doc->createElement("svg:g");
+ Inkscape::GC::release(new_group);
+ node->addChild(new_group, nullptr);
+
+ std::vector<Node *> elements(num_elements, nullptr);
+
+ for (size_t i = 0; i < num_elements; ++i) {
+ Node *circle = xml_doc->createElement("svg:circle");
+ Inkscape::GC::release(circle);
+ circle->setAttribute("cx", "2048");
+ circle->setAttribute("cy", "1024");
+ circle->setAttribute("r", "1.5");
+ new_group->addChild(circle, nullptr);
+ elements[i] = circle;
+ }
+
+ SPGroup *n_group = dynamic_cast<SPGroup *>(group->get_child_by_repr(new_group));
+ ASSERT_TRUE(n_group != nullptr);
+
+ std::vector<SPItem*> ch;
+ sp_item_group_ungroup(n_group, ch, false);
+
+ // Remove those elements
+ for (size_t i = 0; i < num_elements; ++i) {
+ elements[i]->parent()->removeChild(elements[i]);
+ }
+
+}
+
+TEST_F(ObjectTest, Objects) {
+ ASSERT_TRUE(doc != nullptr);
+ ASSERT_TRUE(doc->getRoot() != nullptr);
+
+ SPRoot *root = doc->getRoot();
+ ASSERT_TRUE(root->getRepr() != nullptr);
+ ASSERT_TRUE(root->hasChildren());
+
+ SPPath *path = dynamic_cast<SPPath *>(doc->getObjectById("P"));
+ ASSERT_TRUE(path != nullptr);
+
+ // Test parent behavior
+ SPObject *child = root->firstChild();
+ ASSERT_TRUE(child != nullptr);
+
+ EXPECT_EQ(root, child->parent);
+ EXPECT_EQ(doc.get(), child->document);
+ EXPECT_TRUE(root->isAncestorOf(child));
+
+ // Test list behavior
+ SPObject *next = child->getNext();
+ SPObject *prev = next;
+ EXPECT_EQ(child, next->getPrev());
+
+ prev = next;
+ next = next->getNext();
+ while (next != nullptr) {
+ // Walk the list
+ EXPECT_EQ(prev, next->getPrev());
+ prev = next;
+ next = next->getNext();
+ }
+
+ // Test hrefcount
+ EXPECT_TRUE(path->isReferenced());
+}
diff --git a/testfiles/src/path-boolop-test.cpp b/testfiles/src/path-boolop-test.cpp
new file mode 100644
index 0000000..cc71856
--- /dev/null
+++ b/testfiles/src/path-boolop-test.cpp
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <gtest/gtest.h>
+#include <src/path/path-boolop.h>
+#include <src/svg/svg.h>
+#include <2geom/svg-path-writer.h>
+
+class PathBoolopTest : public ::testing::Test
+{
+ public:
+ std::string rectangle_bigger = "M 0,0 L 0,2 L 2,2 L 2,0 z";
+ std::string rectangle_smaller = "M 0.5,0.5 L 0.5,1.5 L 1.5,1.5 L 1.5,0.5 z";
+ std::string rectangle_outside = "M 0,1.5 L 0.5,1.5 L 0.5,2.5 L 0,2.5 z";
+ std::string rectangle_outside_union = "M 0,0 L 0,1.5 L 0,2 L 0,2.5 L 0.5,2.5 L 0.5,2 L 2,2 L 2,0 L 0,0 z";
+ Geom::PathVector pvRectangleBigger;
+ Geom::PathVector pvRectangleSmaller;
+ Geom::PathVector pvRectangleOutside;
+ Geom::PathVector pvTargetUnion;
+ Geom::PathVector pvEmpty;
+ PathBoolopTest() {
+ pvRectangleBigger = sp_svg_read_pathv(rectangle_bigger.c_str());
+ pvRectangleSmaller = sp_svg_read_pathv(rectangle_smaller.c_str());
+ pvRectangleOutside = sp_svg_read_pathv(rectangle_outside.c_str());
+ pvTargetUnion = sp_svg_read_pathv(rectangle_outside_union.c_str());
+ pvEmpty = sp_svg_read_pathv("");
+ }
+ void comparePaths(Geom::PathVector result, Geom::PathVector target){
+ Geom::SVGPathWriter wr;
+ wr.feed(result);
+ std::string resultD = wr.str();
+ wr.clear();
+ wr.feed(target);
+ std::string targetD = wr.str();
+ EXPECT_EQ(resultD, targetD);
+ EXPECT_EQ(result, target);
+ }
+};
+
+TEST_F(PathBoolopTest, UnionOutside){
+ // test that the union of two objects where one is outside the other results in a new larger shape
+ Geom::PathVector pvRectangleUnion = sp_pathvector_boolop(pvRectangleBigger, pvRectangleOutside, bool_op_union, fill_oddEven, fill_oddEven);
+ comparePaths(pvRectangleUnion, pvTargetUnion);
+}
+
+TEST_F(PathBoolopTest, UnionOutsideSwap){
+ // test that the union of two objects where one is outside the other results in a new larger shape, even when the order is reversed
+ Geom::PathVector pvRectangleUnion = sp_pathvector_boolop(pvRectangleOutside, pvRectangleBigger, bool_op_union, fill_oddEven, fill_oddEven);
+ comparePaths(pvRectangleUnion, pvTargetUnion);
+}
+
+TEST_F(PathBoolopTest, UnionInside){
+ // test that the union of two objects where one is completely inside the other is the larger shape
+ Geom::PathVector pvRectangleUnion = sp_pathvector_boolop(pvRectangleBigger, pvRectangleSmaller, bool_op_union, fill_oddEven, fill_oddEven);
+ comparePaths(pvRectangleUnion, pvRectangleBigger);
+}
+
+TEST_F(PathBoolopTest, UnionInsideSwap){
+ // test that the union of two objects where one is completely inside the other is the larger shape, even when the order is swapped
+ Geom::PathVector pvRectangleUnion = sp_pathvector_boolop(pvRectangleSmaller, pvRectangleBigger, bool_op_union, fill_oddEven, fill_oddEven);
+ comparePaths(pvRectangleUnion, pvRectangleBigger);
+}
+
+TEST_F(PathBoolopTest, IntersectionInside){
+ // test that the intersection of two objects where one is completely inside the other is the smaller shape
+ Geom::PathVector pvRectangleIntersection = sp_pathvector_boolop(pvRectangleBigger, pvRectangleSmaller, bool_op_inters, fill_oddEven, fill_oddEven);
+ comparePaths(pvRectangleIntersection, pvRectangleSmaller);
+}
+
+TEST_F(PathBoolopTest, DifferenceInside){
+ // test that the difference of two objects where one is completely inside the other is an empty path
+ Geom::PathVector pvRectangleDifference = sp_pathvector_boolop(pvRectangleBigger, pvRectangleSmaller, bool_op_diff, fill_oddEven, fill_oddEven);
+ comparePaths(pvRectangleDifference, pvEmpty);
+}
+
+TEST_F(PathBoolopTest, DifferenceOutside){
+ // test that the difference of two objects where one is completely outside the other is multiple shapes
+ Geom::PathVector pvRectangleDifference = sp_pathvector_boolop(pvRectangleSmaller, pvRectangleBigger, bool_op_diff, fill_oddEven, fill_oddEven);
+ Geom::PathVector pvBothPaths = pvRectangleBigger;
+
+ for(Geom::Path _path : pvRectangleSmaller){
+ pvBothPaths.push_back(_path);
+ }
+
+ comparePaths(pvRectangleDifference, pvBothPaths);
+}
+
+// \ No newline at end of file
diff --git a/testfiles/src/rebase-hrefs-test.cpp b/testfiles/src/rebase-hrefs-test.cpp
new file mode 100644
index 0000000..e1c91fb
--- /dev/null
+++ b/testfiles/src/rebase-hrefs-test.cpp
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Test rebasing URI attributes
+ */
+/*
+ * Authors:
+ * Thomas Holder
+ *
+ * Copyright (C) 2020 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "xml/rebase-hrefs.h"
+
+#include <doc-per-case-test.h>
+#include <gtest/gtest.h>
+
+#include "object/sp-object.h"
+
+using namespace Inkscape::XML;
+
+#ifdef _WIN32
+#define BASE_DIR_DIFFERENT_ROOT "D:\\foo\\bar"
+#define BASE_DIR "C:\\foo\\bar"
+#define BASE_URL "file:///C:/foo/bar"
+#else
+#define BASE_DIR_DIFFERENT_ROOT "/different/root"
+#define BASE_DIR "/foo/bar"
+#define BASE_URL "file://" BASE_DIR
+#endif
+
+static char const *const docString = R"""(
+<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>
+
+<image id="img01" xlink:href=")""" BASE_URL R"""(/a.png" />
+<image id="img02" xlink:href=")""" BASE_URL R"""(/c/b/a.png" />
+
+<image id="img03" xlink:href="http://host/a.png" />
+<image id="img04" xlink:href="data:text/plain,xxx" />
+
+<image id="img05" xlink:href="" />
+<image id="img06" xlink:href="#fragment" />
+<image id="img07" xlink:href="?query" />
+<image id="img08" xlink:href="/absolute/path" />
+<image id="img09" xlink:href="//network/path" />
+
+<image id="img10" xlink:href="b/a.png" />
+
+<a id="a01" xlink:href=")""" BASE_URL R"""(/other.svg" />
+<a id="a02" xlink:href="http://host/other.svg"></a>
+
+</svg>
+)""";
+
+class ObjectTest : public DocPerCaseTest
+{
+public:
+ std::unique_ptr<SPDocument> doc;
+
+ ObjectTest() { doc.reset(SPDocument::createNewDocFromMem(docString, strlen(docString), false)); }
+
+ void assert_nonfile_unchanged() const
+ {
+ ASSERT_STREQ(doc->getObjectById("img03")->getAttribute("xlink:href"), "http://host/a.png");
+ ASSERT_STREQ(doc->getObjectById("img04")->getAttribute("xlink:href"), "data:text/plain,xxx");
+
+ ASSERT_STREQ(doc->getObjectById("img05")->getAttribute("xlink:href"), "");
+ ASSERT_STREQ(doc->getObjectById("img06")->getAttribute("xlink:href"), "#fragment");
+ ASSERT_STREQ(doc->getObjectById("img07")->getAttribute("xlink:href"), "?query");
+ ASSERT_STREQ(doc->getObjectById("img08")->getAttribute("xlink:href"), "/absolute/path");
+ ASSERT_STREQ(doc->getObjectById("img09")->getAttribute("xlink:href"), "//network/path");
+ }
+};
+
+TEST_F(ObjectTest, RebaseHrefs)
+{
+ rebase_hrefs(doc.get(), BASE_DIR G_DIR_SEPARATOR_S "c", false);
+ assert_nonfile_unchanged();
+ ASSERT_STREQ(doc->getObjectById("img01")->getAttribute("xlink:href"), "../a.png");
+ ASSERT_STREQ(doc->getObjectById("img02")->getAttribute("xlink:href"), "b/a.png");
+
+ // no base
+ rebase_hrefs(doc.get(), nullptr, false);
+ assert_nonfile_unchanged();
+ ASSERT_STREQ(doc->getObjectById("img01")->getAttribute("xlink:href"), BASE_URL "/a.png");
+ ASSERT_STREQ(doc->getObjectById("img02")->getAttribute("xlink:href"), BASE_URL "/c/b/a.png");
+
+ rebase_hrefs(doc.get(), BASE_DIR, false);
+ assert_nonfile_unchanged();
+ ASSERT_STREQ(doc->getObjectById("img01")->getAttribute("xlink:href"), "a.png");
+ ASSERT_STREQ(doc->getObjectById("img02")->getAttribute("xlink:href"), "c/b/a.png");
+
+ // base with different root
+ rebase_hrefs(doc.get(), BASE_DIR_DIFFERENT_ROOT, false);
+ assert_nonfile_unchanged();
+ ASSERT_STREQ(doc->getObjectById("img01")->getAttribute("xlink:href"), BASE_URL "/a.png");
+ ASSERT_STREQ(doc->getObjectById("img02")->getAttribute("xlink:href"), BASE_URL "/c/b/a.png");
+}
+
+static std::map<std::string, std::string> rebase_attrs_test_helper(SPDocument *doc, char const *id,
+ char const *old_base, char const *new_base)
+{
+ std::map<std::string, std::string> attributemap;
+ auto attributes = rebase_href_attrs(old_base, new_base, doc->getObjectById(id)->getRepr()->attributeList());
+ for (const auto &item : attributes) {
+ attributemap[g_quark_to_string(item.key)] = item.value.pointer();
+ }
+ return attributemap;
+}
+
+TEST_F(ObjectTest, RebaseHrefAttrs)
+{
+ std::map<std::string, std::string> amap;
+
+ amap = rebase_attrs_test_helper(doc.get(), "img01", BASE_DIR, BASE_DIR G_DIR_SEPARATOR_S "c");
+ ASSERT_STREQ(amap["xlink:href"].c_str(), "../a.png");
+ amap = rebase_attrs_test_helper(doc.get(), "img02", BASE_DIR, BASE_DIR G_DIR_SEPARATOR_S "c");
+ ASSERT_STREQ(amap["xlink:href"].c_str(), "b/a.png");
+ amap = rebase_attrs_test_helper(doc.get(), "img06", BASE_DIR, BASE_DIR G_DIR_SEPARATOR_S "c");
+ ASSERT_STREQ(amap["xlink:href"].c_str(), "#fragment");
+ amap = rebase_attrs_test_helper(doc.get(), "img10", BASE_DIR, BASE_DIR G_DIR_SEPARATOR_S "c");
+ ASSERT_STREQ(amap["xlink:href"].c_str(), "../b/a.png");
+
+ amap = rebase_attrs_test_helper(doc.get(), "a01", BASE_DIR, BASE_DIR G_DIR_SEPARATOR_S "c");
+ ASSERT_STREQ(amap["xlink:href"].c_str(), "../other.svg");
+ amap = rebase_attrs_test_helper(doc.get(), "a02", BASE_DIR, BASE_DIR G_DIR_SEPARATOR_S "c");
+ ASSERT_STREQ(amap["xlink:href"].c_str(), "http://host/other.svg");
+
+ amap = rebase_attrs_test_helper(doc.get(), "img01", BASE_DIR, BASE_DIR_DIFFERENT_ROOT);
+ ASSERT_STREQ(amap["xlink:href"].c_str(), BASE_URL "/a.png");
+}
+
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/sp-glyph-kerning-test.cpp b/testfiles/src/sp-glyph-kerning-test.cpp
new file mode 100644
index 0000000..2c737ee
--- /dev/null
+++ b/testfiles/src/sp-glyph-kerning-test.cpp
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * SPGlyphKerning test
+ *//*
+ *
+ * Authors:
+ * Cosmin Dancu
+ *
+ * Copyright (C) 2020 Authors
+ *
+ * Released under GNU GPL version 2 or later, read the file 'COPYING' for more information
+ */
+
+#include <gtest/gtest.h>
+#include <src/object/sp-glyph-kerning.h>
+
+TEST(SPGlyphKerningTest, EmptyGlyphNamesDoNotContainAnything) {
+ GlyphNames empty_glyph_names(nullptr);
+ ASSERT_FALSE(empty_glyph_names.contains("foo"));
+}
+
+TEST(SPGlyphKerningTest, GlyphNamesContainEachName) {
+ GlyphNames glyph_names("name1 name2");
+ ASSERT_TRUE(glyph_names.contains("name1"));
+ ASSERT_TRUE(glyph_names.contains("name2"));
+}
diff --git a/testfiles/src/sp-gradient-test.cpp b/testfiles/src/sp-gradient-test.cpp
new file mode 100644
index 0000000..a4dd115
--- /dev/null
+++ b/testfiles/src/sp-gradient-test.cpp
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Unit tests migrated from cxxtest
+ *
+ * Authors:
+ * Adrian Boguszewski
+ *
+ * Copyright (C) 2018 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtest/gtest.h>
+#include <doc-per-case-test.h>
+#include <src/object/sp-gradient.h>
+#include <src/attributes.h>
+#include <2geom/transforms.h>
+#include <src/xml/node.h>
+#include <src/xml/simple-document.h>
+#include <src/svg/svg.h>
+
+using namespace Inkscape;
+using namespace Inkscape::XML;
+
+class SPGradientTest: public DocPerCaseTest {
+public:
+ SPGradientTest() {
+ DocPerCaseTest::SetUpTestCase();
+ gr = new SPGradient();
+ }
+
+ ~SPGradientTest() override {
+ delete gr;
+ DocPerCaseTest::TearDownTestCase();
+ }
+
+ SPGradient *gr;
+};
+
+TEST_F(SPGradientTest, Init) {
+ ASSERT_TRUE(gr != nullptr);
+ EXPECT_TRUE(gr->gradientTransform.isIdentity());
+ EXPECT_TRUE(Geom::are_near(Geom::identity(), gr->gradientTransform));
+}
+
+TEST_F(SPGradientTest, SetGradientTransform) {
+ gr->document = _doc.get();
+
+ gr->setKeyValue(SPAttr::GRADIENTTRANSFORM, "translate(5, 8)");
+ EXPECT_TRUE(Geom::are_near(Geom::Affine(Geom::Translate(5.0, 8.0)), gr->gradientTransform));
+
+ gr->setKeyValue(SPAttr::GRADIENTTRANSFORM, "");
+ EXPECT_TRUE(Geom::are_near(Geom::identity(), gr->gradientTransform));
+
+ gr->setKeyValue(SPAttr::GRADIENTTRANSFORM, "rotate(90)");
+ EXPECT_TRUE(Geom::are_near(Geom::Affine(Geom::Rotate::from_degrees(90.0)), gr->gradientTransform));
+}
+
+TEST_F(SPGradientTest, Write) {
+ gr->document = _doc.get();
+
+ gr->setKeyValue(SPAttr::GRADIENTTRANSFORM, "matrix(0, 1, -1, 0, 0, 0)");
+ Document *xml_doc = _doc->getReprDoc();
+
+ ASSERT_TRUE(xml_doc != nullptr);
+
+ Node *repr = xml_doc->createElement("svg:radialGradient");
+ gr->updateRepr(xml_doc, repr, SP_OBJECT_WRITE_ALL);
+
+ gchar const *tr = repr->attribute("gradientTransform");
+ Geom::Affine svd;
+ bool const valid = sp_svg_transform_read(tr, &svd);
+
+ EXPECT_TRUE(valid);
+ EXPECT_TRUE(Geom::are_near(Geom::Affine(Geom::Rotate::from_degrees(90.0)), svd));
+}
+
+TEST_F(SPGradientTest, GetG2dGetGs2dSetGs2) {
+ gr->document = _doc.get();
+
+ Geom::Affine grXform(2, 1,
+ 1, 3,
+ 4, 6);
+ gr->gradientTransform = grXform;
+
+ Geom::Rect unit_rect(Geom::Point(0, 0), Geom::Point(1, 1));
+ {
+ Geom::Affine g2d(gr->get_g2d_matrix(Geom::identity(), unit_rect));
+ Geom::Affine gs2d(gr->get_gs2d_matrix(Geom::identity(), unit_rect));
+ EXPECT_TRUE(Geom::are_near(Geom::identity(), g2d));
+ EXPECT_TRUE(Geom::are_near(gs2d, gr->gradientTransform * g2d, 1e-12));
+
+ gr->set_gs2d_matrix(Geom::identity(), unit_rect, gs2d);
+ EXPECT_TRUE(Geom::are_near(gr->gradientTransform, grXform, 1e-12));
+ }
+
+ gr->gradientTransform = grXform;
+ Geom::Affine funny(2, 3,
+ 4, 5,
+ 6, 7);
+ {
+ Geom::Affine g2d(gr->get_g2d_matrix(funny, unit_rect));
+ Geom::Affine gs2d(gr->get_gs2d_matrix(funny, unit_rect));
+ EXPECT_TRUE(Geom::are_near(funny, g2d));
+ EXPECT_TRUE(Geom::are_near(gs2d, gr->gradientTransform * g2d, 1e-12));
+
+ gr->set_gs2d_matrix(funny, unit_rect, gs2d);
+ EXPECT_TRUE(Geom::are_near(gr->gradientTransform, grXform, 1e-12));
+ }
+
+ gr->gradientTransform = grXform;
+ Geom::Rect larger_rect(Geom::Point(5, 6), Geom::Point(8, 10));
+ {
+ Geom::Affine g2d(gr->get_g2d_matrix(funny, larger_rect));
+ Geom::Affine gs2d(gr->get_gs2d_matrix(funny, larger_rect));
+ EXPECT_TRUE(Geom::are_near(Geom::Affine(3, 0,
+ 0, 4,
+ 5, 6) * funny, g2d ));
+ EXPECT_TRUE(Geom::are_near(gs2d, gr->gradientTransform * g2d, 1e-12));
+
+ gr->set_gs2d_matrix(funny, larger_rect, gs2d);
+ EXPECT_TRUE(Geom::are_near(gr->gradientTransform, grXform, 1e-12));
+
+ gr->setKeyValue( SPAttr::GRADIENTUNITS, "userSpaceOnUse");
+ Geom::Affine user_g2d(gr->get_g2d_matrix(funny, larger_rect));
+ Geom::Affine user_gs2d(gr->get_gs2d_matrix(funny, larger_rect));
+ EXPECT_TRUE(Geom::are_near(funny, user_g2d));
+ EXPECT_TRUE(Geom::are_near(user_gs2d, gr->gradientTransform * user_g2d, 1e-12));
+ }
+}
diff --git a/testfiles/src/sp-item-group-test.cpp b/testfiles/src/sp-item-group-test.cpp
new file mode 100644
index 0000000..3439f54
--- /dev/null
+++ b/testfiles/src/sp-item-group-test.cpp
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * SPGroup test
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2020 Authors
+ *
+ * Released under GNU GPL version 2 or later, read the file 'COPYING' for more information
+ */
+
+#include <gtest/gtest.h>
+#include <src/document.h>
+#include <src/inkscape.h>
+#include <src/live_effects/effect.h>
+#include <src/object/sp-lpe-item.h>
+
+using namespace Inkscape;
+using namespace Inkscape::LivePathEffect;
+
+class SPGroupTest : public ::testing::Test {
+ protected:
+ void SetUp() override
+ {
+ // setup hidden dependency
+ Application::create(false);
+ }
+};
+
+TEST_F(SPGroupTest, applyingPowerClipEffectToGroupWithoutClipIsIgnored)
+{
+ std::string svg("\
+<svg width='100' height='100'>\
+ <g id='group1'>\
+ <rect id='rect1' width='100' height='50' />\
+ <rect id='rect2' y='50' width='100' height='50' />\
+ </g>\
+</svg>");
+
+ SPDocument *doc = SPDocument::createNewDocFromMem(svg.c_str(), svg.size(), true);
+
+ auto group = dynamic_cast<SPGroup *>(doc->getObjectById("group1"));
+ Effect::createAndApply(POWERCLIP, doc, group);
+
+ ASSERT_FALSE(group->hasPathEffect());
+}
diff --git a/testfiles/src/sp-object-test.cpp b/testfiles/src/sp-object-test.cpp
new file mode 100644
index 0000000..90a635d
--- /dev/null
+++ b/testfiles/src/sp-object-test.cpp
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Multiindex container for selection
+ *
+ * Authors:
+ * Adrian Boguszewski
+ *
+ * Copyright (C) 2016 Adrian Boguszewski
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <gtest/gtest.h>
+#include <src/object/sp-object.h>
+#include <src/object/sp-item.h>
+#include <src/xml/node.h>
+#include <src/xml/text-node.h>
+#include <doc-per-case-test.h>
+#include <src/xml/simple-document.h>
+
+using namespace Inkscape;
+using namespace Inkscape::XML;
+
+class SPObjectTest: public DocPerCaseTest {
+public:
+ SPObjectTest() {
+ auto *const _doc = this->_doc.get();
+ a = new SPItem();
+ b = new SPItem();
+ c = new SPItem();
+ d = new SPItem();
+ e = new SPItem();
+ auto sd = new SimpleDocument();
+ auto et = new TextNode(Util::share_string("e"), sd);
+ auto dt = new TextNode(Util::share_string("d"), sd);
+ auto ct = new TextNode(Util::share_string("c"), sd);
+ auto bt = new TextNode(Util::share_string("b"), sd);
+ auto at = new TextNode(Util::share_string("a"), sd);
+ e->invoke_build(_doc, et, 0);
+ d->invoke_build(_doc, dt, 0);
+ c->invoke_build(_doc, ct, 0);
+ b->invoke_build(_doc, bt, 0);
+ a->invoke_build(_doc, at, 0);
+ }
+ ~SPObjectTest() override {
+ delete e;
+ delete d;
+ delete c;
+ delete b;
+ delete a;
+ }
+ SPObject* a;
+ SPObject* b;
+ SPObject* c;
+ SPObject* d;
+ SPObject* e;
+};
+
+TEST_F(SPObjectTest, Basics) {
+ a->attach(c, a->lastChild());
+ a->attach(b, nullptr);
+ a->attach(d, c);
+ EXPECT_TRUE(a->hasChildren());
+ EXPECT_EQ(b, a->firstChild());
+ EXPECT_EQ(d, a->lastChild());
+ auto children = a->childList(false);
+ EXPECT_EQ(3, children.size());
+ EXPECT_EQ(b, children[0]);
+ EXPECT_EQ(c, children[1]);
+ EXPECT_EQ(d, children[2]);
+ a->attach(b, a->lastChild());
+ EXPECT_EQ(3, a->children.size());
+ a->reorder(b, b);
+ EXPECT_EQ(3, a->children.size());
+ EXPECT_EQ(b, &a->children.front());
+ EXPECT_EQ(d, &a->children.back());
+ a->reorder(b, d);
+ EXPECT_EQ(3, a->children.size());
+ EXPECT_EQ(c, &a->children.front());
+ EXPECT_EQ(b, &a->children.back());
+ a->reorder(d, nullptr);
+ EXPECT_EQ(3, a->children.size());
+ EXPECT_EQ(d, &a->children.front());
+ EXPECT_EQ(b, &a->children.back());
+ a->reorder(c, b);
+ EXPECT_EQ(3, a->children.size());
+ EXPECT_EQ(d, &a->children.front());
+ EXPECT_EQ(c, &a->children.back());
+ a->detach(b);
+ EXPECT_EQ(c, a->lastChild());
+ children = a->childList(false);
+ EXPECT_EQ(2, children.size());
+ EXPECT_EQ(d, children[0]);
+ EXPECT_EQ(c, children[1]);
+ a->detach(b);
+ EXPECT_EQ(2, a->childList(false).size());
+ a->releaseReferences();
+ EXPECT_FALSE(a->hasChildren());
+ EXPECT_EQ(nullptr, a->firstChild());
+ EXPECT_EQ(nullptr, a->lastChild());
+}
+
+TEST_F(SPObjectTest, Advanced) {
+ a->attach(b, a->lastChild());
+ a->attach(c, a->lastChild());
+ a->attach(d, a->lastChild());
+ a->attach(e, a->lastChild());
+ EXPECT_EQ(e, a->get_child_by_repr(e->getRepr()));
+ EXPECT_EQ(c, a->get_child_by_repr(c->getRepr()));
+ EXPECT_EQ(d, e->getPrev());
+ EXPECT_EQ(c, d->getPrev());
+ EXPECT_EQ(b, c->getPrev());
+ EXPECT_EQ(nullptr, b->getPrev());
+ EXPECT_EQ(nullptr, e->getNext());
+ EXPECT_EQ(e, d->getNext());
+ EXPECT_EQ(d, c->getNext());
+ EXPECT_EQ(c, b->getNext());
+ std::vector<SPObject*> tmp = {b, c, d, e};
+ int index = 0;
+ for(auto& child: a->children) {
+ EXPECT_EQ(tmp[index++], &child);
+ }
+}
diff --git a/testfiles/src/style-elem-test.cpp b/testfiles/src/style-elem-test.cpp
new file mode 100644
index 0000000..edf448e
--- /dev/null
+++ b/testfiles/src/style-elem-test.cpp
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Test the API to the style element, access, read and write functions.
+ *//*
+ *
+ * Authors:
+ * Martin Owens
+ *
+ * Copyright (C) 2018 Authors
+ *
+ * Released under GNU GPL version 2 or later, read the file 'COPYING' for more information
+ */
+
+#include <gtest/gtest.h>
+#include <doc-per-case-test.h>
+
+#include <src/style.h>
+#include <src/object/sp-root.h>
+#include <src/object/sp-style-elem.h>
+
+using namespace Inkscape;
+using namespace Inkscape::XML;
+
+class ObjectTest: public DocPerCaseTest {
+public:
+ ObjectTest() {
+ char const *docString = "\
+<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>\
+<style id='style01'>\
+rect { fill: red; opacity:0.5; }\
+#id1, #id2 { fill: red; stroke: #c0c0c0; }\
+.cls1 { fill: red; opacity:1.0; }\
+</style>\
+<style id='style02'>\
+rect { fill: green; opacity:1.0; }\
+#id3, #id4 { fill: green; stroke: #606060; }\
+.cls2 { fill: green; opacity:0.5; }\
+</style>\
+</svg>";
+ doc.reset(SPDocument::createNewDocFromMem(docString, static_cast<int>(strlen(docString)), false));
+ }
+
+ ~ObjectTest() override = default;
+
+ std::unique_ptr<SPDocument> doc;
+};
+
+/*
+ * Test sp-style-element objects created in document.
+ */
+TEST_F(ObjectTest, StyleElems) {
+ ASSERT_TRUE(doc != nullptr);
+ ASSERT_TRUE(doc->getRoot() != nullptr);
+
+ SPRoot *root = doc->getRoot();
+ ASSERT_TRUE(root->getRepr() != nullptr);
+
+ SPStyleElem *one = dynamic_cast<SPStyleElem *>(doc->getObjectById("style01"));
+ ASSERT_TRUE(one != nullptr);
+
+ for (auto &style : one->get_styles()) {
+ EXPECT_EQ(style->fill.get_value(), Glib::ustring("#ff0000"));
+ }
+
+ SPStyleElem *two = dynamic_cast<SPStyleElem *>(doc->getObjectById("style02"));
+ ASSERT_TRUE(one != nullptr);
+
+ for (auto &style : two->get_styles()) {
+ EXPECT_EQ(style->fill.get_value(), Glib::ustring("#008000"));
+ }
+}
diff --git a/testfiles/src/style-internal-test.cpp b/testfiles/src/style-internal-test.cpp
new file mode 100644
index 0000000..cf14e6d
--- /dev/null
+++ b/testfiles/src/style-internal-test.cpp
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Tests for Style internal classes
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2020 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <gtest/gtest.h>
+#include <src/style-internal.h>
+
+TEST(StyleInternalTest, testSPIDashArrayInequality)
+{
+ SPIDashArray array;
+ array.read("0 1 2 3");
+ SPIDashArray subsetArray;
+ subsetArray.read("0 1");
+
+ ASSERT_FALSE(array == subsetArray);
+ ASSERT_FALSE(subsetArray == array);
+}
+
+TEST(StyleInternalTest, testSPIDashArrayEquality)
+{
+ SPIDashArray anArray;
+ anArray.read("0 1 2 3");
+ SPIDashArray sameArray;
+ sameArray.read("0 1 2 3");
+
+ ASSERT_TRUE(anArray == sameArray);
+ ASSERT_TRUE(sameArray == anArray);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/style-test.cpp b/testfiles/src/style-test.cpp
new file mode 100644
index 0000000..f0f427f
--- /dev/null
+++ b/testfiles/src/style-test.cpp
@@ -0,0 +1,604 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/*
+ * Unit test for style properties.
+ *
+ * Author:
+ * Tavmjong Bah <tavjong@free.fr>
+ *
+ * Copyright (C) 2017 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+#include "style.h"
+
+namespace {
+
+class StyleRead {
+
+public:
+ StyleRead(std::string src, std::string dst, std::string uri) :
+ src(std::move(src)), dst(std::move(dst)), uri(std::move(uri))
+ {
+ }
+
+ StyleRead(std::string src, std::string dst) :
+ src(std::move(src)), dst(std::move(dst)), uri("")
+ {
+ }
+
+ StyleRead(std::string const &src) :
+ src(src), dst(src), uri("")
+ {
+ }
+
+ std::string src;
+ std::string dst;
+ std::string uri;
+
+};
+
+std::vector<StyleRead> getStyleData()
+{
+ StyleRead all_style_data[] = {
+
+ // Paint -----------------------------------------------
+ StyleRead("fill:none"), StyleRead("fill:currentColor"), StyleRead("fill:#ff00ff"),
+ StyleRead("fill:rgb(100%, 0%, 100%)", "fill:#ff00ff"), StyleRead("fill:rgb(255, 0, 255)", "fill:#ff00ff"),
+
+ // TODO - fix this to preserve the string
+ // StyleRead("fill:url(#painter) rgb(100%, 0%, 100%)",
+ // "fill:url(#painter) #ff00ff", "#painter" ),
+
+ // TODO - fix this to preserve the string
+ // StyleRead("fill:url(#painter) rgb(255, 0, 255)",
+ // "fill:url(#painter) #ff00ff", "#painter"),
+
+
+ StyleRead("fill:#ff00ff icc-color(colorChange, 0.1, 0.5, 0.1)"),
+
+ // StyleRead("fill:url(#painter)", "", "#painter"),
+ // StyleRead("fill:url(#painter) none", "", "#painter"),
+ // StyleRead("fill:url(#painter) currentColor", "", "#painter"),
+ // StyleRead("fill:url(#painter) #ff00ff", "", "#painter"),
+ // StyleRead("fill:url(#painter) rgb(100%, 0%, 100%)", "", "#painter"),
+ // StyleRead("fill:url(#painter) rgb(255, 0, 255)", "", "#painter"),
+
+ // StyleRead("fill:url(#painter) #ff00ff icc-color(colorChange, 0.1, 0.5, 0.1)", "", "#painter"),
+
+ // StyleRead("fill:url(#painter) inherit", "", "#painter"),
+
+ StyleRead("fill:inherit"),
+
+
+ // General tests (in general order of appearance in sp_style_read), SPIPaint tested above
+ StyleRead("visibility:hidden"), // SPIEnum
+ StyleRead("visibility:collapse"), StyleRead("visibility:visible"),
+ StyleRead("display:none"), // SPIEnum
+ StyleRead("overflow:visible"), // SPIEnum
+ StyleRead("overflow:auto"), // SPIEnum
+
+ StyleRead("color:#ff0000"), StyleRead("color:blue", "color:#0000ff"),
+ // StyleRead("color:currentColor"), SVG 1.1 does not allow color value 'currentColor'
+
+ // Font shorthand
+ StyleRead("font:bold 12px Arial", "font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;"
+ "font-size:12px;line-height:normal;font-family:Arial"),
+ StyleRead("font:bold 12px/24px 'Times New Roman'",
+ "font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12px;line-"
+ "height:24px;font-family:\'Times New Roman\'"),
+
+ // From CSS 3 Fonts (examples):
+ StyleRead("font: 12pt/15pt sans-serif", "font-style:normal;font-variant:normal;font-weight:normal;font-stretch:"
+ "normal;font-size:16px;line-height:15pt;font-family:sans-serif"),
+ // StyleRead("font: 80% sans-serif",
+ // "font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:80%;line-height:normal;font-family:sans-serif"),
+ // StyleRead("font: x-large/110% 'new century schoolbook', serif",
+ // "font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:x-large;line-height:110%;font-family:\'new
+ //century schoolbook\', serif"),
+ StyleRead("font: bold italic large Palatino, serif",
+ "font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:large;line-"
+ "height:normal;font-family:Palatino, serif"),
+ // StyleRead("font: normal small-caps 120%/120% fantasy",
+ // "font-style:normal;font-variant:small-caps;font-weight:normal;font-stretch:normal;font-size:120%;line-height:120%;font-family:fantasy"),
+ StyleRead("font: condensed oblique 12pt 'Helvetica Neue', serif;",
+ "font-style:oblique;font-variant:normal;font-weight:normal;font-stretch:condensed;font-size:16px;"
+ "line-height:normal;font-family:\'Helvetica Neue\', serif"),
+
+ StyleRead("font-family:sans-serif"), // SPIString, text_private
+ StyleRead("font-family:Arial"),
+ // StyleRead("font-variant:normal;font-stretch:normal;-inkscape-font-specification:Nimbus Roman No9 L Bold
+ // Italic"),
+
+ // Needs to be fixed (quotes should be around each font-family):
+ StyleRead("font-family:Georgia, 'Minion Web'", "font-family:Georgia, \'Minion Web\'"),
+ StyleRead("font-size:12", "font-size:12px"), // SPIFontSize
+ StyleRead("font-size:12px"), StyleRead("font-size:12pt", "font-size:16px"), StyleRead("font-size:medium"),
+ StyleRead("font-size:smaller"),
+ StyleRead("font-style:italic"), // SPIEnum
+ StyleRead("font-variant:small-caps"), // SPIEnum
+ StyleRead("font-weight:100"), // SPIEnum
+ StyleRead("font-weight:normal"), StyleRead("font-weight:bolder"),
+ StyleRead("font-stretch:condensed"), // SPIEnum
+
+ StyleRead("font-variant-ligatures:none"), // SPILigatures
+ StyleRead("font-variant-ligatures:normal"), StyleRead("font-variant-ligatures:no-common-ligatures"),
+ StyleRead("font-variant-ligatures:discretionary-ligatures"),
+ StyleRead("font-variant-ligatures:historical-ligatures"), StyleRead("font-variant-ligatures:no-contextual"),
+ StyleRead("font-variant-ligatures:common-ligatures", "font-variant-ligatures:normal"),
+ StyleRead("font-variant-ligatures:contextual", "font-variant-ligatures:normal"),
+ StyleRead("font-variant-ligatures:no-common-ligatures historical-ligatures"),
+ StyleRead("font-variant-ligatures:historical-ligatures no-contextual"),
+ StyleRead("font-variant-position:normal"), StyleRead("font-variant-position:sub"),
+ StyleRead("font-variant-position:super"), StyleRead("font-variant-caps:normal"),
+ StyleRead("font-variant-caps:small-caps"), StyleRead("font-variant-caps:all-small-caps"),
+ StyleRead("font-variant-numeric:normal"), StyleRead("font-variant-numeric:lining-nums"),
+ StyleRead("font-variant-numeric:oldstyle-nums"), StyleRead("font-variant-numeric:proportional-nums"),
+ StyleRead("font-variant-numeric:tabular-nums"), StyleRead("font-variant-numeric:diagonal-fractions"),
+ StyleRead("font-variant-numeric:stacked-fractions"), StyleRead("font-variant-numeric:ordinal"),
+ StyleRead("font-variant-numeric:slashed-zero"), StyleRead("font-variant-numeric:tabular-nums slashed-zero"),
+ StyleRead("font-variant-numeric:tabular-nums proportional-nums", "font-variant-numeric:proportional-nums"),
+
+ StyleRead("font-variation-settings:'wght' 400"),
+ StyleRead("font-variation-settings:'wght' 400", "font-variation-settings:'wght' 400"),
+ StyleRead("font-variation-settings:'wght' 400, 'slnt' 0.5", "font-variation-settings:'slnt' 0.5, 'wght' 400"),
+ StyleRead("font-variation-settings:\"wght\" 400", "font-variation-settings:'wght' 400"),
+
+ // Should be moved down
+ StyleRead("text-indent:12em"), // SPILength?
+ StyleRead("text-align:center"), // SPIEnum
+
+ // SPITextDecoration
+ // The default value for 'text-decoration-color' is 'currentColor', but
+ // we cannot set the default to that value yet. (We need to switch
+ // SPIPaint to SPIColor and then add the ability to set default.)
+ // StyleRead("text-decoration: underline",
+ // "text-decoration: underline;text-decoration-line: underline;text-decoration-color:currentColor"),
+ // StyleRead("text-decoration: overline underline",
+ // "text-decoration: underline overline;text-decoration-line: underline
+ // overline;text-decoration-color:currentColor"),
+
+ StyleRead("text-decoration: underline wavy #0000ff",
+ "text-decoration:underline;text-decoration-line:"
+ "underline;text-decoration-style:wavy;text-decoration-color:#0000ff"),
+ StyleRead("text-decoration: double overline underline #ff0000",
+ "text-decoration:underline overline;text-decoration-line:underline "
+ "overline;text-decoration-style:double;text-decoration-color:#ff0000"),
+
+ // SPITextDecorationLine
+ // If only "text-decoration-line" is set but not "text-decoration", don't write "text-decoration" (changed in 1.1)
+ StyleRead("text-decoration-line:underline", "text-decoration-line:underline"),
+ // "text-decoration" overwrites "text-decoration-line" and vice versa, last one counts
+ StyleRead("text-decoration-line:overline;text-decoration:underline",
+ "text-decoration:underline;text-decoration-line:underline"),
+ StyleRead("text-decoration:underline;text-decoration-line:overline",
+ "text-decoration:overline;text-decoration-line:overline"),
+
+ // SPITextDecorationStyle
+ StyleRead("text-decoration-style:solid"), StyleRead("text-decoration-style:dotted"),
+
+ // SPITextDecorationColor
+ StyleRead("text-decoration-color:#ff00ff"),
+
+ // Should be moved up
+ StyleRead("line-height:24px"), // SPILengthOrNormal
+ StyleRead("line-height:1.5"),
+ StyleRead("letter-spacing:2px"), // SPILengthOrNormal
+ StyleRead("word-spacing:2px"), // SPILengthOrNormal
+ StyleRead("word-spacing:normal"),
+ StyleRead("text-transform:lowercase"), // SPIEnum
+ // ...
+ StyleRead("baseline-shift:baseline"), // SPIBaselineShift
+ StyleRead("baseline-shift:sub"), StyleRead("baseline-shift:12.5%"), StyleRead("baseline-shift:2px"),
+
+ StyleRead("opacity:0.1"), // SPIScale24
+ // ...
+ StyleRead("stroke-width:2px"), // SPILength
+ StyleRead("stroke-linecap:round"), // SPIEnum
+ StyleRead("stroke-linejoin:round"), // SPIEnum
+ StyleRead("stroke-miterlimit:4"), // SPIFloat
+ StyleRead("marker:url(#Arrow)"), // SPIString
+ StyleRead("marker-start:url(#Arrow)"), StyleRead("marker-mid:url(#Arrow)"), StyleRead("marker-end:url(#Arrow)"),
+ StyleRead("stroke-opacity:0.5"), // SPIScale24
+ // Currently inkscape handle unit conversion in dasharray but need
+ // a active document to do it, so we can't include in any test
+ StyleRead("stroke-dasharray:0, 1, 0, 1"), // SPIDashArray
+ StyleRead("stroke-dasharray:0 1 0 1", "stroke-dasharray:0, 1, 0, 1"),
+ StyleRead("stroke-dasharray:0 1 2 3", "stroke-dasharray:0, 1, 2, 3"),
+ StyleRead("stroke-dashoffset:13"), // SPILength
+ StyleRead("stroke-dashoffset:10px"),
+ // ...
+ // StyleRead("filter:url(#myfilter)"), // SPIFilter segfault in read
+ StyleRead("filter:inherit"),
+
+ StyleRead("opacity:0.1;fill:#ff0000;stroke:#0000ff;stroke-width:2px"),
+ StyleRead("opacity:0.1;fill:#ff0000;stroke:#0000ff;stroke-width:2px;stroke-dasharray:1, 2, 3, "
+ "4;stroke-dashoffset:15"),
+
+ StyleRead("paint-order:stroke"), // SPIPaintOrder
+ StyleRead("paint-order:normal"),
+ StyleRead("paint-order: markers stroke fill", "paint-order:markers stroke fill"),
+
+ // !important (in order of appearance in style-internal.h)
+ StyleRead("stroke-miterlimit:4 !important"), // SPIFloat
+ StyleRead("stroke-opacity:0.5 !important"), // SPIScale24
+ StyleRead("stroke-width:2px !important"), // SPILength
+ StyleRead("line-height:24px !important"), // SPILengthOrNormal
+ StyleRead("line-height:normal !important"),
+ StyleRead("font-stretch:condensed !important"), // SPIEnum
+ StyleRead("marker:url(#Arrow) !important"), // SPIString
+ StyleRead("color:#0000ff !important"), // SPIColor
+ StyleRead("fill:none !important"), // SPIPaint
+ StyleRead("fill:currentColor !important"), StyleRead("fill:#ff00ff !important"),
+ StyleRead("paint-order:stroke !important"), // SPIPaintOrder
+ StyleRead("paint-order:normal !important"),
+ StyleRead("stroke-dasharray:0, 1, 0, 1 !important"), // SPIDashArray
+ StyleRead("font-size:12px !important"), // SPIFontSize
+ StyleRead("baseline-shift:baseline !important"), // SPIBaselineShift
+ StyleRead("baseline-shift:sub !important"),
+ // StyleRead("text-decoration-line: underline !important"), // SPITextDecorationLine
+
+ };
+
+ size_t count = sizeof(all_style_data) / sizeof(all_style_data[0]);
+ std::vector<StyleRead> vect(all_style_data, all_style_data + count);
+ return vect;
+}
+
+TEST(StyleTest, Read) {
+ std::vector<StyleRead> all_style = getStyleData();
+ EXPECT_GT(all_style.size(), 0);
+ for (auto i : all_style) {
+
+ SPStyle style;
+ style.mergeString (i.src.c_str());
+
+ if (!i.uri.empty()) {
+ //EXPECT_EQ (style.fill.value.href->getURI()->toString(), i.uri);
+ }
+
+ std::string out = style.write();
+ if (i.dst.empty()) {
+ // std::cout << "out: " << out << std::endl;
+ // std::cout << "i.src: " << i.src << std::endl;
+ EXPECT_EQ (out, i.src);
+ } else {
+ // std::cout << "out: " << out << std::endl;
+ // std::cout << "i.dst: " << i.dst << std::endl;
+ EXPECT_EQ (out, i.dst);
+ }
+ }
+}
+
+
+// ------------------------------------------------------------------------------------
+
+class StyleMatch {
+
+public:
+ StyleMatch(std::string src, std::string dst, bool const &match) :
+ src(std::move(src)), dst(std::move(dst)), match(match)
+ {
+ }
+
+ std::string src;
+ std::string dst;
+ bool match;
+
+};
+
+std::vector<StyleMatch> getStyleMatchData()
+{
+ StyleMatch all_style_data[] = {
+
+ // SPIFloat
+ StyleMatch("stroke-miterlimit:4", "stroke-miterlimit:4", true ),
+ StyleMatch("stroke-miterlimit:4", "stroke-miterlimit:2", false),
+ StyleMatch("stroke-miterlimit:4", "", true ), // Default
+
+ // SPIScale24
+ StyleMatch("opacity:0.3", "opacity:0.3", true ),
+ StyleMatch("opacity:0.3", "opacity:0.6", false),
+ StyleMatch("opacity:1.0", "", true ), // Default
+
+ // SPILength
+ StyleMatch("text-indent:3", "text-indent:3", true ),
+ StyleMatch("text-indent:6", "text-indent:3", false),
+ StyleMatch("text-indent:6px", "text-indent:3", false),
+ StyleMatch("text-indent:1px", "text-indent:12pc", false),
+ StyleMatch("text-indent:2ex", "text-indent:2ex", false),
+
+ // SPILengthOrNormal
+ StyleMatch("letter-spacing:normal", "letter-spacing:normal", true ),
+ StyleMatch("letter-spacing:2", "letter-spacing:normal", false),
+ StyleMatch("letter-spacing:normal", "letter-spacing:2", false),
+ StyleMatch("letter-spacing:5px", "letter-spacing:5px", true ),
+ StyleMatch("letter-spacing:10px", "letter-spacing:5px", false),
+ StyleMatch("letter-spacing:10em", "letter-spacing:10em", false),
+
+ // SPIEnum
+ StyleMatch("text-anchor:start", "text-anchor:start", true ),
+ StyleMatch("text-anchor:start", "text-anchor:middle", false),
+ StyleMatch("text-anchor:start", "", true ), // Default
+ StyleMatch("text-anchor:start", "text-anchor:junk", true ), // Bad value
+
+ StyleMatch("font-weight:normal", "font-weight:400", true ),
+ StyleMatch("font-weight:bold", "font-weight:700", true ),
+
+
+ // SPIString and SPIFontString
+ StyleMatch("font-family:Arial", "font-family:Arial", true ),
+ StyleMatch("font-family:A B", "font-family:A B", true ),
+ StyleMatch("font-family:A B", "font-family:A C", false),
+ // Default is not set by class... value is NULL which cannot be compared
+ // StyleMatch("font-family:sans-serif", "", true ), // Default
+
+ // SPIColor
+ StyleMatch("color:blue", "color:blue", true ),
+ StyleMatch("color:blue", "color:red", false),
+ StyleMatch("color:red", "color:#ff0000", true ),
+
+ // SPIPaint
+ StyleMatch("fill:blue", "fill:blue", true ),
+ StyleMatch("fill:blue", "fill:red", false),
+ StyleMatch("fill:currentColor", "fill:currentColor", true ),
+ StyleMatch("fill:url(#xxx)", "fill:url(#xxx)", true ),
+ // Needs URL defined as in test 1
+ //StyleMatch("fill:url(#xxx)", "fill:url(#yyy)", false),
+
+ // SPIPaintOrder
+ StyleMatch("paint-order:markers", "paint-order:markers", true ),
+ StyleMatch("paint-order:markers", "paint-order:stroke", false),
+ //StyleMatch("paint-order:fill stroke markers", "", true ), // Default
+ StyleMatch("paint-order:normal", "paint-order:normal", true ),
+ //StyleMatch("paint-order:fill stroke markers", "paint-order:normal", true ),
+
+ // SPIDashArray
+ StyleMatch("stroke-dasharray:0 1 2 3","stroke-dasharray:0 1 2 3",true ),
+ StyleMatch("stroke-dasharray:0 1", "stroke-dasharray:0 2", false),
+
+ // SPIFilter
+
+ // SPIFontSize
+ StyleMatch("font-size:12px", "font-size:12px", true ),
+ StyleMatch("font-size:12px", "font-size:24px", false),
+ StyleMatch("font-size:12ex", "font-size:24ex", false),
+ StyleMatch("font-size:medium", "font-size:medium", true ),
+ StyleMatch("font-size:medium", "font-size:large", false),
+
+ // SPIBaselineShift
+ StyleMatch("baseline-shift:baseline", "baseline-shift:baseline", true ),
+ StyleMatch("baseline-shift:sub", "baseline-shift:sub", true ),
+ StyleMatch("baseline-shift:sub", "baseline-shift:super", false),
+ StyleMatch("baseline-shift:baseline", "baseline-shift:sub", false),
+ StyleMatch("baseline-shift:10px", "baseline-shift:10px", true ),
+ StyleMatch("baseline-shift:10px", "baseline-shift:12px", false),
+
+
+ // SPITextDecorationLine
+ StyleMatch("text-decoration-line:underline", "text-decoration-line:underline", true ),
+ StyleMatch("text-decoration-line:underline", "text-decoration-line:overline", false),
+ StyleMatch("text-decoration-line:underline overline", "text-decoration-line:underline overline", true ),
+ StyleMatch("text-decoration-line:none", "", true ), // Default
+
+
+ // SPITextDecorationStyle
+ StyleMatch("text-decoration-style:solid", "text-decoration-style:solid", true ),
+ StyleMatch("text-decoration-style:dotted", "text-decoration-style:solid", false),
+ StyleMatch("text-decoration-style:solid", "", true ), // Default
+
+ // SPITextDecoration
+ StyleMatch("text-decoration:underline", "text-decoration:underline", true ),
+ StyleMatch("text-decoration:underline", "text-decoration:overline", false),
+ StyleMatch("text-decoration:underline overline","text-decoration:underline overline",true ),
+ StyleMatch("text-decoration:overline underline","text-decoration:underline overline",true ),
+ // StyleMatch("text-decoration:none", "text-decoration-color:currentColor", true ), // Default
+
+ };
+
+ size_t count = sizeof(all_style_data) / sizeof(all_style_data[0]);
+ std::vector<StyleMatch> vect(all_style_data, all_style_data + count);
+ return vect;
+}
+
+TEST(StyleTest, Match) {
+ std::vector<StyleMatch> all_style = getStyleMatchData();
+ EXPECT_GT(all_style.size(), 0);
+ for (auto i : all_style) {
+
+ SPStyle style_src;
+ SPStyle style_dst;
+
+ style_src.mergeString( i.src.c_str() );
+ style_dst.mergeString( i.dst.c_str() );
+
+ // std::cout << "Test:" << std::endl;
+ // std::cout << " C: |" << i.src
+ // << "| |" << i.dst << "|" << std::endl;
+ // std::cout << " S: |" << style_src.write( SP_STYLE_FLAG_IFSET )
+ // << "| |" << style_dst.write( SP_STYLE_FLAG_IFSET ) << "|" <<std::endl;
+
+ EXPECT_TRUE( (style_src == style_dst) == i.match );
+ }
+}
+
+// ------------------------------------------------------------------------------------
+
+class StyleCascade {
+
+public:
+ StyleCascade(std::string parent, std::string child, std::string result, char const *d = nullptr) :
+ parent(std::move(parent)), child(std::move(child)), result(std::move(result))
+ {
+ diff = d ? d : (this->result == this->parent ? "" : this->result);
+ }
+
+ std::string parent;
+ std::string child;
+ std::string result;
+ std::string diff;
+
+};
+
+std::vector<StyleCascade> getStyleCascadeData()
+{
+
+ StyleCascade all_style_data[] = {
+
+ // SPIFloat
+ StyleCascade("stroke-miterlimit:6", "stroke-miterlimit:2", "stroke-miterlimit:2" ),
+ StyleCascade("stroke-miterlimit:6", "", "stroke-miterlimit:6" ),
+ StyleCascade("", "stroke-miterlimit:2", "stroke-miterlimit:2" ),
+
+ // SPIScale24
+ StyleCascade("opacity:0.3", "opacity:0.3", "opacity:0.3", "opacity:0.3" ),
+ StyleCascade("opacity:0.3", "opacity:0.6", "opacity:0.6" ),
+ // 'opacity' does not inherit
+ StyleCascade("opacity:0.3", "", "opacity:1" ),
+ StyleCascade("", "opacity:0.3", "opacity:0.3" ),
+ StyleCascade("opacity:0.5", "opacity:inherit", "opacity:0.5", "opacity:0.5" ),
+ StyleCascade("", "", "opacity:1" ),
+
+ // SPILength
+ StyleCascade("text-indent:3", "text-indent:3", "text-indent:3" ),
+ StyleCascade("text-indent:6", "text-indent:3", "text-indent:3" ),
+ StyleCascade("text-indent:6px", "text-indent:3", "text-indent:3" ),
+ StyleCascade("text-indent:1px", "text-indent:12pc", "text-indent:12pc" ),
+ // ex, em cannot be equal
+ //StyleCascade("text-indent:2ex", "text-indent:2ex", "text-indent:2ex" ),
+ StyleCascade("text-indent:3", "", "text-indent:3" ),
+ StyleCascade("text-indent:3", "text-indent:inherit", "text-indent:3" ),
+
+ // SPILengthOrNormal
+ StyleCascade("letter-spacing:normal", "letter-spacing:normal", "letter-spacing:normal" ),
+ StyleCascade("letter-spacing:2", "letter-spacing:normal", "letter-spacing:normal" ),
+ StyleCascade("letter-spacing:normal", "letter-spacing:2", "letter-spacing:2" ),
+ StyleCascade("letter-spacing:5px", "letter-spacing:5px", "letter-spacing:5px" ),
+ StyleCascade("letter-spacing:10px", "letter-spacing:5px", "letter-spacing:5px" ),
+ // ex, em cannot be equal
+ // StyleCascade("letter-spacing:10em", "letter-spacing:10em", "letter-spacing:10em" ),
+
+ // SPIEnum
+ StyleCascade("text-anchor:start", "text-anchor:start", "text-anchor:start" ),
+ StyleCascade("text-anchor:start", "text-anchor:middle", "text-anchor:middle" ),
+ StyleCascade("text-anchor:start", "", "text-anchor:start" ),
+ StyleCascade("text-anchor:start", "text-anchor:junk", "text-anchor:start" ),
+ StyleCascade("text-anchor:end", "text-anchor:inherit", "text-anchor:end" ),
+
+ StyleCascade("font-weight:400", "font-weight:400", "font-weight:400" ),
+ StyleCascade("font-weight:400", "font-weight:700", "font-weight:700" ),
+ StyleCascade("font-weight:400", "font-weight:bolder", "font-weight:700" ),
+ StyleCascade("font-weight:700", "font-weight:bolder", "font-weight:900" ),
+ StyleCascade("font-weight:400", "font-weight:lighter", "font-weight:100" ),
+ StyleCascade("font-weight:200", "font-weight:lighter", "font-weight:100" ),
+
+ StyleCascade("font-stretch:condensed","font-stretch:expanded", "font-stretch:expanded" ),
+ StyleCascade("font-stretch:condensed","font-stretch:wider", "font-stretch:semi-condensed" ),
+
+ // SPIString and SPIFontString
+
+ StyleCascade("font-variation-settings:'wght' 400", "", "font-variation-settings:'wght' 400"),
+ StyleCascade("font-variation-settings:'wght' 100",
+ "font-variation-settings:'wght' 400",
+ "font-variation-settings:'wght' 400"),
+
+ StyleCascade("font-variant-ligatures:no-common-ligatures", "", "font-variant-ligatures:no-common-ligatures"),
+ StyleCascade("font-variant-ligatures:no-common-ligatures", "inherit", "font-variant-ligatures:no-common-ligatures"),
+ StyleCascade("font-variant-ligatures:normal", "font-variant-ligatures:no-common-ligatures", "font-variant-ligatures:no-common-ligatures"),
+ StyleCascade("", "font-variant-ligatures:no-common-ligatures", "font-variant-ligatures:no-common-ligatures"),
+
+ // SPIPaint
+
+ // SPIPaintOrder
+
+ // SPIDashArray
+
+ // SPIFilter
+
+ // SPIFontSize
+
+ // SPIBaselineShift
+
+
+ // SPITextDecorationLine
+ StyleCascade("text-decoration-line:overline", "text-decoration-line:underline",
+ "text-decoration-line:underline" ),
+ StyleCascade("text-decoration:overline",
+ "text-decoration:underline",
+ "text-decoration:underline;text-decoration-line:underline"),
+ StyleCascade("text-decoration:underline",
+ "text-decoration:underline",
+ "text-decoration:underline;text-decoration-line:underline",
+ ""),
+ StyleCascade("text-decoration:overline;text-decoration-line:underline",
+ "text-decoration:overline",
+ "text-decoration:overline;text-decoration-line:overline"),
+ StyleCascade("text-decoration:overline;text-decoration-line:underline",
+ "text-decoration:underline",
+ "text-decoration:underline;text-decoration-line:underline",
+ ""),
+
+ // SPITextDecorationStyle
+
+ // SPITextDecoration
+ };
+
+ size_t count = sizeof(all_style_data) / sizeof(all_style_data[0]);
+ std::vector<StyleCascade> vect(all_style_data, all_style_data + count);
+ return vect;
+
+}
+
+TEST(StyleTest, Cascade) {
+ std::vector<StyleCascade> all_style = getStyleCascadeData();
+ EXPECT_GT(all_style.size(), 0);
+ for (auto i : all_style) {
+
+ SPStyle style_parent;
+ SPStyle style_child;
+ SPStyle style_result;
+
+ style_parent.mergeString( i.parent.c_str() );
+ style_child.mergeString( i.child.c_str() );
+ style_result.mergeString( i.result.c_str() );
+
+ // std::cout << "Test:" << std::endl;
+ // std::cout << " Input: ";
+ // std::cout << " Parent: " << i.parent
+ // << " Child: " << i.child
+ // << " Result: " << i.result << std::endl;
+ // std::cout << " Write: ";
+ // std::cout << " Parent: " << style_parent.write( SP_STYLE_FLAG_IFSET )
+ // << " Child: " << style_child.write( SP_STYLE_FLAG_IFSET )
+ // << " Result: " << style_result.write( SP_STYLE_FLAG_IFSET ) << std::endl;
+
+ style_child.cascade( &style_parent );
+
+ EXPECT_TRUE(style_child == style_result );
+
+ // if diff
+ EXPECT_STREQ(style_result.writeIfDiff(nullptr).c_str(), i.result.c_str());
+ EXPECT_STREQ(style_result.writeIfDiff(&style_parent).c_str(), i.diff.c_str());
+ }
+}
+
+
+} // namespace
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/testfiles/src/svg-affine-test.cpp b/testfiles/src/svg-affine-test.cpp
new file mode 100644
index 0000000..300242d
--- /dev/null
+++ b/testfiles/src/svg-affine-test.cpp
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Test for SVG colors
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2010 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <cstdlib>
+#include <math.h>
+#include <gtest/gtest.h>
+#include <glib.h>
+
+#include "svg/svg.h"
+#include <2geom/affine.h>
+
+
+struct test_t
+{
+ char const *str;
+ Geom::Affine matrix;
+};
+
+static double const DEGREE = M_PI / 180.;
+
+test_t const read_matrix_tests[5] = {{"matrix(0,0,0,0,0,0)", Geom::Affine(0, 0, 0, 0, 0, 0)},
+ {" matrix(1,2,3,4,5,6)", Geom::Affine(1, 2, 3, 4, 5, 6)},
+ {"matrix (1 2 -3,-4,5e6,-6e-7)", Geom::Affine(1, 2, -3, -4, 5e6, -6e-7)},
+ {"matrix(1,2,3,4,5e6-3)", Geom::Affine(1, 2, 3, 4, 5e6, -3)},
+ {"matrix(1,2,3,4,5e6.3)", Geom::Affine(1, 2, 3, 4, 5e6, 0.3)}};
+test_t const read_translate_tests[3] = {{"translate(1)", Geom::Affine(1, 0, 0, 1, 1, 0)},
+ {"translate(1,1)", Geom::Affine(1, 0, 0, 1, 1, 1)},
+ {"translate(-1e3 .123e2)", Geom::Affine(1, 0, 0, 1, -1e3, .123e2)}};
+test_t const read_scale_tests[3] = {{"scale(2)", Geom::Affine(2, 0, 0, 2, 0, 0)},
+ {"scale(2,3)", Geom::Affine(2, 0, 0, 3, 0, 0)},
+ {"scale(0.1e-2 -.475e0)", Geom::Affine(0.1e-2, 0, 0, -.475e0, 0, 0)}};
+test_t const read_rotate_tests[4] = {
+ {"rotate(13 )", Geom::Affine(cos(13. * DEGREE), sin(13. * DEGREE), -sin(13. * DEGREE), cos(13. * DEGREE), 0, 0)},
+ {"rotate(-13)",
+ Geom::Affine(cos(-13. * DEGREE), sin(-13. * DEGREE), -sin(-13. * DEGREE), cos(-13. * DEGREE), 0, 0)},
+ {"rotate(373)", Geom::Affine(cos(13. * DEGREE), sin(13. * DEGREE), -sin(13. * DEGREE), cos(13. * DEGREE), 0, 0)},
+ {"rotate(13,7,11)", Geom::Affine(cos(13. * DEGREE), sin(13. * DEGREE), -sin(13. * DEGREE), cos(13. * DEGREE),
+ (1 - cos(13. * DEGREE)) * 7 + sin(13. * DEGREE) * 11,
+ (1 - cos(13. * DEGREE)) * 11 - sin(13. * DEGREE) * 7)}};
+test_t const read_skew_tests[3] = {{"skewX( 30)", Geom::Affine(1, 0, tan(30. * DEGREE), 1, 0, 0)},
+ {"skewX(-30)", Geom::Affine(1, 0, tan(-30. * DEGREE), 1, 0, 0)},
+ {"skewY(390)", Geom::Affine(1, tan(30. * DEGREE), 0, 1, 0, 0)}};
+char const *const read_fail_tests[25] = {
+ "matrix((1,2,3,4,5,6)",
+ "matrix((1,2,3,4,5,6))",
+ "matrix(1,2,3,4,5,6))",
+ "matrix(,1,2,3,4,5,6)",
+ "matrix(1,2,3,4,5,6,)",
+ "matrix(1,2,3,4,5,)",
+ "matrix(1,2,3,4,5)",
+ "translate()",
+ "translate(,)",
+ "translate(1,)",
+ "translate(1,6,)",
+ "translate(1,6,0)",
+ "scale()",
+ "scale(1,6,2)",
+ "rotate()",
+ "rotate(1,6)",
+ "rotate(1,6,)",
+ "rotate(1,6,3,4)",
+ "skewX()",
+ "skewX(-)",
+ "skewX(.)",
+ "skewY(,)",
+ "skewY(1,2)"};
+test_t const write_matrix_tests[2] = {
+ {"matrix(1,2,3,4,5,6)", Geom::Affine(1, 2, 3, 4, 5, 6)},
+ {"matrix(-1,2123,3,0.4,1e-8,1e20)", Geom::Affine(-1, 2.123e3, 3 + 1e-14, 0.4, 1e-8, 1e20)}};
+test_t const write_translate_tests[3] = {{"translate(1,1)", Geom::Affine(1, 0, 0, 1, 1, 1)},
+ {"translate(1)", Geom::Affine(1, 0, 0, 1, 1, 0)},
+ {"translate(-1345,0.123)", Geom::Affine(1, 0, 0, 1, -1.345e3, .123)}};
+test_t const write_scale_tests[3] = {{"scale(0)", Geom::Affine(0, 0, 0, 0, 0, 0)},
+ {"scale(7)", Geom::Affine(7, 0, 0, 7, 0, 0)},
+ {"scale(2,3)", Geom::Affine(2, 0, 0, 3, 0, 0)}};
+test_t const write_rotate_tests[3] = {
+ {"rotate(13)", Geom::Affine(cos(13. * DEGREE), sin(13. * DEGREE), -sin(13. * DEGREE), cos(13. * DEGREE), 0, 0)},
+ {"rotate(-13,7,11)", Geom::Affine(cos(-13. * DEGREE), sin(-13. * DEGREE), -sin(-13. * DEGREE), cos(-13. * DEGREE),
+ (1 - cos(-13. * DEGREE)) * 7 + sin(-13. * DEGREE) * 11,
+ (1 - cos(-13. * DEGREE)) * 11 - sin(-13. * DEGREE) * 7)},
+ {"rotate(-34.5,6.7,89)",
+ Geom::Affine(cos(-34.5 * DEGREE), sin(-34.5 * DEGREE), -sin(-34.5 * DEGREE), cos(-34.5 * DEGREE),
+ (1 - cos(-34.5 * DEGREE)) * 6.7 + sin(-34.5 * DEGREE) * 89,
+ (1 - cos(-34.5 * DEGREE)) * 89 - sin(-34.5 * DEGREE) * 6.7)}};
+test_t const write_skew_tests[3] = {{"skewX(30)", Geom::Affine(1, 0, tan(30. * DEGREE), 1, 0, 0)},
+ {"skewX(-30)", Geom::Affine(1, 0, tan(-30. * DEGREE), 1, 0, 0)},
+ {"skewY(30)", Geom::Affine(1, tan(30. * DEGREE), 0, 1, 0, 0)}};
+
+bool approx_equal_pred(Geom::Affine const &ref, Geom::Affine const &cm)
+{
+ double maxabsdiff = 0;
+ for (size_t i = 0; i < 6; i++) {
+ maxabsdiff = std::max(std::abs(ref[i] - cm[i]), maxabsdiff);
+ }
+ return maxabsdiff < 1e-14;
+}
+
+TEST(SvgAffineTest, testReadIdentity)
+{
+ char const *strs[] = {// 0,
+ " ", "", "matrix(1,0,0,1,0,0)", "translate(0,0)", "scale(1,1)", "rotate(0,0,0)", "skewX(0)",
+ "skewY(0)"};
+ size_t n = G_N_ELEMENTS(strs);
+ for (size_t i = 0; i < n; i++) {
+ Geom::Affine cm;
+ EXPECT_TRUE(sp_svg_transform_read(strs[i], &cm)) << i;
+ ASSERT_EQ(Geom::identity(), cm) << strs[i];
+ }
+}
+
+TEST(SvgAffineTest, testWriteIdentity)
+{
+ auto str = sp_svg_transform_write(Geom::identity());
+ ASSERT_TRUE(str == "");
+}
+
+TEST(SvgAffineTest, testReadMatrix)
+{
+ for (size_t i = 0; i < G_N_ELEMENTS(read_matrix_tests); i++) {
+ Geom::Affine cm;
+ ASSERT_TRUE(sp_svg_transform_read(read_matrix_tests[i].str, &cm)) << read_matrix_tests[i].str;
+ ASSERT_TRUE(approx_equal_pred(read_matrix_tests[i].matrix, cm)) << read_matrix_tests[i].str;
+ }
+}
+
+TEST(SvgAffineTest, testReadTranslate)
+{
+ for (size_t i = 0; i < G_N_ELEMENTS(read_translate_tests); i++) {
+ Geom::Affine cm;
+ ASSERT_TRUE(sp_svg_transform_read(read_translate_tests[i].str, &cm)) << read_translate_tests[i].str;
+ ASSERT_TRUE(approx_equal_pred(read_translate_tests[i].matrix, cm)) << read_translate_tests[i].str;
+ }
+}
+
+TEST(SvgAffineTest, testReadScale)
+{
+ for (size_t i = 0; i < G_N_ELEMENTS(read_scale_tests); i++) {
+ Geom::Affine cm;
+ ASSERT_TRUE(sp_svg_transform_read(read_scale_tests[i].str, &cm)) << read_scale_tests[i].str;
+ ASSERT_TRUE(approx_equal_pred(read_scale_tests[i].matrix, cm)) << read_scale_tests[i].str;
+ }
+}
+
+TEST(SvgAffineTest, testReadRotate)
+{
+ for (size_t i = 0; i < G_N_ELEMENTS(read_rotate_tests); i++) {
+ Geom::Affine cm;
+ ASSERT_TRUE(sp_svg_transform_read(read_rotate_tests[i].str, &cm)) << read_rotate_tests[i].str;
+ ASSERT_TRUE(approx_equal_pred(read_rotate_tests[i].matrix, cm)) << read_rotate_tests[i].str;
+ }
+}
+
+TEST(SvgAffineTest, testReadSkew)
+{
+ for (size_t i = 0; i < G_N_ELEMENTS(read_skew_tests); i++) {
+ Geom::Affine cm;
+ ASSERT_TRUE(sp_svg_transform_read(read_skew_tests[i].str, &cm)) << read_skew_tests[i].str;
+ ASSERT_TRUE(approx_equal_pred(read_skew_tests[i].matrix, cm)) << read_skew_tests[i].str;
+ }
+}
+
+TEST(SvgAffineTest, testWriteMatrix)
+{
+ for (size_t i = 0; i < G_N_ELEMENTS(write_matrix_tests); i++) {
+ auto str = sp_svg_transform_write(write_matrix_tests[i].matrix);
+ ASSERT_TRUE(!strcmp(str.c_str(), write_matrix_tests[i].str));
+ }
+}
+
+TEST(SvgAffineTest, testWriteTranslate)
+{
+ for (size_t i = 0; i < G_N_ELEMENTS(write_translate_tests); i++) {
+ auto str = sp_svg_transform_write(write_translate_tests[i].matrix);
+ ASSERT_TRUE(!strcmp(str.c_str(), write_translate_tests[i].str));
+ }
+}
+
+TEST(SvgAffineTest, testWriteScale)
+{
+ for (size_t i = 0; i < G_N_ELEMENTS(write_scale_tests); i++) {
+ auto str = sp_svg_transform_write(write_scale_tests[i].matrix);
+ ASSERT_TRUE(!strcmp(str.c_str(), write_scale_tests[i].str));
+ }
+}
+
+TEST(SvgAffineTest, testWriteRotate)
+{
+ for (size_t i = 0; i < G_N_ELEMENTS(write_rotate_tests); i++) {
+ auto str = sp_svg_transform_write(write_rotate_tests[i].matrix);
+ ASSERT_TRUE(!strcmp(str.c_str(), write_rotate_tests[i].str));
+ }
+}
+
+TEST(SvgAffineTest, testWriteSkew)
+{
+ for (size_t i = 0; i < G_N_ELEMENTS(write_skew_tests); i++) {
+ auto str = sp_svg_transform_write(write_skew_tests[i].matrix);
+ ASSERT_TRUE(!strcmp(str.c_str(), write_skew_tests[i].str));
+ }
+}
+
+TEST(SvgAffineTest, testReadConcatenation)
+{
+ char const *str = "skewY(17)skewX(9)translate(7,13)scale(2)rotate(13)translate(3,5)";
+ Geom::Affine ref(2.0199976232558053, 1.0674773585906016, -0.14125199392774669, 1.9055550612095459,
+ 14.412730624347654, 28.499820929377454); // Precomputed using Mathematica
+ Geom::Affine cm;
+ ASSERT_TRUE(sp_svg_transform_read(str, &cm));
+ ASSERT_TRUE(approx_equal_pred(ref, cm));
+}
+
+TEST(SvgAffineTest, testReadFailures)
+{
+ for (size_t i = 0; i < G_N_ELEMENTS(read_fail_tests); i++) {
+ Geom::Affine cm;
+ EXPECT_FALSE(sp_svg_transform_read(read_fail_tests[i], &cm)) << read_fail_tests[i];
+ }
+}
+
+// vim: filetype=cpp:expandtab:shiftwidth=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/svg-color-test.cpp b/testfiles/src/svg-color-test.cpp
new file mode 100644
index 0000000..c4e9379
--- /dev/null
+++ b/testfiles/src/svg-color-test.cpp
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Test for SVG colors
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2010 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include "svg/svg-color.h"
+
+#include <cstdlib>
+#include <gtest/gtest.h>
+
+#include "preferences.h"
+#include "svg/svg-icc-color.h"
+
+static void check_rgb24(unsigned const rgb24)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ char css[8];
+
+ prefs->setBool("/options/svgoutput/usenamedcolors", false);
+ sp_svg_write_color(css, sizeof(css), rgb24 << 8);
+ ASSERT_EQ(sp_svg_read_color(css, 0xff), rgb24 << 8);
+
+ prefs->setBool("/options/svgoutput/usenamedcolors", true);
+ sp_svg_write_color(css, sizeof(css), rgb24 << 8);
+ ASSERT_EQ(sp_svg_read_color(css, 0xff), rgb24 << 8);
+}
+
+TEST(SvgColorTest, testWrite)
+{
+ unsigned const components[] = {0, 0x80, 0xff, 0xc0, 0x77};
+ unsigned const nc = G_N_ELEMENTS(components);
+ for (unsigned i = nc * nc * nc; i--;) {
+ unsigned tmp = i;
+ unsigned rgb24 = 0;
+ for (unsigned c = 0; c < 3; ++c) {
+ unsigned const component = components[tmp % nc];
+ rgb24 = (rgb24 << 8) | component;
+ tmp /= nc;
+ }
+ ASSERT_TRUE(tmp == 0);
+ check_rgb24(rgb24);
+ }
+
+ /* And a few completely random ones. */
+ for (unsigned i = 500; i--;) { /* Arbitrary number of iterations. */
+ unsigned const rgb24 = (std::rand() >> 4) & 0xffffff;
+ check_rgb24(rgb24);
+ }
+}
+
+TEST(SvgColorTest, testReadColor)
+{
+ gchar const *val[] = {"#f0f", "#ff00ff", "rgb(255,0,255)", "fuchsia"};
+ size_t const n = sizeof(val) / sizeof(*val);
+ for (size_t i = 0; i < n; i++) {
+ gchar const *end = 0;
+ guint32 result = sp_svg_read_color(val[i], &end, 0x3);
+ ASSERT_EQ(result, 0xff00ff00);
+ ASSERT_LT(val[i], end);
+ }
+}
+
+TEST(SvgColorTest, testIccColor)
+{
+ struct
+ {
+ unsigned numEntries;
+ bool shouldPass;
+ char const *name;
+ char const *str;
+ } cases[] = {
+ {1, true, "named", "icc-color(named, 3)"},
+ {0, false, "", "foodle"},
+ {1, true, "a", "icc-color(a, 3)"},
+ {4, true, "named", "icc-color(named, 3, 0, 0.1, 2.5)"},
+ {0, false, "", "icc-color(named, 3"},
+ {0, false, "", "icc-color(space named, 3)"},
+ {0, false, "", "icc-color(tab\tnamed, 3)"},
+ {0, false, "", "icc-color(0name, 3)"},
+ {0, false, "", "icc-color(-name, 3)"},
+ {1, true, "positive", "icc-color(positive, +3)"},
+ {1, true, "negative", "icc-color(negative, -3)"},
+ {1, true, "positive", "icc-color(positive, +0.1)"},
+ {1, true, "negative", "icc-color(negative, -0.1)"},
+ {0, false, "", "icc-color(named, value)"},
+ {1, true, "hyphen-name", "icc-color(hyphen-name, 1)"},
+ {1, true, "under_name", "icc-color(under_name, 1)"},
+ };
+
+ for (size_t i = 0; i < G_N_ELEMENTS(cases); i++) {
+ SVGICCColor tmp;
+ char const *str = cases[i].str;
+ char const *result = nullptr;
+
+ bool parseRet = sp_svg_read_icc_color(str, &result, &tmp);
+ ASSERT_EQ(parseRet, cases[i].shouldPass) << str;
+ ASSERT_EQ(tmp.colors.size(), cases[i].numEntries) << str;
+ if (cases[i].shouldPass) {
+ ASSERT_STRNE(str, result);
+ ASSERT_EQ(tmp.colorProfile, cases[i].name) << str;
+ } else {
+ ASSERT_STREQ(str, result);
+ ASSERT_TRUE(tmp.colorProfile.empty());
+ }
+ }
+}
+
+// vim: filetype=cpp:expandtab:shiftwidth=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/svg-extension-test.cpp b/testfiles/src/svg-extension-test.cpp
new file mode 100644
index 0000000..e650d24
--- /dev/null
+++ b/testfiles/src/svg-extension-test.cpp
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * SVG Extension test
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2020 Authors
+ *
+ * Released under GNU GPL version 2 or later, read the file 'COPYING' for more information
+ */
+
+#include <gtest/gtest.h>
+
+#include <src/extension/db.h>
+#include <src/extension/input.h>
+#include <src/extension/internal/svg.h>
+#include <src/inkscape.h>
+
+#include <glib/gstdio.h>
+
+using namespace Inkscape;
+using namespace Inkscape::Extension;
+using namespace Inkscape::Extension::Internal;
+
+class SvgExtensionTest : public ::testing::Test {
+ public:
+ static std::string create_file(const std::string &filename, const std::string &content)
+ {
+ std::stringstream path_builder;
+ path_builder << "SvgExtensionTest_" << _files.size() << "_" << filename;
+ std::string path = path_builder.str();
+ GError *error = nullptr;
+ if (!g_file_set_contents(path.c_str(), content.c_str(), content.size(), &error)) {
+ std::stringstream msg;
+ msg << "SvgExtensionTest::create_file failed: GError(" << error->domain << ", " << error->code << ", "
+ << error->message << ")";
+ g_error_free(error);
+ throw std::runtime_error(msg.str());
+ }
+ _files.insert(path);
+ return path;
+ }
+
+ static std::set<std::string> _files;
+
+ protected:
+ void SetUp() override
+ {
+ // setup hidden dependency
+ Application::create(false);
+ }
+
+ static void TearDownTestCase()
+ {
+ for (auto file : _files) {
+ if (g_remove(file.c_str())) {
+ std::cout << "SvgExtensionTest was unable to remove file: " << file << std::endl;
+ }
+ }
+ }
+};
+
+std::set<std::string> SvgExtensionTest::_files;
+
+TEST_F(SvgExtensionTest, openingAsLinkInImageASizelessSvgFileReturnsNull)
+{
+ std::string sizeless_svg_file =
+ create_file("sizeless.svg",
+ "<svg><path d=\"M 71.527648,186.14229 A 740.48715,740.48715 0 0 0 696.31258,625.8041 Z\"/></svg>");
+
+ Svg::init();
+ Input *svg_input_extension(dynamic_cast<Input *>(db.get(SP_MODULE_KEY_INPUT_SVG)));
+
+ Preferences *prefs = Preferences::get();
+ prefs->setBool("/options/onimport", true);
+ prefs->setBool("/dialogs/import/ask_svg", false);
+ prefs->setString("/dialogs/import/import_mode_svg", "link");
+
+ ASSERT_EQ(svg_input_extension->open(sizeless_svg_file.c_str()), nullptr);
+} \ No newline at end of file
diff --git a/testfiles/src/svg-length-test.cpp b/testfiles/src/svg-length-test.cpp
new file mode 100644
index 0000000..b96ee14
--- /dev/null
+++ b/testfiles/src/svg-length-test.cpp
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Test for SVG colors
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2010 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include "svg/svg-length.h"
+#include "svg/svg.h"
+
+#include <glib.h>
+#include <gtest/gtest.h>
+#include <utility>
+
+struct test_t
+{
+ char const *str;
+ SVGLength::Unit unit;
+ float value;
+ float computed;
+};
+
+test_t absolute_tests[12] = {
+ // clang-format off
+ {"0", SVGLength::NONE, 0 , 0},
+ {"1", SVGLength::NONE, 1 , 1},
+ {"1.00001", SVGLength::NONE, 1.00001 , 1.00001},
+ {"1px", SVGLength::PX , 1 , 1},
+ {".1px", SVGLength::PX , 0.1 , 0.1},
+ {"100pt", SVGLength::PT , 100 , 400.0/3.0},
+ {"1e2pt", SVGLength::PT , 100 , 400.0/3.0},
+ {"3pc", SVGLength::PC , 3 , 48},
+ {"-3.5pc", SVGLength::PC , -3.5 , -3.5*16.0},
+ {"1.2345678mm", SVGLength::MM , 1.2345678, 1.2345678f*96.0/25.4}, // TODO: More precise constants? (a 7 digit constant when the default precision is 8 digits?)
+ {"123.45678cm", SVGLength::CM , 123.45678 , 123.45678f*96.0/2.54}, // Note that svg_length_read is casting the result from g_ascii_strtod to float.
+ {"73.162987in", SVGLength::INCH, 73.162987 , 73.162987f*96.0/1.00}};
+test_t relative_tests[3] = {
+ {"123em", SVGLength::EM, 123, 123. * 7.},
+ {"123ex", SVGLength::EX, 123, 123. * 13.},
+ {"123%", SVGLength::PERCENT, 1.23, 1.23 * 19.}};
+const char* fail_tests[8] = {
+ "123 px",
+ "123e",
+ "123e+m",
+ "123ec",
+ "123pxt",
+ "--123",
+ "",
+ "px"};
+// clang-format on
+
+TEST(SvgLengthTest, testRead)
+{
+ for (size_t i = 0; i < G_N_ELEMENTS(absolute_tests); i++) {
+ SVGLength len;
+ ASSERT_TRUE( len.read(absolute_tests[i].str)) << absolute_tests[i].str;
+ ASSERT_EQ( len.unit, absolute_tests[i].unit) << absolute_tests[i].str;
+ ASSERT_EQ( len.value, absolute_tests[i].value) << absolute_tests[i].str;
+ ASSERT_EQ( len.computed, absolute_tests[i].computed) << absolute_tests[i].str;
+ }
+ for (size_t i = 0; i < G_N_ELEMENTS(relative_tests); i++) {
+ SVGLength len;
+ ASSERT_TRUE( len.read(relative_tests[i].str)) << relative_tests[i].str;
+ len.update(7, 13, 19);
+ ASSERT_EQ( len.unit, relative_tests[i].unit) << relative_tests[i].str;
+ ASSERT_EQ( len.value, relative_tests[i].value) << relative_tests[i].str;
+ ASSERT_EQ( len.computed, relative_tests[i].computed) << relative_tests[i].str;
+ }
+ for (size_t i = 0; i < G_N_ELEMENTS(fail_tests); i++) {
+ SVGLength len;
+ ASSERT_TRUE( !len.read(fail_tests[i])) << fail_tests[i];
+ }
+}
+
+TEST(SvgLengthTest, testReadOrUnset)
+{
+ for (size_t i = 0; i < G_N_ELEMENTS(absolute_tests); i++) {
+ SVGLength len;
+ len.readOrUnset(absolute_tests[i].str);
+ ASSERT_EQ( len.unit, absolute_tests[i].unit) << absolute_tests[i].str;
+ ASSERT_EQ( len.value, absolute_tests[i].value) << absolute_tests[i].str;
+ ASSERT_EQ( len.computed, absolute_tests[i].computed) << absolute_tests[i].str;
+ }
+ for (size_t i = 0; i < G_N_ELEMENTS(relative_tests); i++) {
+ SVGLength len;
+ len.readOrUnset(relative_tests[i].str);
+ len.update(7, 13, 19);
+ ASSERT_EQ( len.unit, relative_tests[i].unit) << relative_tests[i].str;
+ ASSERT_EQ( len.value, relative_tests[i].value) << relative_tests[i].str;
+ ASSERT_EQ( len.computed, relative_tests[i].computed) << relative_tests[i].str;
+ }
+ for (size_t i = 0; i < G_N_ELEMENTS(fail_tests); i++) {
+ SVGLength len;
+ len.readOrUnset(fail_tests[i], SVGLength::INCH, 123, 456);
+ ASSERT_EQ( len.unit, SVGLength::INCH) << fail_tests[i];
+ ASSERT_EQ( len.value, 123) << fail_tests[i];
+ ASSERT_EQ( len.computed, 456) << fail_tests[i];
+ }
+}
+
+TEST(SvgLengthTest, testReadAbsolute)
+{
+ for (size_t i = 0; i < G_N_ELEMENTS(absolute_tests); i++) {
+ SVGLength len;
+ ASSERT_TRUE( len.readAbsolute(absolute_tests[i].str)) << absolute_tests[i].str;
+ ASSERT_EQ( len.unit, absolute_tests[i].unit) << absolute_tests[i].str;
+ ASSERT_EQ( len.value, absolute_tests[i].value) << absolute_tests[i].str;
+ ASSERT_EQ( len.computed, absolute_tests[i].computed) << absolute_tests[i].str;
+ }
+ for (size_t i = 0; i < G_N_ELEMENTS(relative_tests); i++) {
+ SVGLength len;
+ ASSERT_TRUE( !len.readAbsolute(relative_tests[i].str)) << relative_tests[i].str;
+ }
+ for (size_t i = 0; i < G_N_ELEMENTS(fail_tests); i++) {
+ SVGLength len;
+ ASSERT_TRUE( !len.readAbsolute(fail_tests[i])) << fail_tests[i];
+ }
+}
+
+TEST(SvgLengthTest, testEnumMappedToString)
+{
+ for (int i = (static_cast<int>(SVGLength::NONE) + 1); i <= static_cast<int>(SVGLength::LAST_UNIT); i++) {
+ SVGLength::Unit target = static_cast<SVGLength::Unit>(i);
+ // PX is a special case where we don't have a unit string
+ if ((target != SVGLength::PX)) {
+ gchar const *val = sp_svg_length_get_css_units(target);
+ ASSERT_NE(val, "") << i;
+ }
+ }
+}
+
+TEST(SvgLengthTest, testStringsAreValidSVG)
+{
+ gchar const *valid[] = {"", "em", "ex", "px", "pt", "pc", "cm", "mm", "in", "%"};
+ std::set<std::string> validStrings(valid, valid + G_N_ELEMENTS(valid));
+ for (int i = (static_cast<int>(SVGLength::NONE) + 1); i <= static_cast<int>(SVGLength::LAST_UNIT); i++) {
+ SVGLength::Unit target = static_cast<SVGLength::Unit>(i);
+ gchar const *val = sp_svg_length_get_css_units(target);
+ ASSERT_TRUE( validStrings.find(std::string(val)) != validStrings.end()) << i;
+ }
+}
+
+TEST(SvgLengthTest, testValidSVGStringsSupported)
+{
+ // Note that "px" is omitted from the list, as it will be assumed to be so if not explicitly set.
+ gchar const *valid[] = {"em", "ex", "pt", "pc", "cm", "mm", "in", "%"};
+ std::set<std::string> validStrings(valid, valid + G_N_ELEMENTS(valid));
+ for (int i = (static_cast<int>(SVGLength::NONE) + 1); i <= static_cast<int>(SVGLength::LAST_UNIT); i++) {
+ SVGLength::Unit target = static_cast<SVGLength::Unit>(i);
+ gchar const *val = sp_svg_length_get_css_units(target);
+ std::set<std::string>::iterator iter = validStrings.find(std::string(val));
+ if (iter != validStrings.end()) {
+ validStrings.erase(iter);
+ }
+ }
+ ASSERT_EQ(validStrings.size(), 0u) << validStrings.size();
+}
+
+TEST(SvgLengthTest, testPlaces)
+{
+ struct testd_t
+ {
+ char const *str;
+ double val;
+ int prec;
+ int minexp;
+ };
+
+ testd_t const precTests[] = {
+ {"760", 761.92918978947023, 2, -8},
+ {"761.9", 761.92918978947023, 4, -8},
+ };
+
+ for (size_t i = 0; i < G_N_ELEMENTS(precTests); i++) {
+ std::string buf;
+ buf.append(sp_svg_number_write_de(precTests[i].val, precTests[i].prec, precTests[i].minexp));
+ unsigned int retval = buf.length();
+ ASSERT_EQ( retval, strlen(precTests[i].str)) << "Number of chars written";
+ ASSERT_EQ( std::string(buf), std::string(precTests[i].str)) << "Numeric string written";
+ }
+}
+
+// TODO: More tests
+
+// vim: filetype=cpp:expandtab:shiftwidth=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/svg-path-geom-test.cpp b/testfiles/src/svg-path-geom-test.cpp
new file mode 100644
index 0000000..7fa7a21
--- /dev/null
+++ b/testfiles/src/svg-path-geom-test.cpp
@@ -0,0 +1,499 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Test for SVG colors
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2010 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <2geom/coord.h>
+#include <2geom/curves.h>
+#include <2geom/pathvector.h>
+#include <glib.h>
+#include <gtest/gtest.h>
+#include <vector>
+
+#include "preferences.h"
+#include "svg/svg.h"
+
+class SvgPathGeomTest : public ::testing::Test
+{
+public:
+ std::vector<std::string> rectanglesAbsoluteClosed = {"M 1,2 L 4,2 L 4,8 L 1,8 z", "M 1,2 4,2 4,8 1,8 z",
+ "M 1,2 H 4 V 8 H 1 z"};
+ std::vector<std::string> rectanglesRelativeClosed = {"m 1,2 l 3,0 l 0,6 l -3,0 z", "m 1,2 3,0 0,6 -3,0 z",
+ "m 1,2 h 3 v 6 h -3 z"};
+ std::vector<std::string> rectanglesAbsoluteOpen = {"M 1,2 L 4,2 L 4,8 L 1,8 L 1,2", "M 1,2 4,2 4,8 1,8 1,2",
+ "M 1,2 H 4 V 8 H 1 V 2"};
+ std::vector<std::string> rectanglesRelativeOpen = {"m 1,2 l 3,0 l 0,6 l -3,0 l 0,-6", "m 1,2 3,0 0,6 -3,0 0,-6",
+ "m 1,2 h 3 v 6 h -3 v -6"};
+ std::vector<std::string> rectanglesAbsoluteClosed2 = {"M 1,2 L 4,2 L 4,8 L 1,8 L 1,2 z", "M 1,2 4,2 4,8 1,8 1,2 z",
+ "M 1,2 H 4 V 8 H 1 V 2 z"};
+ std::vector<std::string> rectanglesRelativeClosed2{"m 1,2 l 3,0 l 0,6 l -3,0 l 0,-6 z", "m 1,2 3,0 0,6 -3,0 0,-6 z",
+ "m 1,2 h 3 v 6 h -3 v -6 z"};
+ Geom::PathVector rectanglepvopen;
+ Geom::PathVector rectanglepvclosed;
+ Geom::PathVector rectanglepvclosed2;
+
+ void SetUp() override
+ {
+ rectanglepvopen.clear();
+ rectanglepvopen.push_back(Geom::Path(Geom::Point(1, 2)));
+ rectanglepvopen.back().append(Geom::LineSegment(Geom::Point(1, 2), Geom::Point(4, 2)));
+ rectanglepvopen.back().append(Geom::LineSegment(Geom::Point(4, 2), Geom::Point(4, 8)));
+ rectanglepvopen.back().append(Geom::LineSegment(Geom::Point(4, 8), Geom::Point(1, 8)));
+ rectanglepvopen.back().append(Geom::LineSegment(Geom::Point(1, 8), Geom::Point(1, 2)));
+ rectanglepvclosed.clear();
+ rectanglepvclosed.push_back(Geom::Path(Geom::Point(1, 2)));
+ rectanglepvclosed.back().append(Geom::LineSegment(Geom::Point(1, 2), Geom::Point(4, 2)));
+ rectanglepvclosed.back().append(Geom::LineSegment(Geom::Point(4, 2), Geom::Point(4, 8)));
+ rectanglepvclosed.back().append(Geom::LineSegment(Geom::Point(4, 8), Geom::Point(1, 8)));
+ rectanglepvclosed.back().close();
+ rectanglepvclosed2.clear();
+ rectanglepvclosed2.push_back(Geom::Path(Geom::Point(1, 2)));
+ rectanglepvclosed2.back().append(Geom::LineSegment(Geom::Point(1, 2), Geom::Point(4, 2)));
+ rectanglepvclosed2.back().append(Geom::LineSegment(Geom::Point(4, 2), Geom::Point(4, 8)));
+ rectanglepvclosed2.back().append(Geom::LineSegment(Geom::Point(4, 8), Geom::Point(1, 8)));
+ rectanglepvclosed2.back().append(Geom::LineSegment(Geom::Point(1, 8), Geom::Point(1, 2)));
+ rectanglepvclosed2.back().close();
+ }
+
+ bool bpathEqual(Geom::PathVector const &a, Geom::PathVector const &b, double eps = 1e-16)
+ {
+ if (a.size() != b.size()) {
+ printf("PathVectors not the same size: %u != %u", static_cast<unsigned int>(a.size()),
+ static_cast<unsigned int>(b.size()));
+ return false;
+ }
+ for (size_t i = 0; i < a.size(); i++) {
+ Geom::Path const &pa = a[i];
+ Geom::Path const &pb = b[i];
+ if (pa.closed() && !pb.closed()) {
+ printf("Left subpath is closed, right subpath is open. Subpath: %u", static_cast<unsigned int>(i));
+ return false;
+ }
+ if (!pa.closed() && pb.closed()) {
+ printf("Right subpath is closed, left subpath is open. Subpath: %u", static_cast<unsigned int>(i));
+ return false;
+ }
+ if (pa.size() != pb.size()) {
+ printf("Not the same number of segments: %u != %u, subpath: %u", static_cast<unsigned int>(pa.size()),
+ static_cast<unsigned int>(pb.size()), static_cast<unsigned int>(i));
+ return false;
+ }
+ for (size_t j = 0; j < pa.size(); j++) {
+ Geom::Curve const *ca = &pa[j];
+ Geom::Curve const *cb = &pb[j];
+ if (typeid(*ca) == typeid(*cb)) {
+ if (Geom::LineSegment const *la = dynamic_cast<Geom::LineSegment const *>(ca)) {
+ Geom::LineSegment const *lb = dynamic_cast<Geom::LineSegment const *>(cb);
+ if (!Geom::are_near((*la)[0], (*lb)[0], eps)) {
+ printf("Different start of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u",
+ (*la)[0][Geom::X], (*la)[0][Geom::Y], (*lb)[0][Geom::X], (*lb)[0][Geom::Y],
+ static_cast<unsigned int>(i), static_cast<unsigned int>(j));
+ return false;
+ }
+ if (!Geom::are_near((*la)[1], (*lb)[1], eps)) {
+ printf("Different end of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u",
+ (*la)[1][Geom::X], (*la)[1][Geom::Y], (*lb)[1][Geom::X], (*lb)[1][Geom::Y],
+ static_cast<unsigned int>(i), static_cast<unsigned int>(j));
+ return false;
+ }
+ } else if (Geom::CubicBezier const *la = dynamic_cast<Geom::CubicBezier const *>(ca)) {
+ Geom::CubicBezier const *lb = dynamic_cast<Geom::CubicBezier const *>(cb);
+ if (!Geom::are_near((*la)[0], (*lb)[0], eps)) {
+ printf("Different start of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u",
+ (*la)[0][Geom::X], (*la)[0][Geom::Y], (*lb)[0][Geom::X], (*lb)[0][Geom::Y],
+ static_cast<unsigned int>(i), static_cast<unsigned int>(j));
+ return false;
+ }
+ if (!Geom::are_near((*la)[1], (*lb)[1], eps)) {
+ printf("Different 1st control point: (%g,%g) != (%g,%g), subpath: %u, segment: %u",
+ (*la)[1][Geom::X], (*la)[1][Geom::Y], (*lb)[1][Geom::X], (*lb)[1][Geom::Y],
+ static_cast<unsigned int>(i), static_cast<unsigned int>(j));
+ return false;
+ }
+ if (!Geom::are_near((*la)[2], (*lb)[2], eps)) {
+ printf("Different 2nd control point: (%g,%g) != (%g,%g), subpath: %u, segment: %u",
+ (*la)[2][Geom::X], (*la)[2][Geom::Y], (*lb)[2][Geom::X], (*lb)[2][Geom::Y],
+ static_cast<unsigned int>(i), static_cast<unsigned int>(j));
+ return false;
+ }
+ if (!Geom::are_near((*la)[3], (*lb)[3], eps)) {
+ printf("Different end of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u",
+ (*la)[3][Geom::X], (*la)[3][Geom::Y], (*lb)[3][Geom::X], (*lb)[3][Geom::Y],
+ static_cast<unsigned int>(i), static_cast<unsigned int>(j));
+ return false;
+ }
+ } else {
+ printf("Unknown curve type: %s, subpath: %u, segment: %u", typeid(*ca).name(),
+ static_cast<unsigned int>(i), static_cast<unsigned int>(j));
+ return false;
+ }
+ } else // not same type
+ {
+ printf("Different curve types: %s != %s, subpath: %u, segment: %u", typeid(*ca).name(),
+ typeid(*cb).name(), static_cast<unsigned int>(i), static_cast<unsigned int>(j));
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+};
+
+TEST_F(SvgPathGeomTest, testReadRectanglesAbsoluteClosed)
+{
+ for (size_t i = 0; i < rectanglesAbsoluteClosed.size(); i++) {
+ Geom::PathVector pv = sp_svg_read_pathv(rectanglesAbsoluteClosed[i].c_str());
+ EXPECT_TRUE(bpathEqual(pv, rectanglepvclosed)) << rectanglesAbsoluteClosed[i].c_str();
+ }
+}
+
+TEST_F(SvgPathGeomTest, testReadRectanglesRelativeClosed)
+{
+ for (size_t i = 0; i < rectanglesRelativeClosed.size(); i++) {
+ Geom::PathVector pv = sp_svg_read_pathv(rectanglesRelativeClosed[i].c_str());
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << rectanglesRelativeClosed[i].c_str();
+ }
+}
+
+TEST_F(SvgPathGeomTest, testReadRectanglesAbsoluteOpen)
+{
+ for (size_t i = 0; i < rectanglesAbsoluteOpen.size(); i++) {
+ Geom::PathVector pv = sp_svg_read_pathv(rectanglesAbsoluteOpen[i].c_str());
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvopen)) << rectanglesAbsoluteOpen[i].c_str();
+ }
+}
+
+TEST_F(SvgPathGeomTest, testReadRectanglesRelativeOpen)
+{
+ for (size_t i = 0; i < rectanglesRelativeOpen.size(); i++) {
+ Geom::PathVector pv = sp_svg_read_pathv(rectanglesRelativeOpen[i].c_str());
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvopen)) << rectanglesRelativeOpen[i].c_str();
+ }
+}
+
+TEST_F(SvgPathGeomTest, testReadRectanglesAbsoluteClosed2)
+{
+ for (size_t i = 0; i < rectanglesAbsoluteClosed2.size(); i++) {
+ Geom::PathVector pv = sp_svg_read_pathv(rectanglesAbsoluteClosed2[i].c_str());
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed2)) << rectanglesAbsoluteClosed2[i].c_str();
+ }
+}
+
+TEST_F(SvgPathGeomTest, testReadRectanglesRelativeClosed2)
+{
+ for (size_t i = 0; i < rectanglesRelativeClosed2.size(); i++) {
+ Geom::PathVector pv = sp_svg_read_pathv(rectanglesRelativeClosed2[i].c_str());
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed2)) << rectanglesRelativeClosed2[i].c_str();
+ }
+}
+
+TEST_F(SvgPathGeomTest, testReadConcatenatedPaths)
+{
+ // Note that finalPoint doesn't actually return the final point of the path, just the last given point... (but since
+ // this might be intentional and we're not testing lib2geom here, we just specify the final point explicitly
+ Geom::PathVector pv_good;
+ pv_good.push_back(rectanglepvclosed.back());
+ pv_good.push_back(rectanglepvopen.back() * Geom::Translate(1, 2) /* * Geom::Translate(pv_good[0].finalPoint())*/);
+ pv_good.push_back(rectanglepvclosed.back() * Geom::Translate(2, 4) /* *Geom::Translate(pv_good[1].finalPoint())*/);
+ pv_good.push_back(rectanglepvopen.back());
+ pv_good[0].close();
+ pv_good[1].close(false);
+ pv_good[2].close();
+ pv_good[3].close(false);
+ std::string path_str = rectanglesAbsoluteClosed[0] + rectanglesRelativeOpen[0] + rectanglesRelativeClosed[0] +
+ rectanglesAbsoluteOpen[0];
+ Geom::PathVector pv = sp_svg_read_pathv(path_str.c_str());
+ ASSERT_TRUE(bpathEqual(pv, pv_good));
+}
+
+TEST_F(SvgPathGeomTest, testReadZeroLengthSubpaths)
+{
+ // Per the SVG 1.1 specification (section F5) zero-length subpaths are relevant
+ Geom::PathVector pv_good;
+ pv_good.push_back(Geom::Path(Geom::Point(0, 0)));
+ pv_good.push_back(Geom::Path(Geom::Point(1, 1)));
+ pv_good.back().append(Geom::LineSegment(Geom::Point(1, 1), Geom::Point(2, 2)));
+ pv_good.push_back(Geom::Path(Geom::Point(3, 3)));
+ pv_good.back().close();
+ pv_good.push_back(Geom::Path(Geom::Point(4, 4)));
+ pv_good.back().append(Geom::LineSegment(Geom::Point(4, 4), Geom::Point(5, 5)));
+ pv_good.back().close();
+ pv_good.push_back(Geom::Path(Geom::Point(6, 6)));
+ { // Test absolute version
+ char const *path_str = "M 0,0 M 1,1 L 2,2 M 3,3 z M 4,4 L 5,5 z M 6,6";
+ Geom::PathVector pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, pv_good)) << path_str;
+ }
+ { // Test relative version
+ char const *path_str = "m 0,0 m 1,1 l 1,1 m 1,1 z m 1,1 l 1,1 z m 2,2";
+ Geom::PathVector pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, pv_good)) << path_str;
+ }
+}
+
+TEST_F(SvgPathGeomTest, testReadImplicitMoveto)
+{
+ g_warning("Currently lib2geom (/libnr) has no way of specifying the difference between 'M 0,0 ... z M 0,0 L 1,0' "
+ "and 'M 0,0 ... z L 1,0', the SVG specification does state that these should be handled differently with "
+ "respect to markers however, see the description of the 'orient' attribute of the 'marker' element.");
+ Geom::PathVector pv_good;
+ pv_good.push_back(Geom::Path(Geom::Point(1, 1)));
+ pv_good.back().append(Geom::LineSegment(Geom::Point(1, 1), Geom::Point(2, 2)));
+ pv_good.back().close();
+ pv_good.push_back(Geom::Path(Geom::Point(1, 1)));
+ pv_good.back().append(Geom::LineSegment(Geom::Point(1, 1), Geom::Point(3, 3)));
+ pv_good.back().close();
+ { // Test absolute version
+ char const *path_str = "M 1,1 L 2,2 z L 3,3 z";
+ Geom::PathVector pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, pv_good)) << path_str;
+ }
+ { // Test relative version
+ char const *path_str = "M 1,1 l 1,1 z l 2,2 z";
+ Geom::PathVector pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, pv_good)) << path_str;
+ }
+}
+
+TEST_F(SvgPathGeomTest, testReadFloatingPoint)
+{
+ Geom::PathVector pv_good1;
+ pv_good1.push_back(Geom::Path(Geom::Point(.01, .02)));
+ pv_good1.back().append(Geom::LineSegment(Geom::Point(.01, .02), Geom::Point(.04, .02)));
+ pv_good1.back().append(Geom::LineSegment(Geom::Point(.04, .02), Geom::Point(1.5, 1.6)));
+ pv_good1.back().append(Geom::LineSegment(Geom::Point(1.5, 1.6), Geom::Point(.01, .08)));
+ pv_good1.back().append(Geom::LineSegment(Geom::Point(.01, .08), Geom::Point(.01, .02)));
+ pv_good1.back().close();
+ { // Test decimals
+ char const *path_str = "M .01,.02 L.04.02 L1.5,1.6L0.01,0.08 .01.02 z";
+ Geom::PathVector pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, pv_good1)) << path_str;
+ }
+ Geom::PathVector pv_good2;
+ pv_good2.push_back(Geom::Path(Geom::Point(.01, .02)));
+ pv_good2.back().append(Geom::LineSegment(Geom::Point(.01, .02), Geom::Point(.04, .02)));
+ pv_good2.back().append(Geom::LineSegment(Geom::Point(.04, .02), Geom::Point(1.5, 1.6)));
+ pv_good2.back().append(Geom::LineSegment(Geom::Point(1.5, 1.6), Geom::Point(.01, .08)));
+ pv_good2.back().close();
+ { // Test exponent
+ char const *path_str = "M 1e-2,.2e-1 L 0.004e1,0.0002e+2 L0150E-2,1.6e0L1.0e-2,80e-3 z";
+ Geom::PathVector pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, pv_good2)) << path_str;
+ }
+}
+
+TEST_F(SvgPathGeomTest, testReadImplicitSeparation)
+{
+ // Coordinates need not be separated by whitespace if they can still be read unambiguously
+ Geom::PathVector pv_good;
+ pv_good.push_back(Geom::Path(Geom::Point(.1, .2)));
+ pv_good.back().append(Geom::LineSegment(Geom::Point(.1, .2), Geom::Point(.4, .2)));
+ pv_good.back().append(Geom::LineSegment(Geom::Point(.4, .2), Geom::Point(.4, .8)));
+ pv_good.back().append(Geom::LineSegment(Geom::Point(.4, .8), Geom::Point(.1, .8)));
+ pv_good.back().close();
+ { // Test absolute
+ char const *path_str = "M .1.2+0.4.2e0.4e0+8e-1.1.8 z";
+ Geom::PathVector pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, pv_good)) << path_str;
+ }
+ { // Test relative
+ char const *path_str = "m .1.2+0.3.0e0.0e0+6e-1-.3.0 z";
+ Geom::PathVector pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, pv_good)) << path_str;
+ }
+}
+
+TEST_F(SvgPathGeomTest, testReadErrorMisplacedCharacter)
+{
+
+ char const *path_str;
+ Geom::PathVector pv;
+ // Comma in the wrong place (commas may only appear between parameters)
+ path_str = "M 1,2 4,2 4,8 1,8 z , m 13,15";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << path_str;
+ // Comma in the wrong place (commas may only appear between parameters)
+ path_str = "M 1,2 4,2 4,8 1,8 z m,13,15";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << path_str;
+ // Period in the wrong place (no numbers after a 'z')
+ path_str = "M 1,2 4,2 4,8 1,8 z . m 13,15";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << path_str;
+ // Sign in the wrong place (no numbers after a 'z')
+ path_str = "M 1,2 4,2 4,8 1,8 z + - m 13,15";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << path_str;
+ // Digit in the wrong place (no numbers after a 'z')
+ path_str = "M 1,2 4,2 4,8 1,8 z 9809 m 13,15";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << path_str;
+ // Digit in the wrong place (no numbers after a 'z')
+ path_str = "M 1,2 4,2 4,8 1,8 z 9809 876 m 13,15";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << path_str;
+}
+/*FAIL ??
+TEST_F(SvgPathGeomTest, testReadErrorUnrecognizedCharacter)
+{
+ char const *path_str;
+ Geom::PathVector pv;
+ // Unrecognized character
+ path_str = "M 1,2 4,2 4,8 1,8 z&m 13,15";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << path_str;
+ // Unrecognized character
+ path_str = "M 1,2 4,2 4,8 1,8 z m &13,15";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << path_str;
+}
+
+TEST_F(SvgPathGeomTest, testReadErrorTypo)
+{
+ char const *path_str;
+ Geom::PathVector pv;
+ // Typo
+ path_str = "M 1,2 4,2 4,8 1,8 z j 13,15";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << path_str;
+
+ // Typo
+ path_str = "M 1,2 4,2 4,8 1,8 L 1,2 x m 13,15";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvopen)) << path_str;
+}
+*/
+TEST_F(SvgPathGeomTest, testReadErrorIllformedNumbers)
+{
+ char const *path_str;
+ Geom::PathVector pv;
+ // Double exponent
+ path_str = "M 1,2 4,2 4,8 1,8 z m 13e4e5,15";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << path_str;
+ // Double sign
+ path_str = "M 1,2 4,2 4,8 1,8 z m +-13,15";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << path_str;
+ // Double sign
+ path_str = "M 1,2 4,2 4,8 1,8 z m 13e+-12,15";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << path_str;
+ // No digit
+ path_str = "M 1,2 4,2 4,8 1,8 z m .e12,15";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << path_str;
+ // No digit
+ path_str = "M 1,2 4,2 4,8 1,8 z m .,15";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << path_str;
+ // No digit
+ path_str = "M 1,2 4,2 4,8 1,8 z m +,15";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << path_str;
+ // No digit
+ path_str = "M 1,2 4,2 4,8 1,8 z m +.e+,15";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << path_str;
+}
+
+TEST_F(SvgPathGeomTest, testReadErrorJunk)
+{
+ char const *path_str;
+ Geom::PathVector pv;
+ // Junk
+ path_str = "M 1,2 4,2 4,8 1,8 z j 357 hkjh.,34e34 90ih6kj4 h5k6vlh4N.,6,45wikuyi3yere..3487 m 13,23";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << path_str;
+}
+/* FAIL ???
+TEST_F(SvgPathGeomTest, testReadErrorStopReading)
+{
+ char const *path_str;
+ Geom::PathVector pv;
+ // Unrecognized parameter
+ path_str = "M 1,2 4,2 4,8 1,8 z m #$%,23,34";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << path_str;
+ // Invalid parameter
+ path_str = "M 1,2 4,2 4,8 1,8 z m #$%,23,34";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << path_str;
+ // Illformed parameter
+ path_str = "M 1,2 4,2 4,8 1,8 z m +-12,23,34";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvclosed)) << path_str;
+
+ // "Third" parameter
+ path_str = "M 1,2 4,2 4,8 1,8 1,2,3 M 12,23";
+ pv = sp_svg_read_pathv(path_str);
+ ASSERT_TRUE(bpathEqual(pv, rectanglepvopen)) << path_str;
+}
+*/
+
+TEST_F(SvgPathGeomTest, testRoundTrip)
+{
+ // This is the easiest way to (also) test writing path data, as a path can be written in more than one way.
+ Geom::PathVector pv;
+ Geom::PathVector new_pv;
+ std::string org_path_str;
+ std::string path_str;
+ // Rectangle (closed)
+ org_path_str = rectanglesAbsoluteClosed[0];
+ pv = sp_svg_read_pathv(org_path_str.c_str());
+ path_str = sp_svg_write_path(pv);
+ new_pv = sp_svg_read_pathv(path_str.c_str());
+ ASSERT_TRUE(bpathEqual(pv, new_pv)) << org_path_str.c_str();
+ // Rectangle (open)
+ org_path_str = rectanglesAbsoluteOpen[0];
+ pv = sp_svg_read_pathv(org_path_str.c_str());
+ path_str = sp_svg_write_path(pv);
+ new_pv = sp_svg_read_pathv(path_str.c_str());
+ ASSERT_TRUE(bpathEqual(pv, new_pv)) << org_path_str.c_str();
+ // Concatenated rectangles
+ org_path_str = rectanglesAbsoluteClosed[0] + rectanglesRelativeOpen[0] + rectanglesRelativeClosed[0] +
+ rectanglesAbsoluteOpen[0];
+ pv = sp_svg_read_pathv(org_path_str.c_str());
+ path_str = sp_svg_write_path(pv);
+ new_pv = sp_svg_read_pathv(path_str.c_str());
+ ASSERT_TRUE(bpathEqual(pv, new_pv)) << org_path_str.c_str();
+ // Zero-length subpaths
+ org_path_str = "M 0,0 M 1,1 L 2,2 M 3,3 z M 4,4 L 5,5 z M 6,6";
+ pv = sp_svg_read_pathv(org_path_str.c_str());
+ path_str = sp_svg_write_path(pv);
+ new_pv = sp_svg_read_pathv(path_str.c_str());
+ ASSERT_TRUE(bpathEqual(pv, new_pv)) << org_path_str.c_str();
+ // Floating-point
+ org_path_str = "M .01,.02 L 0.04,0.02 L.04,.08L0.01,0.08 z"
+ "M 1e-2,.2e-1 L 0.004e1,0.0002e+2 L04E-2,.08e0L1.0e-2,80e-3 z";
+ pv = sp_svg_read_pathv(org_path_str.c_str());
+ path_str = sp_svg_write_path(pv);
+ new_pv = sp_svg_read_pathv(path_str.c_str());
+ ASSERT_TRUE(bpathEqual(pv, new_pv, 1e-17)) << org_path_str.c_str();
+}
+
+/*
+ * Please do not change my prefs or put them back after :(
+ * also, fails.
+
+TEST_F(SvgPathGeomTest, testMinexpPrecision)
+{
+ Geom::PathVector pv;
+ char *path_str;
+ // Default values
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool("/options/svgoutput/allowrelativecoordinates", true);
+ prefs->setBool("/options/svgoutput/forcerepeatcommands", false);
+ prefs->setInt("/options/svgoutput/numericprecision", 8);
+ prefs->setInt("/options/svgoutput/minimumexponent", -8);
+ pv = sp_svg_read_pathv("M 123456781,1.23456781e-8 L 123456782,1.23456782e-8 L 123456785,1.23456785e-8 L
+10123456400,1.23456785e-8 L 123456789,1.23456789e-8 L 123456789,101.234564e-8 L 123456789,1.23456789e-8"); path_str =
+sp_svg_write_path(pv); ASSERT_FALSE( strcmp("m 123456780,1.2345678e-8 0,0 10,1e-15 9999999210,0 -9999999210,0
+0,9.99999921e-7 0,-9.99999921e-7" , path_str )); g_free(path_str);
+}*/
+
+// vim: filetype=cpp:expandtab:shiftwidth=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/svg-stringstream-test.cpp b/testfiles/src/svg-stringstream-test.cpp
new file mode 100644
index 0000000..636b018
--- /dev/null
+++ b/testfiles/src/svg-stringstream-test.cpp
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Test CSSOStringStream and SVGOStringStream
+ */
+/*
+ * Authors:
+ * Thomas Holder
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "2geom/point.h"
+#include "svg/css-ostringstream.h"
+#include "svg/stringstream.h"
+
+#include "gtest/gtest.h"
+#include <glibmm/ustring.h>
+
+template <typename S, typename T>
+static void assert_tostring_eq(T value, const char *expected)
+{
+ S os;
+
+ // default of /options/svgoutput/numericprecision
+ os.precision(8);
+
+ os << value;
+ ASSERT_EQ(os.str(), expected);
+}
+
+#define TEST_STRING "Hello & <World>"
+
+template <typename S>
+void test_tostring()
+{
+ assert_tostring_eq<S, char>('A', "A");
+ assert_tostring_eq<S, signed char>('A', "A");
+ assert_tostring_eq<S, unsigned char>('A', "A");
+
+ assert_tostring_eq<S, short>(0x7FFF, "32767");
+ assert_tostring_eq<S, short>(-30000, "-30000");
+ assert_tostring_eq<S, unsigned short>(0xFFFFu, "65535");
+ assert_tostring_eq<S, int>(0x7FFFFFFF, "2147483647");
+ assert_tostring_eq<S, int>(-2000000000, "-2000000000");
+ assert_tostring_eq<S, unsigned int>(0xFFFFFFFFu, "4294967295");
+
+ // long is 32bit on Windows, 64bit on Linux
+ assert_tostring_eq<S, long>(0x7FFFFFFFL, "2147483647");
+ assert_tostring_eq<S, long>(-2000000000L, "-2000000000");
+ assert_tostring_eq<S, unsigned long>(0xFFFFFFFFuL, "4294967295");
+
+ assert_tostring_eq<S>((char const *)TEST_STRING, TEST_STRING);
+ assert_tostring_eq<S>((signed char const *)TEST_STRING, TEST_STRING);
+ assert_tostring_eq<S>((unsigned char const *)TEST_STRING, TEST_STRING);
+ assert_tostring_eq<S, std::string>(TEST_STRING, TEST_STRING);
+ assert_tostring_eq<S, Glib::ustring>(TEST_STRING, TEST_STRING);
+}
+
+TEST(CSSOStringStreamTest, tostring)
+{
+ using S = Inkscape::CSSOStringStream;
+
+ test_tostring<S>();
+
+ // float has 6 significant digits
+ assert_tostring_eq<S, float>(0.0, "0");
+ assert_tostring_eq<S, float>(4.5, "4.5");
+ assert_tostring_eq<S, float>(-4.0, "-4");
+ assert_tostring_eq<S, float>(0.001, "0.001");
+ assert_tostring_eq<S, float>(0.00123456, "0.00123456");
+ assert_tostring_eq<S, float>(-0.00123456, "-0.00123456");
+ assert_tostring_eq<S, float>(-1234560.0, "-1234560");
+
+ // double has 15 significant digits
+ assert_tostring_eq<S, double>(0.0, "0");
+ assert_tostring_eq<S, double>(4.5, "4.5");
+ assert_tostring_eq<S, double>(-4.0, "-4");
+ assert_tostring_eq<S, double>(0.001, "0.001");
+
+ // 9 significant digits
+ assert_tostring_eq<S, double>(1.23456789, "1.23456789");
+ assert_tostring_eq<S, double>(-1.23456789, "-1.23456789");
+ assert_tostring_eq<S, double>(12345678.9, "12345678.9");
+ assert_tostring_eq<S, double>(-12345678.9, "-12345678.9");
+
+ assert_tostring_eq<S, double>(1.234e-12, "0");
+ assert_tostring_eq<S, double>(3e9, "3000000000");
+ assert_tostring_eq<S, double>(-3.5e9, "-3500000000");
+}
+
+TEST(SVGOStringStreamTest, tostring)
+{
+ using S = Inkscape::SVGOStringStream;
+
+ test_tostring<S>();
+
+ assert_tostring_eq<S>(Geom::Point(12, 3.4), "12,3.4");
+
+ // float has 6 significant digits
+ assert_tostring_eq<S, float>(0.0, "0");
+ assert_tostring_eq<S, float>(4.5, "4.5");
+ assert_tostring_eq<S, float>(-4.0, "-4");
+ assert_tostring_eq<S, float>(0.001, "0.001");
+ assert_tostring_eq<S, float>(0.00123456, "0.00123456");
+ assert_tostring_eq<S, float>(-0.00123456, "-0.00123456");
+ assert_tostring_eq<S, float>(-1234560.0, "-1234560");
+
+ // double has 15 significant digits
+ assert_tostring_eq<S, double>(0.0, "0");
+ assert_tostring_eq<S, double>(4.5, "4.5");
+ assert_tostring_eq<S, double>(-4.0, "-4");
+ assert_tostring_eq<S, double>(0.001, "0.001");
+
+ // 8 significant digits
+ assert_tostring_eq<S, double>(1.23456789, "1.2345679");
+ assert_tostring_eq<S, double>(-1.23456789, "-1.2345679");
+ assert_tostring_eq<S, double>(12345678.9, "12345679");
+ assert_tostring_eq<S, double>(-12345678.9, "-12345679");
+
+ assert_tostring_eq<S, double>(1.234e-12, "1.234e-12");
+ assert_tostring_eq<S, double>(3e9, "3e+09");
+ assert_tostring_eq<S, double>(-3.5e9, "-3.5e+09");
+}
+
+template <typename S>
+void test_concat()
+{
+ S s;
+ s << "hello, ";
+ s << -53.5;
+ ASSERT_EQ(s.str(), std::string("hello, -53.5"));
+}
+
+TEST(CSSOStringStreamTest, concat)
+{ //
+ test_concat<Inkscape::CSSOStringStream>();
+}
+
+TEST(SVGOStringStreamTest, concat)
+{ //
+ test_concat<Inkscape::SVGOStringStream>();
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/uri-test.cpp b/testfiles/src/uri-test.cpp
new file mode 100644
index 0000000..91af2e7
--- /dev/null
+++ b/testfiles/src/uri-test.cpp
@@ -0,0 +1,304 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Test Inkscape::URI
+ */
+/*
+ * Authors:
+ * Thomas Holder
+ *
+ * Copyright (C) 2018 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "object/uri.h"
+#include "gtest/gtest.h"
+
+using Inkscape::URI;
+
+#define BASE64_HELLO_WORLD_P1 "SGVsbG8g"
+#define BASE64_HELLO_WORLD_P2 "V29ybGQ="
+#define DATA_BASE64_HEADER "data:text/plain;charset=utf-8;base64,"
+char const *DATA_BASE64_HELLO_WORLD = DATA_BASE64_HEADER BASE64_HELLO_WORLD_P1 BASE64_HELLO_WORLD_P2;
+char const *DATA_BASE64_HELLO_WORLD_WRAPPED = DATA_BASE64_HEADER BASE64_HELLO_WORLD_P1 "\n" BASE64_HELLO_WORLD_P2;
+
+char const *win_url_unc = "file://laptop/My%20Documents/FileSchemeURIs.doc";
+char const *win_url_local = "file:///C:/Documents%20and%20Settings/davris/FileSchemeURIs.doc";
+char const *win_filename_local = "C:\\Documents and Settings\\davris\\FileSchemeURIs.doc";
+
+TEST(UriTest, Malformed)
+{
+ ASSERT_ANY_THROW(URI(nullptr));
+ ASSERT_ANY_THROW(URI("nonhex-%XX"));
+}
+
+TEST(UriTest, GetPath)
+{
+ ASSERT_STREQ(URI().getPath(), nullptr);
+ ASSERT_STREQ(URI("foo.svg").getPath(), "foo.svg");
+ ASSERT_STREQ(URI("foo.svg#bar").getPath(), "foo.svg");
+ ASSERT_STREQ(URI("#bar").getPath(), nullptr);
+ ASSERT_STREQ(URI("scheme://host").getPath(), nullptr);
+ ASSERT_STREQ(URI("scheme://host/path").getPath(), "/path");
+ ASSERT_STREQ(URI("scheme://host/path?query").getPath(), "/path");
+ ASSERT_STREQ(URI("scheme:/path").getPath(), "/path");
+}
+
+TEST(UriTest, FromDir)
+{
+#ifdef _WIN32
+ ASSERT_EQ(URI::from_dirname("C:\\tmp").str(), "file:///C:/tmp/");
+ ASSERT_EQ(URI::from_dirname("C:\\").str(), "file:///C:/");
+ ASSERT_EQ(URI::from_href_and_basedir("uri.svg", "C:\\tmp").str(), "file:///C:/tmp/uri.svg");
+#else
+ ASSERT_EQ(URI::from_dirname("/").str(), "file:///");
+ ASSERT_EQ(URI::from_dirname("/tmp").str(), "file:///tmp/");
+ ASSERT_EQ(URI::from_href_and_basedir("uri.svg", "/tmp").str(), "file:///tmp/uri.svg");
+#endif
+}
+
+TEST(UriTest, Str)
+{
+ ASSERT_EQ(URI().str(), "");
+ ASSERT_EQ(URI("").str(), "");
+ ASSERT_EQ(URI("", "http://a/b").str(), "http://a/b");
+
+ ASSERT_EQ(URI("uri.svg").str(), "uri.svg");
+ ASSERT_EQ(URI("tmp/uri.svg").str(), "tmp/uri.svg");
+ ASSERT_EQ(URI("/tmp/uri.svg").str(), "/tmp/uri.svg");
+ ASSERT_EQ(URI("../uri.svg").str(), "../uri.svg");
+
+ ASSERT_EQ(URI("file:///tmp/uri.svg").str(), "file:///tmp/uri.svg");
+ ASSERT_EQ(URI("uri.svg", "file:///tmp/").str(), "file:///tmp/uri.svg");
+ ASSERT_EQ(URI("file:///tmp/uri.svg").str("file:///tmp/"), "uri.svg");
+ ASSERT_EQ(URI("file:///tmp/up/uri.svg").str("file:///tmp/"), "up/uri.svg");
+ ASSERT_EQ(URI("file:///tmp/uri.svg").str("file:///tmp/up/"), "../uri.svg");
+ ASSERT_EQ(URI("file:///tmp/uri.svg").str("http://web/url"), "file:///tmp/uri.svg");
+ ASSERT_EQ(URI("file:///tmp/uri.svg").str("http://web/url"), "file:///tmp/uri.svg");
+ ASSERT_EQ(URI("foo/uri.svg", "http://web/a/b/c").str(), "http://web/a/b/foo/uri.svg");
+ ASSERT_EQ(URI("foo/uri.svg", "http://web/a/b/c").str("http://web/a/"), "b/foo/uri.svg");
+ ASSERT_EQ(URI("foo/uri.svg", "http://web/a/b/c").str("http://other/a/"), "http://web/a/b/foo/uri.svg");
+
+ ASSERT_EQ(URI("http://web/").str("http://web/"), "");
+ ASSERT_EQ(URI("http://web/").str("http://web/url"), "./");
+
+ // special case: don't cross filesystem root
+ ASSERT_EQ(URI("file:///a").str("file:///"), "a");
+ ASSERT_EQ(URI("file:///ax/b").str("file:///ay/"), "file:///ax/b"); // special case
+ ASSERT_EQ(URI("file:///C:/b").str("file:///D:/"), "file:///C:/b"); // special case
+ ASSERT_EQ(URI("file:///C:/a/b").str("file:///C:/b/"), "../a/b");
+
+ ASSERT_EQ(URI(win_url_unc).str(), win_url_unc);
+ ASSERT_EQ(URI(win_url_unc).str("file://laptop/My%20Documents/"), "FileSchemeURIs.doc");
+ ASSERT_EQ(URI(win_url_local).str(), win_url_local);
+ ASSERT_EQ(URI(win_url_local).str("file:///C:/Documents%20and%20Settings/"), "davris/FileSchemeURIs.doc");
+ ASSERT_EQ(URI(win_url_local).str(win_url_unc), win_url_local);
+#ifdef _WIN32
+ ASSERT_EQ(URI(win_url_local).toNativeFilename(), win_filename_local);
+#else
+ ASSERT_EQ(URI("file:///tmp/uri.svg").toNativeFilename(), "/tmp/uri.svg");
+ ASSERT_EQ(URI("file:///tmp/x%20y.svg").toNativeFilename(), "/tmp/x y.svg");
+ ASSERT_EQ(URI("file:///a/b#hash").toNativeFilename(), "/a/b");
+#endif
+
+ ASSERT_ANY_THROW(URI("http://a/b").toNativeFilename());
+}
+
+TEST(UriTest, StrDataScheme)
+{
+ ASSERT_EQ(URI("data:,text").str(), "data:,text");
+ ASSERT_EQ(URI("data:,white%20space").str(), "data:,white%20space");
+ ASSERT_EQ(URI("data:,umlaut-%C3%96").str(), "data:,umlaut-%C3%96");
+ ASSERT_EQ(URI(DATA_BASE64_HELLO_WORLD).str(), DATA_BASE64_HELLO_WORLD);
+}
+
+TEST(UriTest, Escape)
+{
+ ASSERT_EQ(URI("data:,white space").str(), "data:,white%20space");
+ ASSERT_EQ(URI("data:,white\nspace").str(), "data:,white%0Aspace");
+ ASSERT_EQ(URI("data:,umlaut-\xC3\x96").str(), "data:,umlaut-%C3%96");
+}
+
+TEST(UriTest, GetContents)
+{
+ ASSERT_EQ(URI("data:,white space").getContents(), "white space");
+ ASSERT_EQ(URI("data:,white%20space").getContents(), "white space");
+ ASSERT_EQ(URI("data:,white\nspace").getContents(), "white\nspace");
+ ASSERT_EQ(URI("data:,white%0Aspace").getContents(), "white\nspace");
+ ASSERT_EQ(URI("data:,umlaut-%C3%96").getContents(), "umlaut-\xC3\x96");
+ ASSERT_EQ(URI(DATA_BASE64_HELLO_WORLD).getContents(), "Hello World");
+ ASSERT_EQ(URI(DATA_BASE64_HELLO_WORLD_WRAPPED).getContents(), "Hello World");
+
+ ASSERT_ANY_THROW(URI().getContents());
+}
+
+TEST(UriTest, CssStr)
+{
+ ASSERT_EQ(URI("file:///tmp/uri.svg").cssStr(), "url(file:///tmp/uri.svg)");
+ ASSERT_EQ(URI("uri.svg").cssStr(), "url(uri.svg)");
+}
+
+TEST(UriTest, GetMimeType)
+{
+ ASSERT_EQ(URI("data:image/png;base64,").getMimeType(), "image/png");
+ ASSERT_EQ(URI("data:text/plain,xxx").getMimeType(), "text/plain");
+ ASSERT_EQ(URI("file:///tmp/uri.png").getMimeType(), "image/png");
+ ASSERT_EQ(URI("uri.png").getMimeType(), "image/png");
+ ASSERT_EQ(URI("uri.svg").getMimeType(), "image/svg+xml");
+
+ // can be "text/plain" or "text/*"
+ ASSERT_EQ(URI("file:///tmp/uri.txt").getMimeType().substr(0, 5), "text/");
+}
+
+TEST(UriTest, HasScheme)
+{
+ ASSERT_FALSE(URI().hasScheme("file"));
+ ASSERT_FALSE(URI("uri.svg").hasScheme("file"));
+ ASSERT_FALSE(URI("uri.svg").hasScheme("data"));
+
+ ASSERT_TRUE(URI("file:///uri.svg").hasScheme("file"));
+ ASSERT_TRUE(URI("FILE:///uri.svg").hasScheme("file"));
+ ASSERT_FALSE(URI("file:///uri.svg").hasScheme("data"));
+
+ ASSERT_TRUE(URI("data:,").hasScheme("data"));
+ ASSERT_TRUE(URI("DaTa:,").hasScheme("data"));
+ ASSERT_FALSE(URI("data:,").hasScheme("file"));
+
+ ASSERT_TRUE(URI("http://web/").hasScheme("http"));
+ ASSERT_FALSE(URI("http://web/").hasScheme("file"));
+
+ ASSERT_TRUE(URI::from_href_and_basedir("data:,white\nspace", "/tmp").hasScheme("data"));
+}
+
+TEST(UriTest, isOpaque)
+{
+ ASSERT_FALSE(URI().isOpaque());
+ ASSERT_FALSE(URI("file:///uri.svg").isOpaque());
+ ASSERT_FALSE(URI("/uri.svg").isOpaque());
+ ASSERT_FALSE(URI("uri.svg").isOpaque());
+ ASSERT_FALSE(URI("foo://bar/baz").isOpaque());
+ ASSERT_FALSE(URI("foo://bar").isOpaque());
+ ASSERT_FALSE(URI("foo:/bar").isOpaque());
+
+ ASSERT_TRUE(URI("foo:bar").isOpaque());
+ ASSERT_TRUE(URI("mailto:user@host.xy").isOpaque());
+ ASSERT_TRUE(URI("news:comp.lang.java").isOpaque());
+}
+
+TEST(UriTest, isRelative)
+{
+ ASSERT_TRUE(URI().isRelative());
+
+ ASSERT_FALSE(URI("http://web/uri.svg").isRelative());
+ ASSERT_FALSE(URI("file:///uri.svg").isRelative());
+ ASSERT_FALSE(URI("mailto:user@host.xy").isRelative());
+ ASSERT_FALSE(URI("data:,").isRelative());
+
+ ASSERT_TRUE(URI("//web/uri.svg").isRelative());
+ ASSERT_TRUE(URI("/uri.svg").isRelative());
+ ASSERT_TRUE(URI("uri.svg").isRelative());
+ ASSERT_TRUE(URI("./uri.svg").isRelative());
+ ASSERT_TRUE(URI("../uri.svg").isRelative());
+}
+
+TEST(UriTest, isNetPath)
+{
+ ASSERT_FALSE(URI().isNetPath());
+ ASSERT_FALSE(URI("http://web/uri.svg").isNetPath());
+ ASSERT_FALSE(URI("file:///uri.svg").isNetPath());
+ ASSERT_FALSE(URI("/uri.svg").isNetPath());
+ ASSERT_FALSE(URI("uri.svg").isNetPath());
+
+ ASSERT_TRUE(URI("//web/uri.svg").isNetPath());
+}
+
+TEST(UriTest, isRelativePath)
+{
+ ASSERT_FALSE(URI("foo:bar").isRelativePath());
+ ASSERT_TRUE(URI("foo%3Abar").isRelativePath());
+
+ ASSERT_FALSE(URI("http://web/uri.svg").isRelativePath());
+ ASSERT_FALSE(URI("//web/uri.svg").isRelativePath());
+ ASSERT_FALSE(URI("/uri.svg").isRelativePath());
+
+ ASSERT_TRUE(URI("uri.svg").isRelativePath());
+ ASSERT_TRUE(URI("./uri.svg").isRelativePath());
+ ASSERT_TRUE(URI("../uri.svg").isRelativePath());
+}
+
+TEST(UriTest, isAbsolutePath)
+{
+ ASSERT_FALSE(URI().isAbsolutePath());
+ ASSERT_FALSE(URI("http://web/uri.svg").isAbsolutePath());
+ ASSERT_FALSE(URI("//web/uri.svg").isAbsolutePath());
+ ASSERT_FALSE(URI("uri.svg").isAbsolutePath());
+ ASSERT_FALSE(URI("../uri.svg").isAbsolutePath());
+
+ ASSERT_TRUE(URI("/uri.svg").isAbsolutePath());
+}
+
+TEST(UriTest, getScheme)
+{
+ ASSERT_STREQ(URI().getScheme(), nullptr);
+
+ ASSERT_STREQ(URI("https://web/uri.svg").getScheme(), "https");
+ ASSERT_STREQ(URI("file:///uri.svg").getScheme(), "file");
+ ASSERT_STREQ(URI("data:,").getScheme(), "data");
+
+ ASSERT_STREQ(URI("data").getScheme(), nullptr);
+}
+
+TEST(UriTest, getQuery)
+{
+ ASSERT_STREQ(URI().getQuery(), nullptr);
+ ASSERT_STREQ(URI("uri.svg?a=b&c=d").getQuery(), "a=b&c=d");
+ ASSERT_STREQ(URI("?a=b&c=d#hash").getQuery(), "a=b&c=d");
+}
+
+TEST(UriTest, getFragment)
+{
+ ASSERT_STREQ(URI().getFragment(), nullptr);
+ ASSERT_STREQ(URI("uri.svg").getFragment(), nullptr);
+ ASSERT_STREQ(URI("uri.svg#hash").getFragment(), "hash");
+ ASSERT_STREQ(URI("?a=b&c=d#hash").getFragment(), "hash");
+ ASSERT_STREQ(URI("urn:isbn:096139210x#hash").getFragment(), "hash");
+}
+
+TEST(UriTest, getOpaque)
+{
+ ASSERT_STREQ(URI().getOpaque(), nullptr);
+ ASSERT_STREQ(URI("urn:isbn:096139210x#hash").getOpaque(), "isbn:096139210x");
+ ASSERT_STREQ(URI("data:,foo").getOpaque(), ",foo");
+}
+
+TEST(UriTest, from_native_filename)
+{
+#ifdef _WIN32
+ ASSERT_EQ(URI::from_native_filename(win_filename_local).str(), win_url_local);
+#else
+ ASSERT_EQ(URI::from_native_filename("/tmp/uri.svg").str(), "file:///tmp/uri.svg");
+ ASSERT_EQ(URI::from_native_filename("/tmp/x y.svg").str(), "file:///tmp/x%20y.svg");
+#endif
+}
+
+TEST(UriTest, uri_to_iri)
+{
+ // unescape UTF-8 (U+00D6)
+ ASSERT_EQ(Inkscape::uri_to_iri("data:,umlaut-%C3%96"), "data:,umlaut-\xC3\x96");
+ // don't unescape ASCII (U+003A)
+ ASSERT_EQ(Inkscape::uri_to_iri("foo%3Abar"), "foo%3Abar");
+ // sequence (U+00D6 U+1F37A U+003A)
+ ASSERT_EQ(Inkscape::uri_to_iri("%C3%96%F0%9F%8D%BA%3A"), "\xC3\x96\xF0\x9F\x8D\xBA%3A");
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/util-test.cpp b/testfiles/src/util-test.cpp
new file mode 100644
index 0000000..785e3d3
--- /dev/null
+++ b/testfiles/src/util-test.cpp
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Test utilities from src/util
+ */
+/*
+ * Authors:
+ * Thomas Holder
+ *
+ * Copyright (C) 2020 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "gtest/gtest.h"
+#include "util/longest-common-suffix.h"
+
+TEST(UtilTest, NearestCommonAncestor)
+{
+#define nearest_common_ancestor(a, b, c) \
+ Inkscape::Algorithms::nearest_common_ancestor(a, b, c)
+
+ // simple node with a parent
+ struct Node
+ {
+ Node const *parent;
+ Node(Node const *p) : parent(p){};
+ Node(Node const &other) = delete;
+ };
+
+ // iterator which traverses towards the root node
+ struct iter
+ {
+ Node const *node;
+ iter(Node const &n) : node(&n) {}
+ bool operator==(iter const &rhs) const { return node == rhs.node; }
+ bool operator!=(iter const &rhs) const { return node != rhs.node; }
+ iter &operator++()
+ {
+ node = node->parent;
+ return *this;
+ }
+
+ // TODO remove, the implementation should not require this
+ Node const &operator*() const { return *node; }
+ };
+
+ // construct a tree
+ auto const node0 = Node(nullptr);
+ auto const node1 = Node(&node0);
+ auto const node2 = Node(&node1);
+ auto const node3a = Node(&node2);
+ auto const node4a = Node(&node3a);
+ auto const node5a = Node(&node4a);
+ auto const node3b = Node(&node2);
+ auto const node4b = Node(&node3b);
+ auto const node5b = Node(&node4b);
+
+ // start at each node from 5a to 0 (first argument)
+ ASSERT_EQ(nearest_common_ancestor(iter(node5a), iter(node5b), iter(node0)), iter(node2));
+ ASSERT_EQ(nearest_common_ancestor(iter(node4a), iter(node5b), iter(node0)), iter(node2));
+ ASSERT_EQ(nearest_common_ancestor(iter(node3a), iter(node5b), iter(node0)), iter(node2));
+ ASSERT_EQ(nearest_common_ancestor(iter(node2), iter(node5b), iter(node0)), iter(node2));
+ ASSERT_EQ(nearest_common_ancestor(iter(node1), iter(node5b), iter(node0)), iter(node1));
+ ASSERT_EQ(nearest_common_ancestor(iter(node0), iter(node5b), iter(node0)), iter(node0));
+
+ // start at each node from 5b to 0 (second argument)
+ ASSERT_EQ(nearest_common_ancestor(iter(node5a), iter(node5b), iter(node0)), iter(node2));
+ ASSERT_EQ(nearest_common_ancestor(iter(node5a), iter(node4b), iter(node0)), iter(node2));
+ ASSERT_EQ(nearest_common_ancestor(iter(node5a), iter(node3b), iter(node0)), iter(node2));
+ ASSERT_EQ(nearest_common_ancestor(iter(node5a), iter(node2), iter(node0)), iter(node2));
+ ASSERT_EQ(nearest_common_ancestor(iter(node5a), iter(node1), iter(node0)), iter(node1));
+ ASSERT_EQ(nearest_common_ancestor(iter(node5a), iter(node0), iter(node0)), iter(node0));
+
+ // identity (special case in implementation)
+ ASSERT_EQ(nearest_common_ancestor(iter(node5a), iter(node5a), iter(node0)), iter(node5a));
+
+ // identical parents (special case in implementation)
+ ASSERT_EQ(nearest_common_ancestor(iter(node3a), iter(node3b), iter(node0)), iter(node2));
+}
+
+// vim: filetype=cpp:expandtab:shiftwidth=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/xml-test.cpp b/testfiles/src/xml-test.cpp
new file mode 100644
index 0000000..dee272d
--- /dev/null
+++ b/testfiles/src/xml-test.cpp
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Test xml node
+ */
+/*
+ * Authors:
+ * Ted Gould
+ *
+ * Copyright (C) 2020 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "gtest/gtest.h"
+#include "xml/repr.h"
+
+TEST(XmlTest, nodeiter)
+{
+ auto testdoc = std::shared_ptr<Inkscape::XML::Document>(sp_repr_read_buf("<svg><g/></svg>", SP_SVG_NS_URI));
+ ASSERT_TRUE(testdoc);
+
+ auto count = 0;
+ for (auto &child : *testdoc->root()) {
+ ASSERT_STREQ(child.name(), "svg:g");
+ count++;
+ }
+ ASSERT_EQ(count, 1);
+
+ testdoc =
+ std::shared_ptr<Inkscape::XML::Document>(sp_repr_read_buf("<svg><g/><g/><g><g/></g></svg>", SP_SVG_NS_URI));
+ ASSERT_TRUE(testdoc);
+
+ count = 0;
+ for (auto &child : *testdoc->root()) {
+ ASSERT_STREQ(child.name(), "svg:g");
+ count++;
+ }
+ ASSERT_EQ(count, 3);
+
+ testdoc = std::shared_ptr<Inkscape::XML::Document>(sp_repr_read_buf(R"""(
+<svg>
+ <g/>
+ <!-- comment -->
+ <g>
+ <circle/>
+ </g>
+ <g>
+ <circle id='a'/>
+ <path id='b'/>
+ <path id='c'/>
+ </g>
+</svg>
+)""", SP_SVG_NS_URI));
+ ASSERT_TRUE(testdoc);
+
+ auto path = std::list<std::string>{"svg:g", "svg:path"};
+ auto found = testdoc->root()->findChildPath(path);
+ ASSERT_NE(found, nullptr);
+ ASSERT_STREQ(found->attribute("id"), "b");
+
+ // no such second element
+ path = {"svg:g", "svg:g"};
+ ASSERT_EQ(testdoc->root()->findChildPath(path), nullptr);
+
+ // no such first element
+ path = {"svg:symbol", "svg:path"};
+ ASSERT_EQ(testdoc->root()->findChildPath(path), nullptr);
+
+ // root with no children
+ testdoc = std::shared_ptr<Inkscape::XML::Document>(sp_repr_read_buf("<svg/>", SP_SVG_NS_URI));
+ ASSERT_EQ(testdoc->root()->findChildPath(path), nullptr);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :