diff --git a/PdfForQtLib/sources/pdffont.cpp b/PdfForQtLib/sources/pdffont.cpp index 91154b4..3332992 100644 --- a/PdfForQtLib/sources/pdffont.cpp +++ b/PdfForQtLib/sources/pdffont.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #ifdef Q_OS_WIN #include "Windows.h" @@ -350,6 +351,9 @@ public: /// Returns true, if font has horizontal writing system virtual bool isHorizontalWritingSystem() const = 0; + + /// Dumps information about the font + virtual void dumpFontToTreeItem(QTreeWidgetItem* item) const { Q_UNUSED(item); } }; /// Implementation of the PDFRealizedFont class using PIMPL pattern for Type 3 fonts @@ -379,6 +383,7 @@ public: virtual void fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence, PDFRenderErrorReporter* reporter) override; virtual bool isHorizontalWritingSystem() const override { return !m_isVertical; } + virtual void dumpFontToTreeItem(QTreeWidgetItem* item) const override; static constexpr const PDFReal PIXEL_SIZE_MULTIPLIER = 100.0; @@ -553,6 +558,107 @@ void PDFRealizedFontImpl::fillTextSequence(const QByteArray& byteArray, TextSequ } } +void PDFRealizedFontImpl::dumpFontToTreeItem(QTreeWidgetItem* item) const +{ + QTreeWidgetItem* root = new QTreeWidgetItem(item, { PDFTranslationContext::tr("Details") }); + + if (m_face->family_name) + { + new QTreeWidgetItem(root, { PDFTranslationContext::tr("Font"), QString::fromLatin1(m_face->family_name) }); + } + if (m_face->style_name) + { + new QTreeWidgetItem(root, { PDFTranslationContext::tr("Style"), QString::fromLatin1(m_face->style_name) }); + } + + QString yesString = PDFTranslationContext::tr("Yes"); + QString noString = PDFTranslationContext::tr("No"); + + new QTreeWidgetItem(root, { PDFTranslationContext::tr("Glyph count"), QString::number(m_face->num_glyphs) }); + new QTreeWidgetItem(root, { PDFTranslationContext::tr("Is CID keyed"), (m_face->face_flags & FT_FACE_FLAG_CID_KEYED) ? yesString : noString }); + new QTreeWidgetItem(root, { PDFTranslationContext::tr("Is bold"), (m_face->style_flags & FT_STYLE_FLAG_BOLD) ? yesString : noString }); + new QTreeWidgetItem(root, { PDFTranslationContext::tr("Is italics"), (m_face->style_flags & FT_STYLE_FLAG_ITALIC) ? yesString : noString }); + new QTreeWidgetItem(root, { PDFTranslationContext::tr("Has vertical writing system"), (m_face->face_flags & FT_FACE_FLAG_VERTICAL) ? yesString : noString }); + new QTreeWidgetItem(root, { PDFTranslationContext::tr("Has SFNT storage scheme"), (m_face->face_flags & FT_FACE_FLAG_SFNT) ? yesString : noString }); + new QTreeWidgetItem(root, { PDFTranslationContext::tr("Has glyph names"), (m_face->face_flags & FT_FACE_FLAG_GLYPH_NAMES) ? yesString : noString }); + + if (m_face->num_charmaps > 0) + { + QTreeWidgetItem* encodingRoot = new QTreeWidgetItem(item, { PDFTranslationContext::tr("Encoding") }); + for (FT_Int i = 0; i < m_face->num_charmaps; ++i) + { + FT_CharMap charMap = m_face->charmaps[i]; + + const FT_Encoding encoding = charMap->encoding; + QString encodingName; + switch (encoding) + { + case FT_ENCODING_NONE: + encodingName = PDFTranslationContext::tr("None"); + break; + + case FT_ENCODING_UNICODE: + encodingName = PDFTranslationContext::tr("Unicode"); + break; + + case FT_ENCODING_MS_SYMBOL: + encodingName = PDFTranslationContext::tr("MS Symbol"); + break; + + case FT_ENCODING_SJIS: + encodingName = PDFTranslationContext::tr("Japanese Shift JIS"); + break; + + case FT_ENCODING_PRC: + encodingName = PDFTranslationContext::tr("PRC - Simplified Chinese"); + break; + + case FT_ENCODING_BIG5: + encodingName = PDFTranslationContext::tr("Traditional Chinese"); + break; + + case FT_ENCODING_WANSUNG: + encodingName = PDFTranslationContext::tr("Korean Extended Wansung"); + break; + + case FT_ENCODING_JOHAB: + encodingName = PDFTranslationContext::tr("Korean Standard"); + break; + + case FT_ENCODING_ADOBE_STANDARD: + encodingName = PDFTranslationContext::tr("Adobe Standard"); + break; + + case FT_ENCODING_ADOBE_EXPERT: + encodingName = PDFTranslationContext::tr("Adobe Expert"); + break; + case FT_ENCODING_ADOBE_CUSTOM: + encodingName = PDFTranslationContext::tr("Adobe Custom"); + break; + + case FT_ENCODING_ADOBE_LATIN_1: + encodingName = PDFTranslationContext::tr("Adobe Latin 1"); + break; + + case FT_ENCODING_OLD_LATIN_2: + encodingName = PDFTranslationContext::tr("Old Latin 1"); + break; + + case FT_ENCODING_APPLE_ROMAN: + encodingName = PDFTranslationContext::tr("Apple Roman"); + break; + + default: + encodingName = PDFTranslationContext::tr("Unknown"); + break; + } + + QString encodingString = PDFTranslationContext::tr("Platform/Encoding = %1 %2").arg(charMap->platform_id).arg(charMap->encoding_id); + new QTreeWidgetItem(encodingRoot, { encodingName, encodingString }); + } + } +} + int PDFRealizedFontImpl::outlineMoveTo(const FT_Vector* to, void* user) { Glyph* glyph = reinterpret_cast(user); @@ -647,6 +753,11 @@ bool PDFRealizedFont::isHorizontalWritingSystem() const return m_impl->isHorizontalWritingSystem(); } +void PDFRealizedFont::dumpFontToTreeItem(QTreeWidgetItem* item) const +{ + m_impl->dumpFontToTreeItem(item); +} + PDFRealizedFontPointer PDFRealizedFont::createRealizedFont(PDFFontPointer font, PDFReal pixelSize, PDFRenderErrorReporter* reporter) { PDFRealizedFontPointer result; @@ -1347,6 +1458,59 @@ PDFInteger PDFSimpleFont::getGlyphAdvance(size_t index) const return 0; } +void PDFSimpleFont::dumpFontToTreeItem(QTreeWidgetItem* item) const +{ + BaseClass::dumpFontToTreeItem(item); + + QString encodingTypeString; + switch (m_encodingType) + { + case PDFEncoding::Encoding::Standard: + encodingTypeString = PDFTranslationContext::tr("Standard"); + break; + + case PDFEncoding::Encoding::MacRoman: + encodingTypeString = PDFTranslationContext::tr("Mac Roman"); + break; + + case PDFEncoding::Encoding::WinAnsi: + encodingTypeString = PDFTranslationContext::tr("Win Ansi"); + break; + + case PDFEncoding::Encoding::PDFDoc: + encodingTypeString = PDFTranslationContext::tr("PDF Doc"); + break; + + case PDFEncoding::Encoding::MacExpert: + encodingTypeString = PDFTranslationContext::tr("Mac Expert"); + break; + + case PDFEncoding::Encoding::Symbol: + encodingTypeString = PDFTranslationContext::tr("Symbol"); + break; + + case PDFEncoding::Encoding::ZapfDingbats: + encodingTypeString = PDFTranslationContext::tr("Zapf Dingbats"); + break; + + case PDFEncoding::Encoding::MacOsRoman: + encodingTypeString = PDFTranslationContext::tr("Mac OS Roman"); + break; + + case PDFEncoding::Encoding::Custom: + encodingTypeString = PDFTranslationContext::tr("Custom"); + break; + + default: + { + Q_ASSERT(false); + break; + } + } + + new QTreeWidgetItem(item, { PDFTranslationContext::tr("Encoding"), encodingTypeString }); +} + PDFType1Font::PDFType1Font(FontDescriptor fontDescriptor, QByteArray name, QByteArray baseFont, @@ -1368,6 +1532,53 @@ FontType PDFType1Font::getFontType() const return FontType::Type1; } +void PDFType1Font::dumpFontToTreeItem(QTreeWidgetItem* item) const +{ + BaseClass::dumpFontToTreeItem(item); + + if (m_standardFontType != StandardFontType::Invalid) + { + QString standardFontTypeString; + switch (m_standardFontType) + { + case StandardFontType::TimesRoman: + case StandardFontType::TimesRomanBold: + case StandardFontType::TimesRomanItalics: + case StandardFontType::TimesRomanBoldItalics: + standardFontTypeString = PDFTranslationContext::tr("Times Roman"); + break; + + case StandardFontType::Helvetica: + case StandardFontType::HelveticaBold: + case StandardFontType::HelveticaOblique: + case StandardFontType::HelveticaBoldOblique: + standardFontTypeString = PDFTranslationContext::tr("Helvetica"); + break; + + case StandardFontType::Courier: + case StandardFontType::CourierBold: + case StandardFontType::CourierOblique: + case StandardFontType::CourierBoldOblique: + standardFontTypeString = PDFTranslationContext::tr("Courier"); + break; + + case StandardFontType::Symbol: + standardFontTypeString = PDFTranslationContext::tr("Symbol"); + break; + + case StandardFontType::ZapfDingbats: + standardFontTypeString = PDFTranslationContext::tr("Zapf Dingbats"); + break; + + default: + Q_ASSERT(false); + break; + } + + new QTreeWidgetItem(item, { PDFTranslationContext::tr("Standard font"), standardFontTypeString }); + } +} + FontType PDFTrueTypeFont::getFontType() const { return FontType::TrueType; @@ -1920,6 +2131,11 @@ FontType PDFType3Font::getFontType() const return FontType::Type3; } +void PDFType3Font::dumpFontToTreeItem(QTreeWidgetItem* item) const +{ + new QTreeWidgetItem(item, { PDFTranslationContext::tr("Character count"), QString::number(m_characterContentStreams.size()) }); +} + double PDFType3Font::getWidth(int characterIndex) const { if (characterIndex >= m_firstCharacterIndex && characterIndex <= m_lastCharacterIndex) diff --git a/PdfForQtLib/sources/pdffont.h b/PdfForQtLib/sources/pdffont.h index ae4d43f..10d28af 100644 --- a/PdfForQtLib/sources/pdffont.h +++ b/PdfForQtLib/sources/pdffont.h @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -166,7 +167,7 @@ static constexpr PDFEncoding::Encoding getEncodingForStandardFont(StandardFontTy } } -struct FontDescriptor +struct PDFFORQTLIBSHARED_EXPORT FontDescriptor { bool isEmbedded() const { return !fontFile.isEmpty() || !fontFile2.isEmpty() || !fontFile3.isEmpty(); } @@ -216,7 +217,7 @@ using PDFRealizedFontPointer = QSharedPointer; /// Font, which has fixed pixel size. It is programmed as PIMPL, because we need /// to remove FreeType types from the interface (so we do not include FreeType in the interface). -class PDFRealizedFont +class PDFFORQTLIBSHARED_EXPORT PDFRealizedFont { public: ~PDFRealizedFont(); @@ -231,6 +232,9 @@ public: /// Return true, if we have horizontal writing system bool isHorizontalWritingSystem() const; + /// Adds information about the font into tree item + void dumpFontToTreeItem(QTreeWidgetItem* item) const; + /// Creates new realized font from the standard font. If font can't be created, /// then exception is thrown. static PDFRealizedFontPointer createRealizedFont(PDFFontPointer font, PDFReal pixelSize, PDFRenderErrorReporter* reporter); @@ -243,7 +247,7 @@ private: }; /// Base class representing font in the PDF file -class PDFFont +class PDFFORQTLIBSHARED_EXPORT PDFFont { public: explicit PDFFont(FontDescriptor fontDescriptor); @@ -255,6 +259,9 @@ public: /// Returns font descriptor const FontDescriptor* getFontDescriptor() const { return &m_fontDescriptor; } + /// Adds information about the font into tree item + virtual void dumpFontToTreeItem(QTreeWidgetItem* item) const { Q_UNUSED(item); } + /// Creates font from the object. If font can't be created, exception is thrown. /// \param object Font dictionary /// \param document Document @@ -274,6 +281,8 @@ private: /// which maps single-byte character to the glyph in the font. class PDFSimpleFont : public PDFFont { + using BaseClass = PDFFont; + public: explicit PDFSimpleFont(FontDescriptor fontDescriptor, QByteArray name, @@ -292,6 +301,8 @@ public: /// Returns the glyph advance (or zero, if glyph advance is invalid) PDFInteger getGlyphAdvance(size_t index) const; + virtual void dumpFontToTreeItem(QTreeWidgetItem* item) const override; + protected: QByteArray m_name; QByteArray m_baseFont; @@ -305,6 +316,8 @@ protected: class PDFType1Font : public PDFSimpleFont { + using BaseClass = PDFSimpleFont; + public: explicit PDFType1Font(FontDescriptor fontDescriptor, QByteArray name, @@ -319,6 +332,7 @@ public: virtual ~PDFType1Font() override = default; virtual FontType getFontType() const override; + virtual void dumpFontToTreeItem(QTreeWidgetItem*item) const override; /// Returns the assigned standard font (or invalid, if font is not standard) StandardFontType getStandardFontType() const { return m_standardFontType; } @@ -347,6 +361,7 @@ public: const PDFObject& resources); virtual FontType getFontType() const override; + virtual void dumpFontToTreeItem(QTreeWidgetItem*item) const override; /// Returns width of the character. If character doesn't exist, then zero is returned. double getWidth(int characterIndex) const; diff --git a/PdfForQtLib/sources/pdfobject.h b/PdfForQtLib/sources/pdfobject.h index c6ef347..265cf6f 100644 --- a/PdfForQtLib/sources/pdfobject.h +++ b/PdfForQtLib/sources/pdfobject.h @@ -226,7 +226,7 @@ private: /// an array of pairs key-value, where key is name object and value is any /// PDF object. For this reason, we use QByteArray for key. We do not use /// map, because dictionaries are usually small. -class PDFDictionary : public PDFObjectContent +class PDFFORQTLIBSHARED_EXPORT PDFDictionary : public PDFObjectContent { public: using DictionaryEntry = std::pair; diff --git a/PdfForQtViewer/pdfdocumentpropertiesdialog.cpp b/PdfForQtViewer/pdfdocumentpropertiesdialog.cpp index a812a95..ddd0e1a 100644 --- a/PdfForQtViewer/pdfdocumentpropertiesdialog.cpp +++ b/PdfForQtViewer/pdfdocumentpropertiesdialog.cpp @@ -20,9 +20,15 @@ #include "pdfdocument.h" #include "pdfwidgetutils.h" +#include "pdffont.h" +#include "pdfutils.h" +#include "pdfexception.h" #include #include +#include + +#include namespace pdfviewer { @@ -38,6 +44,7 @@ PDFDocumentPropertiesDialog::PDFDocumentPropertiesDialog(const pdf::PDFDocument* initializeProperties(document); initializeFileInfoProperties(fileInfo); initializeSecurity(document); + initializeFonts(document); const int defaultWidth = PDFWidgetUtils::getPixelSize(this, 240.0); const int defaultHeight = PDFWidgetUtils::getPixelSize(this, 200.0); @@ -46,6 +53,7 @@ PDFDocumentPropertiesDialog::PDFDocumentPropertiesDialog(const pdf::PDFDocument* PDFDocumentPropertiesDialog::~PDFDocumentPropertiesDialog() { + Q_ASSERT(m_fontTreeWidgetItems.empty()); delete ui; } @@ -228,4 +236,164 @@ void PDFDocumentPropertiesDialog::initializeSecurity(const pdf::PDFDocument* doc 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::PDFRealizedFontPointer realizedFont = pdf::PDFRealizedFont::createRealizedFont(font, 8.0, nullptr); + 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)); + } + + 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::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 (pdf::PDFException) + { + // Do nothing, some error occured, continue with next font + continue; + } + } + } + } + } + catch (pdf::PDFException) + { + // Do nothing, some error occured + } + }; + + pdf::PDFIntegerRange indices(pdf::PDFInteger(0), pageCount); + std::for_each(std::execution::parallel_policy(), 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::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 diff --git a/PdfForQtViewer/pdfdocumentpropertiesdialog.h b/PdfForQtViewer/pdfdocumentpropertiesdialog.h index 4cf52ee..f143cb3 100644 --- a/PdfForQtViewer/pdfdocumentpropertiesdialog.h +++ b/PdfForQtViewer/pdfdocumentpropertiesdialog.h @@ -21,6 +21,10 @@ #include "pdfglobal.h" #include +#include +#include + +class QTreeWidgetItem; namespace Ui { @@ -50,18 +54,31 @@ class PDFDocumentPropertiesDialog : public QDialog { Q_OBJECT +private: + using BaseClass = QDialog; + public: explicit PDFDocumentPropertiesDialog(const pdf::PDFDocument* document, const PDFFileInfo* fileInfo, QWidget* parent); virtual ~PDFDocumentPropertiesDialog() override; +protected: + virtual void closeEvent(QCloseEvent* event) override; + private: Ui::PDFDocumentPropertiesDialog* ui; void initializeProperties(const pdf::PDFDocument* document); void initializeFileInfoProperties(const PDFFileInfo* fileInfo); void initializeSecurity(const pdf::PDFDocument* document); + void initializeFonts(const pdf::PDFDocument* document); + + void onFontsFinished(); + + std::vector m_fontTreeWidgetItems; + QFuture m_future; + QFutureWatcher m_futureWatcher; }; } // namespace pdfviewer diff --git a/PdfForQtViewer/pdfdocumentpropertiesdialog.ui b/PdfForQtViewer/pdfdocumentpropertiesdialog.ui index 5845399..9fb6186 100644 --- a/PdfForQtViewer/pdfdocumentpropertiesdialog.ui +++ b/PdfForQtViewer/pdfdocumentpropertiesdialog.ui @@ -25,11 +25,11 @@ - + Properties - + @@ -58,7 +58,7 @@ File Information - + @@ -88,7 +88,7 @@ Security - + @@ -120,6 +120,42 @@ + + + Fonts + + + + + + Fonts + + + + + + 2 + + + false + + + + 1 + + + + + 2 + + + + + + + + +