diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts index 0cf445a86..f4e08ce4d 100644 --- a/share/translations/keepassxc_en.ts +++ b/share/translations/keepassxc_en.ts @@ -7178,6 +7178,26 @@ Do you want to overwrite it? Image format not supported + + Failed to load the PDF + + + + Failed to read the PDF: The file is locked + + + + Failed to read the PDF: No pages found + + + + Failed to read the PDF: Unable to create a page + + + + Failed to render the PDF page + + QMessageBox diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 84c6090ba..ccb96879f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -388,6 +388,12 @@ target_link_libraries(keepassxc_gui ${keeshare_LIB} ${sshagent_LIB}) +# Find Poppler +find_package(PkgConfig) +pkg_check_modules(POPPLER_CPP REQUIRED IMPORTED_TARGET poppler-cpp) + +target_link_libraries(keepassxc_gui PkgConfig::POPPLER_CPP) + if(APPLE) target_link_libraries(keepassxc_gui "-framework Foundation -framework AppKit -framework Carbon -framework Security -framework LocalAuthentication -framework ScreenCaptureKit") if(Qt5MacExtras_FOUND) diff --git a/src/gui/entry/PreviewEntryAttachmentsDialog.cpp b/src/gui/entry/PreviewEntryAttachmentsDialog.cpp index 1bc47c4e0..a5ab50fe7 100644 --- a/src/gui/entry/PreviewEntryAttachmentsDialog.cpp +++ b/src/gui/entry/PreviewEntryAttachmentsDialog.cpp @@ -24,6 +24,12 @@ #include #include +#include + +#include +#include +#include + PreviewEntryAttachmentsDialog::PreviewEntryAttachmentsDialog(QWidget* parent) : QDialog(parent) , m_ui(new Ui::EntryAttachmentsDialog) @@ -84,7 +90,58 @@ void PreviewEntryAttachmentsDialog::update() void PreviewEntryAttachmentsDialog::updatePdfAttachment(const QByteArray& data) { // To preview a PDF as an image, you need to install the qt5-image-formats-plugin-pdf - updateImageAttachment(data); + poppler::byte_array array{std::cbegin(data), std::cend(data)}; + + auto doc = std::unique_ptr(poppler::document::load_from_data(&array)); + if (!doc) { + updateTextAttachment(tr("Failed to load the PDF").toUtf8()); + return; + } + + // Locked PDF files are not supported + if (doc->is_locked()) { + updateTextAttachment(tr("Failed to read the PDF: The file is locked").toUtf8()); + return; + } + + if (!doc->pages()) { + updateTextAttachment(tr("Failed to read the PDF: No pages found").toUtf8()); + return; + } + + // Preview the first page of the document. + auto page = std::unique_ptr(doc->create_page(0)); + if (!page) { + updateTextAttachment(tr("Failed to read the PDF: Unable to create a page").toUtf8()); + return; + } + + poppler::page_renderer renderer{}; + auto popplerImage = renderer.render_page(page.get()); + + QImage image(reinterpret_cast(popplerImage.const_data()), + popplerImage.width(), + popplerImage.height(), + popplerImage.bytes_per_row(), + QImage::Format_ARGB32); + + if (image.isNull()) { + updateTextAttachment(tr("Failed to render the PDF page").toUtf8()); + return; + } + + updateImageAttachment(image); +} + +QSize PreviewEntryAttachmentsDialog::calcucateImageSize() +{ + // Scale the image to the contents rect minus another set of margins to avoid scrollbars + auto margins = m_ui->attachmentTextEdit->contentsMargins(); + auto size = m_ui->attachmentTextEdit->contentsRect().size(); + size.setWidth(size.width() - margins.left() - margins.right()); + size.setHeight(size.height() - margins.top() - margins.bottom()); + + return size; } void PreviewEntryAttachmentsDialog::updateTextAttachment(const QByteArray& data) @@ -100,17 +157,15 @@ void PreviewEntryAttachmentsDialog::updateImageAttachment(const QByteArray& data return; } + updateImageAttachment(image); +} + +void PreviewEntryAttachmentsDialog::updateImageAttachment(const QImage& image) +{ m_ui->attachmentTextEdit->clear(); auto cursor = m_ui->attachmentTextEdit->textCursor(); - // Scale the image to the contents rect minus another set of margins to avoid scrollbars - auto margins = m_ui->attachmentTextEdit->contentsMargins(); - auto size = m_ui->attachmentTextEdit->contentsRect().size(); - size.setWidth(size.width() - margins.left() - margins.right()); - size.setHeight(size.height() - margins.top() - margins.bottom()); - image = image.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation); - - cursor.insertImage(image); + cursor.insertImage(image.scaled(calcucateImageSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } Tools::MimeType PreviewEntryAttachmentsDialog::attachmentType(const QByteArray& data) const diff --git a/src/gui/entry/PreviewEntryAttachmentsDialog.h b/src/gui/entry/PreviewEntryAttachmentsDialog.h index 27fbe8d94..35454e1d7 100644 --- a/src/gui/entry/PreviewEntryAttachmentsDialog.h +++ b/src/gui/entry/PreviewEntryAttachmentsDialog.h @@ -51,6 +51,9 @@ private: void updateTextAttachment(const QByteArray& data); void updateImageAttachment(const QByteArray& data); void updatePdfAttachment(const QByteArray& data); + void updateImageAttachment(const QImage& image); + + QSize calcucateImageSize(); QScopedPointer m_ui; diff --git a/tests/TestTools.cpp b/tests/TestTools.cpp index 27a468929..95b73c86c 100644 --- a/tests/TestTools.cpp +++ b/tests/TestTools.cpp @@ -301,10 +301,13 @@ void TestTools::testMimeTypes() "image/svg+xml" // SVG images }; + const QStringList pdfMimeType = { + "application/pdf", // PDF documents + }; + const QStringList UnknownMimeTypes = { "audio/mpeg", // MPEG audio files "video/mp4", // MP4 video files - "application/pdf", // PDF documents "application/zip", // ZIP archives "application/x-tar", // TAR archives "application/x-rar-compressed", // RAR archives @@ -327,15 +330,15 @@ void TestTools::testMimeTypes() "application/x-shellscript", // Shell scripts }; - for (const auto& mime : TextMimeTypes) { - QCOMPARE(Tools::toMimeType(mime), Tools::MimeType::PlainText); - } + auto compare = [](const QStringList& mimeList, Tools::MimeType exptectedType) { + for (const auto& mime : mimeList) { + QCOMPARE(Tools::toMimeType(mime), exptectedType); + } + }; - for (const auto& mime : ImageMimeTypes) { - QCOMPARE(Tools::toMimeType(mime), Tools::MimeType::Image); - } + compare(TextMimeTypes, Tools::MimeType::PlainText); + compare(ImageMimeTypes, Tools::MimeType::Image); + compare(pdfMimeType, Tools::MimeType::Pdf); - for (const auto& mime : UnknownMimeTypes) { - QCOMPARE(Tools::toMimeType(mime), Tools::MimeType::Unknown); - } + compare(UnknownMimeTypes, Tools::MimeType::Unknown); } diff --git a/vcpkg.json b/vcpkg.json index baa40686c..c64679af0 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -76,6 +76,10 @@ { "name": "zlib", "version>=": "1.3" + }, + { + "name": "poppler", + "version>=": "23.1.0" } ] -} +} \ No newline at end of file