#include #include #include #include #include "../src/parser/content_stream_parser.hpp" #include #include using namespace pdfengine; class MockVisitor : public CommandVisitor { public: std::vector 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 paths; std::vector 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); }