302 lines
9.0 KiB
C++
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
|