summaryrefslogtreecommitdiffstats
path: root/testfiles/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:50:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:50:49 +0000
commitc853ffb5b2f75f5a889ed2e3ef89b818a736e87a (patch)
tree7d13a0883bb7936b84d6ecdd7bc332b41ed04bee /testfiles/src
parentInitial commit. (diff)
downloadinkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.tar.xz
inkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.zip
Adding upstream version 1.3+ds.upstream/1.3+dsupstream
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/async_channel-test.cpp76
-rw-r--r--testfiles/src/async_funclog-test.cpp120
-rw-r--r--testfiles/src/async_progress-test.cpp131
-rw-r--r--testfiles/src/attributes-test.cpp685
-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.cpp277
-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/drawing-pattern-test.cpp117
-rw-r--r--testfiles/src/extract-uri-test.cpp75
-rw-r--r--testfiles/src/lpe-test.cpp75
-rw-r--r--testfiles/src/lpe64-test.cpp30
-rw-r--r--testfiles/src/min-bbox-test.cpp73
-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/oklab-color-test.cpp171
-rw-r--r--testfiles/src/path-boolop-test.cpp86
-rw-r--r--testfiles/src/path-reverse-lpe-test.cpp49
-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-tags-test.cpp235
-rw-r--r--testfiles/src/sp-object-test.cpp122
-rw-r--r--testfiles/src/stream-test.cpp161
-rw-r--r--testfiles/src/style-elem-test.cpp71
-rw-r--r--testfiles/src/style-internal-test.cpp82
-rw-r--r--testfiles/src/style-test.cpp604
-rw-r--r--testfiles/src/svg-affine-test.cpp226
-rw-r--r--testfiles/src/svg-box-test.cpp122
-rw-r--r--testfiles/src/svg-color-test.cpp112
-rw-r--r--testfiles/src/svg-extension-test.cpp131
-rw-r--r--testfiles/src/svg-length-test.cpp225
-rw-r--r--testfiles/src/svg-path-geom-test.cpp507
-rw-r--r--testfiles/src/svg-stringstream-test.cpp156
-rw-r--r--testfiles/src/uri-test.cpp304
-rw-r--r--testfiles/src/util-test.cpp115
-rw-r--r--testfiles/src/visual-bounds-test.cpp132
-rw-r--r--testfiles/src/xml-test.cpp84
48 files changed, 7721 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/async_channel-test.cpp b/testfiles/src/async_channel-test.cpp
new file mode 100644
index 0000000..12dfc6f
--- /dev/null
+++ b/testfiles/src/async_channel-test.cpp
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <thread>
+#include <vector>
+#include <glibmm/main.h>
+#include <gtest/gtest.h>
+#include "async/channel.h"
+using namespace Inkscape::Async;
+
+TEST(Channel, channel)
+{
+ auto test_one = [] (bool soft_close, bool delay_src_destroy, bool delay_dst_destroy) {
+ auto mainloop = Glib::MainLoop::create();
+
+ std::optional<Channel::Source> src;
+ std::optional<Channel::Dest> dst;
+ std::tie(src, dst) = Channel::create();
+
+ std::thread thread;
+ std::vector<int> results;
+
+ Glib::signal_idle().connect([&] {
+ thread = std::thread([&] {
+ EXPECT_TRUE(src);
+
+ EXPECT_TRUE(src->run([&] { results.emplace_back(1); })); // insert temporary function
+
+ auto f = [&, x = 2] { results.emplace_back(x); };
+ EXPECT_TRUE(src->run(f)); // insert copy of function
+
+ auto g = [&, x = 3] { results.emplace_back(x); };
+ EXPECT_TRUE(src->run(std::move(g))); // insert function by move
+
+ // insert function which closes channel
+ EXPECT_TRUE(src->run([&] {
+ ASSERT_TRUE(dst);
+ ASSERT_TRUE(*dst);
+
+ if (delay_dst_destroy) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ }
+
+ if (soft_close) {
+ dst->close();
+ EXPECT_FALSE(*dst);
+ } else {
+ dst.reset();
+ }
+
+ mainloop->quit();
+ }));
+
+ src->run([&] { results.emplace_back(4); });
+
+ if (delay_src_destroy) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ src->run([&] { results.emplace_back(5); });
+ }
+
+ src.reset();
+ });
+
+ return false;
+ });
+
+ mainloop->run();
+ thread.join();
+
+ EXPECT_EQ(results, (std::vector<int>{ 1, 2, 3 }));
+ };
+
+ for (bool x : { true, false }) {
+ test_one(x, false, false);
+ test_one(x, true, false);
+ test_one(x, false, true);
+ }
+}
diff --git a/testfiles/src/async_funclog-test.cpp b/testfiles/src/async_funclog-test.cpp
new file mode 100644
index 0000000..a7dac7a
--- /dev/null
+++ b/testfiles/src/async_funclog-test.cpp
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <vector>
+#include <gtest/gtest.h>
+#include "util/funclog.h"
+using namespace Inkscape::Util;
+
+static int counter;
+
+class LoggedInt
+{
+public:
+ LoggedInt(int x) : x(x) { counter++; }
+ LoggedInt(LoggedInt const &other) noexcept : x(other.x) { counter++; }
+ LoggedInt &operator=(LoggedInt const &other) noexcept { x = other.x; return *this; }
+ ~LoggedInt() { counter--; }
+ operator int() const { return x; }
+ LoggedInt &operator=(int x2) { x = x2; return *this; }
+
+private:
+ int x;
+};
+
+TEST(FuncLogTest, funclog)
+{
+ counter = 0;
+
+ std::vector<int> results;
+ auto write = [&] (int x) { return [&, x = LoggedInt(x)] { results.emplace_back(x); }; };
+
+ auto compare = [&] (std::vector<int> expected) {
+ EXPECT_EQ(results, expected);
+ results.clear();
+ };
+
+ FuncLog a;
+ EXPECT_TRUE(a.empty());
+ a();
+ compare({});
+
+ a.emplace(write(1));
+ a.emplace(write(2));
+ EXPECT_EQ(counter, 2);
+ EXPECT_FALSE(a.empty());
+ a();
+ compare({ 1, 2 });
+
+ a.emplace(write(3));
+ auto b = std::move(a);
+ a();
+ compare({});
+ b();
+ compare({ 3 });
+ auto c = std::move(a);
+ c();
+ compare({});
+
+ b.emplace(write(4));
+ a = std::move(b);
+ b();
+ compare({});
+ a();
+ compare({ 4 });
+ a();
+ compare({});
+
+ for (int N : { 10, 50, 10, 100, 10, 500, 10 }) {
+ for (int i = 0; i < N; i++) {
+ a.emplace(write(4));
+ a.emplace([&, x = i, y = 2 * i, z = 3 * i, w = 4 * i] { results.emplace_back(x + y + z + w); });
+ }
+
+ a();
+
+ ASSERT_EQ(results.size(), 2 * N);
+ for (int i = 0; i < N; i++) {
+ ASSERT_EQ(results[2 * i], 4);
+ ASSERT_EQ(results[2 * i + 1], 10 * i);
+ }
+ results.clear();
+ }
+
+ {
+ auto f1 = [&, x = 1] {
+ results.emplace_back(x);
+ };
+ a.emplace(f1);
+
+ auto f2 = [&, x = 2] {
+ results.emplace_back(x);
+ };
+ a.emplace(std::move(f2));
+ }
+
+ a();
+ compare({ 1, 2 });
+
+ a.emplace([&, x = std::make_unique<int>(5)] { results.emplace_back(*x); });
+ a();
+ compare({ 5 });
+
+ FuncLog().emplace(write(6));
+ compare({});
+
+ for (int i = 0; i < 5; i++) {
+ a.emplace(write(i));
+ }
+ a.exec_while([counter = 0] () mutable { return ++counter <= 3; });
+ compare({ 0, 1, 2 });
+ EXPECT_TRUE(a.empty());
+
+ struct ExceptionMock {};
+ for (int i = 0; i < 5; i++) {
+ a.emplace([&, i] { if (i == 3) throw ExceptionMock(); results.emplace_back(i); });
+ }
+ EXPECT_THROW(a(), ExceptionMock);
+ compare({ 0, 1, 2 });
+ EXPECT_TRUE(a.empty());
+
+ ASSERT_EQ(counter, 0);
+}
diff --git a/testfiles/src/async_progress-test.cpp b/testfiles/src/async_progress-test.cpp
new file mode 100644
index 0000000..039d6ce
--- /dev/null
+++ b/testfiles/src/async_progress-test.cpp
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <functional>
+#include <optional>
+#include <cmath>
+#include <gtest/gtest.h>
+#include "async/progress.h"
+#include "async/progress-splitter.h"
+using namespace Inkscape::Async;
+
+TEST(ProgressTest, subprogress)
+{
+ class ProgressMock final
+ : public Progress<double>
+ {
+ public:
+ mutable bool k_called;
+ double p_arg_saved;
+ bool ret;
+
+ void reset(bool ret_)
+ {
+ k_called = false;
+ p_arg_saved = -1.0;
+ ret = ret_;
+ }
+
+ protected:
+ bool _keepgoing() const override { k_called = true; return ret; }
+ bool _report(double const &progress) override { p_arg_saved = progress; return ret; }
+ };
+
+ auto a = ProgressMock();
+ auto b = SubProgress(a, 0.25, 0.5);
+ auto c = SubProgress(b, 0.1, 0.2);
+
+ for (bool ret : { true, false }) {
+ for (double progress = 0.0; progress < 1.0; progress += 0.3) {
+ a.reset(ret);
+ EXPECT_EQ(c.report(progress), ret);
+ EXPECT_NEAR(a.p_arg_saved, 0.25 + 0.5 * (0.1 + 0.2 * progress), 1e-5);
+ EXPECT_FALSE(a.k_called);
+ }
+ }
+
+ for (bool ret : { true, false }) {
+ a.reset(ret);
+ EXPECT_EQ(c.keepgoing(), ret);
+ EXPECT_EQ(a.p_arg_saved, -1.0);
+ EXPECT_TRUE(a.k_called);
+ }
+
+ a.reset(false);
+ EXPECT_THROW(c.report_or_throw(0.5), CancelledException);
+ EXPECT_THROW(c.throw_if_cancelled(), CancelledException);
+ a.reset(true);
+ EXPECT_NO_THROW(c.report_or_throw(0.5));
+ EXPECT_NO_THROW(c.throw_if_cancelled());
+}
+
+TEST(ProgressTest, throttler)
+{
+ class ProgressMock final
+ : public Progress<double>
+ {
+ public:
+ int calls = 0;
+ double saved = 0.0;
+
+ protected:
+ bool _keepgoing() const override { return true; }
+ bool _report(double const &progress) override { saved = progress; calls++; return true; }
+ };
+
+ double constexpr step = 0.1;
+ auto a = ProgressMock();
+ auto b = ProgressStepThrottler(a, step);
+
+ int constexpr N = 1000;
+ for (int i = 0; i < N; i++) {
+ double progress = (double)i / N;
+ b.report(progress);
+ ASSERT_LE(std::abs(progress - a.saved), 1.1 * step);
+ }
+ ASSERT_GE(a.calls, 9);
+ ASSERT_LE(a.calls, 11);
+}
+
+TEST(ProgressTest, splitter)
+{
+ class ProgressMock final
+ : public Progress<double>
+ {
+ public:
+ double saved;
+
+ protected:
+ bool _keepgoing() const override { return true; }
+ bool _report(double const &progress) override { saved = progress; return true; }
+ };
+
+ auto a = ProgressMock();
+ std::optional<SubProgress<double>> x, y, z;
+
+ auto reset = [&] {
+ a.saved = -1.0;
+ x = y = z = {};
+ };
+
+ reset();
+ ProgressSplitter(a)
+ .add(x, 0.25)
+ .add(y, 0.5)
+ .add(z, 0.25);
+ ASSERT_TRUE(x);
+ ASSERT_TRUE(y);
+ ASSERT_TRUE(z);
+ x->report(0.5); EXPECT_NEAR(a.saved, 0.125, 1e-5);
+ y->report(0.5); EXPECT_NEAR(a.saved, 0.5 , 1e-5);
+ z->report(0.5); EXPECT_NEAR(a.saved, 0.875, 1e-5);
+
+ reset();
+ ProgressSplitter(a)
+ .add_if(x, 0.25, true)
+ .add_if(y, 0.5, false)
+ .add_if(z, 0.25, true);
+ ASSERT_TRUE(x);
+ ASSERT_FALSE(y);
+ ASSERT_TRUE(z);
+ x->report(0.5); EXPECT_NEAR(a.saved, 0.25, 1e-5);
+ z->report(0.5); EXPECT_NEAR(a.saved, 0.75, 1e-5);
+}
diff --git a/testfiles/src/attributes-test.cpp b/testfiles/src/attributes-test.cpp
new file mode 100644
index 0000000..2fa8059
--- /dev/null
+++ b/testfiles/src/attributes-test.cpp
@@ -0,0 +1,685 @@
+// 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("margin", true),
+ AttributeInfo("bleed", true),
+ AttributeInfo("page-size", 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("inkscape:pinned", 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),
+ AttributeInfo("labelstyle", true),
+
+ // SPGrid
+ AttributeInfo("originx", true),
+ AttributeInfo("originy", true),
+ AttributeInfo("spacingx", true),
+ AttributeInfo("spacingy", true),
+ AttributeInfo("gridanglex", true),
+ AttributeInfo("gridanglez", true),
+ AttributeInfo("enabled", true),
+ AttributeInfo("visible", true),
+ AttributeInfo("empopacity", true),
+ AttributeInfo("empcolor", true),
+ AttributeInfo("empspacing", true),
+ AttributeInfo("dotted", true),
+ AttributeInfo("snapvisiblegridlinesonly", 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);
+ }
+ }
+ }
+}
+
+// Equivalent aliases, e.g. with and without namespace
+TEST(AttributesTest, Aliases)
+{
+ EXPECT_EQ(sp_attribute_lookup("href"), SPAttr::XLINK_HREF);
+}
+
+/* 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..4c73496
--- /dev/null
+++ b/testfiles/src/curve-test.cpp
@@ -0,0 +1,277 @@
+// 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;
+
+ 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, 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..afc46db
--- /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();
+ style_elem->document = _doc.get();
+
+ style_elem->setKeyValue( SPAttr::TYPE, "something unrecognized");
+ TS_ASSERT( !style_elem->is_css );
+
+ style_elem->setKeyValue( SPAttr::TYPE, "text/css");
+ TS_ASSERT( style_elem->is_css );
+
+ style_elem->setKeyValue( SPAttr::TYPE, "atext/css");
+ TS_ASSERT( !style_elem->is_css );
+
+ 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();
+ style_elem->document = _doc.get();
+
+ style_elem->setKeyValue( SPAttr::TYPE, "text/css");
+ Inkscape::XML::Node *repr = _doc->getReprDoc()->createElement("svg:style");
+ 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/drawing-pattern-test.cpp b/testfiles/src/drawing-pattern-test.cpp
new file mode 100644
index 0000000..c395169
--- /dev/null
+++ b/testfiles/src/drawing-pattern-test.cpp
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Test drawing_pattern_test
+ */
+/*
+ * Authors:
+ * PBS <pbs3141@gmail.com>
+ *
+ * Copyright (C) 2022 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <gtest/gtest.h>
+
+#include <cairomm/surface.h>
+#include <2geom/int-rect.h>
+#include <2geom/int-point.h>
+
+#include "inkscape.h"
+#include "document.h"
+#include "object/sp-root.h"
+#include "display/drawing.h"
+#include "display/drawing-surface.h"
+#include "display/drawing-context.h"
+
+TEST(DrawingPatternTest, fragments)
+{
+ if (!Inkscape::Application::exists()) {
+ Inkscape::Application::create(false);
+ }
+
+ auto doc = std::unique_ptr<SPDocument>(SPDocument::createNewDoc(INKSCAPE_TESTS_DIR "/rendering_tests/drawing-pattern-test.svg", false));
+ ASSERT_TRUE((bool)doc);
+ ASSERT_TRUE((bool)doc->getRoot());
+
+ doc->ensureUpToDate();
+
+ class Display
+ {
+ public:
+ Display(SPDocument *doc) {
+ root = doc->getRoot();
+ dkey = SPItem::display_key_new(1);
+ rootitem = root->invoke_show(drawing, dkey, SP_ITEM_SHOW_DISPLAY);
+ drawing.setRoot(rootitem);
+ drawing.update();
+ }
+
+ ~Display()
+ {
+ root->invoke_hide(dkey);
+ }
+
+ auto draw(Geom::IntRect const &rect)
+ {
+ auto cs = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, rect.width(), rect.height());
+ auto ds = Inkscape::DrawingSurface(cs->cobj(), rect.min());
+ auto dc = Inkscape::DrawingContext(ds);
+ drawing.render(dc, rect);
+ return cs;
+ }
+
+ private:
+ Inkscape::Drawing drawing;
+ SPRoot *root;
+ Inkscape::DrawingItem *rootitem;
+ unsigned dkey;
+ };
+
+ auto const tile = Geom::IntPoint(30, 30);
+ auto const area = Geom::IntRect::from_xywh(0, 0, 100, 100);
+
+ auto const reference = Display(doc.get()).draw(area);
+
+ uint32_t state = 0;
+ auto rand = [&] {
+ state = (state * 1103515245) + 12345;
+ return state;
+ };
+ rand();
+
+ auto randrect = [&] {
+ int w = rand() % tile.x() / 3 + 1;
+ int h = rand() % tile.y() / 3 + 1;
+ int x = rand() % (area.width() - w + 1);
+ int y = rand() % (area.height() - h + 1);
+ return Geom::IntRect::from_xywh(x, y, w, h);
+ };
+
+ int maxdiff = 0;
+ auto compare = [&] (Cairo::RefPtr<Cairo::ImageSurface> const &part, Geom::IntPoint const &off) {
+ for (int y = 0; y < part->get_height(); y++) {
+ auto p = reference->get_data() + (off.y() + y) * reference->get_stride() + off.x() * 4;
+ auto q = part->get_data() + y * part->get_stride();
+ for (int x = 0; x < part->get_width(); x++) {
+ for (int c = 0; c < 4; c++) {
+ auto diff = std::abs((int)p[c] - (int)q[c]);
+ maxdiff = std::max(maxdiff, diff);
+ }
+ p += 4;
+ q += 4;
+ }
+ }
+ };
+
+ for (int j = 0; j < 5; j++) {
+ auto d = Display(doc.get());
+ for (int i = 0; i < 20; i++) {
+ auto const rect = randrect();
+ auto const part = d.draw(rect);
+ compare(part, rect.min());
+ }
+ }
+
+ ASSERT_LE(maxdiff, 10);
+}
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..b3e9786
--- /dev/null
+++ b/testfiles/src/lpe-test.cpp
@@ -0,0 +1,75 @@
+// 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, Inkscape_0_92) { run(); }
+TEST_F(LPETest, Inkscape_1_0) { run(); }
+TEST_F(LPETest, Inkscape_1_1) { run(); }
+TEST_F(LPETest, Inkscape_1_2) { run(); }
+TEST_F(LPETest, Inkscape_1_3) { 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 = 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 = 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..6bfd8a7
--- /dev/null
+++ b/testfiles/src/lpe64-test.cpp
@@ -0,0 +1,30 @@
+// 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, Inkscape_0_92_64) { run(); }
+TEST_F(LPE64Test, Inkscape_1_0_64) { run(); }
+// B) CUSTOM TESTS \ No newline at end of file
diff --git a/testfiles/src/min-bbox-test.cpp b/testfiles/src/min-bbox-test.cpp
new file mode 100644
index 0000000..ccacd62
--- /dev/null
+++ b/testfiles/src/min-bbox-test.cpp
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <2geom/convex-hull.h>
+#include <2geom/transforms.h>
+#include <gtest/gtest.h>
+#include <helper/geom.h>
+
+// Get the axis-aligned bouding box of a set of points, transforming by affine first.
+auto aligned_bbox(std::vector<Geom::Point> const &pts, Geom::Affine const &affine = Geom::identity())
+{
+ Geom::OptRect rect;
+ for (auto &pt : pts) {
+ rect.expandTo(pt * affine);
+ }
+ return rect;
+}
+
+double area(Geom::OptRect const &rect)
+{
+ return rect ? rect->area() : 0.0;
+}
+
+// Get an approximation to the minimum bouding box area.
+double approx_min(std::vector<Geom::Point> const &pts)
+{
+ int constexpr N = 100;
+
+ double min = std::numeric_limits<double>::max();
+
+ for (int i = 0; i < N; i++) {
+ auto t = (double)i / N * M_PI * 0.5;
+ min = std::min(min, area(aligned_bbox(pts, Geom::Rotate(t))));
+ }
+
+ return min;
+}
+
+// Get a crude random double.
+double ranf()
+{
+ int constexpr N = 1000;
+ return (double)(rand() % N) / N;
+}
+
+// Get a random collection of points.
+auto randpts()
+{
+ std::vector<Geom::Point> pts;
+
+ int count = 5 + (rand() % 10);
+ for (int i = 0; i < count; i++) {
+ pts.emplace_back(ranf(), ranf());
+ }
+
+ return pts;
+}
+
+TEST(MinBBoxTest, random)
+{
+ for (int i = 0; i < 100; i++) {
+ auto const pts = randpts();
+ auto [affine, rect] = min_bounding_box(pts);
+
+ ASSERT_TRUE(affine.isRotation());
+
+ auto rect2 = aligned_bbox(pts, affine);
+ for (int i = 0; i < 2; i++) {
+ ASSERT_NEAR(rect.min()[i], rect2->min()[i], 1e-5);
+ ASSERT_NEAR(rect.max()[i], rect2->max()[i], 1e-5);
+ }
+
+ ASSERT_LE(rect.area(), approx_min(pts));
+ }
+}
diff --git a/testfiles/src/object-set-test.cpp b/testfiles/src/object-set-test.cpp
new file mode 100644
index 0000000..878e6ef
--- /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(cast<SPRect>(_doc->getObjectByRepr(repr)));
+ repr = _doc->getReprDoc()->createElement("svg:rect");
+ _doc->getRoot()->appendChild(repr);
+ r2.reset(cast<SPRect>(_doc->getObjectByRepr(repr)));
+ repr = _doc->getReprDoc()->createElement("svg:rect");
+ _doc->getRoot()->appendChild(repr);
+ r3.reset(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
+};
+
+bool containsClone(ObjectSet* set) {
+ for (auto it : set->items()) {
+ if (is<SPUse>(it)) {
+ return true;
+ }
+ if (is<SPGroup>(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));
+ EXPECT_TRUE(set->includes(A->getRepr()));
+ 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(D->getRepr()));
+ EXPECT_FALSE(set->includes(X));
+ EXPECT_FALSE(set->includes((SPObject*)nullptr));
+ EXPECT_FALSE(set->includes((Inkscape::XML::Node*)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,cast<SPUse>(*(set->items().begin())));
+ EXPECT_EQ(nullptr,cast<SPRect>(*(set->items().begin())));
+ set->unlink();
+ EXPECT_EQ(N + 6, _doc->getRoot()->children.size());
+ EXPECT_EQ(3, set->size());
+ EXPECT_EQ(nullptr,cast<SPUse>(*(set->items().begin())));
+ EXPECT_NE(nullptr,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,cast<SPUse>( ((SPUse*)(*(set->items().begin())))->get_original()));//"original is a Use"
+ set->unlink(); //clone of clone of rect -> rect
+ EXPECT_EQ(nullptr,cast<SPUse>(*(set->items().begin())));
+ EXPECT_NE(nullptr,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,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, cast<SPUse>(*(set->items().begin())));
+ EXPECT_EQ(nullptr, 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, cast<SPUse>(*(set->items().begin())));
+ EXPECT_NE(nullptr, 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, 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, cast<SPUse>(*(set->items().begin())));
+ EXPECT_NE(nullptr, 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, 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,cast<SPPath>(x));
+ EXPECT_EQ(nullptr,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->rotate(180);
+ 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 = 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 = cast<SPRect>(markerchildren[0]);
+ auto *markerrect2 = 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..0223d06
--- /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());
+
+ auto one = 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"));
+
+ auto two = 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"));
+
+ auto three = 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"));
+
+ auto four = 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());
+
+ auto one = 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);
+
+ auto two = 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);
+
+ auto three = 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);
+
+ auto four = 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());
+
+ auto five = 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());
+
+ auto six = 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);
+
+ auto seven = 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);
+
+ auto eight = 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..a2e69e5
--- /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());
+
+ auto path = 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());
+
+ auto group = 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;
+ }
+
+ auto n_group = 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);
+
+ // 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());
+
+ auto path = 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/oklab-color-test.cpp b/testfiles/src/oklab-color-test.cpp
new file mode 100644
index 0000000..f4cc949
--- /dev/null
+++ b/testfiles/src/oklab-color-test.cpp
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file Tests for the OKLab/OKLch color space backend.
+ */
+/*
+ * Authors:
+ * RafaƂ Siejakowski <rs@rs-math.net>
+ *
+ * Copyright (C) 2022 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtest/gtest.h>
+#include <glib.h>
+
+#include "color.h"
+#include "oklab.h"
+
+unsigned constexpr L=0, A=1, B=2;
+double constexpr EPS = 1e-7;
+
+inline Oklab::Triplet random_triplet()
+{
+ return { g_random_double_range(0.0, 1.0),
+ g_random_double_range(0.0, 1.0),
+ g_random_double_range(0.0, 1.0) };
+}
+
+/** Test converting black and white to OKLab. */
+TEST(OklabColorTest, BlackWhite)
+{
+ using namespace Oklab;
+
+ auto const black = linear_rgb_to_oklab({0, 0, 0});
+ EXPECT_NEAR(black[L], 0.0, EPS);
+ EXPECT_NEAR(black[A], 0.0, EPS);
+ EXPECT_NEAR(black[B], 0.0, EPS);
+
+ auto const white = linear_rgb_to_oklab({1.0, 1.0, 1.0});
+ EXPECT_NEAR(white[L], 1.0, EPS);
+ EXPECT_NEAR(white[A], 0.0, EPS);
+ EXPECT_NEAR(white[B], 0.0, EPS);
+}
+
+/** Test linear RGB -> OKLab -> linear RGB roundtrip. */
+TEST(OKlabColorTest, RGBRoundrtip)
+{
+ using namespace Oklab;
+ g_random_set_seed(13375336); // We always seed for tests' repeatability
+
+ for (unsigned i = 0; i < 10'000; i++) {
+ Triplet rgb = random_triplet();
+ auto const roundtrip = oklab_to_linear_rgb(linear_rgb_to_oklab(rgb));
+ for (size_t i : {0, 1, 2}) {
+ EXPECT_NEAR(roundtrip[i], rgb[i], EPS);
+ }
+ }
+}
+
+/** Test OKLab -> linear RGB -> OKLab roundtrip. */
+TEST(OKlabColorTest, OklabRoundrtip)
+{
+ using namespace Oklab;
+ g_random_set_seed(0xCAFECAFE);
+
+ for (unsigned i = 0; i < 10'000; i++) {
+ Triplet lab = linear_rgb_to_oklab(random_triplet());
+ auto const roundtrip = linear_rgb_to_oklab(oklab_to_linear_rgb(lab));
+ for (size_t i : {0, 1, 2}) {
+ EXPECT_NEAR(roundtrip[i], lab[i], EPS);
+ }
+ }
+}
+
+/** Test OKLab -> OKLch -> OKLab roundtrip. */
+TEST(OKlabColorTest, PolarRectRoundrtip)
+{
+ using namespace Oklab;
+ g_random_set_seed(0xB747A380);
+
+ for (unsigned i = 0; i < 10'000; i++) {
+ Triplet lab = linear_rgb_to_oklab(random_triplet());
+ auto const roundtrip = oklch_to_oklab(oklab_to_oklch(lab));
+ for (size_t i : {1, 2}) { // No point testing [0] since L == L
+ EXPECT_NEAR(roundtrip[i], lab[i], EPS);
+ }
+ }
+}
+
+/** Test OKLch -> OKLab -> OKLch roundtrip. */
+TEST(OKlabColorTest, RectPolarRoundrtip)
+{
+ using namespace Oklab;
+ g_random_set_seed(0xFA18B52);
+
+ for (unsigned i = 0; i < 10'000; i++) {
+ Triplet lch = oklab_to_oklch(linear_rgb_to_oklab(random_triplet()));
+ auto const roundtrip = oklab_to_oklch(oklch_to_oklab(lch));
+ for (size_t i : {1, 2}) { // No point testing [0]
+ EXPECT_NEAR(roundtrip[i], lch[i], EPS);
+ }
+ }
+}
+
+/** Test maximum chroma calculations. */
+TEST(OKlabColorTest, Saturate)
+{
+ using namespace Oklab;
+ g_random_set_seed(0x987654);
+
+ /** Test whether a number lies near to the endpoint of the unit interval. */
+ auto const near_end = [](double x) -> bool {
+ return x > 0.999 || x < 0.0001;
+ };
+
+ for (unsigned i = 0; i < 10'000; i++) {
+ // Get a random l, h pair and compute the maximum chroma.
+ auto [l, _, h] = oklab_to_oklch(linear_rgb_to_oklab(random_triplet()));
+ auto const chromax = max_chroma(l, h);
+
+ // Try maximally saturating the color and verifying that after converting
+ // the result to RGB we end up hitting the boundary of the sRGB gamut.
+ auto [r, g, b] = oklab_to_linear_rgb(oklch_to_oklab({l, chromax, h}));
+ EXPECT_TRUE(near_end(r) || near_end(g) || near_end(b));
+ }
+}
+
+/** Test OKHSL -> OKLab -> OKHSL conversion roundtrip. */
+TEST(OKlabColorTest, HSLabRoundtrip)
+{
+ using namespace Oklab;
+ g_random_set_seed(908070);
+
+ for (unsigned i = 0; i < 10'000; i++) {
+ auto const hsl = random_triplet();
+ if (hsl[1] < 0.001) {
+ // Grayscale colors don't have unique hues,
+ // so we skip them (mapping is not bijective).
+ continue;
+ }
+ auto const roundtrip = oklab_to_okhsl(okhsl_to_oklab(hsl));
+ for (size_t i : {0, 1, 2}) {
+ EXPECT_NEAR(roundtrip[i], hsl[i], EPS);
+ }
+ }
+}
+
+/** Test OKLab -> OKHSL -> OKLab conversion roundtrip. */
+TEST(OKlabColorTest, LabHSLRoundtrip)
+{
+ using namespace Oklab;
+ g_random_set_seed(5043071);
+
+ for (unsigned i = 0; i < 10'000; i++) {
+ auto const lab = linear_rgb_to_oklab(random_triplet());
+ auto const roundtrip = okhsl_to_oklab(oklab_to_okhsl(lab));
+ for (size_t i : {0, 1, 2}) {
+ EXPECT_NEAR(roundtrip[i], lab[i], EPS);
+ }
+ }
+}
+
+/*
+ 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 : \ No newline at end of file
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/path-reverse-lpe-test.cpp b/testfiles/src/path-reverse-lpe-test.cpp
new file mode 100644
index 0000000..6e5b21e
--- /dev/null
+++ b/testfiles/src/path-reverse-lpe-test.cpp
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Test for https://gitlab.com/inkscape/inkscape/-/issues/3393
+ *//*
+ *
+ * Authors:
+ * Thomas Holder
+ *
+ * Copyright (C) 2022 Authors
+ *
+ * Released under GNU GPL version 2 or later, read the file 'COPYING' for more information
+ */
+
+#include <doc-per-case-test.h>
+#include <gtest/gtest.h>
+#include <src/object/object-set.h>
+#include <src/object/sp-shape.h>
+
+using namespace Inkscape;
+
+static char const *const docString = R"""(<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
+ <defs>
+ <inkscape:path-effect effect="skeletal" copytype="repeated"
+ id="lpe1" pattern="M 0,0 5,5 0,10" />
+ </defs>
+ <path id="path1"
+ inkscape:path-effect="#lpe1"
+ inkscape:original-d="M 5,10 H 15"
+ d="M 5,5 10,10 5,15 M 10,5 15,10 10,15" />
+</svg>
+)""";
+
+TEST_F(DocPerCaseTest, PathReverse)
+{
+ auto doc = std::unique_ptr<SPDocument>(SPDocument::createNewDocFromMem(docString, strlen(docString), false));
+ doc->ensureUpToDate();
+
+ auto path1 = cast<SPShape>(doc->getObjectById("path1"));
+ auto oset = ObjectSet(doc.get());
+ oset.add(path1);
+
+ ASSERT_EQ(*path1->curve()->first_point(), Geom::Point(5, 5));
+
+ oset.pathReverse();
+
+ ASSERT_EQ(*path1->curve()->first_point(), Geom::Point(15, 15));
+}
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..aefd7d3
--- /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 = cast<SPGroup>(doc->getObjectById("group1"));
+ Effect::createAndApply(POWERCLIP, doc, group);
+
+ ASSERT_FALSE(group->hasPathEffect());
+}
diff --git a/testfiles/src/sp-object-tags-test.cpp b/testfiles/src/sp-object-tags-test.cpp
new file mode 100644
index 0000000..89db015
--- /dev/null
+++ b/testfiles/src/sp-object-tags-test.cpp
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <gtest/gtest.h>
+#include <functional>
+#include <typeinfo>
+
+#include "object/box3d.h"
+#include "object/box3d-side.h"
+#include "object/color-profile.h"
+#include "object/persp3d.h"
+#include "object/sp-anchor.h"
+#include "object/sp-clippath.h"
+#include "object/sp-defs.h"
+#include "object/sp-desc.h"
+#include "object/sp-ellipse.h"
+#include "object/sp-filter.h"
+#include "object/sp-flowdiv.h"
+#include "object/sp-flowregion.h"
+#include "object/sp-flowtext.h"
+#include "object/sp-font.h"
+#include "object/sp-font-face.h"
+#include "object/sp-glyph.h"
+#include "object/sp-glyph-kerning.h"
+#include "object/sp-grid.h"
+#include "object/sp-guide.h"
+#include "object/sp-hatch.h"
+#include "object/sp-hatch-path.h"
+#include "object/sp-image.h"
+#include "object/sp-line.h"
+#include "object/sp-linear-gradient.h"
+#include "object/sp-marker.h"
+#include "object/sp-mask.h"
+#include "object/sp-mesh-gradient.h"
+#include "object/sp-mesh-patch.h"
+#include "object/sp-mesh-row.h"
+#include "object/sp-metadata.h"
+#include "object/sp-missing-glyph.h"
+#include "object/sp-namedview.h"
+#include "object/sp-offset.h"
+#include "object/sp-page.h"
+#include "object/sp-path.h"
+#include "object/sp-pattern.h"
+#include "object/sp-polyline.h"
+#include "object/sp-radial-gradient.h"
+#include "object/sp-rect.h"
+#include "object/sp-root.h"
+#include "object/sp-script.h"
+#include "object/sp-solid-color.h"
+#include "object/sp-spiral.h"
+#include "object/sp-star.h"
+#include "object/sp-stop.h"
+#include "object/sp-string.h"
+#include "object/sp-style-elem.h"
+#include "object/sp-switch.h"
+#include "object/sp-symbol.h"
+#include "object/sp-tag.h"
+#include "object/sp-tag-use.h"
+#include "object/sp-text.h"
+#include "object/sp-textpath.h"
+#include "object/sp-title.h"
+#include "object/sp-tref.h"
+#include "object/sp-tspan.h"
+#include "object/sp-use.h"
+#include "live_effects/lpeobject.h"
+#include "object/filters/blend.h"
+#include "object/filters/colormatrix.h"
+#include "object/filters/componenttransfer.h"
+#include "object/filters/componenttransfer-funcnode.h"
+#include "object/filters/composite.h"
+#include "object/filters/convolvematrix.h"
+#include "object/filters/diffuselighting.h"
+#include "object/filters/displacementmap.h"
+#include "object/filters/distantlight.h"
+#include "object/filters/flood.h"
+#include "object/filters/gaussian-blur.h"
+#include "object/filters/image.h"
+#include "object/filters/merge.h"
+#include "object/filters/mergenode.h"
+#include "object/filters/morphology.h"
+#include "object/filters/offset.h"
+#include "object/filters/pointlight.h"
+#include "object/filters/specularlighting.h"
+#include "object/filters/spotlight.h"
+#include "object/filters/tile.h"
+#include "object/filters/turbulence.h"
+
+namespace {
+
+// Error reporting function, because asserts can only be used inside test body.
+using F = std::function<void(char const*, char const*, bool, bool)>;
+
+// Ensure tree structure is consistent with actual class hierarchy.
+template <typename A, typename B>
+void test_real(F const &f)
+{
+ constexpr bool b1 = std::is_base_of_v<A, B>;
+ constexpr bool b2 = first_tag<A> <= tag_of<B> && tag_of<B> <= last_tag<A>;
+
+ if constexpr (b1 != b2) {
+ f(typeid(A).name(), typeid(B).name(), b1, b2);
+ }
+}
+
+template <typename A, typename B>
+void test_dispatcher2(F const &f)
+{
+ test_real<A, B>(f);
+ test_real<B, A>(f);
+}
+
+template <typename A, typename B, typename... T>
+void test_dispatcher(F const &f)
+{
+ test_dispatcher2<A, B>(f);
+ if constexpr (sizeof...(T) >= 1) {
+ test_dispatcher<A, T...>(f);
+ }
+}
+
+// Calls test_real<A, B> for all distinct pairs of types A, B in T.
+template <typename A, typename... T>
+void test(F const &f)
+{
+ if constexpr (sizeof...(T) >= 1) {
+ test_dispatcher<A, T...>(f);
+ }
+ if constexpr (sizeof...(T) >= 2) {
+ test<T...>(f);
+ }
+}
+
+} // namespace
+
+TEST(SPObjectTagsTest, compare_dynamic_cast)
+{
+ test<
+ SPObject,
+ Inkscape::ColorProfile,
+ LivePathEffectObject,
+ Persp3D,
+ SPDefs,
+ SPDesc,
+ SPFeDistantLight,
+ SPFeFuncNode,
+ SPFeMergeNode,
+ SPFePointLight,
+ SPFeSpotLight,
+ SPFilter,
+ SPFilterPrimitive,
+ SPFeBlend,
+ SPFeColorMatrix,
+ SPFeComponentTransfer,
+ SPFeComposite,
+ SPFeConvolveMatrix,
+ SPFeDiffuseLighting,
+ SPFeDisplacementMap,
+ SPFeFlood,
+ SPFeImage,
+ SPFeMerge,
+ SPFeMorphology,
+ SPFeOffset,
+ SPFeSpecularLighting,
+ SPFeTile,
+ SPFeTurbulence,
+ SPGaussianBlur,
+ SPFlowline,
+ SPFlowregionbreak,
+ SPFont,
+ SPFontFace,
+ SPGlyph,
+ SPGlyphKerning,
+ SPHkern,
+ SPVkern,
+ SPGrid,
+ SPGuide,
+ SPHatchPath,
+ SPItem,
+ SPFlowdiv,
+ SPFlowpara,
+ SPFlowregion,
+ SPFlowregionExclude,
+ SPFlowtext,
+ SPFlowtspan,
+ SPImage,
+ SPLPEItem,
+ SPGroup,
+ SPAnchor,
+ SPBox3D,
+ SPMarker,
+ SPRoot,
+ SPSwitch,
+ SPSymbol,
+ SPShape,
+ SPGenericEllipse,
+ SPLine,
+ SPOffset,
+ SPPath,
+ SPPolyLine,
+ SPPolygon,
+ Box3DSide,
+ SPRect,
+ SPSpiral,
+ SPStar,
+ SPTRef,
+ SPTSpan,
+ SPText,
+ SPTextPath,
+ SPUse,
+ SPMeshpatch,
+ SPMeshrow,
+ SPMetadata,
+ SPMissingGlyph,
+ SPObjectGroup,
+ SPClipPath,
+ SPMask,
+ SPNamedView,
+ SPPage,
+ SPPaintServer,
+ SPGradient,
+ SPLinearGradient,
+ SPMeshGradient,
+ SPRadialGradient,
+ SPHatch,
+ SPPattern,
+ SPSolidColor,
+ SPScript,
+ SPStop,
+ SPString,
+ SPStyleElem,
+ SPTag,
+ SPTagUse,
+ SPTitle
+ >([&] (char const *a, char const *b, bool b1, bool b2) {
+ ADD_FAILURE() << "For downcasting " << a << " -> " << b << ", got " << b2 << ", expected " << b1;
+ });
+}
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/stream-test.cpp b/testfiles/src/stream-test.cpp
new file mode 100644
index 0000000..f6ddd53
--- /dev/null
+++ b/testfiles/src/stream-test.cpp
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Stream IO tests
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2015-2023 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cstdio>
+#include <gtest/gtest.h>
+#include <string>
+
+#include "io/stream/gzipstream.h"
+#include "io/stream/inkscapestream.h"
+#include "io/stream/stringstream.h"
+#include "io/stream/uristream.h"
+#include "io/stream/xsltstream.h"
+
+// names and path storage for other tests
+auto const xmlpath = INKSCAPE_TESTS_DIR "/data/crystalegg.xml";
+auto const xslpath = INKSCAPE_TESTS_DIR "/data/doc2html.xsl";
+
+class MyFile
+{
+protected:
+ std::string _filename;
+ std::string _mode;
+
+public:
+ MyFile(std::string filename, char const *mode = "rb")
+ : _filename(std::move(filename))
+ , _mode(mode)
+ {}
+
+ FILE *open(char const *mode) const { return std::fopen(_filename.c_str(), mode); }
+
+ operator FILE *() const { return open(_mode.c_str()); }
+
+ std::string getContents() const
+ {
+ std::string buf;
+ auto fp = std::unique_ptr<FILE, decltype(&std::fclose)>(open("rb"), &std::fclose);
+
+ if (!fp) {
+ ADD_FAILURE() << "failed to open " << _filename;
+ exit(1);
+ }
+
+ for (int c; (c = std::fgetc(fp.get())) != EOF;) {
+ buf.push_back(c);
+ }
+
+ return buf;
+ }
+};
+
+class MyOutFile : public MyFile
+{
+public:
+ MyOutFile(std::string filename)
+ : MyFile("test_stream-out-" + filename, "wb")
+ {}
+
+ ~MyOutFile() { std::remove(_filename.c_str()); }
+};
+
+TEST(StreamTest, FileStreamCopy)
+{
+ auto inFile = MyFile(xmlpath);
+ auto outFile = MyOutFile("streamtest.copy");
+ {
+ auto ins = Inkscape::IO::FileInputStream(inFile);
+ auto outs = Inkscape::IO::FileOutputStream(outFile);
+ pipeStream(ins, outs);
+ }
+ ASSERT_EQ(inFile.getContents(), outFile.getContents());
+}
+
+TEST(StreamTest, OutputStreamWriter)
+{
+ Inkscape::IO::StdOutputStream outs;
+ Inkscape::IO::OutputStreamWriter writer(outs);
+ writer << "Hello, world! " << 123.45 << " times\n";
+ writer.printf("There are %f quick brown foxes in %d states\n", 123.45, 88);
+}
+
+TEST(StreamTest, StdWriter)
+{
+ Inkscape::IO::StdWriter writer;
+ writer << "Hello, world! " << 123.45 << " times\n";
+ writer.printf("There are %f quick brown foxes in %d states\n", 123.45, 88);
+}
+
+TEST(StreamTest, Xslt)
+{
+ // ######### XSLT Sheet ############
+ auto xsltSheetFile = MyFile(xslpath);
+ auto xsltSheetIns = Inkscape::IO::FileInputStream(xsltSheetFile);
+ auto stylesheet = Inkscape::IO::XsltStyleSheet(xsltSheetIns);
+ xsltSheetIns.close();
+ auto sourceFile = MyFile(xmlpath);
+ auto xmlIns = Inkscape::IO::FileInputStream(sourceFile);
+
+ // ######### XSLT Input ############
+ auto destFile = MyOutFile("test.html");
+ auto xmlOuts = Inkscape::IO::FileOutputStream(destFile);
+ auto xsltIns = Inkscape::IO::XsltInputStream(xmlIns, stylesheet);
+ pipeStream(xsltIns, xmlOuts);
+ xsltIns.close();
+ xmlOuts.close();
+
+ // ######### XSLT Output ############
+ auto xmlIns2 = Inkscape::IO::FileInputStream(sourceFile);
+ auto destFile2 = MyOutFile("test2.html");
+ auto xmlOuts2 = Inkscape::IO::FileOutputStream(destFile2);
+ auto xsltOuts = Inkscape::IO::XsltOutputStream(xmlOuts2, stylesheet);
+ pipeStream(xmlIns2, xsltOuts);
+ xmlIns2.close();
+ xsltOuts.close();
+
+ auto htmlContent = destFile.getContents();
+ ASSERT_NE(htmlContent.find("<html"), std::string::npos);
+ ASSERT_EQ(htmlContent, destFile2.getContents());
+}
+
+TEST(StreamTest, Gzip)
+{
+ auto sourceFile = MyFile(xmlpath);
+ auto gzFile = MyOutFile("test.gz");
+ auto destFile = MyOutFile("crystalegg2.xml");
+
+ // ######### Gzip Output ############
+ {
+ auto sourceIns = Inkscape::IO::FileInputStream(sourceFile);
+ auto gzOuts = Inkscape::IO::FileOutputStream(gzFile);
+ auto gzipOuts = Inkscape::IO::GzipOutputStream(gzOuts);
+ pipeStream(sourceIns, gzipOuts);
+ }
+
+ // ######### Gzip Input ############
+ {
+ auto gzIns = Inkscape::IO::FileInputStream(gzFile.open("rb"));
+ auto destOuts = Inkscape::IO::FileOutputStream(destFile);
+ auto gzipIns = Inkscape::IO::GzipInputStream(gzIns);
+ pipeStream(gzipIns, destOuts);
+ }
+
+ ASSERT_EQ(sourceFile.getContents(), destFile.getContents());
+}
+
+TEST(StreamTest, GzipFExtraFComment)
+{
+ auto inFile = MyFile(INKSCAPE_TESTS_DIR "/data/example-FEXTRA-FCOMMENT.gz");
+ auto inStream = Inkscape::IO::FileInputStream(inFile);
+ auto inStreamGzip = Inkscape::IO::GzipInputStream(inStream);
+ auto outStreamString = Inkscape::IO::StringOutputStream();
+ pipeStream(inStreamGzip, outStreamString);
+ ASSERT_EQ(outStreamString.getString(), "the content");
+}
diff --git a/testfiles/src/style-elem-test.cpp b/testfiles/src/style-elem-test.cpp
new file mode 100644
index 0000000..5398eb9
--- /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);
+
+ auto one = cast<SPStyleElem>(doc->getObjectById("style01"));
+ ASSERT_TRUE(one != nullptr);
+
+ for (auto &style : one->get_styles()) {
+ EXPECT_EQ(style->fill.get_value(), Glib::ustring("#ff0000"));
+ }
+
+ auto two = 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..6312bb4
--- /dev/null
+++ b/testfiles/src/style-internal-test.cpp
@@ -0,0 +1,82 @@
+// 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);
+}
+
+TEST(StyleInternalTest, testSPIDashArrayValidity)
+{
+ // valid dash arrays
+ SPIDashArray array10;
+ array10.read("");
+
+ SPIDashArray array11;
+ array11.read("0");
+
+ SPIDashArray array12;
+ array12.read("0 1e3");
+
+ // invalid dash arrayas
+ SPIDashArray array20;
+ array20.read("1-1");
+
+ SPIDashArray array21;
+ array21.read("10 10 -10");
+
+ SPIDashArray array22;
+ array22.read("-1");
+
+ SPIDashArray array23;
+ array23.read("0 -5e3");
+
+
+ EXPECT_TRUE(array10.is_valid());
+ EXPECT_TRUE(array11.is_valid());
+ EXPECT_TRUE(array12.is_valid());
+
+ // SPIDashArray::read is geared towards happy path, so it may reject negative entries:
+
+ // EXPECT_FALSE(array20.is_valid()); // cannot read "1-1" as numbers, so 0
+ EXPECT_FALSE(array21.is_valid());
+ // EXPECT_FALSE(array22.is_valid()); // lone negative number is deemed invalid and removed by 'read'
+ // EXPECT_FALSE(array23.is_valid()); // negative total: invalid and removed by 'read'
+}
+
+/*
+ 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-box-test.cpp b/testfiles/src/svg-box-test.cpp
new file mode 100644
index 0000000..f3fe67b
--- /dev/null
+++ b/testfiles/src/svg-box-test.cpp
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Test for SVG box
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2010 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include "svg/svg-box.h"
+#include "svg/svg.h"
+
+#include <cmath>
+#include <glib.h>
+#include <gtest/gtest.h>
+#include <utility>
+
+struct read_test_t
+{
+ const std::string str;
+ int top;
+ int right;
+ int bottom;
+ int left;
+};
+struct write_test_t
+{
+ const std::string in;
+ const std::string out;
+};
+
+// clang-format off
+read_test_t read_tests[5] = {
+ {"0", 0, 0, 0, 0},
+ {"1", 1, 1, 1, 1},
+ {"1 2 3 4", 1, 2, 3, 4},
+ {"1,2,3,4", 1, 2, 3, 4},
+ {"2cm 4cm", 76, 151, 76, 151},
+};
+const char* fail_tests[4] = {
+ "",
+ "a b c d",
+ "12miles",
+ "14mmm",
+};
+write_test_t write_tests[7] = {
+ {"0", "0"},
+ {"1", "1"},
+ {"1 1 1 1", "1"},
+ {"1cm", "1cm"},
+ {"4cm 2in", "4cm 2in"},
+ {"7 2 4cm", "7 2 4cm"},
+ {"1,2,3", "1 2 3"},
+};
+read_test_t set_tests[3] = {
+ {"1", 1, 1, 1, 1},
+ {"1 2", 1, 2, 1, 2},
+ {"1 2 3 4", 1, 2, 3, 4},
+};
+// clang-format on
+
+TEST(SvgBoxTest, testRead)
+{
+ for (size_t i = 0; i < G_N_ELEMENTS(read_tests); i++) {
+ SVGBox box;
+ ASSERT_TRUE(box.read(read_tests[i].str)) << read_tests[i].str;
+ ASSERT_EQ(round(box.top().computed), read_tests[i].top) << read_tests[i].str;
+ ASSERT_EQ(round(box.right().computed), read_tests[i].right) << read_tests[i].str;
+ ASSERT_EQ(round(box.bottom().computed), read_tests[i].bottom) << read_tests[i].str;
+ ASSERT_EQ(round(box.left().computed), read_tests[i].left) << read_tests[i].str;
+ }
+}
+
+TEST(SvgBoxTest, testFailures)
+{
+ for (size_t i = 0; i < G_N_ELEMENTS(fail_tests); i++) {
+ SVGLength box;
+ ASSERT_TRUE( !box.read(fail_tests[i])) << fail_tests[i];
+ }
+}
+
+TEST(SvgBoxTest, testWrite)
+{
+ for (size_t i = 0; i < G_N_ELEMENTS(write_tests); i++) {
+ SVGBox box;
+ ASSERT_TRUE(box.read(write_tests[i].in)) << write_tests[i].in;
+ ASSERT_EQ(box.write(), write_tests[i].out) << write_tests[i].in;
+ }
+}
+
+TEST(SvgBoxTest, testSet)
+{
+ for (auto t : set_tests) {
+ SVGBox box;
+ box.set(t.top, t.right, t.bottom, t.left);
+ ASSERT_EQ(box.write(), t.str);
+ }
+}
+
+TEST(SvgBoxTest, testToFromString)
+{
+ SVGBox box;
+ ASSERT_TRUE(box.fromString("10mm 5", "mm"));
+ ASSERT_EQ(box.toString("mm"), "10mm 5.0000001mm");
+}
+
+TEST(SvgBoxTest, testConfine)
+{
+ SVGBox box;
+ box.set(10, 20, 10, 20);
+ ASSERT_EQ(box.write(), "10 20");
+ box.set(BOX_TOP, 5, true);
+ ASSERT_EQ(box.write(), "5 20");
+ box.set(BOX_LEFT, 10, true);
+ ASSERT_EQ(box.write(), "5 10");
+ box.set(BOX_LEFT, 5, true);
+ ASSERT_EQ(box.write(), "5");
+ box.set(BOX_BOTTOM, 7, true);
+ ASSERT_EQ(box.write(), "7");
+}
+
+// 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..4e4d3e5
--- /dev/null
+++ b/testfiles/src/svg-extension-test.cpp
@@ -0,0 +1,131 @@
+// 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/extension/internal/svg.cpp>
+#include <src/inkscape.h>
+#include <src/object/sp-text.h>
+#include <src/object/sp-tspan.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);
+}
+
+TEST_F(SvgExtensionTest, hiddenSvg2TextIsSaved)
+{
+ char const *docString = R"""(
+<svg width="100" height="200">
+ <defs>
+ <rect id="rect1" x="0" y="0" width="100" height="100" />
+ <rect id="rect2" x="0" y="100" width="100" height="100" />
+ </defs>
+ <g>
+ <text id="text1" style="shape-inside:url(#rect1);display:inline;">
+ <tspan id="tspan1" x="0" y="0">foo</tspan>
+ </text>
+ <text id="text2" style="shape-inside:url(#rect2);display:none;" >
+ <tspan id="tspan2" x="0" y="0">bar</tspan>
+ </text>
+ </g>
+</svg>
+)""";
+ SPDocument *doc = SPDocument::createNewDocFromMem(docString, static_cast<int>(strlen(docString)), false);
+ ASSERT_TRUE(doc);
+
+ std::map<std::string,std::string> textMap;
+ textMap["text1"] = "foo";
+ textMap["text2"] = "bar";
+
+ // otherwise the layout reports a size of 0
+ for (const auto& kv : textMap) {
+ auto textElement = cast<SPText>(doc->getObjectById(kv.first));
+ ASSERT_TRUE(textElement);
+ textElement->rebuildLayout();
+ }
+
+ Inkscape::XML::Document *rdoc = doc->getReprDoc();
+ ASSERT_TRUE(rdoc);
+
+ Inkscape::Extension::Internal::insert_text_fallback(rdoc->root(), doc);
+
+ for(const auto& kv : textMap) {
+ auto textElement = doc->getObjectById(kv.first);
+ ASSERT_TRUE(textElement);
+ auto tspanElement = textElement->firstChild();
+ ASSERT_TRUE(tspanElement);
+ auto stringElement = cast<SPString>(tspanElement->firstChild());
+ ASSERT_TRUE(stringElement);
+ ASSERT_EQ(kv.second, stringElement->string);
+ }
+}
diff --git a/testfiles/src/svg-length-test.cpp b/testfiles/src/svg-length-test.cpp
new file mode 100644
index 0000000..3db293c
--- /dev/null
+++ b/testfiles/src/svg-length-test.cpp
@@ -0,0 +1,225 @@
+// 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, testToFromString)
+{
+ SVGLength len;
+ ASSERT_TRUE(len.fromString("10", "mm"));
+ ASSERT_EQ(len.unit, SVGLength::MM);
+ ASSERT_EQ(len.value, 10);
+ ASSERT_EQ(len.toString("mm"), "10mm");
+ ASSERT_EQ(len.toString("in"), "0.3937008in");
+}
+
+struct eq_test_t
+{
+ char const *a;
+ char const *b;
+ bool equal;
+};
+eq_test_t eq_tests[4] = {
+ {"", "", true},
+ {"1", "1", true},
+ {"10mm", "10mm", true},
+ {"20mm", "10mm", false},
+};
+
+TEST(SvgLengthTest, testEquality)
+{
+ for (size_t i = 0; i < G_N_ELEMENTS(eq_tests); i++) {
+ SVGLength len_a;
+ SVGLength len_b;
+ len_a.read(eq_tests[i].a);
+ len_b.read(eq_tests[i].b);
+ if (eq_tests[i].equal) {
+ ASSERT_TRUE(len_a == len_b) << eq_tests[i].a << " == " << eq_tests[i].b;
+ } else {
+ ASSERT_TRUE(len_a != len_b) << eq_tests[i].a << " != " << eq_tests[i].b;
+ }
+ }
+}
+
+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..4021120
--- /dev/null
+++ b/testfiles/src/svg-path-geom-test.cpp
@@ -0,0 +1,507 @@
+// 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"
+#include "helper/geom.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();
+}
+
+TEST(PathVectorToBeziersTest, random)
+{
+ // Evil test will crash if not protected
+ Geom::PathVector pathv = sp_svg_read_pathv("M349 683 A170 170 0 1 0 349.00000000000006 683");
+ pathv_to_linear_and_cubic_beziers(pathv);
+}
+
+/*
+ * 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..35a3cf3
--- /dev/null
+++ b/testfiles/src/util-test.cpp
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Test utilities from src/util
+ */
+/*
+ * Authors:
+ * Thomas Holder
+ * Martin Owens
+ *
+ * Copyright (C) 2020-2022 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "gtest/gtest.h"
+#include "util/longest-common-suffix.h"
+#include "util/parse-int-range.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));
+}
+
+TEST(UtilTest, ParseIntRangeTest)
+{
+ // Single number
+ ASSERT_EQ(Inkscape::parseIntRange("1"), std::set<unsigned int>({1}));
+ ASSERT_EQ(Inkscape::parseIntRange("3"), std::set<unsigned int>({3}));
+
+ // Out of range numbers
+ ASSERT_EQ(Inkscape::parseIntRange("11", 1, 10), std::set<unsigned int>({}));
+ ASSERT_EQ(Inkscape::parseIntRange("3", 5, 10), std::set<unsigned int>({}));
+ ASSERT_EQ(Inkscape::parseIntRange("3", 5), std::set<unsigned int>({}));
+
+ // Comma seperated in various orders
+ ASSERT_EQ(Inkscape::parseIntRange("1,3,5"), std::set<unsigned int>({1, 3, 5}));
+ ASSERT_EQ(Inkscape::parseIntRange("3,1,4"), std::set<unsigned int>({1, 3, 4}));
+ ASSERT_EQ(Inkscape::parseIntRange("3 ,2,9,"), std::set<unsigned int>({2, 3, 9}));
+
+ // Range of numbers using a dash
+ ASSERT_EQ(Inkscape::parseIntRange("1-4"), std::set<unsigned int>({1, 2, 3, 4}));
+ ASSERT_EQ(Inkscape::parseIntRange("2-4"), std::set<unsigned int>({2, 3, 4}));
+ ASSERT_EQ(Inkscape::parseIntRange("-"), std::set<unsigned int>({1})); // 1 is the implied start
+ ASSERT_EQ(Inkscape::parseIntRange("-3"), std::set<unsigned int>({1, 2, 3}));
+ ASSERT_EQ(Inkscape::parseIntRange("8-"), std::set<unsigned int>({8}));
+ ASSERT_EQ(Inkscape::parseIntRange("-", 4, 6), std::set<unsigned int>({4, 5, 6}));
+ ASSERT_EQ(Inkscape::parseIntRange("-7", 5), std::set<unsigned int>({5, 6, 7}));
+ ASSERT_EQ(Inkscape::parseIntRange("8-", 1, 10), std::set<unsigned int>({8, 9, 10}));
+ ASSERT_EQ(Inkscape::parseIntRange("all", 4, 6), std::set<unsigned int>({4, 5, 6}));
+
+ // Mixeed formats
+ ASSERT_EQ(Inkscape::parseIntRange("2-4,7-9", 1, 10), std::set<unsigned int>({2,3,4,7,8,9}));
+}
+
+// vim: filetype=cpp:expandtab:shiftwidth=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/visual-bounds-test.cpp b/testfiles/src/visual-bounds-test.cpp
new file mode 100644
index 0000000..dbc1605
--- /dev/null
+++ b/testfiles/src/visual-bounds-test.cpp
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file Test the computation of visual bounding boxes.
+ */
+/*
+ * Authors:
+ * RafaƂ Siejakowski <rs@rs-math.net>
+ *
+ * Copyright (C) 2023 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <gtest/gtest.h>
+
+#include <2geom/rect.h>
+#include <iostream>
+#include <iomanip>
+#include "inkscape.h"
+#include "document.h"
+#include "object/sp-item.h"
+#include "object/sp-rect.h"
+
+class InkscapeInit // Initialize the Inkscape Application singleton.
+{
+public:
+ InkscapeInit()
+ {
+ if (!Inkscape::Application::exists()) {
+ Inkscape::Application::create(false);
+ }
+ }
+};
+
+class VisualBoundsTest : public ::testing::Test
+{
+protected:
+
+ VisualBoundsTest()
+ : _init{}
+ , _document{SPDocument::createNewDoc(INKSCAPE_TESTS_DIR "/data/visual-bounds.svg", false)}
+ {
+ _document->ensureUpToDate();
+ _findTestCount();
+ }
+
+public:
+ SPItem *getItemById(char const *const id)
+ {
+ auto obj = _document->getObjectById(id);
+ if (!obj) {
+ return nullptr;
+ }
+ return cast<SPItem>(obj);
+ }
+ size_t testCount() const { return _test_count; }
+private:
+ void _findTestCount()
+ {
+ auto tspan = _document->getObjectById("num_tests");
+ if (!tspan) {
+ std::cerr << "Could not get the element with id=\"num_tests\".\n";
+ return;
+ }
+ auto content = tspan->firstChild();
+ if (!content) {
+ std::cerr << "Could not get the content of the element with id=\"num_tests\".\n";
+ return;
+ }
+ auto repr = content->getRepr();
+ if (!repr) {
+ std::cerr << "Could not get the repr of the content of the element with id=\"num_tests\".\n";
+ return;
+ }
+ auto text = repr->content();
+ if (!text) {
+ std::cerr << "Could not get the text content of the element with id=\"num_tests\".\n";
+ return;
+ }
+ try {
+ _test_count = std::stoul(text);
+ } catch (std::invalid_argument const &e) {
+ std::cerr << "Could not parse an integer from the content of element with id=\"num_tests\".\n";
+ return;
+ }
+ }
+
+ InkscapeInit _init;
+ std::unique_ptr<SPDocument> _document;
+ size_t _test_count = 0;
+};
+
+TEST_F(VisualBoundsTest, ShapeBounds)
+{
+ size_t const id_maxlen = 7 + 1;
+ char object_id[id_maxlen], bbox_id[id_maxlen];
+ double const epsilon = 1e-4;
+
+ for (size_t i = 1; i<= testCount(); i++) {
+ snprintf(object_id, id_maxlen, "obj-%lu", i);
+ snprintf(bbox_id, id_maxlen, "vbb-%lu", i);
+
+ auto const *item = getItemById(object_id);
+ auto const *bbox = getItemById(bbox_id);
+ ASSERT_TRUE(item && bbox);
+
+ Geom::Rect const expected_bbox = cast<SPRect>(bbox)->getRect();
+
+ auto const actual_opt_bbox = item->visualBounds(item->transform);
+ ASSERT_TRUE(bool(actual_opt_bbox));
+ Geom::Rect const actual_bbox = *actual_opt_bbox;
+
+ // Check that the item's visual bounding box is as expected, up to epsilon.
+ for (auto const dim : {Geom::X, Geom::Y}) {
+ EXPECT_GE(actual_bbox[dim].min(), expected_bbox[dim].min() - epsilon);
+ EXPECT_LE(actual_bbox[dim].min(), expected_bbox[dim].min() + epsilon);
+
+ EXPECT_GE(actual_bbox[dim].max(), expected_bbox[dim].max() - epsilon);
+ EXPECT_LE(actual_bbox[dim].max(), expected_bbox[dim].max() + epsilon);
+ }
+ }
+}
+
+/*
+ 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 : \ No newline at end of file
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 :