317 lines
17 KiB
C++
317 lines
17 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::DocumentPermissions>(m, "DocumentPermissions")
|
|
.def_readonly("is_encrypted", &pdfengine::DocumentPermissions::isEncrypted)
|
|
.def_readonly("encryption", &pdfengine::DocumentPermissions::encryption)
|
|
.def_readonly("security_revision", &pdfengine::DocumentPermissions::securityRevision)
|
|
.def_readonly("owner_unlocked", &pdfengine::DocumentPermissions::ownerUnlocked)
|
|
.def_readonly("can_print", &pdfengine::DocumentPermissions::canPrint)
|
|
.def_readonly("can_print_high_res", &pdfengine::DocumentPermissions::canPrintHighRes)
|
|
.def_readonly("can_modify", &pdfengine::DocumentPermissions::canModify)
|
|
.def_readonly("can_copy", &pdfengine::DocumentPermissions::canCopy)
|
|
.def_readonly("can_annotate", &pdfengine::DocumentPermissions::canAnnotate)
|
|
.def_readonly("can_fill_forms", &pdfengine::DocumentPermissions::canFillForms)
|
|
.def_readonly("can_extract_for_accessibility", &pdfengine::DocumentPermissions::canExtractForAccessibility)
|
|
.def_readonly("can_assemble", &pdfengine::DocumentPermissions::canAssemble);
|
|
|
|
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)
|
|
.def_readonly("page_object_index", &pdfengine::Glyph::pageObjectIndex);
|
|
|
|
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)
|
|
.def_readonly("object_indices", &pdfengine::TextRun::objectIndices);
|
|
|
|
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_property_readonly("permissions", &pdfengine::PdfDocument::permissions)
|
|
.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());
|
|
});
|
|
} |