diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:50:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:50:49 +0000 |
commit | c853ffb5b2f75f5a889ed2e3ef89b818a736e87a (patch) | |
tree | 7d13a0883bb7936b84d6ecdd7bc332b41ed04bee /testfiles/src | |
parent | Initial commit. (diff) | |
download | inkscape-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')
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 : |