Files
pdf/engine/tests/display_list_test.cpp
T
2026-07-01 17:28:00 +05:30

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);
}