Render tool basics

This commit is contained in:
Jakub Melka 2020-10-26 19:28:56 +01:00
parent 584a992da6
commit 428e5dc2ad
6 changed files with 251 additions and 40 deletions

View File

@ -681,64 +681,73 @@ void PDFPageImageExportSettings::setPixelResolution(int pixelResolution)
m_pixelResolution = pixelResolution; m_pixelResolution = pixelResolution;
} }
bool PDFPageImageExportSettings::validate(QString* errorMessagePtr) bool PDFPageImageExportSettings::validate(QString* errorMessagePtr, bool validatePageSelection, bool validateFileSettings, bool validateResolution) const
{ {
QString dummy; QString dummy;
QString& errorMessage = errorMessagePtr ? *errorMessagePtr : dummy; QString& errorMessage = errorMessagePtr ? *errorMessagePtr : dummy;
if (m_directory.isEmpty()) if (validateFileSettings)
{ {
errorMessage = PDFTranslationContext::tr("Target directory is empty."); if (m_directory.isEmpty())
return false; {
} errorMessage = PDFTranslationContext::tr("Target directory is empty.");
return false;
}
// Check, if target directory exists // Check, if target directory exists
QDir directory(m_directory); QDir directory(m_directory);
if (!directory.exists()) if (!directory.exists())
{ {
errorMessage = PDFTranslationContext::tr("Target directory '%1' doesn't exist.").arg(m_directory); errorMessage = PDFTranslationContext::tr("Target directory '%1' doesn't exist.").arg(m_directory);
return false; return false;
} }
if (m_fileTemplate.isEmpty()) if (m_fileTemplate.isEmpty())
{ {
errorMessage = PDFTranslationContext::tr("File template is empty."); errorMessage = PDFTranslationContext::tr("File template is empty.");
return false; return false;
} }
if (!m_fileTemplate.contains("%")) if (!m_fileTemplate.contains("%"))
{ {
errorMessage = PDFTranslationContext::tr("File template must contain character '%' for page number."); errorMessage = PDFTranslationContext::tr("File template must contain character '%' for page number.");
return false; return false;
}
} }
// Check page selection // Check page selection
if (m_pageSelectionMode == PageSelectionMode::Selection) if (validatePageSelection)
{ {
std::vector<PDFInteger> pages = getPages(); if (m_pageSelectionMode == PageSelectionMode::Selection)
if (pages.empty())
{ {
errorMessage = PDFTranslationContext::tr("Page list is invalid. It should have form such as '1-12,17,24,27-29'."); std::vector<PDFInteger> pages = getPages();
return false; 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())) 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()); errorMessage = PDFTranslationContext::tr("Page list contains page, which is not in the document (%1).").arg(pages.back());
return false; 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()); if (m_resolutionMode == ResolutionMode::DPI && (m_dpiResolution < getMinDPIResolution() || m_dpiResolution > getMaxDPIResolution()))
return false; {
} 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())) 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()); errorMessage = PDFTranslationContext::tr("Pixel resolution should be in range %1 to %2.").arg(getMinPixelResolution()).arg(getMaxPixelResolution());
return false; return false;
}
} }
return true; return true;

View File

@ -338,7 +338,7 @@ public:
void setPixelResolution(int pixelResolution); void setPixelResolution(int pixelResolution);
/// Validates the settings, if they can be used for image generation /// 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 /// Returns list of selected pages
std::vector<PDFInteger> getPages() const; std::vector<PDFInteger> getPages() const;

View File

@ -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-cmyk", "CMYK color profile for CMYK device.", "profile"));
parser->addOption(QCommandLineOption("cms-profile-dir", "External directory containing color profiles.", "directory")); 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 PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser) const
@ -729,6 +738,26 @@ PDFToolOptions PDFToolAbstractApplication::getOptions(QCommandLineParser* parser
setProfile("cms-profile-dir", options.cmsSettings.profileDirectory); 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; return options;
} }
@ -865,4 +894,17 @@ std::vector<pdf::PDFInteger> PDFToolOptions::getPageRange(pdf::PDFInteger pageCo
return pageIndices; return pageIndices;
} }
std::vector<PDFToolOptions::RenderFeatureInfo> 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 } // pdftool

View File

@ -122,11 +122,24 @@ struct PDFToolOptions
// For option 'ColorManagementSystem' // For option 'ColorManagementSystem'
pdf::PDFCMSSettings cmsSettings; 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. /// Returns page range. If page range is invalid, then \p errorMessage is empty.
/// \param pageCount Page count /// \param pageCount Page count
/// \param[out] errorMessage Error message /// \param[out] errorMessage Error message
/// \param zeroBased Convert to zero based page range? /// \param zeroBased Convert to zero based page range?
std::vector<pdf::PDFInteger> getPageRange(pdf::PDFInteger pageCount, QString& errorMessage, bool zeroBased) const; std::vector<pdf::PDFInteger> 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<RenderFeatureInfo> getRenderFeatures();
}; };
/// Base class for all applications /// Base class for all applications

View File

@ -20,5 +20,145 @@
namespace pdftool 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<pdf::PDFInteger> 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 } // namespace pdftool

View File

@ -36,6 +36,13 @@ public:
virtual Options getOptionsFlags() const override; virtual Options getOptionsFlags() const override;
}; };
class PDFToolBenchmark : public PDFToolRenderBase
{
public:
virtual QString getStandardString(StandardString standardString) const override;
virtual Options getOptionsFlags() const override;
};
} // namespace pdftool } // namespace pdftool
#endif // PDFTOOLRENDER_H #endif // PDFTOOLRENDER_H