Files
pdf/engine/src/parser/pdfium_document.cpp
T
2026-08-07 19:09:44 +05:30

302 lines
9.0 KiB
C++

#include "parser/pdfium_internal.hpp"
#if defined(PDFENGINE_WITH_PDFIUM) && defined(PDFENGINE_WITH_QPDF)
#include "qpdf/qpdf_writer.hpp"
#endif
namespace pdfengine {
std::expected<std::shared_ptr<PdfDocument>, EngineError>
PdfDocument::loadFromFile(const std::string& path, const std::string& password) {
#ifdef PDFENGINE_WITH_PDFIUM
parser::ensure_pdfium_initialized();
FPDF_DOCUMENT doc =
FPDF_LoadDocument(path.c_str(), password.empty() ? nullptr : password.c_str());
if (!doc) {
auto err = FPDF_GetLastError();
spdlog::error("Failed to load PDF file from path: {} (error code: {})", path, err);
return std::unexpected(parser::mapPdfiumError(err, !password.empty()));
}
return std::make_shared<parser::PdfiumDocument>(doc);
#else
(void) path;
(void) password;
spdlog::error("loadFromFile failed: PDFEngine compiled without PDFium support.");
return std::unexpected(EngineError::Unknown);
#endif
}
std::expected<std::shared_ptr<PdfDocument>, EngineError>
PdfDocument::loadFromMemory(const std::vector<uint8_t>& data, const std::string& password) {
#ifdef PDFENGINE_WITH_PDFIUM
parser::ensure_pdfium_initialized();
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);
}
std::vector<uint8_t> buffer_copy = data;
FPDF_DOCUMENT doc =
FPDF_LoadMemDocument(buffer_copy.data(), static_cast<int>(buffer_copy.size()),
password.empty() ? nullptr : password.c_str());
if (!doc) {
auto err = FPDF_GetLastError();
spdlog::error("Failed to load PDF from memory (error code: {})", err);
return std::unexpected(parser::mapPdfiumError(err, !password.empty()));
}
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);
}
return std::make_shared<parser::PdfiumDocument>(doc, std::move(buffer_copy));
#else
(void) data;
(void) password;
spdlog::error("loadFromMemory failed: PDFEngine compiled without PDFium support.");
return std::unexpected(EngineError::Unknown);
#endif
}
} // namespace pdfengine
namespace pdfengine::parser {
PdfiumDocument::PdfiumDocument(NativeDocHandle docHandle) : doc_(docHandle) {
}
PdfiumDocument::PdfiumDocument(NativeDocHandle docHandle, std::vector<uint8_t> memoryBuffer)
: doc_(docHandle), memoryBuffer_(std::move(memoryBuffer)) {
}
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
if (doc_)
FPDF_CloseDocument(doc_);
#endif
doc_ = other.doc_;
other.doc_ = nullptr;
memoryBuffer_ = std::move(other.memoryBuffer_);
}
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
if (!doc_)
return meta;
auto fetchMeta = [this](const char* key) -> std::string {
unsigned long len = FPDF_GetMetaText(doc_, key, nullptr, 0);
if (len <= 2)
return "";
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 {
DocumentPermissions perms;
#ifdef PDFENGINE_WITH_PDFIUM
if (!doc_)
return perms;
const int rev = FPDF_GetSecurityHandlerRevision(doc_);
perms.securityRevision = rev;
perms.isEncrypted = (rev != -1);
if (!perms.isEncrypted) {
return perms;
}
switch (rev) {
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; };
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;
}
std::expected<std::vector<PdfDocument::OutlineItem>, EngineError>
PdfiumDocument::extractOutline() const {
#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
}
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);
}
{
std::lock_guard<std::mutex> lock(pageCacheMutex_);
auto it = pageCache_.find(pageIndex);
if (it != pageCache_.end()) {
if (auto cached = it->second.lock()) {
return cached;
}
}
}
FPDF_PAGE pageHandle = FPDF_LoadPage(doc_, pageIndex);
if (!pageHandle) {
return std::unexpected(EngineError::Unknown);
}
auto pageObj = std::make_shared<PdfiumPage>(
doc_, pageHandle, pageIndex, std::static_pointer_cast<PdfiumDocument>(shared_from_this()));
{
std::lock_guard<std::mutex> lock(pageCacheMutex_);
pageCache_[pageIndex] = pageObj;
}
return pageObj;
#else
(void) pageIndex;
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);
}
return writer.buffer;
#else
return std::unexpected(EngineError::Unknown);
#endif
}
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);
if (withAppearances)
return *withAppearances;
spdlog::warn("saveFullForExport: NeedAppearances pass failed ({}); returning plain save",
withAppearances.error());
}
#endif
return bytes;
}
void PdfiumDocument::invalidateCaches() {
{
std::lock_guard<std::mutex> lock(fontsMutex_);
cachedFonts_.clear();
hasCachedFonts_ = false;
}
{
std::lock_guard<std::mutex> lock(pageCacheMutex_);
pageCache_.clear();
}
{
std::lock_guard<std::mutex> lock(resolvedFontsMutex_);
resolvedFontsCache_.clear();
}
#ifdef PDFENGINE_WITH_PDFIUM
{
std::lock_guard<std::mutex> lock(loadedFontsMutex_);
loadedFontsCache_.clear();
loadedFontDataBuffers_.clear();
}
#endif
spdlog::info("Document caches have been invalidated.");
}
} // namespace pdfengine::parser