Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into furqan
This commit is contained in:
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user