From 6f6ddaab04e87e65d123541d5b5a2a27932ae176 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 18 Nov 2023 18:52:18 +0100 Subject: [PATCH] Issue #108: Add accessibility options in pdf reader --- Pdf4QtLib/CMakeLists.txt | 2 + Pdf4QtLib/sources/pdfadvancedtools.cpp | 3 +- Pdf4QtLib/sources/pdfannotation.cpp | 4 +- Pdf4QtLib/sources/pdfannotation.h | 5 +- Pdf4QtLib/sources/pdfcms.cpp | 38 +++- Pdf4QtLib/sources/pdfcms.h | 20 ++ Pdf4QtLib/sources/pdfcolorconvertor.cpp | 210 ++++++++++++++++++ Pdf4QtLib/sources/pdfcolorconvertor.h | 139 ++++++++++++ Pdf4QtLib/sources/pdfdrawspacecontroller.cpp | 7 +- Pdf4QtLib/sources/pdfform.cpp | 24 +- .../sources/pdfpagecontenteditortools.cpp | 3 +- Pdf4QtLib/sources/pdfpainter.cpp | 19 +- Pdf4QtLib/sources/pdfpainter.h | 5 +- Pdf4QtLib/sources/pdfpattern.cpp | 8 +- Pdf4QtLib/sources/pdfpattern.h | 5 +- Pdf4QtLib/sources/pdfrenderer.cpp | 37 ++- Pdf4QtLib/sources/pdfrenderer.h | 38 ++-- Pdf4QtLib/sources/pdftexteditpseudowidget.cpp | 27 +-- Pdf4QtViewer/pdfprogramcontroller.cpp | 21 +- Pdf4QtViewer/pdfprogramcontroller.h | 4 + Pdf4QtViewer/pdfviewermainwindow.cpp | 6 +- Pdf4QtViewer/pdfviewermainwindow.ui | 58 ++++- PdfTool/pdftoolabstractapplication.cpp | 6 +- 23 files changed, 600 insertions(+), 89 deletions(-) create mode 100644 Pdf4QtLib/sources/pdfcolorconvertor.cpp create mode 100644 Pdf4QtLib/sources/pdfcolorconvertor.h diff --git a/Pdf4QtLib/CMakeLists.txt b/Pdf4QtLib/CMakeLists.txt index 476f134..310d131 100644 --- a/Pdf4QtLib/CMakeLists.txt +++ b/Pdf4QtLib/CMakeLists.txt @@ -102,6 +102,8 @@ add_library(Pdf4QtLib SHARED sources/pdfdocumentsanitizer.cpp sources/pdfimageconversion.h sources/pdfimageconversion.cpp + sources/pdfcolorconvertor.h + sources/pdfcolorconvertor.cpp cmaps.qrc ) diff --git a/Pdf4QtLib/sources/pdfadvancedtools.cpp b/Pdf4QtLib/sources/pdfadvancedtools.cpp index aa3c8fb..3c4e8fd 100644 --- a/Pdf4QtLib/sources/pdfadvancedtools.cpp +++ b/Pdf4QtLib/sources/pdfadvancedtools.cpp @@ -851,7 +851,8 @@ void PDFCreateStampTool::drawPage(QPainter* painter, parameters.painter = painter; parameters.annotation = const_cast(&m_stampAnnotation); parameters.key.first = PDFAppeareanceStreams::Appearance::Normal; - parameters.invertColors = getProxy()->getFeatures().testFlag(PDFRenderer::InvertColors); + parameters.colorConvertor = getProxy()->getCMSManager()->getColorConvertor(); + PDFRenderer::applyFeaturesToColorConvertor(getProxy()->getFeatures(), parameters.colorConvertor); m_stampAnnotation.draw(parameters); } diff --git a/Pdf4QtLib/sources/pdfannotation.cpp b/Pdf4QtLib/sources/pdfannotation.cpp index b41f4cf..e93e2a2 100644 --- a/Pdf4QtLib/sources/pdfannotation.cpp +++ b/Pdf4QtLib/sources/pdfannotation.cpp @@ -1474,7 +1474,9 @@ void PDFAnnotationManager::drawAnnotationDirect(const PageAnnotation& annotation parameters.annotation = annotation.annotation.data(); parameters.formManager = m_formManager; parameters.key = std::make_pair(annotation.appearance, annotation.annotation->getAppearanceState()); - parameters.invertColors = m_features.testFlag(PDFRenderer::InvertColors); + parameters.colorConvertor = cms->getColorConvertor(); + PDFRenderer::applyFeaturesToColorConvertor(m_features, parameters.colorConvertor); + annotation.annotation->draw(parameters); if (parameters.boundingRectangle.isValid()) diff --git a/Pdf4QtLib/sources/pdfannotation.h b/Pdf4QtLib/sources/pdfannotation.h index cabc717..6ebc3f7 100644 --- a/Pdf4QtLib/sources/pdfannotation.h +++ b/Pdf4QtLib/sources/pdfannotation.h @@ -29,6 +29,7 @@ #include "pdfrenderer.h" #include "pdfblendfunction.h" #include "pdfdocument.h" +#include "pdfcolorconvertor.h" #include #include @@ -474,8 +475,8 @@ struct AnnotationDrawParameters /// Appeareance mode (normal/rollover/down, and appearance state) PDFAppeareanceStreams::Key key; - /// Invert colors? - bool invertColors = false; + /// Color convertor + PDFColorConvertor colorConvertor; }; /// Base class for all annotation types. Represents PDF annotation object. diff --git a/Pdf4QtLib/sources/pdfcms.cpp b/Pdf4QtLib/sources/pdfcms.cpp index efd63d4..ace9b93 100644 --- a/Pdf4QtLib/sources/pdfcms.cpp +++ b/Pdf4QtLib/sources/pdfcms.cpp @@ -64,7 +64,9 @@ namespace pdf class PDFLittleCMS : public PDFCMS { public: - explicit PDFLittleCMS(const PDFCMSManager* manager, const PDFCMSSettings& settings); + explicit PDFLittleCMS(const PDFCMSManager* manager, + const PDFCMSSettings& settings, + const PDFColorConvertor& colorConvertor); virtual ~PDFLittleCMS() override; virtual bool isCompatible(const PDFCMSSettings& settings) const override; @@ -80,6 +82,7 @@ public: virtual bool fillRGBBufferFromXYZ(const PDFColor3& whitePoint, const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override; virtual bool fillRGBBufferFromICC(const std::vector& colors, RenderingIntent renderingIntent, unsigned char* outputBuffer, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const override; virtual bool transformColorSpace(const ColorSpaceTransformParams& params) const override; + virtual PDFColorConvertor getColorConvertor() const override; private: void init(); @@ -162,6 +165,7 @@ private: PDFCMSSettings m_settings; QColor m_paperColor; std::array m_profiles; + PDFColorConvertor m_colorConvertor; mutable QReadWriteLock m_transformationCacheLock; mutable std::unordered_map m_transformationCache; @@ -445,11 +449,19 @@ bool PDFLittleCMS::transformColorSpace(const PDFCMS::ColorSpaceTransformParams& return false; } -PDFLittleCMS::PDFLittleCMS(const PDFCMSManager* manager, const PDFCMSSettings& settings) : +PDFColorConvertor PDFLittleCMS::getColorConvertor() const +{ + return m_colorConvertor; +} + +PDFLittleCMS::PDFLittleCMS(const PDFCMSManager* manager, + const PDFCMSSettings& settings, + const PDFColorConvertor& colorConvertor) : m_manager(manager), m_settings(settings), m_paperColor(Qt::white), - m_profiles() + m_profiles(), + m_colorConvertor(colorConvertor) { static const int installed = installCmsPlugins(); Q_UNUSED(installed); @@ -1286,6 +1298,12 @@ QString getInfoFromProfile(cmsHPROFILE profile, cmsInfoType infoType) return QString(); } +PDFCMSGeneric::PDFCMSGeneric(const PDFColorConvertor& colorConvertor) : + m_colorConvertor(colorConvertor) +{ + +} + bool PDFCMSGeneric::isCompatible(const PDFCMSSettings& settings) const { return settings.system == PDFCMSSettings::System::Generic; @@ -1397,6 +1415,11 @@ bool PDFCMSGeneric::transformColorSpace(const PDFCMS::ColorSpaceTransformParams& return false; } +PDFColorConvertor PDFCMSGeneric::getColorConvertor() const +{ + return m_colorConvertor; +} + PDFCMSManager::PDFCMSManager(QObject* parent) : BaseClass(parent), m_document(nullptr), @@ -1432,6 +1455,11 @@ void PDFCMSManager::setSettings(const PDFCMSSettings& settings) } } +PDFColorConvertor PDFCMSManager::getColorConvertor() const +{ + return PDFColorConvertor(); +} + const PDFColorProfileIdentifiers& PDFCMSManager::getOutputProfiles() const { QMutexLocker lock(&m_mutex); @@ -1606,10 +1634,10 @@ PDFCMSPointer PDFCMSManager::getCurrentCMSImpl() const switch (m_settings.system) { case PDFCMSSettings::System::Generic: - return PDFCMSPointer(new PDFCMSGeneric()); + return PDFCMSPointer(new PDFCMSGeneric(getColorConvertor())); case PDFCMSSettings::System::LittleCMS2: - return PDFCMSPointer(new PDFLittleCMS(this, m_settings)); + return PDFCMSPointer(new PDFLittleCMS(this, m_settings, getColorConvertor())); default: Q_ASSERT(false); diff --git a/Pdf4QtLib/sources/pdfcms.h b/Pdf4QtLib/sources/pdfcms.h index 380a1a0..a056c63 100644 --- a/Pdf4QtLib/sources/pdfcms.h +++ b/Pdf4QtLib/sources/pdfcms.h @@ -22,6 +22,7 @@ #include "pdfcolorspaces.h" #include "pdfexception.h" #include "pdfutils.h" +#include "pdfcolorconvertor.h" #include #include @@ -211,6 +212,13 @@ public: const QByteArray& iccData, PDFRenderErrorReporter* reporter) const = 0; + /// Returns color convertor of post-processing colors + /// produced by color management system. Color convertor + /// does not have set color conversion mode, it must be + /// set manually. + /// \return Color convertor according to current settings. + virtual PDFColorConvertor getColorConvertor() const = 0; + enum ColorSpaceType { Invalid, @@ -254,6 +262,7 @@ class PDF4QTLIBSHARED_EXPORT PDFCMSGeneric : public PDFCMS { public: explicit inline PDFCMSGeneric() = default; + explicit inline PDFCMSGeneric(const PDFColorConvertor& colorConvertor); virtual bool isCompatible(const PDFCMSSettings& settings) const override; virtual QColor getPaperColor() const override; @@ -268,6 +277,10 @@ public: virtual bool fillRGBBufferFromXYZ(const PDFColor3& whitePoint, const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override; virtual bool fillRGBBufferFromICC(const std::vector& colors, RenderingIntent renderingIntent, unsigned char* outputBuffer, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const override; virtual bool transformColorSpace(const ColorSpaceTransformParams& params) const override; + virtual PDFColorConvertor getColorConvertor() const override; + +private: + PDFColorConvertor m_colorConvertor; }; struct PDFColorProfileIdentifier @@ -359,6 +372,13 @@ public: const PDFCMSSettings& getSettings() const { return m_settings; } void setSettings(const PDFCMSSettings& settings); + /// Returns color convertor of post-processing colors + /// produced by color management system. Color convertor + /// does not have set color conversion mode, it must be + /// set manually. + /// \return Color convertor according to current settings. + PDFColorConvertor getColorConvertor() const; + const PDFColorProfileIdentifiers& getOutputProfiles() const; const PDFColorProfileIdentifiers& getGrayProfiles() const; const PDFColorProfileIdentifiers& getRGBProfiles() const; diff --git a/Pdf4QtLib/sources/pdfcolorconvertor.cpp b/Pdf4QtLib/sources/pdfcolorconvertor.cpp new file mode 100644 index 0000000..8b054ff --- /dev/null +++ b/Pdf4QtLib/sources/pdfcolorconvertor.cpp @@ -0,0 +1,210 @@ +// Copyright (C) 2023 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 "pdfcolorconvertor.h" +#include "pdfimageconversion.h" +#include "pdfutils.h" + +namespace pdf +{ + +PDFColorConvertor::PDFColorConvertor() +{ + calculateSigmoidParams(); +} + +bool PDFColorConvertor::isActive() const +{ + return m_mode != Mode::Normal; +} + +void PDFColorConvertor::setMode(Mode mode) +{ + m_mode = mode; +} + +QColor PDFColorConvertor::convert(QColor color, bool background, bool foreground) const +{ + switch (m_mode) + { + case Mode::Normal: + return color; + + case Mode::InvertedColors: + return invertColor(color); + + case Mode::Grayscale: + { + int gray = qGray(color.red(), color.green(), color.blue()); + return QColor(gray, gray, gray, color.alpha()); + } + + case Mode::HighContrast: + { + const float lightness = color.lightnessF(); + const float adjustedLightness = correctLigthnessBySigmoidFunction(lightness); + QColor hslColor = color.toHsl(); + hslColor.setHslF(hslColor.hueF(), hslColor.saturationF(), adjustedLightness, color.alphaF()); + return hslColor.toRgb(); + } + + case Mode::Bitonal: + { + const int lightness = color.lightness(); + QColor bitonalColor = (lightness >= m_bitonalThreshold) ? QColor(Qt::white) : QColor(Qt::black); + bitonalColor.setAlpha(bitonalColor.alpha()); + return bitonalColor; + } + + case Mode::CustomColors: + { + if (background) + { + return m_backgroundColor; + } + + if (foreground) + { + return m_foregroundColor; + } + + const float lightness = color.lightnessF(); + QColor hslColor = m_foregroundColor; + hslColor.setHslF(hslColor.hueF(), hslColor.saturationF(), lightness, color.alphaF()); + return hslColor.toRgb(); + } + + default: + Q_ASSERT(false); + break; + } + + return color; +} + +QImage PDFColorConvertor::convert(QImage image) const +{ + switch (m_mode) + { + case Mode::Normal: + return image; + + case Mode::InvertedColors: + { + image.invertPixels(QImage::InvertRgb); + return image; + } + + case Mode::Grayscale: + { + QImage alpha = image.convertedTo(QImage::Format_Alpha8); + QImage grayscaleImage = image.convertedTo(QImage::Format_Grayscale8); + QImage resultImage = grayscaleImage; + resultImage = resultImage.convertedTo(QImage::Format_ARGB32); + resultImage.setAlphaChannel(std::move(alpha)); + return resultImage; + } + + case Mode::Bitonal: + { + PDFImageConversion imageConversion; + imageConversion.setConversionMethod(PDFImageConversion::ConversionMethod::Automatic); + imageConversion.setImage(image); + + if (imageConversion.convert()) + { + return imageConversion.getConvertedImage(); + } + + return image; + } + + case Mode::HighContrast: + case Mode::CustomColors: + { + for (int row = 0; row < image.height(); ++row) + { + for (int column = 0; column < image.width(); ++column) + { + QColor color = image.pixelColor(column, row); + QColor adjustedColor = convert(color, false, false); + image.setPixelColor(column, row, adjustedColor); + } + } + + return image; + } + + default: + Q_ASSERT(false); + break; + } + + return image; +} + +void PDFColorConvertor::setHighContrastBrightnessFactor(float factor) +{ + m_sigmoidParamC = factor; + calculateSigmoidParams(); +} + +float PDFColorConvertor::correctLigthnessBySigmoidFunction(float lightness) const +{ + const float adjustedLightness = sigmoidFunction(lightness); + const float normalizedLightness = (adjustedLightness - m_sigmoidParamC_Black) / m_sigmoidParamC_Range; + return qBound(0.0f, normalizedLightness, 1.0f); +} + +float PDFColorConvertor::sigmoidFunction(float value) const +{ + return 1.0f / (1.0f + std::expf(-m_sigmoidParamC * (value - 0.5f))); +} + +float PDFColorConvertor::sigmoidParamC_Black() const +{ + return sigmoidFunction(0.0); +} + +float PDFColorConvertor::sigmoidParamC_White() const +{ + return sigmoidFunction(1.0); +} + +float PDFColorConvertor::sigmoidParamC_Range() const +{ + return sigmoidParamC_White() - sigmoidParamC_Black(); +} + +void PDFColorConvertor::calculateSigmoidParams() +{ + m_sigmoidParamC_Black = sigmoidParamC_Black(); + m_sigmoidParamC_White = sigmoidParamC_White(); + m_sigmoidParamC_Range = sigmoidParamC_Range(); +} + +int PDFColorConvertor::getBitonalThreshold() const +{ + return m_bitonalThreshold; +} + +void PDFColorConvertor::setBitonalThreshold(int newBitonalThreshold) +{ + m_bitonalThreshold = newBitonalThreshold; +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfcolorconvertor.h b/Pdf4QtLib/sources/pdfcolorconvertor.h new file mode 100644 index 0000000..71e4629 --- /dev/null +++ b/Pdf4QtLib/sources/pdfcolorconvertor.h @@ -0,0 +1,139 @@ +// Copyright (C) 2023 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 . + +#ifndef PDFCOLORCONVERTOR_H +#define PDFCOLORCONVERTOR_H + +#include "pdfglobal.h" + +#include +#include + +namespace pdf +{ + +/// \class PDFColorConvertor +/// \brief Performs color conversions in the RGB color space. +/// This class supports multiple modes of operation, making it +/// useful for accessibility purposes, particularly for visually impaired users. +class PDF4QTLIBSHARED_EXPORT PDFColorConvertor +{ +public: + PDFColorConvertor(); + + /// Enumeration for different modes of color conversion + enum class Mode + { + Normal, ///< No color conversion is performed. + InvertedColors, ///< Inverts colors in the RGB space. + Grayscale, ///< Converts colors to grayscale. + HighContrast, ///< Adjusts contrast using a sigmoid function. + Bitonal, ///< Creates a monochromatic (two-color) image. + CustomColors, ///< Applies custom foreground and background colors. + }; + + bool operator==(const PDFColorConvertor&) const = default; + bool operator!=(const PDFColorConvertor&) const = default; + + /// Checks if color conversion is active. + /// \return `true` if color conversion is currently active; `false` otherwise. + /// When `false`, no color conversions are performed, and the `convert` function + /// operates as an identity function. + bool isActive() const; + + /// Sets the mode for color conversion. + /// \param mode Specifies the new mode to be used for color conversion. + /// This mode determines how colors are transformed during the conversion process. + void setMode(Mode mode); + + /// Converts the given color based on the current mode + /// \param color The QColor to be converted + /// \param background Use background color + /// \param foreground Use foreground color + /// \return The converted QColor + QColor convert(QColor color, bool background, bool foreground) const; + + /// Converts the given image based on the current mode + /// \param image The image to be converted + /// \return The converted image + QImage convert(QImage image) const; + + /// Sets the correction factor for enhancing contrast in high contrast mode. + /// This factor determines the level of contrast enhancement: + /// - For subtle enhancement, set the factor between 5 and 10. + /// - For moderate enhancement, use values between 10 and 20. + /// - For strong enhancement, choose values of 20 or higher. + /// \param factor The correction factor used for adjusting contrast. + void setHighContrastBrightnessFactor(float factor); + + /// Retrieves the current bitonal threshold value used for determining lightness levels. + /// This threshold helps in differentiating between light and dark areas in an image processing context. + /// \returns The current bitonal threshold value as an integer. This value is used to classify pixels + /// as either light or dark based on their lightness. + int getBitonalThreshold() const; + + /// Sets a new bitonal threshold value to be used in image processing. + /// This threshold determines how lightness levels are classified into binary categories (light or dark). + /// \param newBitonalThreshold The new threshold value as an integer. It should be chosen carefully + /// to ensure accurate differentiation between light and dark areas in images. + void setBitonalThreshold(int newBitonalThreshold); + +private: + /// Correct lightness using sigmoid function + /// \return Adjusted lightness normalized in range [0.0, 1.0] + /// \param lightness Lightness in range [0.0, 1.0] + float correctLigthnessBySigmoidFunction(float lightness) const; + + /// Calculates the value of the sigmoid function based on the given input. + /// The returned value is unscaled and should be scaled to the range [0.0, 1.0] + /// using other functions. + /// \param value The input value for the sigmoid function. + /// \return The unscaled result of the sigmoid function. + float sigmoidFunction(float value) const; + + /// Determines the sigmoid function value for a completely black color + /// (i.e., when lightness is set to zero). + /// \return The sigmoid function value corresponding to black color. + float sigmoidParamC_Black() const; + + /// Determines the sigmoid function value for a completely white color + /// (i.e., when lightness is set to one). + /// \return The sigmoid function value corresponding to white color. + float sigmoidParamC_White() const; + + /// Determines the range of the sigmoid function's output, + /// useful for scaling the range to the normalized interval [0.0, 1.0]. + /// \return The range of the sigmoid function's output. + float sigmoidParamC_Range() const; + + /// Calculates parameters for the sigmoid function, + /// which are used to scale color values to the interval [0.0, 1.0]. + void calculateSigmoidParams(); + + Mode m_mode = Mode::Normal; + float m_sigmoidParamC = 10.0f; + float m_sigmoidParamC_Black = 0.0f; + float m_sigmoidParamC_White = 1.0f; + float m_sigmoidParamC_Range = 1.0f; + int m_bitonalThreshold = 128; + QColor m_backgroundColor = Qt::black; + QColor m_foregroundColor = Qt::green; +}; + +} // namespace pdf + +#endif // PDFCOLORCONVERTOR_H diff --git a/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp b/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp index a565a3e..6ddcee9 100644 --- a/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp +++ b/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp @@ -767,11 +767,10 @@ void PDFDrawWidgetProxy::draw(QPainter* painter, QRect rect) QColor PDFDrawWidgetProxy::getPaperColor() { QColor paperColor = getCMSManager()->getCurrentCMS()->getPaperColor(); - if (m_features.testFlag(PDFRenderer::InvertColors)) - { - paperColor = invertColor(paperColor); - } + PDFColorConvertor colorConvertor = getCMSManager()->getColorConvertor(); + PDFRenderer::applyFeaturesToColorConvertor(getFeatures(), colorConvertor); + paperColor = colorConvertor.convert(paperColor, true, false); return paperColor; } diff --git a/Pdf4QtLib/sources/pdfform.cpp b/Pdf4QtLib/sources/pdfform.cpp index 47a191c..92136a6 100644 --- a/Pdf4QtLib/sources/pdfform.cpp +++ b/Pdf4QtLib/sources/pdfform.cpp @@ -2241,7 +2241,7 @@ void PDFFormFieldComboBoxEditor::draw(AnnotationDrawParameters& parameters, bool AnnotationDrawParameters listBoxParameters = parameters; listBoxParameters.boundingRectangle = m_listBoxPopupRectangle; - QColor color = parameters.invertColors ? Qt::black : Qt::white; + QColor color = parameters.colorConvertor.convert(Qt::white, true, false); listBoxParameters.painter->fillRect(listBoxParameters.boundingRectangle, color); m_listBox.draw(listBoxParameters, true); @@ -2690,37 +2690,25 @@ void PDFListBoxPseudowidget::draw(AnnotationDrawParameters& parameters, bool edi } QPalette palette = QApplication::palette(); - - auto getAdjustedColor = [¶meters](QColor color) - { - if (parameters.invertColors) - { - return invertColor(color); - } - - return color; - }; - QTransform matrix = createListBoxTransformMatrix(); - QPainter* painter = parameters.painter; if (edit) { pdf::PDFPainterStateGuard guard2(painter); - painter->setPen(getAdjustedColor(Qt::black)); + painter->setPen(parameters.colorConvertor.convert(Qt::black, false, true)); painter->setBrush(Qt::NoBrush); painter->drawRect(parameters.boundingRectangle); } painter->setClipRect(parameters.boundingRectangle, Qt::IntersectClip); painter->setWorldTransform(QTransform(matrix), true); - painter->setPen(getAdjustedColor(m_textColor)); + painter->setPen(parameters.colorConvertor.convert(m_textColor, false, true)); painter->setFont(m_font); - QColor textColor = getAdjustedColor(m_textColor); - QColor highlightTextColor = getAdjustedColor(palette.color(QPalette::HighlightedText)); - QColor highlightColor = getAdjustedColor(palette.color(QPalette::Highlight)); + QColor textColor = parameters.colorConvertor.convert(m_textColor, false, true); + QColor highlightTextColor = parameters.colorConvertor.convert(palette.color(QPalette::HighlightedText), false, true); + QColor highlightColor = parameters.colorConvertor.convert(palette.color(QPalette::Highlight), false, false); QRectF rect(0, 0, m_widgetRect.width(), m_lineSpacing); for (int i = m_topIndex; i < int(m_options.size()); ++i) diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp index 443ccfb..59bdd59 100644 --- a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp @@ -706,7 +706,8 @@ void PDFCreatePCElementTextTool::drawPage(QPainter* painter, parameters.painter = painter; parameters.boundingRectangle = m_element->getRectangle(); parameters.key.first = PDFAppeareanceStreams::Appearance::Normal; - parameters.invertColors = getProxy()->getFeatures().testFlag(PDFRenderer::InvertColors); + parameters.colorConvertor = getProxy()->getCMSManager()->getColorConvertor(); + PDFRenderer::applyFeaturesToColorConvertor(getProxy()->getFeatures(), parameters.colorConvertor); painter->setWorldTransform(QTransform(pagePointToDevicePointMatrix), true); m_textEditWidget->draw(parameters, true); diff --git a/Pdf4QtLib/sources/pdfpainter.cpp b/Pdf4QtLib/sources/pdfpainter.cpp index dfafc48..7cde3a3 100644 --- a/Pdf4QtLib/sources/pdfpainter.cpp +++ b/Pdf4QtLib/sources/pdfpainter.cpp @@ -764,36 +764,41 @@ void PDFPrecompiledPage::optimize() m_compositionModes.shrink_to_fit(); } -void PDFPrecompiledPage::invertColors() +void PDFPrecompiledPage::convertColors(const PDFColorConvertor& colorConvertor) { - // Jakub Melka: we must invert colors in following areas: + // Jakub Melka: we must apply color convertor in following areas: // - painter paths // - images // - meshes + if (!colorConvertor.isActive()) + { + return; + } + for (PathPaintData& pathData : m_paths) { if (pathData.pen.style() != Qt::NoPen) { - pathData.pen.setColor(invertColor(pathData.pen.color())); + pathData.pen.setColor(colorConvertor.convert(pathData.pen.color(), false, pathData.isText)); } if (pathData.brush.style() == Qt::SolidPattern) { - pathData.brush.setColor(invertColor(pathData.brush.color())); + pathData.brush.setColor(colorConvertor.convert(pathData.brush.color(), false, pathData.isText)); } } for (ImageData& imageData : m_images) { - imageData.image.invertPixels(QImage::InvertRgb); + imageData.image = colorConvertor.convert(imageData.image); } for (MeshPaintData& meshPaintData : m_meshes) { - meshPaintData.mesh.invertColors(); + meshPaintData.mesh.convertColors(colorConvertor); } - m_paperColor = invertColor(m_paperColor); + m_paperColor = colorConvertor.convert(m_paperColor, true, false); } void PDFPrecompiledPage::finalize(qint64 compilingTimeNS, QList errors) diff --git a/Pdf4QtLib/sources/pdfpainter.h b/Pdf4QtLib/sources/pdfpainter.h index 39e6432..336211b 100644 --- a/Pdf4QtLib/sources/pdfpainter.h +++ b/Pdf4QtLib/sources/pdfpainter.h @@ -24,6 +24,7 @@ #include "pdfpagecontentprocessor.h" #include "pdftextlayout.h" #include "pdfsnapper.h" +#include "pdfcolorconvertor.h" #include #include @@ -213,8 +214,8 @@ public: /// Optimizes page memory allocation to contain less space void optimize(); - /// Inverts all colors - void invertColors(); + /// Converts all colors + void convertColors(const PDFColorConvertor& colorConvertor); /// Finalizes precompiled page /// \param compilingTimeNS Compiling time in nanoseconds diff --git a/Pdf4QtLib/sources/pdfpattern.cpp b/Pdf4QtLib/sources/pdfpattern.cpp index d1ca842..77de70d 100644 --- a/Pdf4QtLib/sources/pdfpattern.cpp +++ b/Pdf4QtLib/sources/pdfpattern.cpp @@ -1406,14 +1406,16 @@ qint64 PDFMesh::getMemoryConsumptionEstimate() const return memoryConsumption; } -void PDFMesh::invertColors() +void PDFMesh::convertColors(const PDFColorConvertor& colorConvertor) { for (Triangle& triangle : m_triangles) { - triangle.color = 0x00FFFFFF - triangle.color; + QColor color = QColor::fromRgb(triangle.color); + QColor adjustedColor = colorConvertor.convert(color, false, false); + triangle.color = adjustedColor.rgb(); } - m_backgroundColor = invertColor(m_backgroundColor); + m_backgroundColor = colorConvertor.convert(m_backgroundColor, true, false); } void PDFMeshQualitySettings::initResolution() diff --git a/Pdf4QtLib/sources/pdfpattern.h b/Pdf4QtLib/sources/pdfpattern.h index 889e983..a50d4ab 100644 --- a/Pdf4QtLib/sources/pdfpattern.h +++ b/Pdf4QtLib/sources/pdfpattern.h @@ -22,6 +22,7 @@ #include "pdffunction.h" #include "pdfcolorspaces.h" #include "pdfmeshqualitysettings.h" +#include "pdfcolorconvertor.h" #include #include @@ -144,8 +145,8 @@ public: /// Returns estimate of number of bytes, which this mesh occupies in memory qint64 getMemoryConsumptionEstimate() const; - /// Invert colors - void invertColors(); + /// Apply color conversion + void convertColors(const PDFColorConvertor& colorConvertor); private: std::vector m_vertices; diff --git a/Pdf4QtLib/sources/pdfrenderer.cpp b/Pdf4QtLib/sources/pdfrenderer.cpp index 22c878e..4f25219 100644 --- a/Pdf4QtLib/sources/pdfrenderer.cpp +++ b/Pdf4QtLib/sources/pdfrenderer.cpp @@ -115,6 +115,36 @@ QTransform PDFRenderer::createMediaBoxToDevicePointMatrix(const QRectF& mediaBox return matrix; } +void PDFRenderer::applyFeaturesToColorConvertor(const Features& features, PDFColorConvertor& convertor) +{ + convertor.setMode(PDFColorConvertor::Mode::Normal); + + if (features.testFlag(ColorAdjust_Invert)) + { + convertor.setMode(PDFColorConvertor::Mode::InvertedColors); + } + + if (features.testFlag(ColorAdjust_Grayscale)) + { + convertor.setMode(PDFColorConvertor::Mode::Grayscale); + } + + if (features.testFlag(ColorAdjust_HighContrast)) + { + convertor.setMode(PDFColorConvertor::Mode::HighContrast); + } + + if (features.testFlag(ColorAdjust_Bitonal)) + { + convertor.setMode(PDFColorConvertor::Mode::Bitonal); + } + + if (features.testFlag(ColorAdjust_CustomColors)) + { + convertor.setMode(PDFColorConvertor::Mode::CustomColors); + } +} + const PDFOperationControl* PDFRenderer::getOperationControl() const { return m_operationControl; @@ -181,10 +211,9 @@ void PDFRenderer::compile(PDFPrecompiledPage* precompiledPage, size_t pageIndex) generator.setOperationControl(m_operationControl); QList errors = generator.processContents(); - if (m_features.testFlag(InvertColors)) - { - precompiledPage->invertColors(); - } + PDFColorConvertor colorConvertor = m_cms->getColorConvertor(); + PDFRenderer::applyFeaturesToColorConvertor(m_features, colorConvertor); + precompiledPage->convertColors(colorConvertor); precompiledPage->optimize(); precompiledPage->finalize(timer.nsecsElapsed(), qMove(errors)); diff --git a/Pdf4QtLib/sources/pdfrenderer.h b/Pdf4QtLib/sources/pdfrenderer.h index b851f1e..5045101 100644 --- a/Pdf4QtLib/sources/pdfrenderer.h +++ b/Pdf4QtLib/sources/pdfrenderer.h @@ -23,6 +23,7 @@ #include "pdfoperationcontrol.h" #include "pdfmeshqualitysettings.h" #include "pdfutils.h" +#include "pdfcolorconvertor.h" #include #include @@ -76,19 +77,24 @@ public: enum Feature { - None = 0x0000, - Antialiasing = 0x0001, ///< Antialiasing for lines, shapes, etc. - TextAntialiasing = 0x0002, ///< Antialiasing for drawing text - SmoothImages = 0x0004, ///< Adjust images to the device space using smooth transformation (slower, but better image quality) - IgnoreOptionalContent = 0x0008, ///< Ignore optional content (so all is drawn ignoring settings of optional content) - ClipToCropBox = 0x0010, ///< Clip page content to crop box (items outside crop box will not be visible) - DisplayTimes = 0x0020, ///< Display page compile/draw time - DebugTextBlocks = 0x0040, ///< Debug text block layout algorithm - DebugTextLines = 0x0080, ///< Debug text line layout algorithm - InvertColors = 0x0100, ///< Invert colors - DenyExtraGraphics = 0x0200, ///< Do not display additional graphics, for example from tools - DisplayAnnotations = 0x0400, ///< Display annotations - LogicalSizeZooming = 0x0800, ///< Use logical pixel resolution instead of physical one when zooming + None = 0x0000, + Antialiasing = 0x0001, ///< Antialiasing for lines, shapes, etc. + TextAntialiasing = 0x0002, ///< Antialiasing for drawing text + SmoothImages = 0x0004, ///< Adjust images to the device space using smooth transformation (slower, but better image quality) + IgnoreOptionalContent = 0x0008, ///< Ignore optional content (so all is drawn ignoring settings of optional content) + ClipToCropBox = 0x0010, ///< Clip page content to crop box (items outside crop box will not be visible) + DisplayTimes = 0x0020, ///< Display page compile/draw time + DebugTextBlocks = 0x0040, ///< Debug text block layout algorithm + DebugTextLines = 0x0080, ///< Debug text line layout algorithm + DenyExtraGraphics = 0x0100, ///< Do not display additional graphics, for example from tools + DisplayAnnotations = 0x0200, ///< Display annotations + LogicalSizeZooming = 0x0400, ///< Use logical pixel resolution instead of physical one when zooming + + ColorAdjust_Invert = 0x0800, ///< Invert colors + ColorAdjust_Grayscale = 0x1000, ///< Convert colors to grayscale + ColorAdjust_HighContrast = 0x2000, ///< Convert colors to high constrast colors + ColorAdjust_Bitonal = 0x4000, ///< Convert colors to bitonal (monochromatic) + ColorAdjust_CustomColors = 0x8000, ///< Convert colors to custom color settings }; Q_DECLARE_FLAGS(Features, Feature) @@ -137,9 +143,15 @@ public: const QRectF& rectangle, PageRotation rotation); + /// Applies rendering flags to the color convertor + static void applyFeaturesToColorConvertor(const Features& features, PDFColorConvertor& convertor); + /// Returns default renderer features static constexpr Features getDefaultFeatures() { return Features(Antialiasing | TextAntialiasing | ClipToCropBox | DisplayAnnotations); } + /// Returns color transformation features + static constexpr Features getColorFeatures() { return Features(ColorAdjust_Invert | ColorAdjust_Grayscale | ColorAdjust_HighContrast | ColorAdjust_Bitonal | ColorAdjust_CustomColors); } + const PDFOperationControl* getOperationControl() const; void setOperationControl(const PDFOperationControl* newOperationControl); diff --git a/Pdf4QtLib/sources/pdftexteditpseudowidget.cpp b/Pdf4QtLib/sources/pdftexteditpseudowidget.cpp index 9ded20e..1cb7e39 100644 --- a/Pdf4QtLib/sources/pdftexteditpseudowidget.cpp +++ b/Pdf4QtLib/sources/pdftexteditpseudowidget.cpp @@ -640,30 +640,19 @@ void PDFTextEditPseudowidget::draw(AnnotationDrawParameters& parameters, bool ed } QPalette palette = QApplication::palette(); - - auto getAdjustedColor = [¶meters](QColor color) - { - if (parameters.invertColors) - { - return invertColor(color); - } - - return color; - }; - QPainter* painter = parameters.painter; if (edit) { pdf::PDFPainterStateGuard guard2(painter); - painter->setPen(getAdjustedColor(Qt::black)); + painter->setPen(parameters.colorConvertor.convert(Qt::black, false, true)); painter->setBrush(Qt::NoBrush); painter->drawRect(parameters.boundingRectangle); } painter->setClipRect(parameters.boundingRectangle, Qt::IntersectClip); painter->setWorldTransform(QTransform(createTextBoxTransformMatrix(edit)), true); - painter->setPen(getAdjustedColor(Qt::black)); + painter->setPen(parameters.colorConvertor.convert(Qt::black, false, true)); if (isComb()) { @@ -672,9 +661,9 @@ void PDFTextEditPseudowidget::draw(AnnotationDrawParameters& parameters, bool ed QRectF combRect(0.0, 0.0, combWidth, m_widgetRect.height()); painter->setFont(m_textLayout.font()); - QColor textColor = getAdjustedColor(m_textColor); - QColor highlightTextColor = getAdjustedColor(palette.color(QPalette::HighlightedText)); - QColor highlightColor = getAdjustedColor(palette.color(QPalette::Highlight)); + QColor textColor = parameters.colorConvertor.convert(m_textColor, false, true); + QColor highlightTextColor = parameters.colorConvertor.convert(palette.color(QPalette::HighlightedText), false, true); + QColor highlightColor = parameters.colorConvertor.convert(palette.color(QPalette::Highlight), false, false); std::vector positions = getCursorPositions(); for (size_t i = 1; i < positions.size(); ++i) @@ -720,7 +709,7 @@ void PDFTextEditPseudowidget::draw(AnnotationDrawParameters& parameters, bool ed defaultFormat.start = getPositionStart(); defaultFormat.length = getTextLength(); defaultFormat.format.clearBackground(); - defaultFormat.format.setForeground(QBrush(getAdjustedColor(m_textColor), Qt::SolidPattern)); + defaultFormat.format.setForeground(QBrush(parameters.colorConvertor.convert(m_textColor, false, true), Qt::SolidPattern)); // If we are editing, draw selections if (edit && isTextSelected()) @@ -736,8 +725,8 @@ void PDFTextEditPseudowidget::draw(AnnotationDrawParameters& parameters, bool ed QTextLayout::FormatRange selectedFormat = defaultFormat; selectedFormat.start = m_selectionStart; selectedFormat.length = getSelectionLength(); - selectedFormat.format.setForeground(QBrush(getAdjustedColor(palette.color(QPalette::HighlightedText)), Qt::SolidPattern)); - selectedFormat.format.setBackground(QBrush(getAdjustedColor(palette.color(QPalette::Highlight)), Qt::SolidPattern)); + selectedFormat.format.setForeground(QBrush(parameters.colorConvertor.convert(palette.color(QPalette::HighlightedText), false, true), Qt::SolidPattern)); + selectedFormat.format.setBackground(QBrush(parameters.colorConvertor.convert(palette.color(QPalette::Highlight), false, false), Qt::SolidPattern)); selections = { before, selectedFormat, after}; } diff --git a/Pdf4QtViewer/pdfprogramcontroller.cpp b/Pdf4QtViewer/pdfprogramcontroller.cpp index b8dd48a..88d9dbc 100644 --- a/Pdf4QtViewer/pdfprogramcontroller.cpp +++ b/Pdf4QtViewer/pdfprogramcontroller.cpp @@ -141,6 +141,10 @@ std::vector PDFActionManager::getRenderingOptionActions() const RenderOptionIgnoreOptionalContentSettings, RenderOptionDisplayAnnotations, RenderOptionInvertColors, + RenderOptionGrayscale, + RenderOptionBitonal, + RenderOptionHighContrast, + RenderOptionCustomColors, RenderOptionShowTextBlocks, RenderOptionShowTextLines }); @@ -249,7 +253,11 @@ void PDFActionManager::initActions(QSize iconSize, bool initializeStampActions) setUserData(RenderOptionSmoothPictures, pdf::PDFRenderer::SmoothImages); setUserData(RenderOptionIgnoreOptionalContentSettings, pdf::PDFRenderer::IgnoreOptionalContent); setUserData(RenderOptionDisplayAnnotations, pdf::PDFRenderer::DisplayAnnotations); - setUserData(RenderOptionInvertColors, pdf::PDFRenderer::InvertColors); + setUserData(RenderOptionInvertColors, pdf::PDFRenderer::ColorAdjust_Invert); + setUserData(RenderOptionGrayscale, pdf::PDFRenderer::ColorAdjust_Grayscale); + setUserData(RenderOptionBitonal, pdf::PDFRenderer::ColorAdjust_Bitonal); + setUserData(RenderOptionHighContrast, pdf::PDFRenderer::ColorAdjust_HighContrast); + setUserData(RenderOptionCustomColors, pdf::PDFRenderer::ColorAdjust_CustomColors); setUserData(RenderOptionShowTextBlocks, pdf::PDFRenderer::DebugTextBlocks); setUserData(RenderOptionShowTextLines, pdf::PDFRenderer::DebugTextLines); @@ -1051,8 +1059,17 @@ void PDFProgramController::onActionRenderingOptionTriggered(bool checked) Q_ASSERT(action); pdf::PDFRenderer::Features features = m_settings->getFeatures(); - features.setFlag(static_cast(action->data().toInt()), checked); + pdf::PDFRenderer::Feature affectedFeature = static_cast(action->data().toInt()); + pdf::PDFRenderer::Features colorFeatures = pdf::PDFRenderer::getColorFeatures(); + + if (colorFeatures.testFlag(affectedFeature) && checked) + { + features = features & ~colorFeatures; + } + + features.setFlag(affectedFeature, checked); m_settings->setFeatures(features); + updateRenderingOptionActions(); } void PDFProgramController::performSaveAs() diff --git a/Pdf4QtViewer/pdfprogramcontroller.h b/Pdf4QtViewer/pdfprogramcontroller.h index d248224..fb4366f 100644 --- a/Pdf4QtViewer/pdfprogramcontroller.h +++ b/Pdf4QtViewer/pdfprogramcontroller.h @@ -160,6 +160,10 @@ public: RenderOptionIgnoreOptionalContentSettings, RenderOptionDisplayAnnotations, RenderOptionInvertColors, + RenderOptionGrayscale, + RenderOptionBitonal, + RenderOptionHighContrast, + RenderOptionCustomColors, RenderOptionShowTextBlocks, RenderOptionShowTextLines, PageLayoutSinglePage, diff --git a/Pdf4QtViewer/pdfviewermainwindow.cpp b/Pdf4QtViewer/pdfviewermainwindow.cpp index 037a194..4e55547 100644 --- a/Pdf4QtViewer/pdfviewermainwindow.cpp +++ b/Pdf4QtViewer/pdfviewermainwindow.cpp @@ -150,7 +150,11 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : m_actionManager->setAction(PDFActionManager::RenderOptionSmoothPictures, ui->actionRenderOptionSmoothPictures); m_actionManager->setAction(PDFActionManager::RenderOptionIgnoreOptionalContentSettings, ui->actionRenderOptionIgnoreOptionalContentSettings); m_actionManager->setAction(PDFActionManager::RenderOptionDisplayAnnotations, ui->actionRenderOptionDisplayAnnotations); - m_actionManager->setAction(PDFActionManager::RenderOptionInvertColors, ui->actionInvertColors); + m_actionManager->setAction(PDFActionManager::RenderOptionInvertColors, ui->actionColorInvert); + m_actionManager->setAction(PDFActionManager::RenderOptionGrayscale, ui->actionColorGrayscale); + m_actionManager->setAction(PDFActionManager::RenderOptionHighContrast, ui->actionColorHighContrast); + m_actionManager->setAction(PDFActionManager::RenderOptionBitonal, ui->actionColorBitonal); + m_actionManager->setAction(PDFActionManager::RenderOptionCustomColors, ui->actionColorCustom); m_actionManager->setAction(PDFActionManager::RenderOptionShowTextBlocks, ui->actionShow_Text_Blocks); m_actionManager->setAction(PDFActionManager::RenderOptionShowTextLines, ui->actionShow_Text_Lines); m_actionManager->setAction(PDFActionManager::Properties, ui->actionProperties); diff --git a/Pdf4QtViewer/pdfviewermainwindow.ui b/Pdf4QtViewer/pdfviewermainwindow.ui index d7b3ffa..ba41469 100644 --- a/Pdf4QtViewer/pdfviewermainwindow.ui +++ b/Pdf4QtViewer/pdfviewermainwindow.ui @@ -91,7 +91,11 @@ - + + + + + @@ -527,7 +531,7 @@ Copy text - + true @@ -536,7 +540,55 @@ :/resources/invert-colors.svg:/resources/invert-colors.svg - Invert Colors + Color | Inverted + + + + + true + + + + :/resources/invert-colors.svg:/resources/invert-colors.svg + + + Color | Grayscale + + + + + true + + + + :/resources/invert-colors.svg:/resources/invert-colors.svg + + + Color | High Contrast + + + + + true + + + + :/resources/invert-colors.svg:/resources/invert-colors.svg + + + Color | Monochromatic + + + + + true + + + + :/resources/invert-colors.svg:/resources/invert-colors.svg + + + Color | Custom diff --git a/PdfTool/pdftoolabstractapplication.cpp b/PdfTool/pdftoolabstractapplication.cpp index 928602b..f366125 100644 --- a/PdfTool/pdftoolabstractapplication.cpp +++ b/PdfTool/pdftoolabstractapplication.cpp @@ -1167,7 +1167,11 @@ std::vector PDFToolOptions::getRenderFeatures 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-invert-colors", "Color conversion: invert all colors.", pdf::PDFRenderer::ColorAdjust_Invert }, + RenderFeatureInfo{ "render-grayscale", "Color conversion: convert to grayscale", pdf::PDFRenderer::ColorAdjust_Grayscale }, + RenderFeatureInfo{ "render-high-contrast", "Color conversion: high contrast colors", pdf::PDFRenderer::ColorAdjust_HighContrast }, + RenderFeatureInfo{ "render-bitonal", "Color conversion: bitonal page image", pdf::PDFRenderer::ColorAdjust_Bitonal }, + RenderFeatureInfo{ "render-custom-colors", "Color conversion: custom colors", pdf::PDFRenderer::ColorAdjust_CustomColors }, RenderFeatureInfo{ "render-display-annot", "Display annotations.", pdf::PDFRenderer::DisplayAnnotations } }; }