This commit is contained in:
Furqan-14
2026-06-17 11:39:58 +05:30
40 changed files with 2633 additions and 5 deletions
+121
View File
@@ -45,9 +45,130 @@ void get_or_throw(std::expected<void, pdfengine::EngineError>&& res) {
}
#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>
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 < -500.0) combinedText += " ";
}
}
if (!combinedText.empty()) {
if (textCount == object_index) {
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");