From d290e65685d62493692551afdeb15770e30485de Mon Sep 17 00:00:00 2001 From: saqib mir Date: Mon, 22 Jun 2026 15:21:39 +0530 Subject: [PATCH] Path construction interpreter (m l c re S f B) --- engine/CMakeLists.txt | 1 + engine/include/pdfengine/content_object.hpp | 1 + engine/include/pdfengine/display_list.hpp | 14 +++- engine/include/pdfengine/path.hpp | 5 ++ engine/include/pdfengine/path_interpreter.hpp | 14 ++++ engine/include/pdfengine/skia_renderer.hpp | 1 + engine/src/core/display_list.cpp | 9 ++- engine/src/core/path_interpreter.cpp | 28 ++++++++ engine/src/core/skia_renderer.cpp | 64 +++++++++++++++++++ engine/src/parser/content_builder.cpp | 13 ++-- engine/src/parser/content_builder.hpp | 2 +- engine/src/parser/pdfium_document.cpp | 2 + engine/tests/display_list_test.cpp | 1 + 13 files changed, 146 insertions(+), 9 deletions(-) create mode 100644 engine/include/pdfengine/path_interpreter.hpp create mode 100644 engine/src/core/path_interpreter.cpp diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index a7242df..6c7b183 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -13,6 +13,7 @@ add_library(pdfengine STATIC src/core/engine_info.cpp src/core/graphics_state.cpp src/core/display_list.cpp + src/core/path_interpreter.cpp src/core/skia_renderer.cpp src/parser/pdfium_loader.cpp src/parser/pdfium_document.cpp diff --git a/engine/include/pdfengine/content_object.hpp b/engine/include/pdfengine/content_object.hpp index 3e58600..c76d7ff 100644 --- a/engine/include/pdfengine/content_object.hpp +++ b/engine/include/pdfengine/content_object.hpp @@ -72,6 +72,7 @@ public: Path path; PathPaintOp paintOp = PathPaintOp::Stroke; + FillRule fillRule = FillRule::NonZero; Matrix transform; }; diff --git a/engine/include/pdfengine/display_list.hpp b/engine/include/pdfengine/display_list.hpp index 91cdac2..5d43186 100644 --- a/engine/include/pdfengine/display_list.hpp +++ b/engine/include/pdfengine/display_list.hpp @@ -48,7 +48,8 @@ struct DrawTextCommand : public Command { struct FillPathCommand : public Command { Path path; - explicit FillPathCommand(Path path) : path(std::move(path)) {} + FillRule rule; + explicit FillPathCommand(Path path, FillRule rule = FillRule::NonZero) : path(std::move(path)), rule(rule) {} void accept(CommandVisitor& visitor) const override; }; @@ -58,6 +59,13 @@ struct StrokePathCommand : public Command { void accept(CommandVisitor& visitor) const override; }; +struct FillStrokePathCommand : public Command { + Path path; + FillRule rule; + explicit FillStrokePathCommand(Path path, FillRule rule = FillRule::NonZero) : path(std::move(path)), rule(rule) {} + void accept(CommandVisitor& visitor) const override; +}; + struct DrawImageCommand : public Command { ImageInfo image; Matrix matrix; @@ -82,6 +90,7 @@ public: virtual void visit(const DrawTextCommand& cmd) = 0; virtual void visit(const FillPathCommand& cmd) = 0; virtual void visit(const StrokePathCommand& cmd) = 0; + virtual void visit(const FillStrokePathCommand& cmd) = 0; virtual void visit(const DrawImageCommand& cmd) = 0; }; @@ -104,8 +113,9 @@ public: void setTransform(const Matrix& m); void fillRect(float x, float y, float w, float h); void drawText(const std::string& text, float x, float y); - void fillPath(const Path& path); + void fillPath(const Path& path, FillRule rule = FillRule::NonZero); void strokePath(const Path& path); + void fillStrokePath(const Path& path, FillRule rule = FillRule::NonZero); void drawImage(const ImageInfo& image, const Matrix& m, float opacity = 1.0f); [[nodiscard]] size_t size() const noexcept { return m_commands.size(); } diff --git a/engine/include/pdfengine/path.hpp b/engine/include/pdfengine/path.hpp index 7b12865..b04668d 100644 --- a/engine/include/pdfengine/path.hpp +++ b/engine/include/pdfengine/path.hpp @@ -4,6 +4,11 @@ namespace pdfengine { +enum class FillRule { + NonZero, + EvenOdd +}; + // Basic point structure struct Point { float x = 0.0f; diff --git a/engine/include/pdfengine/path_interpreter.hpp b/engine/include/pdfengine/path_interpreter.hpp new file mode 100644 index 0000000..b7e46b5 --- /dev/null +++ b/engine/include/pdfengine/path_interpreter.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +namespace pdfengine { + +// PathObjectInterpreter converts a PathObject into DisplayList commands +class PathObjectInterpreter { +public: + static void interpret(const PathObject& pathObj, DisplayList& displayList); +}; + +} // namespace pdfengine diff --git a/engine/include/pdfengine/skia_renderer.hpp b/engine/include/pdfengine/skia_renderer.hpp index 0545d10..cb36026 100644 --- a/engine/include/pdfengine/skia_renderer.hpp +++ b/engine/include/pdfengine/skia_renderer.hpp @@ -21,6 +21,7 @@ public: void visit(const DrawTextCommand& cmd) override; void visit(const FillPathCommand& cmd) override; void visit(const StrokePathCommand& cmd) override; + void visit(const FillStrokePathCommand& cmd) override; void visit(const DrawImageCommand& cmd) override; // Renders the entire display list to the canvas void render(const DisplayList& displayList); diff --git a/engine/src/core/display_list.cpp b/engine/src/core/display_list.cpp index 3bd19b7..3bfd79e 100644 --- a/engine/src/core/display_list.cpp +++ b/engine/src/core/display_list.cpp @@ -9,6 +9,7 @@ void FillRectCommand::accept(CommandVisitor& visitor) const { visitor.visit(*thi void DrawTextCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); } void FillPathCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); } void StrokePathCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); } +void FillStrokePathCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); } void DrawImageCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); } void DisplayList::addCommand(std::unique_ptr cmd) { @@ -43,14 +44,18 @@ void DisplayList::drawText(const std::string& text, float x, float y) { addCommand(std::make_unique(text, x, y)); } -void DisplayList::fillPath(const Path& path) { - addCommand(std::make_unique(path)); +void DisplayList::fillPath(const Path& path, FillRule rule) { + addCommand(std::make_unique(path, rule)); } void DisplayList::strokePath(const Path& path) { addCommand(std::make_unique(path)); } +void DisplayList::fillStrokePath(const Path& path, FillRule rule) { + addCommand(std::make_unique(path, rule)); +} + void DisplayList::drawImage(const ImageInfo& image, const Matrix& m, float opacity) { addCommand(std::make_unique(image, m, opacity)); } diff --git a/engine/src/core/path_interpreter.cpp b/engine/src/core/path_interpreter.cpp new file mode 100644 index 0000000..073fa2c --- /dev/null +++ b/engine/src/core/path_interpreter.cpp @@ -0,0 +1,28 @@ +#include + +namespace pdfengine { + +void PathObjectInterpreter::interpret(const PathObject& pathObj, DisplayList& displayList) { + if (pathObj.path.empty()) { + return; + } + + displayList.saveState(); + displayList.setTransform(pathObj.transform); + + switch (pathObj.paintOp) { + case PathPaintOp::Stroke: + displayList.strokePath(pathObj.path); + break; + case PathPaintOp::Fill: + displayList.fillPath(pathObj.path, pathObj.fillRule); + break; + case PathPaintOp::FillStroke: + displayList.fillStrokePath(pathObj.path, pathObj.fillRule); + break; + } + + displayList.restoreState(); +} + +} // namespace pdfengine diff --git a/engine/src/core/skia_renderer.cpp b/engine/src/core/skia_renderer.cpp index 0046ba2..fc0b0dd 100644 --- a/engine/src/core/skia_renderer.cpp +++ b/engine/src/core/skia_renderer.cpp @@ -129,6 +129,12 @@ void SkiaRenderer::visit(const FillPathCommand& cmd) { } } + if (cmd.rule == FillRule::EvenOdd) { + skPath.setFillType(SkPathFillType::kEvenOdd); + } else { + skPath.setFillType(SkPathFillType::kWinding); + } + SkPaint paint; paint.setAntiAlias(true); paint.setStyle(SkPaint::kFill_Style); @@ -186,6 +192,64 @@ void SkiaRenderer::visit(const StrokePathCommand& cmd) { #endif } +void SkiaRenderer::visit(const FillStrokePathCommand& cmd) { + (void)cmd; +#ifdef PDFENGINE_WITH_SKIA + if (!m_canvas || cmd.path.empty()) return; + + SkPath skPath; + for (const auto& segment : cmd.path.segments()) { + switch (segment.verb) { + case Path::Verb::MoveTo: + skPath.moveTo(segment.points[0].x, segment.points[0].y); + break; + case Path::Verb::LineTo: + skPath.lineTo(segment.points[0].x, segment.points[0].y); + break; + case Path::Verb::CubicBezierTo: + skPath.cubicTo( + segment.points[0].x, segment.points[0].y, + segment.points[1].x, segment.points[1].y, + segment.points[2].x, segment.points[2].y + ); + break; + case Path::Verb::Close: + skPath.close(); + break; + } + } + + if (cmd.rule == FillRule::EvenOdd) { + skPath.setFillType(SkPathFillType::kEvenOdd); + } else { + skPath.setFillType(SkPathFillType::kWinding); + } + + const auto& color = m_stateStack.current().fillColor; + + // First fill + SkPaint fillPaint; + fillPaint.setAntiAlias(true); + fillPaint.setStyle(SkPaint::kFill_Style); + fillPaint.setColor(SkColorSetARGB(255, + static_cast(color.r * 255), + static_cast(color.g * 255), + static_cast(color.b * 255))); + m_canvas->drawPath(skPath, fillPaint); + + // Then stroke + SkPaint strokePaint; + strokePaint.setAntiAlias(true); + strokePaint.setStyle(SkPaint::kStroke_Style); + strokePaint.setColor(SkColorSetARGB(255, + static_cast(color.r * 255), + static_cast(color.g * 255), + static_cast(color.b * 255))); + strokePaint.setStrokeWidth(1.0f); + m_canvas->drawPath(skPath, strokePaint); +#endif +} + void SkiaRenderer::visit(const DrawImageCommand& cmd) { (void)cmd; #ifdef PDFENGINE_WITH_SKIA diff --git a/engine/src/parser/content_builder.cpp b/engine/src/parser/content_builder.cpp index 76d830d..4f54285 100644 --- a/engine/src/parser/content_builder.cpp +++ b/engine/src/parser/content_builder.cpp @@ -46,11 +46,14 @@ void ContentBuilder::processOperation(const Operation& op, std::vector>& outObjects, - bool closePath) { + bool closePath, + FillRule fillRule) { if (closePath) { if (!currentPath_.empty()) { currentPath_.close(); @@ -300,6 +304,7 @@ void ContentBuilder::handlePathPaint(PathPaintOp paintOp, auto pathObj = std::make_unique(); pathObj->path = currentPath_; pathObj->paintOp = paintOp; + pathObj->fillRule = fillRule; pathObj->transform = state_.ctm; outObjects.push_back(std::move(pathObj)); diff --git a/engine/src/parser/content_builder.hpp b/engine/src/parser/content_builder.hpp index ef0065c..34778bb 100644 --- a/engine/src/parser/content_builder.hpp +++ b/engine/src/parser/content_builder.hpp @@ -39,7 +39,7 @@ private: void handleCm(const Operation& op); void handleDo(const Operation& op, std::vector>& outObjects); void handlePathConstruction(const Operation& op); - void handlePathPaint(PathPaintOp paintOp, std::vector>& outObjects, bool closePath = false); + void handlePathPaint(PathPaintOp paintOp, std::vector>& outObjects, bool closePath = false, FillRule fillRule = FillRule::NonZero); }; } // namespace pdfengine diff --git a/engine/src/parser/pdfium_document.cpp b/engine/src/parser/pdfium_document.cpp index e010b57..fba417b 100644 --- a/engine/src/parser/pdfium_document.cpp +++ b/engine/src/parser/pdfium_document.cpp @@ -951,6 +951,7 @@ std::expected, EngineError> PdfiumPage::extractTextWith } namespace { +#ifdef PDFENGINE_WITH_PDFIUM inline void repagSetParaId(FPDF_DOCUMENT doc, FPDF_PAGEOBJECT obj, const std::string& paraId) { if (paraId.empty() || !obj) return; FPDF_PAGEOBJECTMARK mark = FPDFPageObj_AddMark(obj, "PDFPARA"); @@ -982,6 +983,7 @@ inline std::string repagGetParaId(FPDF_PAGEOBJECT obj) { } return ""; } +#endif } std::expected PdfiumPage::extractDocumentModel() const { diff --git a/engine/tests/display_list_test.cpp b/engine/tests/display_list_test.cpp index 83e1179..7c590ed 100644 --- a/engine/tests/display_list_test.cpp +++ b/engine/tests/display_list_test.cpp @@ -17,6 +17,7 @@ public: 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"); } };