500 lines
26 KiB
C++
500 lines
26 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());
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#include <pdfengine/content_object.hpp>
|
|
#include <qpdf/qpdf_extractor.hpp>
|
|
#include <qpdf/qpdf_writer.hpp>
|
|
#include <parser/lexer.hpp>
|
|
#include <parser/parser.hpp>
|
|
#include <parser/content_builder.hpp>
|
|
#include <serializer/content_serializer.hpp>
|
|
#include <serializer/ast_serializer.hpp>
|
|
|
|
static constexpr double kTjSpaceKern = -500.0;
|
|
|
|
class StreamEditor {
|
|
public:
|
|
StreamEditor(const std::string& filepath) : filepath_(filepath) {}
|
|
|
|
py::list extract_text_objects(int page_index) {
|
|
pdfengine::qpdf_layer::QpdfExtractor extractor;
|
|
auto stream = extractor.extractPageStream(filepath_, page_index);
|
|
if (!stream.has_value()) {
|
|
throw std::runtime_error("Failed to extract page stream");
|
|
}
|
|
|
|
pdfengine::Lexer lexer(stream->decodedContent);
|
|
auto tokens = lexer.tokenize();
|
|
pdfengine::ContentParser parser(tokens);
|
|
pdfengine::ContentBuilder builder;
|
|
|
|
auto objects = builder.build(parser.parse());
|
|
|
|
py::list result;
|
|
for (const auto& obj : objects) {
|
|
if (obj->getType() == pdfengine::ContentObjectType::Text) {
|
|
auto* textObj = static_cast<pdfengine::TextObject*>(obj.get());
|
|
py::dict d;
|
|
d["text"] = py::bytes(textObj->text);
|
|
d["fontName"] = textObj->fontName;
|
|
d["fontSize"] = textObj->fontSize;
|
|
|
|
py::list tm;
|
|
for (int i = 0; i < 6; ++i) {
|
|
tm.append(textObj->tm[i]);
|
|
}
|
|
d["tm"] = tm;
|
|
result.append(d);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool replace_text_object(int page_index, int object_index, const py::bytes& new_text_bytes, const std::string& dest_path) {
|
|
std::string new_text = new_text_bytes;
|
|
pdfengine::qpdf_layer::QpdfExtractor extractor;
|
|
auto stream = extractor.extractPageStream(filepath_, page_index);
|
|
if (!stream.has_value()) return false;
|
|
|
|
pdfengine::Lexer lexer(stream->decodedContent);
|
|
auto tokens = lexer.tokenize();
|
|
pdfengine::ContentParser parser(tokens);
|
|
auto operations = parser.parse();
|
|
|
|
int textCount = 0;
|
|
bool modified = false;
|
|
for (auto& op : operations) {
|
|
if (op.op == "Tj" || op.op == "'") {
|
|
if (op.operands.empty()) continue;
|
|
auto& strNode = op.operands.back();
|
|
if (strNode->type == pdfengine::AstNodeType::String || strNode->type == pdfengine::AstNodeType::HexString) {
|
|
if (textCount == object_index) {
|
|
strNode->type = pdfengine::AstNodeType::String;
|
|
strNode->stringValue = new_text;
|
|
modified = true;
|
|
break;
|
|
}
|
|
textCount++;
|
|
}
|
|
} else if (op.op == "TJ") {
|
|
if (op.operands.empty()) continue;
|
|
auto& arrNode = op.operands.back();
|
|
if (arrNode->type == pdfengine::AstNodeType::Array) {
|
|
std::string combinedText;
|
|
for (const auto& item : arrNode->arrayItems) {
|
|
if (item->type == pdfengine::AstNodeType::String) {
|
|
combinedText += item->stringValue;
|
|
} else if (item->type == pdfengine::AstNodeType::HexString) {
|
|
combinedText += std::string(item->bytesValue.begin(), item->bytesValue.end());
|
|
} else if (item->type == pdfengine::AstNodeType::Number) {
|
|
if (item->numberValue < kTjSpaceKern) combinedText += " ";
|
|
}
|
|
}
|
|
if (!combinedText.empty()) {
|
|
if (textCount == object_index) {
|
|
bool redistributed = false;
|
|
if (new_text.size() == combinedText.size()) {
|
|
std::vector<std::pair<pdfengine::AstNode*, std::string>> assign;
|
|
size_t pos = 0; bool ok = true;
|
|
for (const auto& item : arrNode->arrayItems) {
|
|
if (item->type == pdfengine::AstNodeType::String ||
|
|
item->type == pdfengine::AstNodeType::HexString) {
|
|
size_t L = (item->type == pdfengine::AstNodeType::HexString)
|
|
? item->bytesValue.size() : item->stringValue.size();
|
|
assign.emplace_back(item.get(), new_text.substr(pos, L));
|
|
pos += L;
|
|
} else if (item->type == pdfengine::AstNodeType::Number &&
|
|
item->numberValue < kTjSpaceKern) {
|
|
if (pos >= new_text.size() || new_text[pos] != ' ') { ok = false; break; }
|
|
pos += 1;
|
|
}
|
|
}
|
|
if (ok && pos == new_text.size()) {
|
|
for (auto& [node, content] : assign) {
|
|
node->type = pdfengine::AstNodeType::String;
|
|
node->stringValue = content;
|
|
}
|
|
redistributed = true;
|
|
}
|
|
}
|
|
if (!redistributed) {
|
|
arrNode->arrayItems.clear();
|
|
auto newStrNode = std::make_shared<pdfengine::AstNode>(pdfengine::AstNodeType::String);
|
|
newStrNode->stringValue = new_text;
|
|
arrNode->arrayItems.push_back(std::move(newStrNode));
|
|
}
|
|
modified = true;
|
|
break;
|
|
}
|
|
textCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!modified) return false;
|
|
|
|
pdfengine::AstSerializer astSerializer;
|
|
std::string newRawStream = astSerializer.serialize(operations);
|
|
|
|
pdfengine::qpdf_layer::QpdfWriter writer;
|
|
auto res = writer.replacePageStreamAndSave(filepath_, dest_path, page_index, newRawStream);
|
|
return res.has_value();
|
|
}
|
|
|
|
private:
|
|
std::string filepath_;
|
|
};
|
|
|
|
PYBIND11_MODULE(pdfengine, m) {
|
|
m.doc() = "Python bindings for the PdfEngine C++ Core SDK";
|
|
|
|
py::class_<StreamEditor>(m, "StreamEditor")
|
|
.def(py::init<const std::string&>(), py::arg("filepath"))
|
|
.def("extract_text_objects", &StreamEditor::extract_text_objects, py::arg("page_index"))
|
|
.def("replace_text_object", &StreamEditor::replace_text_object, py::arg("page_index"), py::arg("object_index"), py::arg("new_text"), py::arg("dest_path"));
|
|
|
|
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)
|
|
.def_readonly("fill_color", &pdfengine::TextRun::fillColor)
|
|
.def_readonly("para_id", &pdfengine::TextRun::paraId)
|
|
.def_readonly("font_fidelity", &pdfengine::TextRun::fontFidelity);
|
|
|
|
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("render_region_raw", [](const pdfengine::PdfPage& self, int dpi, double y_top_pt, double height_pt) {
|
|
auto img = get_or_throw(self.renderRegionRaw(dpi, y_top_pt, height_pt));
|
|
return py::make_tuple(img.width, img.height,
|
|
py::bytes(reinterpret_cast<const char*>(img.data.data()), img.data.size()));
|
|
}, py::arg("dpi"), py::arg("y_top_pt"), py::arg("height_pt") = 0.0)
|
|
.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)
|
|
.def("extract_display_list", [](const pdfengine::PdfPage& self) {
|
|
return get_or_throw(self.extractDisplayListJson());
|
|
})
|
|
.def("extract_image_xobject", [](const pdfengine::PdfPage& self, const std::string& name) {
|
|
auto res = get_or_throw(self.extractImageXObject(name));
|
|
return py::bytes(reinterpret_cast<const char*>(res.data()), res.size());
|
|
}, py::arg("name"));
|
|
|
|
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("get_font_data", [](const pdfengine::PdfDocument& self, const std::string& internal_font_id) {
|
|
auto res = self.getFontData(internal_font_id);
|
|
if (!res || res->empty()) {
|
|
return py::bytes();
|
|
}
|
|
return py::bytes(reinterpret_cast<const char*>(res->data()), res->size());
|
|
}, py::arg("internal_font_id"))
|
|
.def("get_reconstructed_font_data", [](pdfengine::PdfDocument& self, const std::string& internal_font_id) {
|
|
auto res = self.getReconstructedFontData(internal_font_id);
|
|
if (!res || res->empty()) {
|
|
return py::bytes();
|
|
}
|
|
return py::bytes(reinterpret_cast<const char*>(res->data()), res->size());
|
|
}, py::arg("internal_font_id"))
|
|
.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());
|
|
})
|
|
.def("save_full_for_export", [](const pdfengine::PdfDocument& self) {
|
|
std::vector<uint8_t> res = get_or_throw(self.saveFullForExport());
|
|
return py::bytes(reinterpret_cast<const char*>(res.data()), res.size());
|
|
});
|
|
} |