blob: 9af07ef4a9f627912d42416f738584f39628cffe [file]
/*
* Copyright 2024 Rive
*/
#include "rive/math/math_types.hpp"
#include "../src/rive_render_path.hpp"
#include <catch.hpp>
namespace rive::gpu
{
class PLSTestPath : public RiveRenderPath
{
public:
void addRect(float x,
float y,
float width,
float height,
PathDirection wind)
{
RawPath rawPath;
rawPath.addRect({x, y, x + width, y + height}, wind);
RiveRenderPath path2(FillRule::nonZero, rawPath);
addRenderPath(&path2, Mat2D());
}
void addCircle(float x, float y, float radius, PathDirection wind)
{
RawPath rawPath;
rawPath.addOval({x - radius, y - radius, x + radius, y + radius}, wind);
RiveRenderPath path2(FillRule::nonZero, rawPath);
addRenderPath(&path2, Mat2D());
}
};
// Check that RiveRenderPath::getCoarseArea() is positive for clockwise paths
// and negative for counterclockwise. Also check that it's within a margin of
// error of the true area for curves.
TEST_CASE("getCoarseArea", "[RiveRenderPath]")
{
PLSTestPath path;
CHECK(path.getCoarseArea() == 0);
path.addRect(10, 20, 10, 200, PathDirection::clockwise); // 2000 px
CHECK(path.getCoarseArea() == 2000);
path.addRect(15, 30, 2, 20, PathDirection::counterclockwise); // -40 px
CHECK(path.getCoarseArea() == (2000 - 40));
path.addRect(0, 0, 1, 1, PathDirection::clockwise); // +1 px
CHECK(path.getCoarseArea() == (2000 - 40 + 1));
path.addRect(-1,
-1,
300,
100,
PathDirection::counterclockwise); // -30000 px
CHECK(path.getCoarseArea() == (2000 - 40 + 1 - 30000));
CHECK(path.getCoarseArea() == (2000 - 40 + 1 - 30000));
path.rewind();
CHECK(path.getCoarseArea() == 0);
CHECK(path.getCoarseArea() == 0);
path.addCircle(0, 0, 1000, PathDirection::clockwise);
CHECK(path.getCoarseArea() / (math::PI * 1000 * 1000) ==
Approx(1).margin(1e-2f));
path.rewind();
path.addCircle(30, -100, 900, PathDirection::counterclockwise);
CHECK(path.getCoarseArea() / (math::PI * 900 * 900) ==
Approx(-1).margin(1e-2f));
path.addCircle(0, 0, 1000, PathDirection::clockwise);
CHECK(path.getCoarseArea() /
(math::PI * 1000 * 1000 - math::PI * 900 * 900) ==
Approx(1).margin(1e-2f));
}
TEST_CASE("addRawPath invalidates derived state", "[RiveRenderPath]")
{
RiveRenderPath path;
RawPath first;
first.addRect({0, 0, 10, 10}, PathDirection::clockwise);
path.addRawPath(first);
// Warm the caches so a missing invalidation shows up below.
CHECK(path.getBounds().right() == 10);
CHECK(path.getCoarseArea() == 100);
uint64_t firstMutationID = path.getRawPathMutationID();
RawPath second;
second.addRect({20, 20, 40, 40}, PathDirection::clockwise);
path.addRawPath(second);
CHECK(path.getBounds().right() == 40);
CHECK(path.getCoarseArea() == 100 + 400);
CHECK(path.getRawPathMutationID() != firstMutationID);
}
// A script can hand us a contour of coincident points, which the tessellator
// cannot find a join tangent in.
TEST_CASE("addUntrustedRawPath prunes empty segments", "[RiveRenderPath]")
{
RawPath degenerate;
degenerate.move({304, 160});
degenerate.line({304, 160});
degenerate.line({304, 160});
degenerate.line({304, 160});
degenerate.close();
RiveRenderPath path;
path.addUntrustedRawPath(degenerate);
const RawPath& pruned = path.getRawPath();
REQUIRE(pruned.verbs().size() == 2);
CHECK(pruned.verbs()[0] == PathVerb::move);
CHECK(pruned.verbs()[1] == PathVerb::close);
CHECK(pruned.points().size() == 1);
// The trusted entry point stays a plain append.
RiveRenderPath trusted;
trusted.addRawPath(degenerate);
CHECK(trusted.getRawPath().verbs().size() == 5);
}
} // namespace rive::gpu