// Copyright (C) 2021 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 "inkcoveragedialog.h" #include "ui_inkcoveragedialog.h" #include "pdfdrawspacecontroller.h" #include "pdfwidgetutils.h" #include #include namespace pdfplugin { InkCoverageDialog::InkCoverageDialog(const pdf::PDFDocument* document, pdf::PDFWidget* widget, QWidget* parent) : QDialog(parent, Qt::Dialog | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint), ui(new Ui::InkCoverageDialog), m_inkMapper(widget->getCMSManager(), document), m_document(document), m_widget(widget), m_model(nullptr), m_futureWatcher(nullptr) { ui->setupUi(this); m_inkMapper.createSpotColors(true); m_model = new InkCoverageStatisticsModel(this); ui->coverageTableView->setModel(m_model); ui->coverageTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); setMinimumSize(pdf::PDFWidgetUtils::scaleDPI(this, QSize(800, 600))); updateInkCoverage(); pdf::PDFWidgetUtils::style(this); } InkCoverageDialog::~InkCoverageDialog() { delete ui; } void InkCoverageDialog::updateInkCoverage() { Q_ASSERT(!m_future.isRunning()); Q_ASSERT(!m_futureWatcher); auto calculateInkCoverage = [this]() -> InkCoverageResults { InkCoverageResults results; pdf::PDFTransparencyRendererSettings settings; // Jakub Melka: debug is very slow, use multithreading #ifdef QT_DEBUG settings.flags.setFlag(pdf::PDFTransparencyRendererSettings::MultithreadedPathSampler, true); #endif settings.flags.setFlag(pdf::PDFTransparencyRendererSettings::ActiveColorMask, false); settings.flags.setFlag(pdf::PDFTransparencyRendererSettings::SeparationSimulation, m_inkMapper.getActiveSpotColorCount() > 0); settings.flags.setFlag(pdf::PDFTransparencyRendererSettings::SaveOriginalProcessImage, true); pdf::PDFInkCoverageCalculator calculator(m_document, m_widget->getDrawWidgetProxy()->getFontCache(), m_widget->getCMSManager(), m_widget->getDrawWidgetProxy()->getOptionalContentActivity(), &m_inkMapper, m_widget->getDrawWidgetProxy()->getProgress(), settings); std::vector pageIndices; pageIndices.resize(m_document->getCatalog()->getPageCount(), 0); std::iota(pageIndices.begin(), pageIndices.end(), 0); calculator.perform(QSize(RESOLUTION, RESOLUTION), pageIndices); CoverageInfo coverageInfo; for (const pdf::PDFInteger pageIndex : pageIndices) { const std::vector* coverage = calculator.getInkCoverage(pageIndex); if (!coverage) { // Jakub Melka: Something bad happened? continue; } for (const auto& info : *coverage) { if (!pdf::PDFInkCoverageCalculator::findCoverageInfoByName(coverageInfo, info.name)) { coverageInfo.push_back(info); coverageInfo.back().coveredArea = 0.0; coverageInfo.back().ratio = 0.0; } } } CoverageInfo templateInfo = coverageInfo; CoverageInfo sumInfo = coverageInfo; for (const pdf::PDFInteger pageIndex : pageIndices) { const std::vector* coverage = calculator.getInkCoverage(pageIndex); results.pageInfo.push_back(templateInfo); if (!coverage) { // Jakub Melka: Something bad happened? continue; } CoverageInfo& currentInfo = results.pageInfo.back(); for (const auto& info : *coverage) { pdf::PDFInkCoverageCalculator::InkCoverageChannelInfo* channelInfo = pdf::PDFInkCoverageCalculator::findCoverageInfoByName(currentInfo, info.name); pdf::PDFInkCoverageCalculator::InkCoverageChannelInfo* sumChannelInfo = pdf::PDFInkCoverageCalculator::findCoverageInfoByName(sumInfo, info.name); channelInfo->coveredArea = info.coveredArea; channelInfo->ratio = info.ratio; sumChannelInfo->coveredArea += info.coveredArea; } } results.sumInfo = qMove(sumInfo); return results; }; m_future = QtConcurrent::run(calculateInkCoverage); m_futureWatcher = new QFutureWatcher(); connect(m_futureWatcher, &QFutureWatcher::finished, this, &InkCoverageDialog::onInkCoverageCalculated); m_futureWatcher->setFuture(m_future); } void InkCoverageDialog::closeEvent(QCloseEvent* event) { if (!isInkCoverageCalculated()) { event->ignore(); return; } BaseClass::closeEvent(event); } void InkCoverageDialog::accept() { if (!isInkCoverageCalculated()) { return; } BaseClass::accept(); } void InkCoverageDialog::reject() { if (!isInkCoverageCalculated()) { return; } BaseClass::reject(); } void InkCoverageDialog::onInkCoverageCalculated() { Q_ASSERT(m_future.isFinished()); InkCoverageResults results = m_future.result(); m_model->setInkCoverageResults(qMove(results)); } bool InkCoverageDialog::isInkCoverageCalculated() const { return !(m_futureWatcher && m_futureWatcher->isRunning()); } InkCoverageStatisticsModel::InkCoverageStatisticsModel(QObject* parent) : BaseClass(parent) { } QModelIndex InkCoverageStatisticsModel::index(int row, int column, const QModelIndex& parent) const { Q_UNUSED(parent); return createIndex(row, column, nullptr); } QModelIndex InkCoverageStatisticsModel::parent(const QModelIndex& child) const { Q_UNUSED(child); return QModelIndex(); } int InkCoverageStatisticsModel::rowCount(const QModelIndex& parent) const { Q_UNUSED(parent); if (m_inkCoverageResults.pageInfo.empty()) { return 0; } return int(m_inkCoverageResults.pageInfo.size() + 1); } int InkCoverageStatisticsModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); return LastStandardColumn + getChannelCount() * LastChannelColumn; } QVariant InkCoverageStatisticsModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } switch (role) { case Qt::DisplayRole: { const int row = index.row(); QLocale locale; const bool isTotalRow = row == rowCount(index.parent()) - 1; if (index.column() == StandardColumnPageIndex) { if (isTotalRow) { return tr("Total"); } return locale.toString(row + 1); } Q_ASSERT(index.column() >= LastStandardColumn); const CoverageInfo* info = nullptr; if (row < m_inkCoverageResults.pageInfo.size()) { info = &m_inkCoverageResults.pageInfo[row]; } else { info = &m_inkCoverageResults.sumInfo; } const int channelIndex = getChannelIndex(index.column()); EChannelColumn channelColumn = getChannelColumn(index.column()); ChannelCoverageInfo channelInfo = info->at(channelIndex); switch (channelColumn) { case pdfplugin::InkCoverageStatisticsModel::ChannelColumnColorant: return QString(); case pdfplugin::InkCoverageStatisticsModel::ChannelColumnCoverageArea: return locale.toString(channelInfo.coveredArea, 'f', 2); case pdfplugin::InkCoverageStatisticsModel::ChannelColumnCoverageRatio: return !isTotalRow ? locale.toString(channelInfo.ratio * 100.0, 'f', 0) : QString(); default: Q_ASSERT(false); break; } break; } case Qt::TextAlignmentRole: { if (index.column() == StandardColumnPageIndex) { return int(Qt::AlignCenter); } Q_ASSERT(index.column() >= LastStandardColumn); EChannelColumn channelColumn = getChannelColumn(index.column()); switch (channelColumn) { case pdfplugin::InkCoverageStatisticsModel::ChannelColumnColorant: return int(Qt::AlignLeft | Qt::AlignVCenter); case pdfplugin::InkCoverageStatisticsModel::ChannelColumnCoverageArea: case pdfplugin::InkCoverageStatisticsModel::ChannelColumnCoverageRatio: return int(Qt::AlignRight | Qt::AlignVCenter); default: Q_ASSERT(false); break; } } case Qt::BackgroundColorRole: { if (index.column() >= LastStandardColumn && getChannelColumn(index.column()) == ChannelColumnColorant) { const int channelIndex = getChannelIndex(index.column()); return m_inkCoverageResults.sumInfo[channelIndex].color; } break; } default: break; } return QVariant(); } QVariant InkCoverageStatisticsModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole || orientation != Qt::Horizontal) { return QVariant(); } if (section < LastStandardColumn) { Q_ASSERT(section == StandardColumnPageIndex); return tr("Page Index"); } const EChannelColumn channelColumn = getChannelColumn(section); switch (channelColumn) { case ChannelColumnColorant: { const int channelIndex = getChannelIndex(section); return m_inkCoverageResults.sumInfo[channelIndex].textName; } case ChannelColumnCoverageArea: return tr("[ mm² ]"); case ChannelColumnCoverageRatio: return tr("[ % ]"); case LastChannelColumn: Q_ASSERT(false); break; default: Q_ASSERT(false); break; } return QVariant(); } void InkCoverageStatisticsModel::setInkCoverageResults(InkCoverageResults inkCoverageResults) { beginResetModel(); m_inkCoverageResults = qMove(inkCoverageResults); endResetModel(); } int InkCoverageStatisticsModel::getChannelCount() const { return int(m_inkCoverageResults.sumInfo.size()); } InkCoverageStatisticsModel::EChannelColumn InkCoverageStatisticsModel::getChannelColumn(int index) const { Q_ASSERT(index >= LastStandardColumn); index -= LastStandardColumn; return static_cast(index % LastChannelColumn); } int InkCoverageStatisticsModel::getChannelIndex(int index) const { Q_ASSERT(index >= LastStandardColumn); index -= LastStandardColumn; return index / LastChannelColumn; } } // pdfplugin