140 lines
5.5 KiB
C++
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
|
|
}
|
|
|
|
} |