From 97f838e73397a04504da8f83f84e8fba7cb9b230 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 19 Jun 2021 18:57:02 +0200 Subject: [PATCH] Statistics widget --- Pdf4QtLib/sources/pdfobjectutils.cpp | 42 ++++++ Pdf4QtLib/sources/pdfobjectutils.h | 6 +- .../objectstatisticsdialog.cpp | 125 ++++++++++++++++++ .../objectstatisticsdialog.h | 5 +- .../statisticsgraphwidget.cpp | 62 ++++++++- .../statisticsgraphwidget.h | 45 +++++++ 6 files changed, 282 insertions(+), 3 deletions(-) diff --git a/Pdf4QtLib/sources/pdfobjectutils.cpp b/Pdf4QtLib/sources/pdfobjectutils.cpp index 7381508..8b0d1a3 100644 --- a/Pdf4QtLib/sources/pdfobjectutils.cpp +++ b/Pdf4QtLib/sources/pdfobjectutils.cpp @@ -228,6 +228,48 @@ PDFObject PDFObjectUtils::replaceReferences(const PDFObject& object, const std:: return replaceReferencesVisitor.getObject(); } +QString PDFObjectUtils::getObjectTypeName(PDFObject::Type type) +{ + switch (type) + { + case pdf::PDFObject::Type::Null: + return PDFTranslationContext::tr("Null"); + + case pdf::PDFObject::Type::Bool: + return PDFTranslationContext::tr("Boolean"); + + case pdf::PDFObject::Type::Int: + return PDFTranslationContext::tr("Integer"); + + case pdf::PDFObject::Type::Real: + return PDFTranslationContext::tr("Real"); + + case pdf::PDFObject::Type::String: + return PDFTranslationContext::tr("String"); + + case pdf::PDFObject::Type::Name: + return PDFTranslationContext::tr("Name"); + + case pdf::PDFObject::Type::Array: + return PDFTranslationContext::tr("Array"); + + case pdf::PDFObject::Type::Dictionary: + return PDFTranslationContext::tr("Dictionary"); + + case pdf::PDFObject::Type::Stream: + return PDFTranslationContext::tr("Stream"); + + case pdf::PDFObject::Type::Reference: + return PDFTranslationContext::tr("Reference"); + + default: + Q_ASSERT(false); + break; + } + + return QString(); +} + void PDFObjectClassifier::classify(const PDFDocument* document) { // Clear old classification, if it exist diff --git a/Pdf4QtLib/sources/pdfobjectutils.h b/Pdf4QtLib/sources/pdfobjectutils.h index 56e8a6c..4027414 100644 --- a/Pdf4QtLib/sources/pdfobjectutils.h +++ b/Pdf4QtLib/sources/pdfobjectutils.h @@ -32,7 +32,7 @@ class PDFObjectStorage; class PDFDocument; /// Utilities for manipulation with objects -class PDFObjectUtils +class Pdf4QtLIBSHARED_EXPORT PDFObjectUtils { public: /// Returns a list of references referenced by \p objects. So, all references, which are present @@ -47,6 +47,10 @@ public: static PDFObject replaceReferences(const PDFObject& object, const std::map& referenceMapping); + /// Returns name for object type + /// \param type Type + static QString getObjectTypeName(PDFObject::Type type); + private: PDFObjectUtils() = delete; }; diff --git a/Pdf4QtViewerPlugins/ObjectInspectorPlugin/objectstatisticsdialog.cpp b/Pdf4QtViewerPlugins/ObjectInspectorPlugin/objectstatisticsdialog.cpp index 4ba553d..c838197 100644 --- a/Pdf4QtViewerPlugins/ObjectInspectorPlugin/objectstatisticsdialog.cpp +++ b/Pdf4QtViewerPlugins/ObjectInspectorPlugin/objectstatisticsdialog.cpp @@ -28,9 +28,16 @@ ObjectStatisticsDialog::ObjectStatisticsDialog(const pdf::PDFDocument* document, { ui->setupUi(this); + ui->comboBox->addItem(tr("Statistics by Object Function"), int(ByObjectClass)); + ui->comboBox->addItem(tr("Statistics by Object Type"), int(ByObjectType)); + ui->comboBox->setCurrentIndex(ui->comboBox->findData(int(ByObjectClass), Qt::UserRole, Qt::MatchExactly)); + connect(ui->comboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ObjectStatisticsDialog::updateStatisticsWidget); + pdf::PDFObjectClassifier classifier; classifier.classify(document); m_statistics = classifier.calculateStatistics(document); + + updateStatisticsWidget(); } ObjectStatisticsDialog::~ObjectStatisticsDialog() @@ -38,4 +45,122 @@ ObjectStatisticsDialog::~ObjectStatisticsDialog() delete ui; } +void ObjectStatisticsDialog::updateStatisticsWidget() +{ + StatisticsGraphWidget::Statistics statistics; + + QLocale locale; + + std::array colors = { + Qt::green, + Qt::red, + Qt::blue, + Qt::cyan, + Qt::magenta, + Qt::yellow, + Qt::darkGreen, + Qt::darkRed, + Qt::darkBlue, + Qt::darkCyan, + Qt::darkMagenta, + Qt::darkYellow + }; + + const StatisticsType statisticsType = static_cast(ui->comboBox->currentData().toInt()); + switch (statisticsType) + { + case pdfplugin::ObjectStatisticsDialog::ByObjectClass: + { + statistics.title = tr("Statistics by Object Class"); + statistics.headers = QStringList() << tr("Class") << tr("Percentage [%]") << tr("Count [#]") << tr("Space Usage [bytes]"); + + size_t colorId = 0; + qint64 totalBytesCount = 0; + for (const auto& item : m_statistics.statistics) + { + totalBytesCount += item.second.bytes; + } + + auto addItem = [&, this](pdf::PDFObjectClassifier::Type type, QString classText) + { + auto it = m_statistics.statistics.find(type); + + if (it == m_statistics.statistics.cend()) + { + // Jakub Melka: no type found + return; + } + + const pdf::PDFObjectClassifier::StatisticsItem& statisticsItem = it->second; + + qreal percentage = qreal(100.0) * qreal(statisticsItem.bytes) / qreal(totalBytesCount); + + StatisticsGraphWidget::StatisticsItem item; + item.portion = percentage / qreal(100.0); + item.color = colors[colorId++ % colors.size()]; + item.texts = QStringList() << classText << locale.toString(percentage) << locale.toString(statisticsItem.count) << locale.toString(statisticsItem.bytes); + statistics.items.emplace_back(qMove(item)); + }; + + addItem(pdf::PDFObjectClassifier::Page, tr("Page")); + addItem(pdf::PDFObjectClassifier::ContentStream, tr("Content Stream")); + addItem(pdf::PDFObjectClassifier::GraphicState, tr("Graphic State")); + addItem(pdf::PDFObjectClassifier::ColorSpace, tr("Color Space")); + addItem(pdf::PDFObjectClassifier::Pattern, tr("Pattern")); + addItem(pdf::PDFObjectClassifier::Shading, tr("Shading")); + addItem(pdf::PDFObjectClassifier::Image, tr("Image")); + addItem(pdf::PDFObjectClassifier::Form, tr("Form")); + addItem(pdf::PDFObjectClassifier::Font, tr("Font")); + addItem(pdf::PDFObjectClassifier::Action, tr("Action")); + addItem(pdf::PDFObjectClassifier::Annotation, tr("Annotation")); + addItem(pdf::PDFObjectClassifier::None, tr("Other")); + + break; + } + + case pdfplugin::ObjectStatisticsDialog::ByObjectType: + { + statistics.title = tr("Statistics by Object Type"); + statistics.headers = QStringList() << tr("Type") << tr("Percentage [%]") << tr("Count [#]"); + + qint64 totalObjectCount = 0; + for (pdf::PDFObject::Type type : pdf::PDFObject::getTypes()) + { + const qint64 currentObjectCount = m_statistics.objectCountByType[size_t(type)]; + totalObjectCount += currentObjectCount; + } + + if (totalObjectCount > 0) + { + size_t colorId = 0; + for (pdf::PDFObject::Type type : pdf::PDFObject::getTypes()) + { + const qint64 currentObjectCount = m_statistics.objectCountByType[size_t(type)]; + + if (currentObjectCount == 0) + { + continue; + } + + qreal percentage = qreal(100.0) * qreal(currentObjectCount) / qreal(totalObjectCount); + + StatisticsGraphWidget::StatisticsItem item; + item.portion = percentage / qreal(100.0); + item.color = colors[colorId++ % colors.size()]; + item.texts = QStringList() << pdf::PDFObjectUtils::getObjectTypeName(type) << locale.toString(percentage) << locale.toString(currentObjectCount); + statistics.items.emplace_back(qMove(item)); + } + } + + break; + } + + default: + Q_ASSERT(false); + break; + } + + ui->graphWidget->setStatistics(qMove(statistics)); +} + } // namespace pdfplugin diff --git a/Pdf4QtViewerPlugins/ObjectInspectorPlugin/objectstatisticsdialog.h b/Pdf4QtViewerPlugins/ObjectInspectorPlugin/objectstatisticsdialog.h index 96fada0..41fde7c 100644 --- a/Pdf4QtViewerPlugins/ObjectInspectorPlugin/objectstatisticsdialog.h +++ b/Pdf4QtViewerPlugins/ObjectInspectorPlugin/objectstatisticsdialog.h @@ -44,9 +44,12 @@ private: enum StatisticsType { - + ByObjectClass, + ByObjectType }; + void updateStatisticsWidget(); + const pdf::PDFDocument* m_document; pdf::PDFObjectClassifier::Statistics m_statistics; }; diff --git a/Pdf4QtViewerPlugins/ObjectInspectorPlugin/statisticsgraphwidget.cpp b/Pdf4QtViewerPlugins/ObjectInspectorPlugin/statisticsgraphwidget.cpp index 163950a..34a3673 100644 --- a/Pdf4QtViewerPlugins/ObjectInspectorPlugin/statisticsgraphwidget.cpp +++ b/Pdf4QtViewerPlugins/ObjectInspectorPlugin/statisticsgraphwidget.cpp @@ -18,10 +18,12 @@ #include "statisticsgraphwidget.h" #include "ui_statisticsgraphwidget.h" +#include "pdfwidgetutils.h" + namespace pdfplugin { -StatisticsGraphWidget::StatisticsGraphWidget(QWidget *parent) : +StatisticsGraphWidget::StatisticsGraphWidget(QWidget* parent) : QWidget(parent), ui(new Ui::StatisticsGraphWidget) { @@ -33,4 +35,62 @@ StatisticsGraphWidget::~StatisticsGraphWidget() delete ui; } +void StatisticsGraphWidget::setStatistics(Statistics statistics) +{ + if (m_statistics != statistics) + { + m_statistics = qMove(statistics); + updateGeometry(); + update(); + } +} + +StatisticsGraphWidget::GeometryHint StatisticsGraphWidget::getGeometryHint() const +{ + GeometryHint hint; + + int fivePixelsX = pdf::PDFWidgetUtils::scaleDPI_x(this, 5); + int fivePixelsY = pdf::PDFWidgetUtils::scaleDPI_y(this, 5); + + hint.margins = QMargins(fivePixelsX, fivePixelsY, fivePixelsX, fivePixelsY); + hint.colorRectangleWidth = pdf::PDFWidgetUtils::scaleDPI_x(this, 40); + hint.linesWidthLeft = pdf::PDFWidgetUtils::scaleDPI_x(this, 15); + hint.linesWidthRight = pdf::PDFWidgetUtils::scaleDPI_x(this, 5); + + QFontMetrics fontMetricsTitle(getTitleFont()); + hint.titleWidth = fontMetricsTitle.horizontalAdvance(m_statistics.title); + hint.titleHeight = fontMetricsTitle.lineSpacing(); + + QFontMetrics fontMetricsHeader(getHeaderFont()); + hint.textHeight = fontMetricsHeader.lineSpacing(); + + QFontMetrics fontMetricsText(getTextFont()); + hint.textHeight += fontMetricsText.lineSpacing() * int(m_statistics.items.size()); + + // Determine text header width + s + + return hint; +} + +QFont StatisticsGraphWidget::getTitleFont() const +{ + QFont font = this->font(); + font.setPointSize(font.pointSize() * 2); + font.setBold(true); + return font; +} + +QFont StatisticsGraphWidget::getHeaderFont() const +{ + QFont font = this->font(); + font.setBold(true); + return font; +} + +QFont StatisticsGraphWidget::getTextFont() const +{ + return this->font(); +} + } // namespace pdfplugin diff --git a/Pdf4QtViewerPlugins/ObjectInspectorPlugin/statisticsgraphwidget.h b/Pdf4QtViewerPlugins/ObjectInspectorPlugin/statisticsgraphwidget.h index c208fb0..154389f 100644 --- a/Pdf4QtViewerPlugins/ObjectInspectorPlugin/statisticsgraphwidget.h +++ b/Pdf4QtViewerPlugins/ObjectInspectorPlugin/statisticsgraphwidget.h @@ -33,11 +33,56 @@ class StatisticsGraphWidget : public QWidget Q_OBJECT public: + + struct StatisticsItem + { + bool operator==(const StatisticsItem&) const = default; + + qreal portion; + QColor color; + QStringList texts; + }; + + struct Statistics + { + bool operator==(const Statistics&) const = default; + + QString title; + QStringList headers; + std::vector items; + }; + explicit StatisticsGraphWidget(QWidget* parent); virtual ~StatisticsGraphWidget() override; + void setStatistics(Statistics statistics); + private: Ui::StatisticsGraphWidget* ui; + + struct GeometryHint + { + QMargins margins; + int colorRectangleWidth = 0; + int linesWidthLeft = 0; + int linesWidthRight = 0; + + int titleWidth = 0; + int titleHeight = 0; + int textHeight = 0; + + std::vector textWidths; + + QSize minimalWidgetSize = 0; + }; + + GeometryHint getGeometryHint() const; + + QFont getTitleFont() const; + QFont getHeaderFont() const; + QFont getTextFont() const; + + Statistics m_statistics; }; } // namespace pdfplugin