// 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 . #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 "pdfdbgheap.h" #include #include #include #include namespace pdfviewer { 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(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.type() == QVariant::DateTime) ? 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::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 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, 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 fontRootItemPtr = std::make_unique(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") }); font->dumpFontToTreeItem(fontRootItem); realizedFont->dumpFontToTreeItem(fontRootItem); // 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 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::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& 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) < right->data(0, Qt::DisplayRole); }); 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