From 3c512d0e9871b4f5296b949c2ad70b83eaaec0a8 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Wed, 28 Oct 2020 16:35:16 +0100 Subject: [PATCH] Rendering tool + benchmarking tool --- PdfForQtLib/sources/pdffont.h | 2 +- PdfForQtLib/sources/pdfrenderer.cpp | 41 ++- PdfForQtLib/sources/pdfrenderer.h | 26 +- PdfForQtViewer/pdfrendertoimagesdialog.cpp | 12 +- PdfForQtViewer/pdfrendertoimagesdialog.h | 2 +- PdfTool/main.cpp | 5 +- PdfTool/pdftoolabstractapplication.cpp | 38 +++ PdfTool/pdftoolabstractapplication.h | 3 + PdfTool/pdftoolrender.cpp | 304 +++++++++++++++++++-- PdfTool/pdftoolrender.h | 34 +++ 10 files changed, 423 insertions(+), 44 deletions(-) diff --git a/PdfForQtLib/sources/pdffont.h b/PdfForQtLib/sources/pdffont.h index 9ef25de..44c306d 100644 --- a/PdfForQtLib/sources/pdffont.h +++ b/PdfForQtLib/sources/pdffont.h @@ -374,7 +374,7 @@ public: /// Font cache which caches both fonts, and realized fonts. Cache has individual limit /// for fonts, and realized fonts. -class PDFFontCache +class PDFFORQTLIBSHARED_EXPORT PDFFontCache { public: inline explicit PDFFontCache(size_t fontCacheLimit, size_t realizedFontCacheLimit) : diff --git a/PdfForQtLib/sources/pdfrenderer.cpp b/PdfForQtLib/sources/pdfrenderer.cpp index 63688dc..add3eec 100644 --- a/PdfForQtLib/sources/pdfrenderer.cpp +++ b/PdfForQtLib/sources/pdfrenderer.cpp @@ -400,7 +400,7 @@ void PDFRasterizerPool::render(const std::vector& pageIndices, QElapsedTimer timer; timer.start(); - emit renderError(PDFRenderError(RenderErrorType::Information, PDFTranslationContext::tr("Start at %1...").arg(QTime::currentTime().toString(Qt::TextDate)))); + emit renderError(PDFCatalog::INVALID_PAGE_INDEX, PDFRenderError(RenderErrorType::Information, PDFTranslationContext::tr("Start at %1...").arg(QTime::currentTime().toString(Qt::TextDate)))); if (progress) { @@ -419,19 +419,27 @@ void PDFRasterizerPool::render(const std::vector& pageIndices, { progress->step(); } - emit renderError(PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Page %1 not found.").arg(pageIndex))); + emit renderError(pageIndex, PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Page %1 not found.").arg(pageIndex))); return; } + QElapsedTimer totalPageTimer; + totalPageTimer.start(); + + QElapsedTimer pageTimer; + pageTimer.start(); + // Precompile the page PDFPrecompiledPage precompiledPage; PDFCMSPointer cms = m_cmsManager->getCurrentCMS(); PDFRenderer renderer(m_document, m_fontCache, cms.data(), m_optionalContentActivity, m_features, m_meshQualitySettings); renderer.compile(&precompiledPage, pageIndex); - for (const PDFRenderError error : precompiledPage.getErrors()) + qint64 pageCompileTime = pageTimer.restart(); + + for (const PDFRenderError& error : precompiledPage.getErrors()) { - emit renderError(PDFRenderError(error.type, PDFTranslationContext::tr("Page %1: %2").arg(pageIndex + 1).arg(error.message))); + emit renderError(pageIndex, error); } // We can const-cast here, because we do not modify the document in annotation manager. @@ -443,12 +451,22 @@ void PDFRasterizerPool::render(const std::vector& pageIndices, annotationManager.setDocument(modifiedDocument); // Render page to image + pageTimer.restart(); PDFRasterizer* rasterizer = acquire(); + qint64 pageWaitTime = pageTimer.restart(); QImage image = rasterizer->render(pageIndex, page, &precompiledPage, imageSizeGetter(page), m_features, &annotationManager); + qint64 pageRenderTime = pageTimer.elapsed(); release(rasterizer); // Now, process the image - processImage(pageIndex, qMove(image)); + PDFRenderedPageImage renderedPageImage; + renderedPageImage.pageIndex = pageIndex; + renderedPageImage.pageImage = qMove(image); + renderedPageImage.pageCompileTime = pageCompileTime; + renderedPageImage.pageWaitTime = pageWaitTime; + renderedPageImage.pageRenderTime = pageRenderTime; + renderedPageImage.pageTotalTime = totalPageTimer.elapsed(); + processImage(renderedPageImage); if (progress) { @@ -462,14 +480,19 @@ void PDFRasterizerPool::render(const std::vector& pageIndices, progress->finish(); } - emit renderError(PDFRenderError(RenderErrorType::Information, PDFTranslationContext::tr("Finished at %1...").arg(QTime::currentTime().toString(Qt::TextDate)))); - emit renderError(PDFRenderError(RenderErrorType::Information, PDFTranslationContext::tr("%1 miliseconds elapsed to render %2 pages...").arg(timer.nsecsElapsed() / 1000000).arg(pageIndices.size()))); + emit renderError(PDFCatalog::INVALID_PAGE_INDEX, PDFRenderError(RenderErrorType::Information, PDFTranslationContext::tr("Finished at %1...").arg(QTime::currentTime().toString(Qt::TextDate)))); + emit renderError(PDFCatalog::INVALID_PAGE_INDEX, PDFRenderError(RenderErrorType::Information, PDFTranslationContext::tr("%1 miliseconds elapsed to render %2 pages...").arg(timer.nsecsElapsed() / 1000000).arg(pageIndices.size()))); } int PDFRasterizerPool::getDefaultRasterizerCount() { int hint = QThread::idealThreadCount() / 2; - return qBound(1, hint, 16); + return getCorrectedRasterizerCount(hint); +} + +int PDFRasterizerPool::getCorrectedRasterizerCount(int rasterizerCount) +{ + return qBound(1, rasterizerCount, 16); } PDFImageWriterSettings::PDFImageWriterSettings() @@ -833,7 +856,7 @@ std::vector PDFPageImageExportSettings::getPages() const return result; } -QString PDFPageImageExportSettings::getOutputFileName(PDFInteger pageIndex, const QByteArray& outputFormat) +QString PDFPageImageExportSettings::getOutputFileName(PDFInteger pageIndex, const QByteArray& outputFormat) const { QString fileName = m_fileTemplate; fileName.replace('%', QString::number(pageIndex + 1)); diff --git a/PdfForQtLib/sources/pdfrenderer.h b/PdfForQtLib/sources/pdfrenderer.h index 03fe8ee..1f96959 100644 --- a/PdfForQtLib/sources/pdfrenderer.h +++ b/PdfForQtLib/sources/pdfrenderer.h @@ -26,6 +26,7 @@ #include #include #include +#include class QPainter; class QOpenGLContext; @@ -168,6 +169,17 @@ private: QOpenGLFramebufferObject* m_fbo; }; +/// Simple structure for storing rendered page images +struct PDFRenderedPageImage +{ + qint64 pageCompileTime = 0; + qint64 pageWaitTime = 0; + qint64 pageRenderTime = 0; + qint64 pageTotalTime = 0; + PDFInteger pageIndex; + QImage pageImage; +}; + /// Pool of page image renderers. It can use predefined number of renderers to /// render page images asynchronously. You can use this object in two ways - /// first one is as standard object pool, second one is to directly render @@ -181,8 +193,9 @@ private: public: + using PageImageSizeGetter = std::function; - using ProcessImageMethod = std::function; + using ProcessImageMethod = std::function; /// Creates new rasterizer pool /// \param document Document @@ -229,8 +242,15 @@ public: /// Returns default rasterizer count static int getDefaultRasterizerCount(); + /// Returns corrected rasterizer count (so, if user + /// select too high or too low rasterizer count, this function + /// corrects it to acceptable number. + /// \param rasterizerCount Requested number of rasterizers + /// \returns Corrected number of rasterizers + static int getCorrectedRasterizerCount(int rasterizerCount); + signals: - void renderError(PDFRenderError error); + void renderError(PDFInteger pageIndex, PDFRenderError error); private: const PDFDocument* m_document; @@ -344,7 +364,7 @@ public: std::vector getPages() const; /// Returns output file name for given page - QString getOutputFileName(PDFInteger pageIndex, const QByteArray& outputFormat); + QString getOutputFileName(PDFInteger pageIndex, const QByteArray& outputFormat) const; static constexpr int getMinDPIResolution() { return 72; } static constexpr int getMaxDPIResolution() { return 6000; } diff --git a/PdfForQtViewer/pdfrendertoimagesdialog.cpp b/PdfForQtViewer/pdfrendertoimagesdialog.cpp index 776ebc6..797e86f 100644 --- a/PdfForQtViewer/pdfrendertoimagesdialog.cpp +++ b/PdfForQtViewer/pdfrendertoimagesdialog.cpp @@ -243,9 +243,9 @@ void PDFRenderToImagesDialog::onProgressiveScanWriteChanged(bool value) m_imageWriterSettings.setProgressiveScanWrite(value); } -void PDFRenderToImagesDialog::onRenderError(pdf::PDFRenderError error) +void PDFRenderToImagesDialog::onRenderError(pdf::PDFInteger pageIndex, pdf::PDFRenderError error) { - ui->progressMessagesEdit->setPlainText(QString("%1\n%2").arg(ui->progressMessagesEdit->toPlainText()).arg(error.message)); + ui->progressMessagesEdit->setPlainText(QString("%1\n%2").arg(ui->progressMessagesEdit->toPlainText()).arg(tr("Page %1: %2").arg(pageIndex + 1).arg(error.message))); } void PDFRenderToImagesDialog::onRenderingFinished() @@ -319,9 +319,9 @@ void PDFRenderToImagesDialog::on_buttonBox_clicked(QAbstractButton* button) return QSize(); }; - auto processImage = [this](const pdf::PDFInteger pageIndex, QImage&& image) + auto processImage = [this](pdf::PDFRenderedPageImage& renderedPageImage) { - QString fileName = m_imageExportSettings.getOutputFileName(pageIndex, m_imageWriterSettings.getCurrentFormat()); + QString fileName = m_imageExportSettings.getOutputFileName(renderedPageImage.pageIndex, m_imageWriterSettings.getCurrentFormat()); QImageWriter imageWriter(fileName, m_imageWriterSettings.getCurrentFormat()); imageWriter.setSubType(m_imageWriterSettings.getCurrentSubtype()); @@ -331,9 +331,9 @@ void PDFRenderToImagesDialog::on_buttonBox_clicked(QAbstractButton* button) imageWriter.setOptimizedWrite(m_imageWriterSettings.hasOptimizedWrite()); imageWriter.setProgressiveScanWrite(m_imageWriterSettings.hasProgressiveScanWrite()); - if (!imageWriter.write(image)) + if (!imageWriter.write(renderedPageImage.pageImage)) { - emit m_rasterizerPool->renderError(pdf::PDFRenderError(pdf::RenderErrorType::Error, tr("Can't write page image to file '%1', because: %2.").arg(fileName).arg(imageWriter.errorString()))); + emit m_rasterizerPool->renderError(renderedPageImage.pageIndex, pdf::PDFRenderError(pdf::RenderErrorType::Error, tr("Cannot write page image to file '%1', because: %2.").arg(fileName).arg(imageWriter.errorString()))); } }; diff --git a/PdfForQtViewer/pdfrendertoimagesdialog.h b/PdfForQtViewer/pdfrendertoimagesdialog.h index ef39b6d..071a9c0 100644 --- a/PdfForQtViewer/pdfrendertoimagesdialog.h +++ b/PdfForQtViewer/pdfrendertoimagesdialog.h @@ -76,7 +76,7 @@ private: void onGammaChanged(double value); void onOptimizedWriteChanged(bool value); void onProgressiveScanWriteChanged(bool value); - void onRenderError(pdf::PDFRenderError error); + void onRenderError(pdf::PDFInteger pageIndex, pdf::PDFRenderError error); void onRenderingFinished(); Ui::PDFRenderToImagesDialog* ui; diff --git a/PdfTool/main.cpp b/PdfTool/main.cpp index 2b7c499..91dbfb9 100644 --- a/PdfTool/main.cpp +++ b/PdfTool/main.cpp @@ -17,12 +17,13 @@ #include "pdftoolabstractapplication.h" -#include +#include #include int main(int argc, char *argv[]) { - QCoreApplication a(argc, argv); + QGuiApplication a(argc, argv); + QGuiApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity, true); QCoreApplication::setOrganizationName("MelkaJ"); QCoreApplication::setApplicationName("PdfTool"); QCoreApplication::setApplicationVersion("1.0.0"); diff --git a/PdfTool/pdftoolabstractapplication.cpp b/PdfTool/pdftoolabstractapplication.cpp index 5910753..555040f 100644 --- a/PdfTool/pdftoolabstractapplication.cpp +++ b/PdfTool/pdftoolabstractapplication.cpp @@ -286,6 +286,10 @@ void PDFToolAbstractApplication::initializeCommandLineParser(QCommandLineParser* { parser->addOption(QCommandLineOption(info.option, info.description, "bool", defaultFeatures.testFlag(info.feature) ? "1" : "0")); } + + parser->addOption(QCommandLineOption("render-hw-accel", "Use hardware acceleration (using GPU).", "bool", "1")); + parser->addOption(QCommandLineOption("render-msaa-samples", "MSAA sample count for GPU rendering.", "samples", "4")); + parser->addOption(QCommandLineOption("render-rasterizers", "Number of rasterizer contexts.", "rasterizers", QString::number(pdf::PDFRasterizerPool::getDefaultRasterizerCount()))); } } @@ -756,6 +760,40 @@ PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser PDFConsole::writeError(PDFToolTranslationContext::tr("Uknown bool value '%1'. Default value is used.").arg(textValue), options.outputCodec); } } + + QString textValue = parser->value("render-hw-accel"); + bool ok = false; + bool value = textValue.toInt(&ok); + if (ok) + { + options.renderUseHardwareRendering = value; + } + else + { + PDFConsole::writeError(PDFToolTranslationContext::tr("Uknown bool value '%1'. GPU rendering is used as default.").arg(textValue), options.outputCodec); + } + + textValue = parser->value("render-msaa-samples"); + options.renderMSAAsamples = textValue.toInt(&ok); + if (!ok) + { + PDFConsole::writeError(PDFToolTranslationContext::tr("Uknown MSAA sample count '%1'. 4 samples are used as default.").arg(textValue), options.outputCodec); + options.renderMSAAsamples = 4; + } + + textValue = parser->value("render-rasterizers"); + options.renderRasterizerCount = textValue.toInt(&ok); + if (!ok) + { + options.renderRasterizerCount = pdf::PDFRasterizerPool::getDefaultRasterizerCount(); + PDFConsole::writeError(PDFToolTranslationContext::tr("Uknown rasterizer count '%1'. %2 rasterizers are used as default.").arg(textValue).arg(options.renderRasterizerCount), options.outputCodec); + } + int correctedRasterizerCount = pdf::PDFRasterizerPool::getCorrectedRasterizerCount(options.renderRasterizerCount); + if (correctedRasterizerCount != options.renderRasterizerCount) + { + PDFConsole::writeError(PDFToolTranslationContext::tr("Invalid raterizer count: %1. Correcting to use %2 rasterizers.").arg(options.renderRasterizerCount).arg(correctedRasterizerCount), options.outputCodec); + options.renderRasterizerCount = correctedRasterizerCount; + } } return options; diff --git a/PdfTool/pdftoolabstractapplication.h b/PdfTool/pdftoolabstractapplication.h index b5ab464..815b1e9 100644 --- a/PdfTool/pdftoolabstractapplication.h +++ b/PdfTool/pdftoolabstractapplication.h @@ -124,6 +124,9 @@ struct PDFToolOptions // For option 'RenderFlags' pdf::PDFRenderer::Features renderFeatures = pdf::PDFRenderer::getDefaultFeatures(); + bool renderUseHardwareRendering = true; + int renderMSAAsamples = 4; + int renderRasterizerCount = pdf::PDFRasterizerPool::getDefaultRasterizerCount(); /// Returns page range. If page range is invalid, then \p errorMessage is empty. /// \param pageCount Page count diff --git a/PdfTool/pdftoolrender.cpp b/PdfTool/pdftoolrender.cpp index ab3a9c0..e6b4b2c 100644 --- a/PdfTool/pdftoolrender.cpp +++ b/PdfTool/pdftoolrender.cpp @@ -16,6 +16,10 @@ // along with PDFForQt. If not, see . #include "pdftoolrender.h" +#include "pdffont.h" +#include "pdfconstants.h" + +#include namespace pdftool { @@ -49,6 +53,43 @@ PDFToolAbstractApplication::Options PDFToolRender::getOptionsFlags() const return ConsoleFormat | OpenDocument | PageSelector | ImageWriterSettings | ImageExportSettingsFiles | ImageExportSettingsResolution | ColorManagementSystem | RenderFlags; } +void PDFToolRender::finish(const PDFToolOptions& options) +{ + PDFOutputFormatter formatter(options.outputStyle, options.outputCodec); + formatter.beginDocument("render", PDFToolTranslationContext::tr("Render document %1").arg(options.document)); + formatter.endl(); + + writeStatistics(formatter); + writeErrors(formatter); + + formatter.endDocument(); + PDFConsole::writeText(formatter.getString(), options.outputCodec); +} + +void PDFToolRender::onPageRendered(const PDFToolOptions& options, pdf::PDFRenderedPageImage& renderedPageImage) +{ + writePageInfoStatistics(renderedPageImage); + QString fileName = options.imageExportSettings.getOutputFileName(renderedPageImage.pageIndex, options.imageWriterSettings.getCurrentFormat()); + + QElapsedTimer imageWriterTimer; + imageWriterTimer.start(); + + QImageWriter imageWriter(fileName, options.imageWriterSettings.getCurrentFormat()); + imageWriter.setSubType(options.imageWriterSettings.getCurrentSubtype()); + imageWriter.setCompression(options.imageWriterSettings.getCompression()); + imageWriter.setQuality(options.imageWriterSettings.getQuality()); + imageWriter.setGamma(options.imageWriterSettings.getGamma()); + imageWriter.setOptimizedWrite(options.imageWriterSettings.hasOptimizedWrite()); + imageWriter.setProgressiveScanWrite(options.imageWriterSettings.hasProgressiveScanWrite()); + + if (!imageWriter.write(renderedPageImage.pageImage)) + { + m_pageInfo[renderedPageImage.pageIndex].errors.emplace_back(pdf::PDFRenderError(pdf::RenderErrorType::Error, PDFToolTranslationContext::tr("Cannot write page image to file '%1', because: %2.").arg(fileName).arg(imageWriter.errorString()))); + } + + m_pageInfo[renderedPageImage.pageIndex].pageWriteTime = imageWriterTimer.elapsed(); +} + QString PDFToolBenchmark::getStandardString(PDFToolAbstractApplication::StandardString standardString) const { switch (standardString) @@ -75,6 +116,26 @@ PDFToolAbstractApplication::Options PDFToolBenchmark::getOptionsFlags() const return ConsoleFormat | OpenDocument | PageSelector | ImageExportSettingsResolution | ColorManagementSystem | RenderFlags; } +void PDFToolBenchmark::finish(const PDFToolOptions& options) +{ + PDFOutputFormatter formatter(options.outputStyle, options.outputCodec); + formatter.beginDocument("benchmark", PDFToolTranslationContext::tr("Benchmark rendering of document %1").arg(options.document)); + formatter.endl(); + + writeStatistics(formatter); + writePageStatistics(formatter); + writeErrors(formatter); + + formatter.endDocument(); + PDFConsole::writeText(formatter.getString(), options.outputCodec); +} + +void PDFToolBenchmark::onPageRendered(const PDFToolOptions& options, pdf::PDFRenderedPageImage& renderedPageImage) +{ + Q_UNUSED(options); + writePageInfoStatistics(renderedPageImage); +} + int PDFToolRenderBase::execute(const PDFToolOptions& options) { pdf::PDFDocument document; @@ -85,7 +146,7 @@ int PDFToolRenderBase::execute(const PDFToolOptions& options) } QString parseError; - std::vector pages = options.getPageRange(document.getCatalog()->getPageCount(), parseError, true); + std::vector pageIndices = options.getPageRange(document.getCatalog()->getPageCount(), parseError, true); if (!parseError.isEmpty()) { @@ -103,11 +164,39 @@ int PDFToolRenderBase::execute(const PDFToolOptions& options) // We are ready to render the document pdf::PDFOptionalContentActivity optionalContentActivity(&document, pdf::OCUsage::Export, nullptr); - m_cms = m_proxy->getCMSManager()->getCurrentCMS(); - pdf::PDFRasterizerPool rasterizerPool(&document, m_proxy->getFontCache(), m_proxy->getCMSManager(), - &optionalContentActivity, m_proxy->getFeatures(), m_proxy->getMeshQualitySettings(), - pdf::PDFRasterizerPool::getDefaultRasterizerCount(), m_proxy->isUsingOpenGL(), m_proxy->getSurfaceFormat(), this); - connect(&rasterizerPool, &pdf::PDFRasterizerPool::renderError, this, &PDFRenderToImagesDialog::onRenderError); + pdf::PDFCMSManager cmsManager(nullptr); + cmsManager.setSettings(options.cmsSettings); + pdf::PDFMeshQualitySettings meshQualitySettings; + pdf::PDFFontCache fontCache(pdf::DEFAULT_FONT_CACHE_LIMIT, pdf::DEFAULT_REALIZED_FONT_CACHE_LIMIT); + pdf::PDFModifiedDocument md(&document, &optionalContentActivity); + fontCache.setDocument(md); + fontCache.setCacheShrinkEnabled(nullptr, false); + + QSurfaceFormat surfaceFormat; + if (options.renderUseHardwareRendering) + { + surfaceFormat = QSurfaceFormat::defaultFormat(); + surfaceFormat.setProfile(QSurfaceFormat::CoreProfile); + surfaceFormat.setSamples(options.renderMSAAsamples); + surfaceFormat.setColorSpace(QSurfaceFormat::sRGBColorSpace); + surfaceFormat.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); + } + + m_pageInfo.resize(document.getCatalog()->getPageCount()); + pdf::PDFRasterizerPool rasterizerPool(&document, &fontCache, &cmsManager, + &optionalContentActivity, options.renderFeatures, meshQualitySettings, + pdf::PDFRasterizerPool::getCorrectedRasterizerCount(options.renderRasterizerCount), + options.renderUseHardwareRendering, surfaceFormat, nullptr); + + auto onRenderError = [this](pdf::PDFInteger pageIndex, pdf::PDFRenderError error) + { + if (pageIndex != pdf::PDFCatalog::INVALID_PAGE_INDEX) + { + m_pageInfo[pageIndex].errors.emplace_back(qMove(error)); + } + }; + QObject holder; + QObject::connect(&rasterizerPool, &pdf::PDFRasterizerPool::renderError, &holder, onRenderError, Qt::DirectConnection); auto imageSizeGetter = [&options](const pdf::PDFPage* page) -> QSize { @@ -138,27 +227,198 @@ int PDFToolRenderBase::execute(const PDFToolOptions& options) return QSize(); }; - auto processImage = [this](const pdf::PDFInteger pageIndex, QImage&& image) - { - QString fileName = m_imageExportSettings.getOutputFileName(pageIndex, m_imageWriterSettings.getCurrentFormat()); + QElapsedTimer timer; + timer.start(); - QImageWriter imageWriter(fileName, m_imageWriterSettings.getCurrentFormat()); - imageWriter.setSubType(m_imageWriterSettings.getCurrentSubtype()); - imageWriter.setCompression(m_imageWriterSettings.getCompression()); - imageWriter.setQuality(m_imageWriterSettings.getQuality()); - imageWriter.setGamma(m_imageWriterSettings.getGamma()); - imageWriter.setOptimizedWrite(m_imageWriterSettings.hasOptimizedWrite()); - imageWriter.setProgressiveScanWrite(m_imageWriterSettings.hasProgressiveScanWrite()); + rasterizerPool.render(pageIndices, imageSizeGetter, std::bind(&PDFToolRenderBase::onPageRendered, this, options, std::placeholders::_1), nullptr); - if (!imageWriter.write(image)) - { - emit m_rasterizerPool->renderError(pdf::PDFRenderError(pdf::RenderErrorType::Error, tr("Can't write page image to file '%1', because: %2.").arg(fileName).arg(imageWriter.errorString()))); - } - }; + m_wallTime = timer.elapsed(); - rasterizerPool.render(pageIndices, imageSizeGetter, processImage, m_progress); + fontCache.setCacheShrinkEnabled(nullptr, true); + finish(options); return ExitSuccess; } +void PDFToolRenderBase::writePageInfoStatistics(const pdf::PDFRenderedPageImage& renderedPageImage) +{ + PageInfo& info = m_pageInfo[renderedPageImage.pageIndex]; + info.isRendered = true; + info.pageCompileTime = renderedPageImage.pageCompileTime; + info.pageWaitTime = renderedPageImage.pageWaitTime; + info.pageRenderTime = renderedPageImage.pageRenderTime; + info.pageTotalTime = renderedPageImage.pageTotalTime; + info.pageIndex = renderedPageImage.pageIndex; +} + +void PDFToolRenderBase::writeStatistics(PDFOutputFormatter& formatter) +{ + // Jakub Melka: Write overall statistics + qint64 pagesRendered = 0; + qint64 pageCompileTime = 0; + qint64 pageWaitTime = 0; + qint64 pageRenderTime = 0; + qint64 pageTotalTime = 0; + qint64 pageWriteTime = 0; + + for (const PageInfo& info : m_pageInfo) + { + if (!info.isRendered) + { + continue; + } + + ++pagesRendered; + pageCompileTime += info.pageCompileTime; + pageWaitTime += info.pageWaitTime; + pageRenderTime += info.pageRenderTime; + pageTotalTime += info.pageTotalTime + info.pageWriteTime; + pageWriteTime += info.pageWriteTime; + } + + if (pagesRendered > 0 && pageTotalTime > 0 && m_wallTime > 0) + { + QLocale locale; + + double renderingSpeedPerCore = double(pagesRendered) / (double(pageTotalTime) / 1000.0); + double renderingSpeedWallTime = double(pagesRendered) / (double(m_wallTime) / 1000.0); + + double compileRatio = 100.0 * double(pageCompileTime) / double(pageTotalTime); + double waitRatio = 100.0 * double(pageWaitTime) / double(pageTotalTime); + double renderRatio = 100.0 * double(pageRenderTime) / double(pageTotalTime); + double writeRatio = 100.0 * double(pageWriteTime) / double(pageTotalTime); + + formatter.beginTable("statistics", PDFToolTranslationContext::tr("Statistics")); + + formatter.beginTableHeaderRow("header"); + formatter.writeTableHeaderColumn("description", PDFToolTranslationContext::tr("Description"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("value", PDFToolTranslationContext::tr("Value"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("unit", PDFToolTranslationContext::tr("Unit"), Qt::AlignLeft); + formatter.endTableHeaderRow(); + + auto writeValue = [&formatter](QString name, QString description, QString value, QString unit) + { + formatter.beginTableRow(name); + formatter.writeTableColumn("description", description); + formatter.writeTableColumn("value", value, Qt::AlignRight); + formatter.writeTableColumn("unit", unit); + formatter.endTableRow(); + }; + + writeValue("pages-rendered", PDFToolTranslationContext::tr("Pages rendered"), locale.toString(pagesRendered), PDFToolTranslationContext::tr("-")); + writeValue("compile-time", PDFToolTranslationContext::tr("Total compile time"), locale.toString(pageCompileTime), PDFToolTranslationContext::tr("msec")); + writeValue("render-time", PDFToolTranslationContext::tr("Total render time"), locale.toString(pageRenderTime), PDFToolTranslationContext::tr("msec")); + writeValue("wait-time", PDFToolTranslationContext::tr("Total wait time"), locale.toString(pageWaitTime), PDFToolTranslationContext::tr("msec")); + writeValue("write-time", PDFToolTranslationContext::tr("Total write time"), locale.toString(pageWriteTime), PDFToolTranslationContext::tr("msec")); + writeValue("total-time", PDFToolTranslationContext::tr("Total time"), locale.toString(pageTotalTime), PDFToolTranslationContext::tr("msec")); + writeValue("wall-time", PDFToolTranslationContext::tr("Wall time"), locale.toString(m_wallTime), PDFToolTranslationContext::tr("msec")); + writeValue("pages-per-second-core", PDFToolTranslationContext::tr("Rendering speed (per core)"), locale.toString(renderingSpeedPerCore, 'f', 3), PDFToolTranslationContext::tr("pages / sec (one core)")); + writeValue("pages-per-second-wall", PDFToolTranslationContext::tr("Rendering speed (wall time)"), locale.toString(renderingSpeedWallTime, 'f', 3), PDFToolTranslationContext::tr("pages / sec")); + writeValue("compile-time-ratio", PDFToolTranslationContext::tr("Compile time ratio"), locale.toString(compileRatio, 'f', 2), PDFToolTranslationContext::tr("%")); + writeValue("render-time-ratio", PDFToolTranslationContext::tr("Render time ratio"), locale.toString(renderRatio, 'f', 2), PDFToolTranslationContext::tr("%")); + writeValue("wait-time-ratio", PDFToolTranslationContext::tr("Wait time ratio"), locale.toString(waitRatio, 'f', 2), PDFToolTranslationContext::tr("%")); + writeValue("write-time-ratio", PDFToolTranslationContext::tr("Write time ratio"), locale.toString(writeRatio, 'f', 2), PDFToolTranslationContext::tr("%")); + + formatter.endTable(); + formatter.endl(); + } +} + +void PDFToolRenderBase::writePageStatistics(PDFOutputFormatter& formatter) +{ + formatter.beginTable("page-statistics", PDFToolTranslationContext::tr("Page Statistics")); + + formatter.beginTableHeaderRow("header"); + formatter.writeTableHeaderColumn("page-no", PDFToolTranslationContext::tr("Page No."), Qt::AlignLeft); + formatter.writeTableHeaderColumn("compile-time", PDFToolTranslationContext::tr("Compile Time [msec]"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("render-time", PDFToolTranslationContext::tr("Render Time [msec]"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("wait-time", PDFToolTranslationContext::tr("Wait Time [msec]"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("write-time", PDFToolTranslationContext::tr("Write Time [msec]"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("total-time", PDFToolTranslationContext::tr("Total Time [msec]"), Qt::AlignLeft); + formatter.endTableHeaderRow(); + + QLocale locale; + + for (const PageInfo& info : m_pageInfo) + { + if (!info.isRendered) + { + continue; + } + + formatter.beginTableRow("page", info.pageIndex + 1); + formatter.writeTableColumn("page-no", locale.toString(info.pageIndex + 1), Qt::AlignRight); + formatter.writeTableColumn("compile-time", locale.toString(info.pageCompileTime), Qt::AlignRight); + formatter.writeTableColumn("render-time", locale.toString(info.pageRenderTime), Qt::AlignRight); + formatter.writeTableColumn("wait-time", locale.toString(info.pageWaitTime), Qt::AlignRight); + formatter.writeTableColumn("write-time", locale.toString(info.pageWaitTime), Qt::AlignRight); + formatter.writeTableColumn("total-time", locale.toString(info.pageTotalTime), Qt::AlignRight); + formatter.endTableRow(); + } + + formatter.endTable(); + formatter.endl(); +} + +void PDFToolRenderBase::writeErrors(PDFOutputFormatter& formatter) +{ + formatter.beginTable("rendering-errors", PDFToolTranslationContext::tr("Rendering Errors")); + + formatter.beginTableHeaderRow("header"); + formatter.writeTableHeaderColumn("page-no", PDFToolTranslationContext::tr("Page No."), Qt::AlignLeft); + formatter.writeTableHeaderColumn("type", PDFToolTranslationContext::tr("Type"), Qt::AlignLeft); + formatter.writeTableHeaderColumn("message", PDFToolTranslationContext::tr("Message"), Qt::AlignLeft); + formatter.endTableHeaderRow(); + + QLocale locale; + + for (const PageInfo& info : m_pageInfo) + { + if (!info.isRendered) + { + continue; + } + + for (const pdf::PDFRenderError& error : info.errors) + { + QString type; + switch (error.type) + { + case pdf::RenderErrorType::Error: + type = PDFToolTranslationContext::tr("Error"); + break; + + case pdf::RenderErrorType::Warning: + type = PDFToolTranslationContext::tr("Warning"); + break; + + case pdf::RenderErrorType::NotImplemented: + type = PDFToolTranslationContext::tr("Not implemented"); + break; + + case pdf::RenderErrorType::NotSupported: + type = PDFToolTranslationContext::tr("Not supported"); + break; + + case pdf::RenderErrorType::Information: + type = PDFToolTranslationContext::tr("Information"); + break; + + default: + Q_ASSERT(false); + break; + } + + formatter.beginTableRow("page", info.pageIndex + 1); + formatter.writeTableColumn("page-no", locale.toString(info.pageIndex + 1), Qt::AlignRight); + formatter.writeTableColumn("type", type, Qt::AlignLeft); + formatter.writeTableColumn("message", error.message, Qt::AlignLeft); + formatter.endTableRow(); + } + } + + formatter.endTable(); + formatter.endl(); +} + } // namespace pdftool diff --git a/PdfTool/pdftoolrender.h b/PdfTool/pdftoolrender.h index da0886d..431a97d 100644 --- a/PdfTool/pdftoolrender.h +++ b/PdfTool/pdftoolrender.h @@ -19,6 +19,7 @@ #define PDFTOOLRENDER_H #include "pdftoolabstractapplication.h" +#include "pdfexception.h" namespace pdftool { @@ -27,6 +28,31 @@ class PDFToolRenderBase : public PDFToolAbstractApplication { public: virtual int execute(const PDFToolOptions& options) override; + +protected: + virtual void finish(const PDFToolOptions& options) = 0; + virtual void onPageRendered(const PDFToolOptions& options, pdf::PDFRenderedPageImage& renderedPageImage) = 0; + + void writePageInfoStatistics(const pdf::PDFRenderedPageImage& renderedPageImage); + + void writeStatistics(PDFOutputFormatter& formatter); + void writePageStatistics(PDFOutputFormatter& formatter); + void writeErrors(PDFOutputFormatter& formatter); + + struct PageInfo + { + bool isRendered = false; + pdf::PDFInteger pageIndex = 0; + qint64 pageCompileTime = 0; + qint64 pageWaitTime = 0; + qint64 pageRenderTime = 0; + qint64 pageTotalTime = 0; + qint64 pageWriteTime = 0; + std::vector errors; + }; + + std::vector m_pageInfo; + qint64 m_wallTime = 0; }; class PDFToolRender : public PDFToolRenderBase @@ -34,6 +60,10 @@ class PDFToolRender : public PDFToolRenderBase public: virtual QString getStandardString(StandardString standardString) const override; virtual Options getOptionsFlags() const override; + +protected: + virtual void finish(const PDFToolOptions& options) override; + virtual void onPageRendered(const PDFToolOptions& options, pdf::PDFRenderedPageImage& renderedPageImage) override; }; class PDFToolBenchmark : public PDFToolRenderBase @@ -41,6 +71,10 @@ class PDFToolBenchmark : public PDFToolRenderBase public: virtual QString getStandardString(StandardString standardString) const override; virtual Options getOptionsFlags() const override; + +protected: + virtual void finish(const PDFToolOptions& options) override; + virtual void onPageRendered(const PDFToolOptions& options, pdf::PDFRenderedPageImage& renderedPageImage) override; }; } // namespace pdftool