diff --git a/PdfForQtLib/sources/pdfrenderer.cpp b/PdfForQtLib/sources/pdfrenderer.cpp index 102aa23..63688dc 100644 --- a/PdfForQtLib/sources/pdfrenderer.cpp +++ b/PdfForQtLib/sources/pdfrenderer.cpp @@ -681,64 +681,73 @@ void PDFPageImageExportSettings::setPixelResolution(int pixelResolution) m_pixelResolution = pixelResolution; } -bool PDFPageImageExportSettings::validate(QString* errorMessagePtr) +bool PDFPageImageExportSettings::validate(QString* errorMessagePtr, bool validatePageSelection, bool validateFileSettings, bool validateResolution) const { QString dummy; QString& errorMessage = errorMessagePtr ? *errorMessagePtr : dummy; - if (m_directory.isEmpty()) + if (validateFileSettings) { - errorMessage = PDFTranslationContext::tr("Target directory is empty."); - return false; - } + if (m_directory.isEmpty()) + { + errorMessage = PDFTranslationContext::tr("Target directory is empty."); + return false; + } - // Check, if target directory exists - QDir directory(m_directory); - if (!directory.exists()) - { - errorMessage = PDFTranslationContext::tr("Target directory '%1' doesn't exist.").arg(m_directory); - return false; - } + // Check, if target directory exists + QDir directory(m_directory); + if (!directory.exists()) + { + errorMessage = PDFTranslationContext::tr("Target directory '%1' doesn't exist.").arg(m_directory); + return false; + } - if (m_fileTemplate.isEmpty()) - { - errorMessage = PDFTranslationContext::tr("File template is empty."); - return false; - } + if (m_fileTemplate.isEmpty()) + { + errorMessage = PDFTranslationContext::tr("File template is empty."); + return false; + } - if (!m_fileTemplate.contains("%")) - { - errorMessage = PDFTranslationContext::tr("File template must contain character '%' for page number."); - return false; + if (!m_fileTemplate.contains("%")) + { + errorMessage = PDFTranslationContext::tr("File template must contain character '%' for page number."); + return false; + } } // Check page selection - if (m_pageSelectionMode == PageSelectionMode::Selection) + if (validatePageSelection) { - std::vector pages = getPages(); - if (pages.empty()) + if (m_pageSelectionMode == PageSelectionMode::Selection) { - errorMessage = PDFTranslationContext::tr("Page list is invalid. It should have form such as '1-12,17,24,27-29'."); - return false; - } + std::vector pages = getPages(); + if (pages.empty()) + { + errorMessage = PDFTranslationContext::tr("Page list is invalid. It should have form such as '1-12,17,24,27-29'."); + return false; + } - if (pages.back() >= PDFInteger(m_document->getCatalog()->getPageCount())) - { - errorMessage = PDFTranslationContext::tr("Page list contains page, which is not in the document (%1).").arg(pages.back()); - return false; + if (pages.back() >= PDFInteger(m_document->getCatalog()->getPageCount())) + { + errorMessage = PDFTranslationContext::tr("Page list contains page, which is not in the document (%1).").arg(pages.back()); + return false; + } } } - if (m_resolutionMode == ResolutionMode::DPI && (m_dpiResolution < getMinDPIResolution() || m_dpiResolution > getMaxDPIResolution())) + if (validateResolution) { - errorMessage = PDFTranslationContext::tr("DPI resolution should be in range %1 to %2.").arg(getMinDPIResolution()).arg(getMaxDPIResolution()); - return false; - } + if (m_resolutionMode == ResolutionMode::DPI && (m_dpiResolution < getMinDPIResolution() || m_dpiResolution > getMaxDPIResolution())) + { + errorMessage = PDFTranslationContext::tr("DPI resolution should be in range %1 to %2.").arg(getMinDPIResolution()).arg(getMaxDPIResolution()); + return false; + } - if (m_resolutionMode == ResolutionMode::Pixels && (m_pixelResolution < getMinPixelResolution() || m_pixelResolution > getMaxPixelResolution())) - { - errorMessage = PDFTranslationContext::tr("Pixel resolution should be in range %1 to %2.").arg(getMinPixelResolution()).arg(getMaxPixelResolution()); - return false; + if (m_resolutionMode == ResolutionMode::Pixels && (m_pixelResolution < getMinPixelResolution() || m_pixelResolution > getMaxPixelResolution())) + { + errorMessage = PDFTranslationContext::tr("Pixel resolution should be in range %1 to %2.").arg(getMinPixelResolution()).arg(getMaxPixelResolution()); + return false; + } } return true; diff --git a/PdfForQtLib/sources/pdfrenderer.h b/PdfForQtLib/sources/pdfrenderer.h index 7a0b58d..03fe8ee 100644 --- a/PdfForQtLib/sources/pdfrenderer.h +++ b/PdfForQtLib/sources/pdfrenderer.h @@ -338,7 +338,7 @@ public: void setPixelResolution(int pixelResolution); /// Validates the settings, if they can be used for image generation - bool validate(QString* errorMessagePtr); + bool validate(QString* errorMessagePtr, bool validatePageSelection = true, bool validateFileSettings = true, bool validateResolution = true) const; /// Returns list of selected pages std::vector getPages() const; diff --git a/PdfTool/pdftoolabstractapplication.cpp b/PdfTool/pdftoolabstractapplication.cpp index 0eb46e5..5910753 100644 --- a/PdfTool/pdftoolabstractapplication.cpp +++ b/PdfTool/pdftoolabstractapplication.cpp @@ -278,6 +278,15 @@ void PDFToolAbstractApplication::initializeCommandLineParser(QCommandLineParser* parser->addOption(QCommandLineOption("cms-profile-cmyk", "CMYK color profile for CMYK device.", "profile")); parser->addOption(QCommandLineOption("cms-profile-dir", "External directory containing color profiles.", "directory")); } + + if (optionFlags.testFlag(RenderFlags)) + { + const pdf::PDFRenderer::Features defaultFeatures = pdf::PDFRenderer::getDefaultFeatures(); + for (const PDFToolOptions::RenderFeatureInfo& info : PDFToolOptions::getRenderFeatures()) + { + parser->addOption(QCommandLineOption(info.option, info.description, "bool", defaultFeatures.testFlag(info.feature) ? "1" : "0")); + } + } } PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser) const @@ -729,6 +738,26 @@ PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser setProfile("cms-profile-dir", options.cmsSettings.profileDirectory); } + if (optionFlags.testFlag(RenderFlags)) + { + for (const PDFToolOptions::RenderFeatureInfo& info : PDFToolOptions::getRenderFeatures()) + { + QString textValue = parser->value(info.option); + + bool ok = false; + bool value = textValue.toInt(&ok); + + if (ok) + { + options.renderFeatures.setFlag(info.feature, value); + } + else + { + PDFConsole::writeError(PDFToolTranslationContext::tr("Uknown bool value '%1'. Default value is used.").arg(textValue), options.outputCodec); + } + } + } + return options; } @@ -865,4 +894,17 @@ std::vector PDFToolOptions::getPageRange(pdf::PDFInteger pageCo return pageIndices; } +std::vector PDFToolOptions::getRenderFeatures() +{ + return { + RenderFeatureInfo{ "render-antialiasing", "Antialiasing for lines, shapes, etc.", pdf::PDFRenderer::Antialiasing }, + RenderFeatureInfo{ "render-text-antialiasing", "Antialiasing for text outlines.", pdf::PDFRenderer::TextAntialiasing }, + RenderFeatureInfo{ "render-smooth-img", "Smooth image transformation (slower, but better quality images).", pdf::PDFRenderer::SmoothImages }, + RenderFeatureInfo{ "render-ignore-opt-content", "Ignore optional content settings (draw everything).", pdf::PDFRenderer::IgnoreOptionalContent }, + RenderFeatureInfo{ "render-clip-to-crop-box", "Clip page graphics to crop box.", pdf::PDFRenderer::ClipToCropBox }, + RenderFeatureInfo{ "render-invert-colors", "Invert all colors.", pdf::PDFRenderer::InvertColors }, + RenderFeatureInfo{ "render-display-annot", "Display annotations.", pdf::PDFRenderer::DisplayAnnotations } + }; +} + } // pdftool diff --git a/PdfTool/pdftoolabstractapplication.h b/PdfTool/pdftoolabstractapplication.h index b46fe01..b5ab464 100644 --- a/PdfTool/pdftoolabstractapplication.h +++ b/PdfTool/pdftoolabstractapplication.h @@ -122,11 +122,24 @@ struct PDFToolOptions // For option 'ColorManagementSystem' pdf::PDFCMSSettings cmsSettings; + // For option 'RenderFlags' + pdf::PDFRenderer::Features renderFeatures = pdf::PDFRenderer::getDefaultFeatures(); + /// Returns page range. If page range is invalid, then \p errorMessage is empty. /// \param pageCount Page count /// \param[out] errorMessage Error message /// \param zeroBased Convert to zero based page range? std::vector getPageRange(pdf::PDFInteger pageCount, QString& errorMessage, bool zeroBased) const; + + struct RenderFeatureInfo + { + QString option; + QString description; + pdf::PDFRenderer::Feature feature; + }; + + /// Returns a list of available renderer features + static std::vector getRenderFeatures(); }; /// Base class for all applications diff --git a/PdfTool/pdftoolrender.cpp b/PdfTool/pdftoolrender.cpp index cb90d72..ab3a9c0 100644 --- a/PdfTool/pdftoolrender.cpp +++ b/PdfTool/pdftoolrender.cpp @@ -20,5 +20,145 @@ namespace pdftool { +static PDFToolRender s_toolRenderApplication; +static PDFToolBenchmark s_toolBenchmarkApplication; + +QString PDFToolRender::getStandardString(PDFToolAbstractApplication::StandardString standardString) const +{ + switch (standardString) + { + case Command: + return "render"; + + case Name: + return PDFToolTranslationContext::tr("Render document"); + + case Description: + return PDFToolTranslationContext::tr("Render selected pages of document into image files."); + + default: + Q_ASSERT(false); + break; + } + + return QString(); +} + +PDFToolAbstractApplication::Options PDFToolRender::getOptionsFlags() const +{ + return ConsoleFormat | OpenDocument | PageSelector | ImageWriterSettings | ImageExportSettingsFiles | ImageExportSettingsResolution | ColorManagementSystem | RenderFlags; +} + +QString PDFToolBenchmark::getStandardString(PDFToolAbstractApplication::StandardString standardString) const +{ + switch (standardString) + { + case Command: + return "benchmark"; + + case Name: + return PDFToolTranslationContext::tr("Benchmark rendering"); + + case Description: + return PDFToolTranslationContext::tr("Benchmark page rendering (measure time, detect errors)."); + + default: + Q_ASSERT(false); + break; + } + + return QString(); +} + +PDFToolAbstractApplication::Options PDFToolBenchmark::getOptionsFlags() const +{ + return ConsoleFormat | OpenDocument | PageSelector | ImageExportSettingsResolution | ColorManagementSystem | RenderFlags; +} + +int PDFToolRenderBase::execute(const PDFToolOptions& options) +{ + pdf::PDFDocument document; + QByteArray sourceData; + if (!readDocument(options, document, &sourceData)) + { + return ErrorDocumentReading; + } + + QString parseError; + std::vector pages = options.getPageRange(document.getCatalog()->getPageCount(), parseError, true); + + if (!parseError.isEmpty()) + { + PDFConsole::writeError(parseError, options.outputCodec); + return ErrorInvalidArguments; + } + + QString errorMessage; + Options optionFlags = getOptionsFlags(); + if (!options.imageExportSettings.validate(&errorMessage, false, optionFlags.testFlag(ImageExportSettingsFiles), optionFlags.testFlag(ImageExportSettingsResolution))) + { + PDFConsole::writeError(errorMessage, options.outputCodec); + return ErrorInvalidArguments; + } + + // 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); + + auto imageSizeGetter = [&options](const pdf::PDFPage* page) -> QSize + { + Q_ASSERT(page); + + switch (options.imageExportSettings.getResolutionMode()) + { + case pdf::PDFPageImageExportSettings::ResolutionMode::DPI: + { + QSizeF size = page->getRotatedMediaBox().size() * pdf::PDF_POINT_TO_INCH * options.imageExportSettings.getDpiResolution(); + return size.toSize(); + } + + case pdf::PDFPageImageExportSettings::ResolutionMode::Pixels: + { + int pixelResolution = options.imageExportSettings.getPixelResolution(); + QSizeF size = page->getRotatedMediaBox().size().scaled(pixelResolution, pixelResolution, Qt::KeepAspectRatio); + return size.toSize(); + } + + default: + { + Q_ASSERT(false); + break; + } + } + + return QSize(); + }; + + auto processImage = [this](const pdf::PDFInteger pageIndex, QImage&& image) + { + QString fileName = m_imageExportSettings.getOutputFileName(pageIndex, m_imageWriterSettings.getCurrentFormat()); + + 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()); + + 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()))); + } + }; + + rasterizerPool.render(pageIndices, imageSizeGetter, processImage, m_progress); + + return ExitSuccess; +} } // namespace pdftool diff --git a/PdfTool/pdftoolrender.h b/PdfTool/pdftoolrender.h index fd88a7a..da0886d 100644 --- a/PdfTool/pdftoolrender.h +++ b/PdfTool/pdftoolrender.h @@ -36,6 +36,13 @@ public: virtual Options getOptionsFlags() const override; }; +class PDFToolBenchmark : public PDFToolRenderBase +{ +public: + virtual QString getStandardString(StandardString standardString) const override; + virtual Options getOptionsFlags() const override; +}; + } // namespace pdftool #endif // PDFTOOLRENDER_H