Merge 1dd3a5f154eb7c114091afb0a63b945557e18368 into 86f74a00d0fb1a5ea351649450c12bb85772e496

This commit is contained in:
Kuznetsov Oleg 2025-02-23 10:07:19 -05:00 committed by GitHub
commit e5764bcffb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 151 additions and 26 deletions

View File

@ -37,7 +37,7 @@ jobs:
run: |
sudo apt update
sudo apt install build-essential cmake g++
sudo apt install qtbase5-dev qtbase5-private-dev qttools5-dev qttools5-dev-tools libqt5svg5-dev libargon2-dev libkeyutils-dev libminizip-dev libbotan-2-dev libqrencode-dev zlib1g-dev asciidoctor libreadline-dev libpcsclite-dev libusb-1.0-0-dev libxi-dev libxtst-dev libqt5x11extras5-dev
sudo apt install qtbase5-dev qtbase5-private-dev qttools5-dev qttools5-dev-tools libqt5svg5-dev libargon2-dev libkeyutils-dev libminizip-dev libbotan-2-dev libqrencode-dev zlib1g-dev asciidoctor libreadline-dev libpcsclite-dev libusb-1.0-0-dev libxi-dev libxtst-dev libqt5x11extras5-dev libpoppler-cpp-dev
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL

View File

@ -7252,6 +7252,26 @@ Do you want to overwrite it?</source>
<source>Image format not supported</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Failed to load the PDF</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Failed to render the PDF page</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>The file is locked and cannot be processed</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>The document contains no pages</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Unable to create the first page of the document</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QMessageBox</name>

View File

@ -390,6 +390,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)

View File

@ -486,7 +486,9 @@ namespace Tools
"application/x-yaml",
"application/protobuf",
};
static QStringList imageFormats = {"image/"};
static QStringList pdfFormats = {"application/pdf"};
static auto isCompatible = [](const QString& format, const QStringList& list) {
return std::any_of(
@ -501,6 +503,10 @@ namespace Tools
return MimeType::PlainText;
}
if (isCompatible(mimeName, pdfFormats)) {
return MimeType::Pdf;
}
return MimeType::Unknown;
}
} // namespace Tools

View File

@ -119,6 +119,7 @@ namespace Tools
{
Image,
PlainText,
Pdf,
Unknown
};

View File

@ -24,6 +24,13 @@
#include <QTextCursor>
#include <QtDebug>
#include <memory>
#include <poppler-document.h>
#include <poppler-image.h>
#include <poppler-page-renderer.h>
#include <poppler-page.h>
PreviewEntryAttachmentsDialog::PreviewEntryAttachmentsDialog(QWidget* parent)
: QDialog(parent)
, m_ui(new Ui::EntryAttachmentsDialog)
@ -76,9 +83,80 @@ void PreviewEntryAttachmentsDialog::update()
updateImageAttachment(m_data);
} else if (m_type == Tools::MimeType::PlainText) {
updateTextAttachment(m_data);
} else if (m_type == Tools::MimeType::Pdf) {
updatePdfAttachment(m_data);
}
}
void PreviewEntryAttachmentsDialog::updatePdfAttachment(const QByteArray& data)
{
if (!m_hashedImage.contains(data)) {
poppler::byte_array array{std::cbegin(data), std::cend(data)};
auto doc = std::unique_ptr<poppler::document>(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("The file is locked and cannot be processed").toUtf8());
return;
}
if (!doc->pages()) {
updateTextAttachment(tr("The document contains no pages").toUtf8());
return;
}
// Preview the first page of the document.
auto page = std::unique_ptr<poppler::page>(doc->create_page(0));
if (!page) {
updateTextAttachment(tr("Unable to create the first page of the document").toUtf8());
return;
}
poppler::page_renderer renderer{};
renderer.set_image_format(poppler::image::format_argb32);
// Use a resolution of 150 DPI for the preview.
constexpr int Dpi = 150;
auto popplerImage = renderer.render_page(page.get(), Dpi, Dpi);
if (!popplerImage.is_valid()) {
updateTextAttachment(tr("Failed to render the PDF page").toUtf8());
return;
}
QImage image(reinterpret_cast<const uchar*>(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;
}
// Make a copy of the image to prevent data corruption once the Poppler image is destroyed.
m_hashedImage.insert(data, image.copy());
}
updateImageAttachment(m_hashedImage.value(data));
}
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)
{
m_ui->attachmentTextEdit->setPlainText(QString::fromUtf8(data));
@ -86,23 +164,26 @@ void PreviewEntryAttachmentsDialog::updateTextAttachment(const QByteArray& data)
void PreviewEntryAttachmentsDialog::updateImageAttachment(const QByteArray& data)
{
QImage image{};
if (!image.loadFromData(data)) {
updateTextAttachment(tr("Image format not supported").toUtf8());
return;
if (!m_hashedImage.contains(data)) {
QImage image{};
if (!image.loadFromData(data)) {
updateTextAttachment(tr("Image format not supported").toUtf8());
return;
}
m_hashedImage.insert(data, std::move(image));
}
updateImageAttachment(m_hashedImage.value(data));
}
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
@ -117,7 +198,5 @@ void PreviewEntryAttachmentsDialog::resizeEvent(QResizeEvent* event)
{
QDialog::resizeEvent(event);
if (m_type == Tools::MimeType::Image) {
update();
}
update();
}

View File

@ -50,10 +50,16 @@ private:
void update();
void updateTextAttachment(const QByteArray& data);
void updateImageAttachment(const QByteArray& data);
void updatePdfAttachment(const QByteArray& data);
void updateImageAttachment(const QImage& image);
QSize calcucateImageSize();
QScopedPointer<Ui::EntryAttachmentsDialog> m_ui;
QString m_name;
QByteArray m_data;
Tools::MimeType m_type{Tools::MimeType::Unknown};
QHash<QByteArray, QImage> m_hashedImage{};
};

View File

@ -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);
}

View File

@ -76,6 +76,10 @@
{
"name": "zlib",
"version>=": "1.3"
},
{
"name": "poppler",
"version>=": "23.1.0"
}
]
}