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

302 lines
9.0 KiB
C++
Raw Normal View History

2026-06-29 10:51:04 +05:30
#include "parser/pdfium_internal.hpp"
2026-05-22 15:47:48 +05:30
2026-06-30 21:18:46 +05:30
#if defined(PDFENGINE_WITH_PDFIUM) && defined(PDFENGINE_WITH_QPDF)
#include "qpdf/qpdf_writer.hpp"
#endif
2026-05-22 15:47:48 +05:30
namespace pdfengine {
2026-08-07 19:09:44 +05:30
std::expected<std::shared_ptr<PdfDocument>, EngineError>
2026-05-22 15:47:48 +05:30
PdfDocument::loadFromFile(const std::string& path, const std::string& password) {
#ifdef PDFENGINE_WITH_PDFIUM
2026-06-29 10:51:04 +05:30
parser::ensure_pdfium_initialized();
2026-08-07 19:09:44 +05:30
FPDF_DOCUMENT doc =
FPDF_LoadDocument(path.c_str(), password.empty() ? nullptr : password.c_str());
2026-05-22 15:47:48 +05:30
if (!doc) {
auto err = FPDF_GetLastError();
spdlog::error("Failed to load PDF file from path: {} (error code: {})", path, err);
2026-06-29 10:51:04 +05:30
return std::unexpected(parser::mapPdfiumError(err, !password.empty()));
2026-05-22 15:47:48 +05:30
}
return std::make_shared<parser::PdfiumDocument>(doc);
#else
2026-08-07 19:09:44 +05:30
(void) path;
(void) password;
2026-05-22 15:47:48 +05:30
spdlog::error("loadFromFile failed: PDFEngine compiled without PDFium support.");
return std::unexpected(EngineError::Unknown);
#endif
}
2026-08-07 19:09:44 +05:30
std::expected<std::shared_ptr<PdfDocument>, EngineError>
2026-05-22 15:47:48 +05:30
PdfDocument::loadFromMemory(const std::vector<uint8_t>& data, const std::string& password) {
#ifdef PDFENGINE_WITH_PDFIUM
2026-06-29 10:51:04 +05:30
parser::ensure_pdfium_initialized();
2026-05-22 15:47:48 +05:30
if (data.empty()) {
return std::unexpected(EngineError::InvalidFormat);
}
if (!limits::documentSizeOk(data.size())) {
spdlog::error("Refusing to load PDF: {} bytes exceeds hardened limit ({} bytes)",
data.size(), limits::kMaxDocumentBytes);
return std::unexpected(EngineError::InvalidFormat);
}
2026-05-26 11:23:29 +05:30
std::vector<uint8_t> buffer_copy = data;
2026-08-07 19:09:44 +05:30
FPDF_DOCUMENT doc =
FPDF_LoadMemDocument(buffer_copy.data(), static_cast<int>(buffer_copy.size()),
password.empty() ? nullptr : password.c_str());
2026-05-22 15:47:48 +05:30
if (!doc) {
auto err = FPDF_GetLastError();
spdlog::error("Failed to load PDF from memory (error code: {})", err);
2026-06-29 10:51:04 +05:30
return std::unexpected(parser::mapPdfiumError(err, !password.empty()));
2026-05-22 15:47:48 +05:30
}
if (!limits::pageCountOk(FPDF_GetPageCount(doc))) {
spdlog::error("Refusing to load PDF: page count exceeds hardened limit ({})",
limits::kMaxPageCount);
FPDF_CloseDocument(doc);
return std::unexpected(EngineError::InvalidFormat);
}
2026-05-26 11:23:29 +05:30
return std::make_shared<parser::PdfiumDocument>(doc, std::move(buffer_copy));
2026-05-22 15:47:48 +05:30
#else
2026-08-07 19:09:44 +05:30
(void) data;
(void) password;
2026-05-22 15:47:48 +05:30
spdlog::error("loadFromMemory failed: PDFEngine compiled without PDFium support.");
return std::unexpected(EngineError::Unknown);
#endif
}
2026-08-07 19:09:44 +05:30
} // namespace pdfengine
2026-06-29 10:51:04 +05:30
2026-05-22 15:47:48 +05:30
namespace pdfengine::parser {
2026-08-07 19:09:44 +05:30
PdfiumDocument::PdfiumDocument(NativeDocHandle docHandle) : doc_(docHandle) {
}
2026-05-22 15:47:48 +05:30
2026-05-26 11:23:29 +05:30
PdfiumDocument::PdfiumDocument(NativeDocHandle docHandle, std::vector<uint8_t> memoryBuffer)
2026-08-07 19:09:44 +05:30
: doc_(docHandle), memoryBuffer_(std::move(memoryBuffer)) {
}
2026-05-26 11:23:29 +05:30
2026-05-22 15:47:48 +05:30
PdfiumDocument::~PdfiumDocument() {
#ifdef PDFENGINE_WITH_PDFIUM
if (doc_) {
FPDF_CloseDocument(doc_);
}
#endif
}
PdfiumDocument::PdfiumDocument(PdfiumDocument&& other) noexcept {
*this = std::move(other);
}
PdfiumDocument& PdfiumDocument::operator=(PdfiumDocument&& other) noexcept {
if (this != &other) {
#ifdef PDFENGINE_WITH_PDFIUM
2026-08-07 19:09:44 +05:30
if (doc_)
FPDF_CloseDocument(doc_);
2026-05-22 15:47:48 +05:30
#endif
doc_ = other.doc_;
other.doc_ = nullptr;
2026-05-26 11:23:29 +05:30
memoryBuffer_ = std::move(other.memoryBuffer_);
2026-05-22 15:47:48 +05:30
}
return *this;
}
int PdfiumDocument::pageCount() const noexcept {
#ifdef PDFENGINE_WITH_PDFIUM
return doc_ ? FPDF_GetPageCount(doc_) : 0;
#else
return 0;
#endif
}
DocumentMetadata PdfiumDocument::metadata() const noexcept {
DocumentMetadata meta;
#ifdef PDFENGINE_WITH_PDFIUM
2026-08-07 19:09:44 +05:30
if (!doc_)
return meta;
2026-05-22 15:47:48 +05:30
auto fetchMeta = [this](const char* key) -> std::string {
unsigned long len = FPDF_GetMetaText(doc_, key, nullptr, 0);
2026-08-07 19:09:44 +05:30
if (len <= 2)
return "";
2026-05-22 15:47:48 +05:30
std::vector<unsigned short> buf(len / 2);
FPDF_GetMetaText(doc_, key, buf.data(), len);
return utf16le_to_utf8(reinterpret_cast<const char16_t*>(buf.data()), buf.size());
};
meta.title = fetchMeta("Title");
meta.author = fetchMeta("Author");
meta.creator = fetchMeta("Creator");
meta.producer = fetchMeta("Producer");
meta.creationDate = fetchMeta("CreationDate");
meta.modificationDate = fetchMeta("ModDate");
#endif
return meta;
}
DocumentPermissions PdfiumDocument::permissions() const noexcept {
2026-06-18 16:48:29 +05:30
DocumentPermissions perms;
#ifdef PDFENGINE_WITH_PDFIUM
2026-08-07 19:09:44 +05:30
if (!doc_)
return perms;
const int rev = FPDF_GetSecurityHandlerRevision(doc_);
perms.securityRevision = rev;
perms.isEncrypted = (rev != -1);
if (!perms.isEncrypted) {
2026-06-18 16:48:29 +05:30
return perms;
}
switch (rev) {
2026-08-07 19:09:44 +05:30
case 2:
perms.encryption = "RC4-40";
break;
case 3:
perms.encryption = "RC4-128";
break;
case 4:
perms.encryption = "AES-128";
break;
case 5:
case 6:
perms.encryption = "AES-256";
break;
default:
perms.encryption = "Unknown";
break;
}
const unsigned long p = FPDF_GetDocPermissions(doc_);
const unsigned long up = FPDF_GetDocUserPermissions(doc_);
perms.ownerUnlocked = (p != up);
auto allowed = [p](unsigned long bit) { return (p & bit) != 0ul; };
2026-06-18 16:48:29 +05:30
perms.canPrint = allowed(0x4);
perms.canModify = allowed(0x8);
perms.canCopy = allowed(0x10);
perms.canAnnotate = allowed(0x20);
perms.canFillForms = allowed(0x100);
perms.canExtractForAccessibility = allowed(0x200);
perms.canAssemble = allowed(0x400);
perms.canPrintHighRes = allowed(0x800);
#endif
return perms;
}
2026-08-07 19:09:44 +05:30
std::expected<std::vector<PdfDocument::OutlineItem>, EngineError>
PdfiumDocument::extractOutline() const {
2026-06-10 21:51:43 +05:30
#ifdef PDFENGINE_WITH_PDFIUM
std::vector<PdfDocument::OutlineItem> result;
if (doc_) {
walkOutline(doc_, FPDFBookmark_GetFirstChild(doc_, nullptr), 0, result);
}
return result;
#else
return std::unexpected(EngineError::Unknown);
#endif
}
2026-05-22 15:47:48 +05:30
std::expected<std::shared_ptr<PdfPage>, EngineError> PdfiumDocument::getPage(int pageIndex) {
#ifdef PDFENGINE_WITH_PDFIUM
if (!doc_) {
return std::unexpected(EngineError::Unknown);
}
if (pageIndex < 0 || pageIndex >= pageCount()) {
return std::unexpected(EngineError::PageOutOfBounds);
}
2026-06-05 10:27:39 +05:30
{
std::lock_guard<std::mutex> lock(pageCacheMutex_);
auto it = pageCache_.find(pageIndex);
if (it != pageCache_.end()) {
if (auto cached = it->second.lock()) {
return cached;
}
2026-06-05 10:27:39 +05:30
}
}
FPDF_PAGE pageHandle = FPDF_LoadPage(doc_, pageIndex);
if (!pageHandle) {
2026-05-22 15:47:48 +05:30
return std::unexpected(EngineError::Unknown);
}
2026-06-05 10:27:39 +05:30
auto pageObj = std::make_shared<PdfiumPage>(
2026-08-07 19:09:44 +05:30
doc_, pageHandle, pageIndex, std::static_pointer_cast<PdfiumDocument>(shared_from_this()));
2026-06-05 10:27:39 +05:30
{
std::lock_guard<std::mutex> lock(pageCacheMutex_);
pageCache_[pageIndex] = pageObj;
}
return pageObj;
2026-05-22 15:47:48 +05:30
#else
2026-08-07 19:09:44 +05:30
(void) pageIndex;
2026-05-22 15:47:48 +05:30
return std::unexpected(EngineError::Unknown);
#endif
}
std::expected<std::vector<uint8_t>, EngineError> PdfiumDocument::saveIncremental() const {
#ifdef PDFENGINE_WITH_PDFIUM
if (!doc_) {
return std::unexpected(EngineError::Unknown);
}
VectorWriter writer;
if (!FPDF_SaveWithVersion(doc_, &writer, FPDF_INCREMENTAL, 14)) {
return std::unexpected(EngineError::WriteFailed);
}
return writer.buffer;
#else
return std::unexpected(EngineError::Unknown);
#endif
}
std::expected<std::vector<uint8_t>, EngineError> PdfiumDocument::saveFull() const {
#ifdef PDFENGINE_WITH_PDFIUM
if (!doc_) {
return std::unexpected(EngineError::Unknown);
}
VectorWriter writer;
if (!FPDF_SaveWithVersion(doc_, &writer, 0, 14)) {
return std::unexpected(EngineError::WriteFailed);
2026-05-22 15:47:48 +05:30
}
return writer.buffer;
#else
return std::unexpected(EngineError::Unknown);
#endif
}
2026-06-30 21:18:46 +05:30
std::expected<std::vector<uint8_t>, EngineError> PdfiumDocument::saveFullForExport() const {
auto bytes = saveFull();
#if defined(PDFENGINE_WITH_PDFIUM) && defined(PDFENGINE_WITH_QPDF)
if (bytes) {
qpdf_layer::QpdfWriter writer;
auto withAppearances = writer.setNeedAppearances(*bytes);
2026-08-07 19:09:44 +05:30
if (withAppearances)
return *withAppearances;
2026-06-30 21:18:46 +05:30
spdlog::warn("saveFullForExport: NeedAppearances pass failed ({}); returning plain save",
withAppearances.error());
}
#endif
return bytes;
}
2026-06-05 10:27:39 +05:30
void PdfiumDocument::invalidateCaches() {
{
std::lock_guard<std::mutex> lock(fontsMutex_);
cachedFonts_.clear();
hasCachedFonts_ = false;
}
{
std::lock_guard<std::mutex> lock(pageCacheMutex_);
pageCache_.clear();
}
2026-06-08 11:23:27 +05:30
{
std::lock_guard<std::mutex> lock(resolvedFontsMutex_);
resolvedFontsCache_.clear();
}
2026-06-11 18:19:26 +05:30
#ifdef PDFENGINE_WITH_PDFIUM
{
std::lock_guard<std::mutex> lock(loadedFontsMutex_);
loadedFontsCache_.clear();
loadedFontDataBuffers_.clear();
}
#endif
2026-06-05 10:27:39 +05:30
spdlog::info("Document caches have been invalidated.");
2026-05-26 15:55:48 +05:30
}
2026-08-07 19:09:44 +05:30
} // namespace pdfengine::parser