Files
pdf/engine/src/parser/pdfium_edit.cpp
T

140 lines
5.5 KiB
C++

#include "parser/pdfium_internal.hpp"
#include <set>
#include <algorithm>
#if defined(PDFENGINE_WITH_PDFIUM) && defined(PDFENGINE_WITH_QPDF)
#include "qpdf/qpdf_writer.hpp"
#endif
namespace pdfengine::parser {
std::expected<void, EngineError> PdfiumDocument::applyEdits(const std::string& editsJson) {
#ifdef PDFENGINE_WITH_PDFIUM
if (!doc_) {
return std::unexpected(EngineError::Unknown);
}
lastReflowLayout_.clear();
std::vector<int> editedPages;
auto markEdited = [&editedPages](int p) {
if (std::find(editedPages.begin(), editedPages.end(), p) == editedPages.end())
editedPages.push_back(p);
};
try {
auto root = nlohmann::json::parse(editsJson);
if (!root.contains("version") || root["version"] != "1.0") {
spdlog::error("Invalid edits JSON: missing or unsupported version (expected '1.0')");
return std::unexpected(EngineError::InvalidFormat);
}
if (!root.contains("operations") || !root["operations"].is_array()) {
spdlog::error("Invalid edits JSON: missing 'operations' array");
return std::unexpected(EngineError::InvalidFormat);
}
for (const auto& op : root["operations"]) {
std::string type = op.value("type", "");
int pageIndex = op.value("pageIndex", -1);
if (pageIndex < 0 || pageIndex >= pageCount()) {
spdlog::error("Page index {} out of bounds (total pages: {})", pageIndex, pageCount());
return std::unexpected(EngineError::PageOutOfBounds);
}
static const std::set<std::string> kContentOps = {
"replace_text", "reflow_paragraph", "text_overlay", "add_text", "stamp",
"underline", "strikeout", "squiggly", "redaction",
"image_overlay", "highlight", "free_text", "comment", "freehand"};
if (kContentOps.count(type)) markEdited(pageIndex);
std::expected<void, EngineError> r{};
if (type == "replace_text") {
r = applyOp_replaceText(op, pageIndex);
} else if (type == "reflow_paragraph") {
r = applyOp_reflow(op, pageIndex);
} else if (type == "text_overlay" || type == "add_text") {
r = applyOp_textOverlay(op, pageIndex);
} else if (type == "stamp") {
r = applyOp_stamp(op, pageIndex);
} else if (type == "underline" || type == "strikeout" || type == "squiggly") {
r = applyOp_decoration(op, pageIndex);
} else if (type == "redaction") {
r = applyOp_redaction(op, pageIndex);
} else if (type == "update_field") {
r = applyOp_updateField(op, pageIndex);
} else if (type == "image_overlay") {
r = applyOp_imageOverlay(op, pageIndex);
} else if (type == "highlight") {
r = applyOp_highlight(op, pageIndex);
} else if (type == "free_text") {
r = applyOp_freeText(op, pageIndex);
} else if (type == "comment") {
r = applyOp_comment(op, pageIndex);
} else if (type == "freehand") {
r = applyOp_freehand(op, pageIndex);
} else if (type == "page_rotation") {
r = applyOp_pageRotation(op, pageIndex);
} else if (type == "page_deletion") {
r = applyOp_pageDeletion(op, pageIndex);
} else if (type == "page_reorder") {
r = applyOp_pageReorder(op, pageIndex);
} else if (type == "delete_annotation") {
r = applyOp_deleteAnnotation(op, pageIndex);
} else if (type == "update_annotation") {
r = applyOp_updateAnnotation(op, pageIndex);
} else {
spdlog::warn("Unsupported edit operation type: {}", type);
}
if (!r) {
return std::unexpected(r.error());
}
}
} catch (const nlohmann::json::parse_error& e) {
spdlog::error("JSON parse error in applyEdits: {}", e.what());
return std::unexpected(EngineError::InvalidFormat);
} catch (const std::exception& e) {
spdlog::error("Exception in applyEdits: {}", e.what());
return std::unexpected(EngineError::Unknown);
}
rebalanceEditedPages(editedPages);
invalidateCaches();
return {};
#else
(void)editsJson;
return std::unexpected(EngineError::Unknown);
#endif
}
void PdfiumDocument::rebalanceEditedPages(const std::vector<int>& editedPages) {
#if defined(PDFENGINE_WITH_PDFIUM) && defined(PDFENGINE_WITH_QPDF)
if (!doc_ || editedPages.empty()) return;
auto bytesRes = saveFull();
if (!bytesRes || bytesRes->empty()) return;
qpdf_layer::QpdfWriter writer;
auto reb = writer.rebalanceContentStreams(*bytesRes, editedPages);
if (!reb) {
spdlog::warn("rebalanceEditedPages: {}", reb.error());
return;
}
if (!reb->has_value()) return;
std::vector<uint8_t> newBytes = std::move(**reb);
FPDF_DOCUMENT nd = FPDF_LoadMemDocument(newBytes.data(), static_cast<int>(newBytes.size()), nullptr);
if (!nd) {
spdlog::warn("rebalanceEditedPages: reload of repaired doc failed; keeping original");
return;
}
FPDF_CloseDocument(doc_);
doc_ = nd;
memoryBuffer_ = std::move(newBytes);
spdlog::info("rebalanceEditedPages: repaired q/Q underflow on edited page(s) and reloaded doc");
#else
(void)editedPages;
#endif
}
}