Files
pdf/bindings/python/pdfengine_py.cpp
T

300 lines
15 KiB
C++

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pdfengine/pdf_document.hpp>
#include <pdfengine/pdf_engine.hpp>
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<typename T>
T get_or_throw(std::expected<T, pdfengine::EngineError>&& res) {
if (!res.has_value()) {
throw_on_error(res.error());
}
return std::move(res.value());
}
void get_or_throw(std::expected<void, pdfengine::EngineError>&& 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_<pdfengine::Point2D>(m, "Point2D")
.def(py::init<double, double>(), 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_<pdfengine::DevicePoint>(m, "DevicePoint")
.def(py::init<int, int>(), 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_<pdfengine::DocumentMetadata>(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_<pdfengine::PageImage>(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<const char*>(self.data.data()), self.data.size());
});
py::class_<pdfengine::FontInfo>(m, "FontInfo")
.def_readonly("font_name", &pdfengine::FontInfo::fontName)
.def_readonly("type", &pdfengine::FontInfo::type)
.def_readonly("is_embedded", &pdfengine::FontInfo::isEmbedded)
.def_readonly("is_subset", &pdfengine::FontInfo::isSubset)
.def_readonly("is_vertical", &pdfengine::FontInfo::isVertical)
.def_readonly("encoding", &pdfengine::FontInfo::encoding)
.def_readonly("has_to_unicode", &pdfengine::FontInfo::hasToUnicode)
.def_readonly("cmap_name", &pdfengine::FontInfo::cmapName)
.def_readonly("cid_system_info", &pdfengine::FontInfo::cidSystemInfo)
.def_readonly("subset_tag", &pdfengine::FontInfo::subsetTag)
.def_readonly("source_type", &pdfengine::FontInfo::sourceType)
.def_readonly("substituted_from", &pdfengine::FontInfo::substitutedFrom)
.def_readonly("substituted_to", &pdfengine::FontInfo::substitutedTo)
.def_readonly("normalized_family", &pdfengine::FontInfo::normalizedFamily)
.def_readonly("internal_font_id", &pdfengine::FontInfo::internalFontId)
.def_readonly("flags", &pdfengine::FontInfo::flags)
.def_readonly("ascent", &pdfengine::FontInfo::ascent)
.def_readonly("descent", &pdfengine::FontInfo::descent)
.def_readonly("cap_height", &pdfengine::FontInfo::capHeight)
.def("__repr__", [](const pdfengine::FontInfo& self) {
return "FontInfo(font_name='" + self.fontName + "', type='" + self.type + "', is_embedded=" + (self.isEmbedded ? "True" : "False") + ")";
});
py::class_<pdfengine::Glyph>(m, "Glyph")
.def_readonly("text", &pdfengine::Glyph::text)
.def_readonly("unicode", &pdfengine::Glyph::unicode)
.def_readonly("font_name", &pdfengine::Glyph::fontName)
.def_readonly("flags", &pdfengine::Glyph::flags)
.def_readonly("font_size", &pdfengine::Glyph::fontSize)
.def_readonly("origin_x", &pdfengine::Glyph::originX)
.def_readonly("origin_y", &pdfengine::Glyph::originY)
.def_readonly("bbox_x", &pdfengine::Glyph::bboxX)
.def_readonly("bbox_y", &pdfengine::Glyph::bboxY)
.def_readonly("bbox_w", &pdfengine::Glyph::bboxW)
.def_readonly("bbox_h", &pdfengine::Glyph::bboxH)
.def_readonly("angle", &pdfengine::Glyph::angle);
py::class_<pdfengine::TextRun>(m, "TextRun")
.def_readonly("text", &pdfengine::TextRun::text)
.def_readonly("font_name", &pdfengine::TextRun::fontName)
.def_readonly("flags", &pdfengine::TextRun::flags)
.def_readonly("font_size", &pdfengine::TextRun::fontSize)
.def_readonly("internal_font_id", &pdfengine::TextRun::internalFontId)
.def_readonly("is_embedded", &pdfengine::TextRun::isEmbedded)
.def_readonly("type", &pdfengine::TextRun::type)
.def_readonly("glyphs", &pdfengine::TextRun::glyphs)
.def_readonly("x", &pdfengine::TextRun::x)
.def_readonly("y", &pdfengine::TextRun::y)
.def_readonly("w", &pdfengine::TextRun::w)
.def_readonly("h", &pdfengine::TextRun::h);
py::class_<pdfengine::TextLine>(m, "TextLine")
.def_readonly("runs", &pdfengine::TextLine::runs)
.def_readonly("baseline_y", &pdfengine::TextLine::baselineY)
.def_readonly("x", &pdfengine::TextLine::x)
.def_readonly("y", &pdfengine::TextLine::y)
.def_readonly("w", &pdfengine::TextLine::w)
.def_readonly("h", &pdfengine::TextLine::h);
py::class_<pdfengine::Paragraph>(m, "Paragraph")
.def_readonly("lines", &pdfengine::Paragraph::lines)
.def_readonly("x", &pdfengine::Paragraph::x)
.def_readonly("y", &pdfengine::Paragraph::y)
.def_readonly("w", &pdfengine::Paragraph::w)
.def_readonly("h", &pdfengine::Paragraph::h);
py::class_<pdfengine::PageModel>(m, "PageModel")
.def_readonly("paragraphs", &pdfengine::PageModel::paragraphs)
.def_readonly("width", &pdfengine::PageModel::width)
.def_readonly("height", &pdfengine::PageModel::height)
.def_readonly("page_index", &pdfengine::PageModel::pageIndex);
py::class_<pdfengine::PdfPage::AnnotationInfo>(m, "AnnotationInfo")
.def_readonly("id", &pdfengine::PdfPage::AnnotationInfo::id)
.def_readonly("type", &pdfengine::PdfPage::AnnotationInfo::type)
.def_readonly("x", &pdfengine::PdfPage::AnnotationInfo::x)
.def_readonly("y", &pdfengine::PdfPage::AnnotationInfo::y)
.def_readonly("width", &pdfengine::PdfPage::AnnotationInfo::width)
.def_readonly("height", &pdfengine::PdfPage::AnnotationInfo::height)
.def_readonly("color", &pdfengine::PdfPage::AnnotationInfo::color)
.def_readonly("author", &pdfengine::PdfPage::AnnotationInfo::author)
.def_readonly("content", &pdfengine::PdfPage::AnnotationInfo::content)
.def_readonly("timestamp", &pdfengine::PdfPage::AnnotationInfo::timestamp)
.def_readonly("page_index", &pdfengine::PdfPage::AnnotationInfo::pageIndex)
.def_readonly("paths", &pdfengine::PdfPage::AnnotationInfo::paths)
.def_readonly("field_name", &pdfengine::PdfPage::AnnotationInfo::fieldName)
.def_readonly("field_value", &pdfengine::PdfPage::AnnotationInfo::fieldValue)
.def_readonly("field_type", &pdfengine::PdfPage::AnnotationInfo::fieldType)
.def_readonly("field_flags", &pdfengine::PdfPage::AnnotationInfo::fieldFlags)
.def_readonly("field_options", &pdfengine::PdfPage::AnnotationInfo::fieldOptions);
py::class_<pdfengine::PdfPage, std::shared_ptr<pdfengine::PdfPage>>(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_document_model", [](const pdfengine::PdfPage& self) {
return get_or_throw(self.extractDocumentModel());
})
.def("extract_text", [](const pdfengine::PdfPage& self) {
return get_or_throw(self.extractText());
})
.def("extract_annotations_text", [](const pdfengine::PdfPage& self) {
return get_or_throw(self.extractAnnotationsText());
})
.def("extract_annotations", [](const pdfengine::PdfPage& self) {
return get_or_throw(self.extractAnnotations());
})
.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("ordered_glyphs", [](const pdfengine::PdfPage& self) {
auto res = get_or_throw(self.orderedGlyphs());
py::list py_list;
for (const auto& g : res) {
py::dict d;
d["text"] = g.text; d["x"] = g.x; d["y"] = g.y;
d["w"] = g.w; d["h"] = g.h; d["fontSize"] = g.fontSize;
py_list.append(d);
}
return py_list;
})
.def("hit_glyph", [](const pdfengine::PdfPage& self, double x, double y) {
auto hit = get_or_throw(self.hitGlyph(x, y));
py::dict d;
d["glyphIndex"] = hit.glyphIndex;
d["caret"] = hit.caret;
d["line"] = hit.line;
return d;
}, py::arg("x"), py::arg("y"))
.def("select_range", [](const pdfengine::PdfPage& self, double ax, double ay, double bx, double by) {
auto sel = get_or_throw(self.selectRange(ax, ay, bx, by));
py::dict d;
d["startGlyph"] = sel.startGlyph;
d["endGlyph"] = sel.endGlyph;
d["text"] = sel.text;
py::list rects;
for (const auto& r : sel.rects) {
py::dict rd;
rd["x"] = r.x; rd["y"] = r.y; rd["w"] = r.w; rd["h"] = r.h;
rects.append(rd);
}
d["rects"] = rects;
return d;
}, py::arg("ax"), py::arg("ay"), py::arg("bx"), py::arg("by"))
.def("get_fonts", [](const pdfengine::PdfPage& self) {
return get_or_throw(self.getFonts());
})
.def("get_glyph_width", [](const pdfengine::PdfPage& self, const std::string& fontName, uint32_t charcode, double fontSize) {
return get_or_throw(self.getGlyphWidth(fontName, charcode, fontSize));
}, py::arg("font_name"), py::arg("charcode"), py::arg("font_size"))
.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_<pdfengine::PdfDocument, std::shared_ptr<pdfengine::PdfDocument>>(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<uint8_t> 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("extract_outline", [](const pdfengine::PdfDocument& self) {
auto res = get_or_throw(self.extractOutline());
py::list out;
for (const auto& item : res) {
py::dict d;
d["title"] = item.title;
d["pageIndex"] = item.pageIndex;
d["level"] = item.level;
out.append(d);
}
return out;
})
.def("get_page", [](pdfengine::PdfDocument& self, int pageIndex) {
return get_or_throw(self.getPage(pageIndex));
}, py::arg("page_index"))
.def("get_fonts", [](const pdfengine::PdfDocument& self, int start_page, int end_page) {
return get_or_throw(self.getFonts(start_page, end_page));
}, py::arg("start_page") = 0, py::arg("end_page") = -1)
.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<uint8_t> res = get_or_throw(self.saveIncremental());
return py::bytes(reinterpret_cast<const char*>(res.data()), res.size());
})
.def("save_full", [](const pdfengine::PdfDocument& self) {
std::vector<uint8_t> res = get_or_throw(self.saveFull());
return py::bytes(reinterpret_cast<const char*>(res.data()), res.size());
});
}