2026-05-22 15:47:48 +05:30
|
|
|
#include <pdfengine/pdf_document.hpp>
|
|
|
|
|
#include <pdfengine/pdf_engine.hpp>
|
2026-08-10 14:21:54 +05:30
|
|
|
#include <spdlog/spdlog.h>
|
2026-08-06 12:12:56 +05:30
|
|
|
#include <pybind11/pybind11.h>
|
|
|
|
|
#include <pybind11/stl.h>
|
2026-05-22 15:47:48 +05:30
|
|
|
|
|
|
|
|
namespace py = pybind11;
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
void throw_on_error(pdfengine::EngineError err) {
|
|
|
|
|
switch (err) {
|
2026-08-06 12:12:56 +05:30
|
|
|
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");
|
2026-05-22 15:47:48 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-06 12:12:56 +05:30
|
|
|
template <typename T> T get_or_throw(std::expected<T, pdfengine::EngineError>&& res) {
|
2026-05-22 15:47:48 +05:30
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-06 12:12:56 +05:30
|
|
|
} // namespace
|
2026-05-22 15:47:48 +05:30
|
|
|
|
2026-08-06 12:12:56 +05:30
|
|
|
#include <parser/content_builder.hpp>
|
|
|
|
|
#include <parser/lexer.hpp>
|
|
|
|
|
#include <parser/parser.hpp>
|
2026-06-16 19:06:48 +05:30
|
|
|
#include <pdfengine/content_object.hpp>
|
2026-08-07 19:09:44 +05:30
|
|
|
#include <pdfengine/ocr/ocr_coordinator.hpp>
|
2026-06-16 19:06:48 +05:30
|
|
|
#include <qpdf/qpdf_extractor.hpp>
|
|
|
|
|
#include <qpdf/qpdf_writer.hpp>
|
|
|
|
|
#include <serializer/ast_serializer.hpp>
|
2026-08-06 12:12:56 +05:30
|
|
|
#include <serializer/content_serializer.hpp>
|
2026-06-16 19:06:48 +05:30
|
|
|
|
2026-06-17 17:12:53 +05:30
|
|
|
static constexpr double kTjSpaceKern = -500.0;
|
|
|
|
|
|
2026-06-16 19:06:48 +05:30
|
|
|
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");
|
|
|
|
|
}
|
2026-08-06 12:12:56 +05:30
|
|
|
|
2026-06-16 19:06:48 +05:30
|
|
|
pdfengine::Lexer lexer(stream->decodedContent);
|
|
|
|
|
auto tokens = lexer.tokenize();
|
|
|
|
|
pdfengine::ContentParser parser(tokens);
|
|
|
|
|
pdfengine::ContentBuilder builder;
|
2026-08-06 12:12:56 +05:30
|
|
|
|
2026-06-16 19:06:48 +05:30
|
|
|
auto objects = builder.build(parser.parse());
|
2026-08-06 12:12:56 +05:30
|
|
|
|
2026-06-16 19:06:48 +05:30
|
|
|
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;
|
2026-06-16 19:27:32 +05:30
|
|
|
d["text"] = py::bytes(textObj->text);
|
2026-06-16 19:06:48 +05:30
|
|
|
d["fontName"] = textObj->fontName;
|
|
|
|
|
d["fontSize"] = textObj->fontSize;
|
2026-08-06 12:12:56 +05:30
|
|
|
|
2026-06-16 19:06:48 +05:30
|
|
|
py::list tm;
|
|
|
|
|
for (int i = 0; i < 6; ++i) {
|
|
|
|
|
tm.append(textObj->tm[i]);
|
|
|
|
|
}
|
|
|
|
|
d["tm"] = tm;
|
|
|
|
|
result.append(d);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-06 12:12:56 +05:30
|
|
|
bool replace_text_object(int page_index, int object_index, const py::bytes& new_text_bytes,
|
|
|
|
|
const std::string& dest_path) {
|
2026-06-16 19:27:32 +05:30
|
|
|
std::string new_text = new_text_bytes;
|
2026-06-16 19:06:48 +05:30
|
|
|
pdfengine::qpdf_layer::QpdfExtractor extractor;
|
|
|
|
|
auto stream = extractor.extractPageStream(filepath_, page_index);
|
2026-08-06 12:12:56 +05:30
|
|
|
if (!stream.has_value())
|
|
|
|
|
return false;
|
|
|
|
|
|
2026-06-16 19:06:48 +05:30
|
|
|
pdfengine::Lexer lexer(stream->decodedContent);
|
|
|
|
|
auto tokens = lexer.tokenize();
|
|
|
|
|
pdfengine::ContentParser parser(tokens);
|
2026-06-17 11:03:47 +05:30
|
|
|
auto operations = parser.parse();
|
2026-08-06 12:12:56 +05:30
|
|
|
|
2026-06-16 19:06:48 +05:30
|
|
|
int textCount = 0;
|
|
|
|
|
bool modified = false;
|
2026-06-17 11:03:47 +05:30
|
|
|
for (auto& op : operations) {
|
|
|
|
|
if (op.op == "Tj" || op.op == "'") {
|
2026-08-06 12:12:56 +05:30
|
|
|
if (op.operands.empty())
|
|
|
|
|
continue;
|
2026-06-17 11:03:47 +05:30
|
|
|
auto& strNode = op.operands.back();
|
2026-08-06 12:12:56 +05:30
|
|
|
if (strNode->type == pdfengine::AstNodeType::String ||
|
|
|
|
|
strNode->type == pdfengine::AstNodeType::HexString) {
|
2026-06-17 11:03:47 +05:30
|
|
|
if (textCount == object_index) {
|
|
|
|
|
strNode->type = pdfengine::AstNodeType::String;
|
|
|
|
|
strNode->stringValue = new_text;
|
|
|
|
|
modified = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
textCount++;
|
|
|
|
|
}
|
|
|
|
|
} else if (op.op == "TJ") {
|
2026-08-06 12:12:56 +05:30
|
|
|
if (op.operands.empty())
|
|
|
|
|
continue;
|
2026-06-17 11:03:47 +05:30
|
|
|
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) {
|
2026-08-06 12:12:56 +05:30
|
|
|
combinedText +=
|
|
|
|
|
std::string(item->bytesValue.begin(), item->bytesValue.end());
|
2026-06-17 11:03:47 +05:30
|
|
|
} else if (item->type == pdfengine::AstNodeType::Number) {
|
2026-08-06 12:12:56 +05:30
|
|
|
if (item->numberValue < kTjSpaceKern)
|
|
|
|
|
combinedText += " ";
|
2026-06-17 11:03:47 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!combinedText.empty()) {
|
|
|
|
|
if (textCount == object_index) {
|
2026-06-17 17:12:53 +05:30
|
|
|
bool redistributed = false;
|
|
|
|
|
if (new_text.size() == combinedText.size()) {
|
|
|
|
|
std::vector<std::pair<pdfengine::AstNode*, std::string>> assign;
|
2026-08-06 12:12:56 +05:30
|
|
|
size_t pos = 0;
|
|
|
|
|
bool ok = true;
|
2026-06-17 17:12:53 +05:30
|
|
|
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)
|
2026-08-06 12:12:56 +05:30
|
|
|
? item->bytesValue.size()
|
|
|
|
|
: item->stringValue.size();
|
2026-06-17 17:12:53 +05:30
|
|
|
assign.emplace_back(item.get(), new_text.substr(pos, L));
|
|
|
|
|
pos += L;
|
|
|
|
|
} else if (item->type == pdfengine::AstNodeType::Number &&
|
|
|
|
|
item->numberValue < kTjSpaceKern) {
|
2026-08-06 12:12:56 +05:30
|
|
|
if (pos >= new_text.size() || new_text[pos] != ' ') {
|
|
|
|
|
ok = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2026-06-18 16:48:29 +05:30
|
|
|
pos += 1;
|
2026-06-17 17:12:53 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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();
|
2026-08-06 12:12:56 +05:30
|
|
|
auto newStrNode = std::make_shared<pdfengine::AstNode>(
|
|
|
|
|
pdfengine::AstNodeType::String);
|
2026-06-17 17:12:53 +05:30
|
|
|
newStrNode->stringValue = new_text;
|
|
|
|
|
arrNode->arrayItems.push_back(std::move(newStrNode));
|
|
|
|
|
}
|
2026-06-17 11:03:47 +05:30
|
|
|
modified = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
textCount++;
|
|
|
|
|
}
|
2026-06-16 19:06:48 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-06 12:12:56 +05:30
|
|
|
|
|
|
|
|
if (!modified)
|
|
|
|
|
return false;
|
|
|
|
|
|
2026-06-16 19:06:48 +05:30
|
|
|
pdfengine::AstSerializer astSerializer;
|
2026-06-17 11:03:47 +05:30
|
|
|
std::string newRawStream = astSerializer.serialize(operations);
|
2026-08-06 12:12:56 +05:30
|
|
|
|
2026-06-16 19:06:48 +05:30
|
|
|
pdfengine::qpdf_layer::QpdfWriter writer;
|
|
|
|
|
auto res = writer.replacePageStreamAndSave(filepath_, dest_path, page_index, newRawStream);
|
|
|
|
|
return res.has_value();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
std::string filepath_;
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-22 15:47:48 +05:30
|
|
|
PYBIND11_MODULE(pdfengine, m) {
|
|
|
|
|
m.doc() = "Python bindings for the PdfEngine C++ Core SDK";
|
|
|
|
|
|
2026-06-16 19:06:48 +05:30
|
|
|
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"))
|
2026-08-06 12:12:56 +05:30
|
|
|
.def("replace_text_object", &StreamEditor::replace_text_object, py::arg("page_index"),
|
|
|
|
|
py::arg("object_index"), py::arg("new_text"), py::arg("dest_path"));
|
2026-06-16 19:06:48 +05:30
|
|
|
|
2026-05-22 15:47:48 +05:30
|
|
|
m.def("engine_version", &pdfengine::engineVersion, "Get the engine version string");
|
|
|
|
|
m.def("engine_build_info", &pdfengine::engineBuildInfo, "Get the engine build info string");
|
2026-08-06 12:12:56 +05:30
|
|
|
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");
|
2026-05-22 15:47:48 +05:30
|
|
|
|
|
|
|
|
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) {
|
2026-08-06 12:12:56 +05:30
|
|
|
return "DevicePoint(x=" + std::to_string(self.x) + ", y=" + std::to_string(self.y) +
|
|
|
|
|
")";
|
2026-05-22 15:47:48 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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 + "')";
|
|
|
|
|
});
|
|
|
|
|
|
2026-06-12 15:04:09 +05:30
|
|
|
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)
|
2026-08-06 12:12:56 +05:30
|
|
|
.def_readonly("can_extract_for_accessibility",
|
|
|
|
|
&pdfengine::DocumentPermissions::canExtractForAccessibility)
|
2026-06-12 15:04:09 +05:30
|
|
|
.def_readonly("can_assemble", &pdfengine::DocumentPermissions::canAssemble);
|
|
|
|
|
|
2026-05-22 15:47:48 +05:30
|
|
|
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());
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-26 15:55:48 +05:30
|
|
|
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) {
|
2026-08-06 12:12:56 +05:30
|
|
|
return "FontInfo(font_name='" + self.fontName + "', type='" + self.type +
|
|
|
|
|
"', is_embedded=" + (self.isEmbedded ? "True" : "False") + ")";
|
2026-05-26 15:55:48 +05:30
|
|
|
});
|
|
|
|
|
|
2026-06-08 16:28:10 +05:30
|
|
|
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)
|
2026-06-11 18:19:26 +05:30
|
|
|
.def_readonly("angle", &pdfengine::Glyph::angle)
|
|
|
|
|
.def_readonly("page_object_index", &pdfengine::Glyph::pageObjectIndex);
|
2026-06-08 16:28:10 +05:30
|
|
|
|
|
|
|
|
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)
|
2026-08-07 19:09:44 +05:30
|
|
|
.def_readonly("is_embedded_font", &pdfengine::TextRun::isEmbeddedFont)
|
|
|
|
|
.def_readonly("is_predicted_font", &pdfengine::TextRun::isPredictedFont)
|
2026-06-08 16:28:10 +05:30
|
|
|
.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)
|
2026-06-11 18:19:26 +05:30
|
|
|
.def_readonly("h", &pdfengine::TextRun::h)
|
2026-06-12 19:13:29 +05:30
|
|
|
.def_readonly("object_indices", &pdfengine::TextRun::objectIndices)
|
2026-06-18 16:48:29 +05:30
|
|
|
.def_readonly("fill_color", &pdfengine::TextRun::fillColor)
|
2026-06-26 11:33:28 +05:30
|
|
|
.def_readonly("para_id", &pdfengine::TextRun::paraId)
|
|
|
|
|
.def_readonly("font_fidelity", &pdfengine::TextRun::fontFidelity);
|
2026-06-08 16:28:10 +05:30
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2026-06-09 10:35:12 +05:30
|
|
|
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)
|
2026-06-10 15:22:34 +05:30
|
|
|
.def_readonly("page_index", &pdfengine::PdfPage::AnnotationInfo::pageIndex)
|
2026-07-09 10:38:26 +05:30
|
|
|
.def_readonly("thickness", &pdfengine::PdfPage::AnnotationInfo::thickness)
|
2026-06-10 18:38:38 +05:30
|
|
|
.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)
|
2026-07-07 19:03:11 +05:30
|
|
|
.def_readonly("field_options", &pdfengine::PdfPage::AnnotationInfo::fieldOptions)
|
|
|
|
|
.def_property_readonly("quad_points", [](const pdfengine::PdfPage::AnnotationInfo& self) {
|
|
|
|
|
py::list out;
|
|
|
|
|
for (const auto& quad : self.quadPoints) {
|
|
|
|
|
py::list quad_list;
|
|
|
|
|
for (const auto& pt : quad) {
|
|
|
|
|
py::dict d;
|
|
|
|
|
d["x"] = pt.x;
|
|
|
|
|
d["y"] = pt.y;
|
|
|
|
|
quad_list.append(d);
|
|
|
|
|
}
|
|
|
|
|
out.append(quad_list);
|
|
|
|
|
}
|
|
|
|
|
return out;
|
|
|
|
|
});
|
2026-06-09 10:35:12 +05:30
|
|
|
|
2026-05-22 15:47:48 +05:30
|
|
|
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)
|
2026-08-06 12:12:56 +05:30
|
|
|
.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(
|
|
|
|
|
"render_tile",
|
|
|
|
|
[](const pdfengine::PdfPage& self, int dpi, double xPt, double yPt, double wPt,
|
|
|
|
|
double hPt) {
|
|
|
|
|
auto img = get_or_throw(self.renderTile(dpi, xPt, yPt, wPt, hPt));
|
|
|
|
|
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("xPt"), py::arg("yPt"), py::arg("wPt"), py::arg("hPt"))
|
|
|
|
|
.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));
|
2026-05-25 14:47:38 +05:30
|
|
|
py::dict d;
|
2026-08-06 12:12:56 +05:30
|
|
|
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));
|
2026-06-11 19:19:01 +05:30
|
|
|
py::dict d;
|
2026-08-06 12:12:56 +05:30
|
|
|
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"));
|
2026-05-22 15:47:48 +05:30
|
|
|
|
|
|
|
|
py::class_<pdfengine::PdfDocument, std::shared_ptr<pdfengine::PdfDocument>>(m, "PdfDocument")
|
2026-08-06 12:12:56 +05:30
|
|
|
.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") = "")
|
2026-05-22 15:47:48 +05:30
|
|
|
.def_property_readonly("page_count", &pdfengine::PdfDocument::pageCount)
|
|
|
|
|
.def_property_readonly("metadata", &pdfengine::PdfDocument::metadata)
|
2026-06-12 15:04:09 +05:30
|
|
|
.def_property_readonly("permissions", &pdfengine::PdfDocument::permissions)
|
2026-08-06 12:12:56 +05:30
|
|
|
.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) {
|
|
|
|
|
auto regions = get_or_throw(self.applyEdits(editsJson));
|
|
|
|
|
py::list py_regions;
|
|
|
|
|
for (const auto& r : regions) {
|
|
|
|
|
py::dict d;
|
|
|
|
|
d["pageIndex"] = r.pageIndex;
|
|
|
|
|
d["x"] = r.x;
|
|
|
|
|
d["y"] = r.y;
|
|
|
|
|
d["width"] = r.width;
|
|
|
|
|
d["height"] = r.height;
|
|
|
|
|
py_regions.append(d);
|
|
|
|
|
}
|
|
|
|
|
return py_regions;
|
|
|
|
|
},
|
|
|
|
|
py::arg("edits_json"))
|
|
|
|
|
.def("last_reflow_layout",
|
|
|
|
|
[](const pdfengine::PdfDocument& self) { return self.lastReflowLayout(); })
|
|
|
|
|
.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());
|
|
|
|
|
})
|
2026-06-30 21:18:46 +05:30
|
|
|
.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());
|
2026-05-22 15:47:48 +05:30
|
|
|
});
|
2026-08-07 19:09:44 +05:30
|
|
|
|
|
|
|
|
py::class_<pdfengine::ocr::OCRCoordinator>(m, "OCRCoordinator")
|
|
|
|
|
.def(py::init<>())
|
|
|
|
|
.def(
|
|
|
|
|
"process_document",
|
|
|
|
|
[](const pdfengine::ocr::OCRCoordinator& self, int pageIndex, double imgW, double imgH,
|
|
|
|
|
double pdfW, double pdfH, const py::list& lines_list) {
|
|
|
|
|
std::vector<pdfengine::document::RawOCRLine> cpp_lines;
|
2026-08-10 14:21:54 +05:30
|
|
|
|
|
|
|
|
auto get_str_safe = [](py::dict d, const char* key, const std::string& fallback = "") -> std::string {
|
|
|
|
|
if (d.contains(key) && !d[key].is_none()) {
|
|
|
|
|
try { return d[key].cast<std::string>(); } catch (...) {}
|
|
|
|
|
}
|
|
|
|
|
return fallback;
|
|
|
|
|
};
|
|
|
|
|
auto get_double_safe = [](py::dict d, const char* key, double fallback = 0.0) -> double {
|
|
|
|
|
if (d.contains(key) && !d[key].is_none()) {
|
|
|
|
|
try { return d[key].cast<double>(); } catch (...) {}
|
|
|
|
|
}
|
|
|
|
|
return fallback;
|
|
|
|
|
};
|
|
|
|
|
auto get_int_safe = [](py::dict d, const char* key, int fallback = 0) -> int {
|
|
|
|
|
if (d.contains(key) && !d[key].is_none()) {
|
|
|
|
|
try { return d[key].cast<int>(); } catch (...) {}
|
|
|
|
|
}
|
|
|
|
|
return fallback;
|
|
|
|
|
};
|
|
|
|
|
auto get_bool_safe = [](py::dict d, const char* key, bool fallback = false) -> bool {
|
|
|
|
|
if (d.contains(key) && !d[key].is_none()) {
|
|
|
|
|
try { return d[key].cast<bool>(); } catch (...) {}
|
|
|
|
|
}
|
|
|
|
|
return fallback;
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-07 19:09:44 +05:30
|
|
|
for (auto item : lines_list) {
|
2026-08-10 14:21:54 +05:30
|
|
|
if (item.is_none()) continue;
|
2026-08-07 19:09:44 +05:30
|
|
|
py::dict d = item.cast<py::dict>();
|
|
|
|
|
pdfengine::document::RawOCRLine line;
|
|
|
|
|
|
2026-08-10 14:21:54 +05:30
|
|
|
line.text = get_str_safe(d, "text", "");
|
|
|
|
|
line.confidence = get_double_safe(d, "confidence", 0.0);
|
|
|
|
|
line.fontSize = get_double_safe(d, "fontSize", 12.0);
|
|
|
|
|
line.fontName = get_str_safe(d, "fontName", "Helvetica");
|
|
|
|
|
line.fontId = get_str_safe(d, "fontId", "");
|
|
|
|
|
line.fontFace = get_str_safe(d, "fontFace", "");
|
|
|
|
|
line.fontWeight = get_int_safe(d, "fontWeight", 400);
|
|
|
|
|
line.fontStyle = get_str_safe(d, "fontStyle", "normal");
|
|
|
|
|
line.isBold = get_bool_safe(d, "isBold", false);
|
|
|
|
|
line.isItalic = get_bool_safe(d, "isItalic", false);
|
|
|
|
|
line.lineSpacing = get_double_safe(d, "lineSpacing", 1.2);
|
|
|
|
|
line.letterSpacing = get_double_safe(d, "letterSpacing", 0.0);
|
|
|
|
|
|
|
|
|
|
if (d.contains("box") && !d["box"].is_none()) {
|
2026-08-07 19:09:44 +05:30
|
|
|
py::dict box = d["box"].cast<py::dict>();
|
2026-08-10 14:21:54 +05:30
|
|
|
line.x = get_double_safe(box, "x", 0.0);
|
|
|
|
|
line.y = get_double_safe(box, "y", 0.0);
|
|
|
|
|
line.width = get_double_safe(box, "width", 0.0);
|
|
|
|
|
line.height = get_double_safe(box, "height", 0.0);
|
2026-08-07 19:09:44 +05:30
|
|
|
} else {
|
2026-08-10 14:21:54 +05:30
|
|
|
line.x = get_double_safe(d, "x", 0.0);
|
|
|
|
|
line.y = get_double_safe(d, "y", 0.0);
|
|
|
|
|
line.width = get_double_safe(d, "width", 0.0);
|
|
|
|
|
line.height = get_double_safe(d, "height", 0.0);
|
2026-08-07 19:09:44 +05:30
|
|
|
}
|
2026-08-10 14:21:54 +05:30
|
|
|
|
|
|
|
|
spdlog::info(
|
|
|
|
|
"[OCR_CPP_RUN] text='{}' fontName='{}' fontId='{}' "
|
|
|
|
|
"fontFace='{}' fontWeight={} fontStyle='{}'",
|
|
|
|
|
line.text,
|
|
|
|
|
line.fontName.empty() ? "<empty>" : line.fontName,
|
|
|
|
|
line.fontId.empty() ? "<empty>" : line.fontId,
|
|
|
|
|
line.fontFace.empty() ? "<empty>" : line.fontFace,
|
|
|
|
|
line.fontWeight,
|
|
|
|
|
line.fontStyle.empty() ? "<empty>" : line.fontStyle
|
|
|
|
|
);
|
|
|
|
|
|
2026-08-07 19:09:44 +05:30
|
|
|
cpp_lines.push_back(line);
|
|
|
|
|
}
|
|
|
|
|
return self.processDocument(pageIndex, imgW, imgH, pdfW, pdfH, cpp_lines);
|
|
|
|
|
},
|
|
|
|
|
py::arg("page_index"), py::arg("img_w"), py::arg("img_h"), py::arg("pdf_w"),
|
|
|
|
|
py::arg("pdf_h"), py::arg("lines"));
|
2026-05-22 15:47:48 +05:30
|
|
|
}
|