185 lines
6.3 KiB
C++
185 lines
6.3 KiB
C++
#include <gtest/gtest.h>
|
|
#include <pdfengine/display_list.hpp>
|
|
#include <pdfengine/path_interpreter.hpp>
|
|
#include <pdfengine/content_object.hpp>
|
|
#include "../src/parser/content_stream_parser.hpp"
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
using namespace pdfengine;
|
|
|
|
class MockVisitor : public CommandVisitor {
|
|
public:
|
|
std::vector<std::string> calls;
|
|
|
|
void visit(const SaveStateCommand&) override { calls.push_back("SaveState"); }
|
|
void visit(const RestoreStateCommand&) override { calls.push_back("RestoreState"); }
|
|
void visit(const SetTransformCommand& cmd) override { calls.push_back("SetTransform(" + std::to_string(cmd.matrix.a) + ")"); }
|
|
void visit(const FillRectCommand& cmd) override { calls.push_back("FillRect(" + std::to_string(cmd.width) + ")"); }
|
|
void visit(const DrawTextCommand& cmd) override { calls.push_back("DrawText(" + cmd.text + ")"); }
|
|
void visit(const FillPathCommand&) override { calls.push_back("FillPath"); }
|
|
void visit(const StrokePathCommand&) override { calls.push_back("StrokePath"); }
|
|
void visit(const FillStrokePathCommand&) override { calls.push_back("FillStrokePath"); }
|
|
void visit(const DrawImageCommand&) override { calls.push_back("DrawImage"); }
|
|
};
|
|
|
|
class PathVerifyVisitor : public CommandVisitor {
|
|
public:
|
|
struct PathInfo {
|
|
std::string type;
|
|
size_t segmentCount = 0;
|
|
FillRule rule = FillRule::NonZero;
|
|
};
|
|
std::vector<PathInfo> paths;
|
|
std::vector<std::string> calls;
|
|
|
|
void visit(const SaveStateCommand&) override { calls.push_back("SaveState"); }
|
|
void visit(const RestoreStateCommand&) override { calls.push_back("RestoreState"); }
|
|
void visit(const SetTransformCommand& cmd) override {
|
|
calls.push_back("SetTransform(" + std::to_string(cmd.matrix.a) + "," + std::to_string(cmd.matrix.d) + ")");
|
|
}
|
|
void visit(const FillRectCommand&) override {}
|
|
void visit(const DrawTextCommand&) override {}
|
|
void visit(const FillPathCommand& cmd) override {
|
|
calls.push_back("FillPath");
|
|
paths.push_back({"Fill", cmd.path.segments().size(), cmd.rule});
|
|
}
|
|
void visit(const StrokePathCommand& cmd) override {
|
|
calls.push_back("StrokePath");
|
|
paths.push_back({"Stroke", cmd.path.segments().size(), FillRule::NonZero});
|
|
}
|
|
void visit(const FillStrokePathCommand& cmd) override {
|
|
calls.push_back("FillStrokePath");
|
|
paths.push_back({"FillStroke", cmd.path.segments().size(), cmd.rule});
|
|
}
|
|
void visit(const DrawImageCommand&) override {}
|
|
};
|
|
|
|
TEST(DisplayListTest, RecordAndReplay) {
|
|
DisplayList list;
|
|
|
|
EXPECT_EQ(list.size(), 0);
|
|
|
|
list.saveState();
|
|
list.setTransform(Matrix(2.0f, 0.0f, 0.0f, 2.0f, 0.0f, 0.0f));
|
|
list.fillRect(10.0f, 10.0f, 100.0f, 50.0f);
|
|
list.drawText("Hello World", 20.0f, 30.0f);
|
|
list.restoreState();
|
|
|
|
EXPECT_EQ(list.size(), 5);
|
|
|
|
MockVisitor visitor;
|
|
list.replay(visitor);
|
|
|
|
ASSERT_EQ(visitor.calls.size(), 5);
|
|
EXPECT_EQ(visitor.calls[0], "SaveState");
|
|
EXPECT_EQ(visitor.calls[1], "SetTransform(2.000000)");
|
|
EXPECT_EQ(visitor.calls[2], "FillRect(100.000000)");
|
|
EXPECT_EQ(visitor.calls[3], "DrawText(Hello World)");
|
|
EXPECT_EQ(visitor.calls[4], "RestoreState");
|
|
}
|
|
|
|
TEST(DisplayListTest, Clear) {
|
|
DisplayList list;
|
|
list.saveState();
|
|
list.fillRect(0, 0, 10, 10);
|
|
EXPECT_EQ(list.size(), 2);
|
|
|
|
list.clear();
|
|
EXPECT_EQ(list.size(), 0);
|
|
}
|
|
|
|
TEST(DisplayListTest, PathObjectInterpreterStroke) {
|
|
Path path;
|
|
path.moveTo(10.0f, 20.0f);
|
|
path.lineTo(30.0f, 40.0f);
|
|
|
|
PathObject pathObj;
|
|
pathObj.path = path;
|
|
pathObj.paintOp = PathPaintOp::Stroke;
|
|
pathObj.transform = Matrix(1.5f, 0.0f, 0.0f, 1.5f, 5.0f, 5.0f);
|
|
|
|
DisplayList list;
|
|
PathObjectInterpreter::interpret(pathObj, list);
|
|
|
|
PathVerifyVisitor visitor;
|
|
list.replay(visitor);
|
|
|
|
ASSERT_EQ(visitor.calls.size(), 4);
|
|
EXPECT_EQ(visitor.calls[0], "SaveState");
|
|
EXPECT_EQ(visitor.calls[1], "SetTransform(1.500000,1.500000)");
|
|
EXPECT_EQ(visitor.calls[2], "StrokePath");
|
|
EXPECT_EQ(visitor.calls[3], "RestoreState");
|
|
|
|
ASSERT_EQ(visitor.paths.size(), 1);
|
|
EXPECT_EQ(visitor.paths[0].type, "Stroke");
|
|
EXPECT_EQ(visitor.paths[0].segmentCount, 2);
|
|
}
|
|
|
|
TEST(DisplayListTest, PathObjectInterpreterFillEvenOdd) {
|
|
Path path;
|
|
path.addRect(0.0f, 0.0f, 10.0f, 10.0f);
|
|
|
|
PathObject pathObj;
|
|
pathObj.path = path;
|
|
pathObj.paintOp = PathPaintOp::Fill;
|
|
pathObj.fillRule = FillRule::EvenOdd;
|
|
pathObj.transform = Matrix(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
|
|
|
|
DisplayList list;
|
|
PathObjectInterpreter::interpret(pathObj, list);
|
|
|
|
PathVerifyVisitor visitor;
|
|
list.replay(visitor);
|
|
|
|
ASSERT_EQ(visitor.calls.size(), 4);
|
|
EXPECT_EQ(visitor.calls[2], "FillPath");
|
|
|
|
ASSERT_EQ(visitor.paths.size(), 1);
|
|
EXPECT_EQ(visitor.paths[0].type, "Fill");
|
|
EXPECT_EQ(visitor.paths[0].segmentCount, 5); // moveTo + 3 lineTo + close
|
|
EXPECT_EQ(visitor.paths[0].rule, FillRule::EvenOdd);
|
|
}
|
|
|
|
TEST(DisplayListTest, ContentStreamParserPaths) {
|
|
DisplayList list;
|
|
ContentStreamParser parser;
|
|
|
|
// Parse stroke path (m, l, S)
|
|
parser.parse("10 20 m 30 40 l S", list);
|
|
|
|
// Parse fill path (re, f)
|
|
parser.parse("5 6 7 8 re f", list);
|
|
|
|
// Parse fill and stroke (m, c, B)
|
|
parser.parse("1 2 m 3 4 5 6 7 8 c B", list);
|
|
|
|
PathVerifyVisitor visitor;
|
|
list.replay(visitor);
|
|
|
|
// S -> StrokePath
|
|
// f -> FillPath
|
|
// B -> FillPath, StrokePath
|
|
ASSERT_EQ(visitor.calls.size(), 4);
|
|
EXPECT_EQ(visitor.calls[0], "StrokePath");
|
|
EXPECT_EQ(visitor.calls[1], "FillPath");
|
|
EXPECT_EQ(visitor.calls[2], "FillPath");
|
|
EXPECT_EQ(visitor.calls[3], "StrokePath");
|
|
|
|
ASSERT_EQ(visitor.paths.size(), 4);
|
|
|
|
// 1st: 10 20 m 30 40 l S -> MoveTo, LineTo
|
|
EXPECT_EQ(visitor.paths[0].type, "Stroke");
|
|
EXPECT_EQ(visitor.paths[0].segmentCount, 2);
|
|
|
|
// 2nd: 5 6 7 8 re f -> MoveTo, 3xLineTo, Close
|
|
EXPECT_EQ(visitor.paths[1].type, "Fill");
|
|
EXPECT_EQ(visitor.paths[1].segmentCount, 5);
|
|
|
|
// 3rd & 4th: 1 2 m 3 4 5 6 7 8 c B -> MoveTo, CubicBezierTo
|
|
EXPECT_EQ(visitor.paths[2].type, "Fill");
|
|
EXPECT_EQ(visitor.paths[2].segmentCount, 2);
|
|
EXPECT_EQ(visitor.paths[3].type, "Stroke");
|
|
EXPECT_EQ(visitor.paths[3].segmentCount, 2);
|
|
}
|