#include #include #include #include namespace py = pybind11; namespace { void throw_on_error(pdfengine::EngineError err) { switch (err) { case pdfengine::EngineError::FileNotFound: PyErr_SetString(PyExc_FileNotFoundError, "PDF file not found"); throw py::error_already_set(); case pdfengine::EngineError::InvalidFormat: throw py::value_error("Invalid PDF format"); case pdfengine::EngineError::PasswordRequired: throw py::value_error("Password required to open this PDF"); case pdfengine::EngineError::InvalidPassword: throw py::value_error("Invalid password provided for this PDF"); case pdfengine::EngineError::PageOutOfBounds: throw py::index_error("Page index out of bounds"); case pdfengine::EngineError::RenderFailed: throw std::runtime_error("Failed to render PDF page"); case pdfengine::EngineError::WriteFailed: throw std::runtime_error("Failed to write PDF data"); default: throw std::runtime_error("Unknown PDF engine error"); } } template T get_or_throw(std::expected&& res) { if (!res.has_value()) { throw_on_error(res.error()); } return std::move(res.value()); } void get_or_throw(std::expected&& res) { if (!res.has_value()) { throw_on_error(res.error()); } } } PYBIND11_MODULE(pdfengine, m) { m.doc() = "Python bindings for the PdfEngine C++ Core SDK"; m.def("engine_version", &pdfengine::engineVersion, "Get the engine version string"); m.def("engine_build_info", &pdfengine::engineBuildInfo, "Get the engine build info string"); m.def("engine_has_pdfium", &pdfengine::engineHasPdfium, "Check if the engine was built with PDFium support"); m.def("engine_has_skia", &pdfengine::engineHasSkia, "Check if the engine was built with Skia support"); py::class_(m, "Point2D") .def(py::init(), py::arg("x") = 0.0, py::arg("y") = 0.0) .def_readwrite("x", &pdfengine::Point2D::x) .def_readwrite("y", &pdfengine::Point2D::y) .def("__repr__", [](const pdfengine::Point2D& self) { return "Point2D(x=" + std::to_string(self.x) + ", y=" + std::to_string(self.y) + ")"; }); py::class_(m, "DevicePoint") .def(py::init(), py::arg("x") = 0, py::arg("y") = 0) .def_readwrite("x", &pdfengine::DevicePoint::x) .def_readwrite("y", &pdfengine::DevicePoint::y) .def("__repr__", [](const pdfengine::DevicePoint& self) { return "DevicePoint(x=" + std::to_string(self.x) + ", y=" + std::to_string(self.y) + ")"; }); py::class_(m, "DocumentMetadata") .def_readonly("title", &pdfengine::DocumentMetadata::title) .def_readonly("author", &pdfengine::DocumentMetadata::author) .def_readonly("creator", &pdfengine::DocumentMetadata::creator) .def_readonly("producer", &pdfengine::DocumentMetadata::producer) .def_readonly("creation_date", &pdfengine::DocumentMetadata::creationDate) .def_readonly("modification_date", &pdfengine::DocumentMetadata::modificationDate) .def("__repr__", [](const pdfengine::DocumentMetadata& self) { return "DocumentMetadata(title='" + self.title + "', author='" + self.author + "')"; }); py::class_(m, "PageImage") .def_readonly("width", &pdfengine::PageImage::width) .def_readonly("height", &pdfengine::PageImage::height) .def_property_readonly("data", [](const pdfengine::PageImage& self) { return py::bytes(reinterpret_cast(self.data.data()), self.data.size()); }); py::class_>(m, "PdfPage") .def_property_readonly("width", &pdfengine::PdfPage::width) .def_property_readonly("height", &pdfengine::PdfPage::height) .def("render", [](const pdfengine::PdfPage& self, int dpi) { return get_or_throw(self.render(dpi)); }, py::arg("dpi") = 96) .def("extract_text", [](const pdfengine::PdfPage& self) { return get_or_throw(self.extractText()); }) .def("extract_text_with_bounds", [](const pdfengine::PdfPage& self) { auto res = get_or_throw(self.extractTextWithBounds()); py::list py_list; for (const auto& glyph : res) { py::dict d; d["text"] = glyph.text; d["x"] = glyph.x; d["y"] = glyph.y; d["w"] = glyph.w; d["h"] = glyph.h; d["fontSize"] = glyph.fontSize; py_list.append(d); } return py_list; }) .def("page_to_device", &pdfengine::PdfPage::pageToDevice, py::arg("page_point"), py::arg("device_width"), py::arg("device_height"), py::arg("rotate") = 0) .def("device_to_page", &pdfengine::PdfPage::deviceToPage, py::arg("device_point"), py::arg("device_width"), py::arg("device_height"), py::arg("rotate") = 0); py::class_>(m, "PdfDocument") .def_static("load_from_file", [](const std::string& path, const std::string& password) { return get_or_throw(pdfengine::PdfDocument::loadFromFile(path, password)); }, py::arg("path"), py::arg("password") = "") .def_static("load_from_memory", [](const py::bytes& bytes, const std::string& password) { std::string_view sv = bytes; std::vector data(sv.begin(), sv.end()); return get_or_throw(pdfengine::PdfDocument::loadFromMemory(data, password)); }, py::arg("data"), py::arg("password") = "") .def_property_readonly("page_count", &pdfengine::PdfDocument::pageCount) .def_property_readonly("metadata", &pdfengine::PdfDocument::metadata) .def("get_page", [](pdfengine::PdfDocument& self, int pageIndex) { return get_or_throw(self.getPage(pageIndex)); }, py::arg("page_index")) .def("apply_edits", [](pdfengine::PdfDocument& self, const std::string& editsJson) { get_or_throw(self.applyEdits(editsJson)); }, py::arg("edits_json")) .def("save_incremental", [](const pdfengine::PdfDocument& self) { std::vector res = get_or_throw(self.saveIncremental()); return py::bytes(reinterpret_cast(res.data()), res.size()); }); }