PDF4QT/Pdf4QtLibGui/pdfdocumentpropertiesdialog.cpp
2024-04-20 19:41:14 +02:00

609 lines
24 KiB
C++

// Copyright (C) 2019-2022 Jakub Melka
//
// This file is part of PDF4QT.
//
// PDF4QT is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// with the written consent of the copyright owner, any later version.
//
// PDF4QT is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with PDF4QT. If not, see <https://www.gnu.org/licenses/>.
#include "pdfdocumentpropertiesdialog.h"
#include "ui_pdfdocumentpropertiesdialog.h"
#include "pdfdocument.h"
#include "pdfwidgetutils.h"
#include "pdffont.h"
#include "pdfutils.h"
#include "pdfexception.h"
#include "pdfexecutionpolicy.h"
#include <QLocale>
#include <QPageSize>
#include <QtConcurrent/QtConcurrent>
#include "pdfdbgheap.h"
#include <stack>
#include <execution>
namespace pdfviewer
{
class PDFTreeFactory : public pdf::ITreeFactory
{
public:
PDFTreeFactory(QTreeWidgetItem* root)
{
m_roots.push(root);
}
// ITreeFactory interface
virtual void pushItem(QStringList texts) override;
virtual void addItem(QStringList texts) override;
virtual void popItem() override;
private:
std::stack<QTreeWidgetItem*> m_roots;
};
void PDFTreeFactory::pushItem(QStringList texts)
{
Q_ASSERT(!m_roots.empty());
m_roots.push(new QTreeWidgetItem(m_roots.top(), texts));
}
void PDFTreeFactory::addItem(QStringList texts)
{
Q_ASSERT(!m_roots.empty());
new QTreeWidgetItem(m_roots.top(), texts);
}
void PDFTreeFactory::popItem()
{
Q_ASSERT(!m_roots.empty());
m_roots.pop();
}
PDFDocumentPropertiesDialog::PDFDocumentPropertiesDialog(const pdf::PDFDocument* document,
const PDFFileInfo* fileInfo,
QWidget* parent) :
QDialog(parent),
ui(new Ui::PDFDocumentPropertiesDialog)
{
ui->setupUi(this);
initializeProperties(document);
initializeFileInfoProperties(fileInfo);
initializeSecurity(document);
initializeFonts(document);
initializeDisplayAndPrintSettings(document);
const int minimumSectionSize = pdf::PDFWidgetUtils::scaleDPI_x(this, 300);
for (QTreeWidget* widget : findChildren<QTreeWidget*>(QString(), Qt::FindChildrenRecursively))
{
widget->header()->setMinimumSectionSize(minimumSectionSize);
}
pdf::PDFWidgetUtils::scaleWidget(this, QSize(750, 600));
pdf::PDFWidgetUtils::style(this);
}
PDFDocumentPropertiesDialog::~PDFDocumentPropertiesDialog()
{
Q_ASSERT(m_fontTreeWidgetItems.empty());
delete ui;
}
void PDFDocumentPropertiesDialog::initializeProperties(const pdf::PDFDocument* document)
{
QLocale locale;
// Initialize document properties
QTreeWidgetItem* propertiesRoot = new QTreeWidgetItem({ tr("Properties") });
const pdf::PDFDocumentInfo* info = document->getInfo();
const pdf::PDFCatalog* catalog = document->getCatalog();
new QTreeWidgetItem(propertiesRoot, { tr("PDF version"), QString::fromLatin1(document->getVersion()) });
new QTreeWidgetItem(propertiesRoot, { tr("Title"), info->title });
new QTreeWidgetItem(propertiesRoot, { tr("Subject"), info->subject });
new QTreeWidgetItem(propertiesRoot, { tr("Author"), info->author });
new QTreeWidgetItem(propertiesRoot, { tr("Keywords"), info->keywords });
new QTreeWidgetItem(propertiesRoot, { tr("Creator"), info->creator });
new QTreeWidgetItem(propertiesRoot, { tr("Producer"), info->producer });
new QTreeWidgetItem(propertiesRoot, { tr("Creation date"), locale.toString(info->creationDate) });
new QTreeWidgetItem(propertiesRoot, { tr("Modified date"), locale.toString(info->modifiedDate) });
QString trapped;
switch (info->trapped)
{
case pdf::PDFDocumentInfo::Trapped::True:
trapped = tr("Yes");
break;
case pdf::PDFDocumentInfo::Trapped::False:
trapped = tr("No");
break;
case pdf::PDFDocumentInfo::Trapped::Unknown:
trapped = tr("Unknown");
break;
default:
Q_ASSERT(false);
break;
}
QTreeWidgetItem* contentRoot = new QTreeWidgetItem({ tr("Content") });
const pdf::PDFInteger pageCount = catalog->getPageCount();
new QTreeWidgetItem(contentRoot, { tr("Page count"), locale.toString(pageCount) });
if (pageCount > 0)
{
const pdf::PDFPage* firstPage = catalog->getPage(0);
QSizeF pageSizeMM = firstPage->getRectMM(firstPage->getRotatedMediaBox()).size();
QPageSize pageSize(pageSizeMM, QPageSize::Millimeter, QString(), QPageSize::FuzzyOrientationMatch);
QString paperSizeString = QString("%1 x %2 mm").arg(locale.toString(pageSizeMM.width()), locale.toString(pageSizeMM.height()));
new QTreeWidgetItem(contentRoot, { tr("Paper format"), pageSize.name() });
new QTreeWidgetItem(contentRoot, { tr("Paper size"), paperSizeString });
}
new QTreeWidgetItem(contentRoot, { tr("Trapped"), trapped });
ui->propertiesTreeWidget->addTopLevelItem(propertiesRoot);
ui->propertiesTreeWidget->addTopLevelItem(contentRoot);
if (!info->extra.empty())
{
QTreeWidgetItem* customRoot = new QTreeWidgetItem({ tr("Custom properties") });
for (const auto& item : info->extra)
{
QString key = QString::fromLatin1(item.first);
QVariant valueVariant = item.second;
QString value = (valueVariant.typeId() == QMetaType::QDateTime) ? locale.toString(valueVariant.toDateTime()) : valueVariant.toString();
new QTreeWidgetItem(customRoot, { key, value });
}
ui->propertiesTreeWidget->addTopLevelItem(customRoot);
}
ui->propertiesTreeWidget->expandAll();
ui->propertiesTreeWidget->resizeColumnToContents(0);
}
void PDFDocumentPropertiesDialog::initializeFileInfoProperties(const PDFFileInfo* fileInfo)
{
QLocale locale;
// Initialize document file info
QTreeWidgetItem* fileInfoRoot = new QTreeWidgetItem({ tr("File information") });
new QTreeWidgetItem(fileInfoRoot, { tr("Name"), fileInfo->fileName });
new QTreeWidgetItem(fileInfoRoot, { tr("Directory"), fileInfo->path });
new QTreeWidgetItem(fileInfoRoot, { tr("Writable"), fileInfo->writable ? tr("Yes") : tr("No") });
QString fileSize;
if (fileInfo->fileSize > 1024 * 1024)
{
fileSize = QString("%1 MB (%2 bytes)").arg(locale.toString(fileInfo->fileSize / (1024.0 * 1024.0)), locale.toString(fileInfo->fileSize));
}
else
{
fileSize = QString("%1 kB (%2 bytes)").arg(locale.toString(fileInfo->fileSize / 1024.0), locale.toString(fileInfo->fileSize));
}
new QTreeWidgetItem(fileInfoRoot, { tr("Size"), fileSize });
new QTreeWidgetItem(fileInfoRoot, { tr("Created date"), locale.toString(fileInfo->creationTime) });
new QTreeWidgetItem(fileInfoRoot, { tr("Modified date"), locale.toString(fileInfo->lastModifiedTime) });
new QTreeWidgetItem(fileInfoRoot, { tr("Last read date"), locale.toString(fileInfo->lastReadTime) });
ui->fileInfoTreeWidget->addTopLevelItem(fileInfoRoot);
ui->fileInfoTreeWidget->expandAll();
ui->fileInfoTreeWidget->resizeColumnToContents(0);
}
void PDFDocumentPropertiesDialog::initializeSecurity(const pdf::PDFDocument* document)
{
QLocale locale;
QTreeWidgetItem* securityRoot = new QTreeWidgetItem({ tr("Security") });
const pdf::PDFSecurityHandler* securityHandler = document->getStorage().getSecurityHandler();
const pdf::EncryptionMode mode = securityHandler->getMode();
QString modeString;
switch (mode)
{
case pdf::EncryptionMode::None:
modeString = tr("None");
break;
case pdf::EncryptionMode::Standard:
modeString = tr("Standard");
break;
case pdf::EncryptionMode::PublicKey:
modeString = tr("Public Key");
break;
case pdf::EncryptionMode::Custom:
modeString = tr("Custom");
break;
default:
Q_ASSERT(false);
break;
}
QString authorizationMode;
switch (securityHandler->getAuthorizationResult())
{
case pdf::PDFSecurityHandler::AuthorizationResult::NoAuthorizationRequired:
authorizationMode = tr("No authorization required");
break;
case pdf::PDFSecurityHandler::AuthorizationResult::OwnerAuthorized:
authorizationMode = tr("Authorized as owner");
break;
case pdf::PDFSecurityHandler::AuthorizationResult::UserAuthorized:
authorizationMode = tr("Authorized as user");
break;
default:
Q_ASSERT(false);
break;
}
new QTreeWidgetItem(securityRoot, { tr("Document encryption"), modeString });
new QTreeWidgetItem(securityRoot, { tr("Authorized as"), authorizationMode });
if (securityHandler->getAuthorizationResult() != pdf::PDFSecurityHandler::AuthorizationResult::NoAuthorizationRequired)
{
new QTreeWidgetItem(securityRoot, { tr("Metadata encrypted"), securityHandler->isMetadataEncrypted() ? tr("Yes") : tr("No") });
new QTreeWidgetItem(securityRoot, { tr("Version"), locale.toString(securityHandler->getVersion()) });
}
QTreeWidgetItem* permissionsRoot = new QTreeWidgetItem({ tr("Permissions") });
auto addPermissionInfo = [securityHandler, permissionsRoot](QString caption, pdf::PDFSecurityHandler::Permission permission)
{
new QTreeWidgetItem(permissionsRoot, { caption, securityHandler->isAllowed(permission) ? tr("Yes") : tr("No")});
};
addPermissionInfo(tr("Print (low resolution)"), pdf::PDFSecurityHandler::Permission::PrintLowResolution);
addPermissionInfo(tr("Print (high resolution)"), pdf::PDFSecurityHandler::Permission::PrintHighResolution);
addPermissionInfo(tr("Content extraction"), pdf::PDFSecurityHandler::Permission::CopyContent);
addPermissionInfo(tr("Content extraction (accessibility)"), pdf::PDFSecurityHandler::Permission::Accessibility);
addPermissionInfo(tr("Page assembling"), pdf::PDFSecurityHandler::Permission::Assemble);
addPermissionInfo(tr("Modify content"), pdf::PDFSecurityHandler::Permission::Modify);
addPermissionInfo(tr("Modify interactive items"), pdf::PDFSecurityHandler::Permission::ModifyInteractiveItems);
addPermissionInfo(tr("Fill form fields"), pdf::PDFSecurityHandler::Permission::ModifyFormFields);
ui->securityTreeWidget->addTopLevelItem(securityRoot);
ui->securityTreeWidget->addTopLevelItem(permissionsRoot);
ui->securityTreeWidget->expandAll();
ui->securityTreeWidget->resizeColumnToContents(0);
}
void PDFDocumentPropertiesDialog::initializeFonts(const pdf::PDFDocument* document)
{
auto createFontInfo = [this, document]()
{
pdf::PDFInteger pageCount = document->getCatalog()->getPageCount();
QMutex fontTreeItemMutex;
QMutex usedFontReferencesMutex;
std::set<pdf::PDFObjectReference> usedFontReferences;
auto processPage = [&](pdf::PDFInteger pageIndex)
{
try
{
const pdf::PDFPage* page = document->getCatalog()->getPage(pageIndex);
if (const pdf::PDFDictionary* resourcesDictionary = document->getDictionaryFromObject(page->getResources()))
{
if (const pdf::PDFDictionary* fontsDictionary = document->getDictionaryFromObject(resourcesDictionary->get("Font")))
{
// Iterate trough each font
const size_t fontsCount = fontsDictionary->getCount();
for (size_t i = 0; i < fontsCount; ++i)
{
pdf::PDFObject object = fontsDictionary->getValue(i);
if (object.isReference())
{
// Check, if we have not processed the object. If we have it processed,
// then do nothing, otherwise insert it into the processed objects.
// We must also use mutex, because we use multithreading.
QMutexLocker lock(&usedFontReferencesMutex);
if (usedFontReferences.count(object.getReference()))
{
continue;
}
else
{
usedFontReferences.insert(object.getReference());
}
}
try
{
if (pdf::PDFFontPointer font = pdf::PDFFont::createFont(object, fontsDictionary->getKey(i).getString(), document))
{
pdf::PDFRenderErrorReporterDummy dummyReporter;
pdf::PDFRealizedFontPointer realizedFont = pdf::PDFRealizedFont::createRealizedFont(font, 8.0, &dummyReporter);
if (realizedFont)
{
const pdf::FontType fontType = font->getFontType();
const pdf::FontDescriptor* fontDescriptor = font->getFontDescriptor();
QString fontName = fontDescriptor->fontName;
// Try to remove characters from +, if we have font name 'SDFDSF+ValidFontName'
int plusPos = fontName.lastIndexOf('+');
if (plusPos != -1 && plusPos < fontName.size() - 1)
{
fontName = fontName.mid(plusPos + 1);
}
if (fontName.isEmpty())
{
fontName = QString::fromLatin1(fontsDictionary->getKey(i).getString());
}
std::unique_ptr<QTreeWidgetItem> fontRootItemPtr = std::make_unique<QTreeWidgetItem>(QStringList({ fontName }));
QTreeWidgetItem* fontRootItem = fontRootItemPtr.get();
QString fontTypeString;
switch (fontType)
{
case pdf::FontType::TrueType:
fontTypeString = tr("TrueType");
break;
case pdf::FontType::Type0:
fontTypeString = tr("Type0 (CID keyed)");
break;
case pdf::FontType::Type1:
fontTypeString = tr("Type1 (8 bit keyed)");
break;
case pdf::FontType::MMType1:
fontTypeString = tr("MMType1 (8 bit keyed)");
break;
case pdf::FontType::Type3:
fontTypeString = tr("Type3 (content streams for font glyphs)");
break;
default:
Q_ASSERT(false);
break;
}
new QTreeWidgetItem(fontRootItem, { tr("Type"), fontTypeString });
if (!fontDescriptor->fontFamily.isEmpty())
{
new QTreeWidgetItem(fontRootItem, { tr("Font family"), fontDescriptor->fontFamily });
}
new QTreeWidgetItem(fontRootItem, { tr("Embedded subset"), fontDescriptor->getEmbeddedFontData() ? tr("Yes") : tr("No") });
PDFTreeFactory treeFactory1(fontRootItem);
font->dumpFontToTreeItem(&treeFactory1);
PDFTreeFactory treeFactory2(fontRootItem);
realizedFont->dumpFontToTreeItem(&treeFactory2);
// Separator item
new QTreeWidgetItem(fontRootItem, QStringList());
// Finally add the tree item
QMutexLocker lock(&fontTreeItemMutex);
m_fontTreeWidgetItems.push_back(fontRootItemPtr.release());
}
}
}
catch (const pdf::PDFException &)
{
// Do nothing, some error occured, continue with next font
continue;
}
}
}
}
}
catch (const pdf::PDFException &)
{
// Do nothing, some error occured
}
};
pdf::PDFIntegerRange<pdf::PDFInteger> indices(pdf::PDFInteger(0), pageCount);
pdf::PDFExecutionPolicy::execute(pdf::PDFExecutionPolicy::Scope::Page, indices.begin(), indices.end(), processPage);
};
m_future = QtConcurrent::run(createFontInfo);
connect(&m_futureWatcher, &QFutureWatcher<void>::finished, this, &PDFDocumentPropertiesDialog::onFontsFinished);
m_futureWatcher.setFuture(m_future);
}
void PDFDocumentPropertiesDialog::initializeDisplayAndPrintSettings(const pdf::PDFDocument* document)
{
const pdf::PDFCatalog* catalog = document->getCatalog();
pdf::PageLayout pageLayout = catalog->getPageLayout();
pdf::PageMode pageMode = catalog->getPageMode();
const pdf::PDFViewerPreferences* viewerPreferences = catalog->getViewerPreferences();
QTreeWidgetItem* viewerSettingsRoot = new QTreeWidgetItem({ tr("Viewer settings") });
QTreeWidgetItem* printerSettingsRoot = new QTreeWidgetItem({ tr("Default printer settings") });
QString pageLayoutString;
switch (pageLayout)
{
case pdf::PageLayout::SinglePage:
pageLayoutString = tr("Single page");
break;
case pdf::PageLayout::OneColumn:
pageLayoutString = tr("Continuous column");
break;
case pdf::PageLayout::TwoColumnLeft:
case pdf::PageLayout::TwoColumnRight:
pageLayoutString = tr("Two continuous columns");
break;
case pdf::PageLayout::TwoPagesLeft:
case pdf::PageLayout::TwoPagesRight:
pageLayoutString = tr("Two pages");
break;
default:
Q_ASSERT(false);
break;
}
QString pageModeString;
switch (pageMode)
{
case pdf::PageMode::UseNone:
pageModeString = tr("Default");
break;
case pdf::PageMode::UseOutlines:
pageModeString = tr("Show outlines");
break;
case pdf::PageMode::UseThumbnails:
pageModeString = tr("Show thumbnails");
break;
case pdf::PageMode::Fullscreen:
pageModeString = tr("Fullscreen");
break;
case pdf::PageMode::UseOptionalContent:
pageModeString = tr("Show optional content");
break;
case pdf::PageMode::UseAttachments:
pageModeString = tr("Show attachments");
break;
default:
Q_ASSERT(false);
break;
}
QString directionString;
switch (viewerPreferences->getDirection())
{
case pdf::PDFViewerPreferences::Direction::LeftToRight:
directionString = tr("Left to right");
break;
case pdf::PDFViewerPreferences::Direction::RightToLeft:
directionString = tr("Right to left");
break;
default:
Q_ASSERT(false);
break;
}
new QTreeWidgetItem(viewerSettingsRoot, { tr("Page layout"), pageLayoutString });
new QTreeWidgetItem(viewerSettingsRoot, { tr("View mode"), pageModeString });
new QTreeWidgetItem(viewerSettingsRoot, { tr("Writing direction"), directionString });
QString printScalingString;
switch (viewerPreferences->getPrintScaling())
{
case pdf::PDFViewerPreferences::PrintScaling::None:
printScalingString = tr("None");
break;
case pdf::PDFViewerPreferences::PrintScaling::AppDefault:
printScalingString = tr("Application default");
break;
default:
Q_ASSERT(false);
break;
}
new QTreeWidgetItem(printerSettingsRoot, { tr("Scale"), printScalingString });
QString duplexString;
switch (viewerPreferences->getDuplex())
{
case pdf::PDFViewerPreferences::Duplex::None:
duplexString = tr("None");
break;
case pdf::PDFViewerPreferences::Duplex::Simplex:
duplexString = tr("Simplex");
break;
case pdf::PDFViewerPreferences::Duplex::DuplexFlipLongEdge:
duplexString = tr("Duplex (flip long edge)");
break;
case pdf::PDFViewerPreferences::Duplex::DuplexFlipShortEdge:
duplexString = tr("Duplex (flip long edge)");
break;
default:
Q_ASSERT(false);
break;
}
new QTreeWidgetItem(printerSettingsRoot, { tr("Duplex mode"), duplexString });
new QTreeWidgetItem(printerSettingsRoot, { tr("Pick tray by page size"), viewerPreferences->getOptions().testFlag(pdf::PDFViewerPreferences::PickTrayByPDFSize) ? tr("Yes") : tr("No") });
QStringList pageRanges;
for (const std::pair<pdf::PDFInteger, pdf::PDFInteger>& pageRange : viewerPreferences->getPrintPageRanges())
{
pageRanges << QString("%1-%2").arg(pageRange.first).arg(pageRange.second);
}
QString pageRangesString = pageRanges.join(",");
new QTreeWidgetItem(printerSettingsRoot, { tr("Default print page ranges"), pageRangesString });
new QTreeWidgetItem(printerSettingsRoot, { tr("Number of copies"), QString::number(viewerPreferences->getNumberOfCopies()) });
ui->displayAndPrintTreeWidget->addTopLevelItem(viewerSettingsRoot);
ui->displayAndPrintTreeWidget->addTopLevelItem(printerSettingsRoot);
ui->displayAndPrintTreeWidget->expandAll();
ui->displayAndPrintTreeWidget->resizeColumnToContents(0);
}
void PDFDocumentPropertiesDialog::onFontsFinished()
{
if (!m_fontTreeWidgetItems.empty())
{
std::sort(m_fontTreeWidgetItems.begin(), m_fontTreeWidgetItems.end(), [](QTreeWidgetItem* left, QTreeWidgetItem* right) { return left->data(0, Qt::DisplayRole).toString() < right->data(0, Qt::DisplayRole).toString(); });
for (QTreeWidgetItem* item : m_fontTreeWidgetItems)
{
ui->fontsTreeWidget->addTopLevelItem(item);
}
m_fontTreeWidgetItems.clear();
ui->fontsTreeWidget->collapseAll();
ui->fontsTreeWidget->expandToDepth(0);
ui->fontsTreeWidget->resizeColumnToContents(0);
}
}
void PDFDocumentPropertiesDialog::closeEvent(QCloseEvent* event)
{
// We must wait for finishing of font loading;
m_futureWatcher.waitForFinished();
// We must delete all font tree items, because of asynchronous signal sent
qDeleteAll(m_fontTreeWidgetItems);
m_fontTreeWidgetItems.clear();
BaseClass::closeEvent(event);
}
} // namespace pdfviewer