mirror of
				https://github.com/JakubMelka/PDF4QT.git
				synced 2025-06-05 21:59:17 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			3876 lines
		
	
	
		
			146 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			3876 lines
		
	
	
		
			146 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
//    Copyright (C) 2020 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
 | 
						|
//    (at your option) 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 <https://www.gnu.org/licenses/>.
 | 
						|
 | 
						|
#include "pdfannotation.h"
 | 
						|
#include "pdfdocument.h"
 | 
						|
#include "pdfencoding.h"
 | 
						|
#include "pdfpainter.h"
 | 
						|
#include "pdfdrawspacecontroller.h"
 | 
						|
#include "pdfcms.h"
 | 
						|
#include "pdfwidgetutils.h"
 | 
						|
#include "pdfpagecontentprocessor.h"
 | 
						|
#include "pdfparser.h"
 | 
						|
#include "pdfdrawwidget.h"
 | 
						|
#include "pdfform.h"
 | 
						|
#include "pdfpainterutils.h"
 | 
						|
#include "pdfdocumentbuilder.h"
 | 
						|
#include "pdfobjecteditorwidget.h"
 | 
						|
#include "pdfselectpagesdialog.h"
 | 
						|
 | 
						|
#include <QMenu>
 | 
						|
#include <QDialog>
 | 
						|
#include <QApplication>
 | 
						|
#include <QMouseEvent>
 | 
						|
#include <QGroupBox>
 | 
						|
#include <QScrollArea>
 | 
						|
#include <QTextEdit>
 | 
						|
#include <QVBoxLayout>
 | 
						|
#include <QLabel>
 | 
						|
#include <QStyleOptionButton>
 | 
						|
 | 
						|
namespace pdf
 | 
						|
{
 | 
						|
 | 
						|
PDFAnnotationBorder PDFAnnotationBorder::parseBorder(const PDFObjectStorage* storage, PDFObject object)
 | 
						|
{
 | 
						|
    PDFAnnotationBorder result;
 | 
						|
    object = storage->getObject(object);
 | 
						|
 | 
						|
    if (object.isArray())
 | 
						|
    {
 | 
						|
        const PDFArray* array = object.getArray();
 | 
						|
        if (array->getCount() >= 3)
 | 
						|
        {
 | 
						|
            PDFDocumentDataLoaderDecorator loader(storage);
 | 
						|
            result.m_definition = Definition::Simple;
 | 
						|
            result.m_hCornerRadius = loader.readNumber(array->getItem(0), 0.0);
 | 
						|
            result.m_vCornerRadius = loader.readNumber(array->getItem(1), 0.0);
 | 
						|
            result.m_width = loader.readNumber(array->getItem(2), 1.0);
 | 
						|
 | 
						|
            if (array->getCount() >= 4)
 | 
						|
            {
 | 
						|
                result.m_dashPattern = loader.readNumberArray(array->getItem(3));
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
PDFAnnotationBorder PDFAnnotationBorder::parseBS(const PDFObjectStorage* storage, PDFObject object)
 | 
						|
{
 | 
						|
    PDFAnnotationBorder result;
 | 
						|
 | 
						|
    if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object))
 | 
						|
    {
 | 
						|
        PDFDocumentDataLoaderDecorator loader(storage);
 | 
						|
        result.m_definition = Definition::BorderStyle;
 | 
						|
        result.m_width = loader.readNumberFromDictionary(dictionary, "W", 1.0);
 | 
						|
 | 
						|
        constexpr const std::array<std::pair<const char*, Style>, 6> styles = {
 | 
						|
            std::pair<const char*, Style>{ "S", Style::Solid },
 | 
						|
            std::pair<const char*, Style>{ "D", Style::Dashed },
 | 
						|
            std::pair<const char*, Style>{ "B", Style::Beveled },
 | 
						|
            std::pair<const char*, Style>{ "I", Style::Inset },
 | 
						|
            std::pair<const char*, Style>{ "U", Style::Underline }
 | 
						|
        };
 | 
						|
 | 
						|
        result.m_style = loader.readEnumByName(dictionary->get("S"), styles.begin(), styles.end(), Style::Solid);
 | 
						|
    }
 | 
						|
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
PDFAnnotationBorderEffect PDFAnnotationBorderEffect::parse(const PDFObjectStorage* storage, PDFObject object)
 | 
						|
{
 | 
						|
    PDFAnnotationBorderEffect result;
 | 
						|
 | 
						|
    if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object))
 | 
						|
    {
 | 
						|
        PDFDocumentDataLoaderDecorator loader(storage);
 | 
						|
        result.m_intensity = loader.readNumberFromDictionary(dictionary, "I", 0.0);
 | 
						|
 | 
						|
        constexpr const std::array<std::pair<const char*, Effect>, 2> effects = {
 | 
						|
            std::pair<const char*, Effect>{ "S", Effect::None },
 | 
						|
            std::pair<const char*, Effect>{ "C", Effect::Cloudy }
 | 
						|
        };
 | 
						|
 | 
						|
        result.m_effect = loader.readEnumByName(dictionary->get("S"), effects.begin(), effects.end(), Effect::None);
 | 
						|
    }
 | 
						|
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
PDFAppeareanceStreams PDFAppeareanceStreams::parse(const PDFObjectStorage* storage, PDFObject object)
 | 
						|
{
 | 
						|
    PDFAppeareanceStreams result;
 | 
						|
 | 
						|
    auto processSubdictionary = [&result, storage](Appearance appearance, PDFObject subdictionaryObject)
 | 
						|
    {
 | 
						|
        subdictionaryObject = storage->getObject(subdictionaryObject);
 | 
						|
        if (subdictionaryObject.isDictionary())
 | 
						|
        {
 | 
						|
            const PDFDictionary* subdictionary = storage->getDictionaryFromObject(subdictionaryObject);
 | 
						|
            for (size_t i = 0; i < subdictionary->getCount(); ++i)
 | 
						|
            {
 | 
						|
                result.m_appearanceStreams[std::make_pair(appearance, subdictionary->getKey(i).getString())] = subdictionary->getValue(i);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        else if (!subdictionaryObject.isNull())
 | 
						|
        {
 | 
						|
            result.m_appearanceStreams[std::make_pair(appearance, QByteArray())] = subdictionaryObject;
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object))
 | 
						|
    {
 | 
						|
        processSubdictionary(Appearance::Normal, dictionary->get("N"));
 | 
						|
        processSubdictionary(Appearance::Rollover, dictionary->get("R"));
 | 
						|
        processSubdictionary(Appearance::Down, dictionary->get("D"));
 | 
						|
    }
 | 
						|
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
PDFObject PDFAppeareanceStreams::getAppearance(Appearance appearance, const QByteArray& state) const
 | 
						|
{
 | 
						|
    Key key(appearance, state);
 | 
						|
 | 
						|
    auto it = m_appearanceStreams.find(key);
 | 
						|
    if (it == m_appearanceStreams.cend() && appearance != Appearance::Normal)
 | 
						|
    {
 | 
						|
        key.first = Appearance::Normal;
 | 
						|
        it = m_appearanceStreams.find(key);
 | 
						|
    }
 | 
						|
    if (it == m_appearanceStreams.cend() && !state.isEmpty())
 | 
						|
    {
 | 
						|
        key.second = QByteArray();
 | 
						|
        it = m_appearanceStreams.find(key);
 | 
						|
    }
 | 
						|
 | 
						|
    if (it != m_appearanceStreams.cend())
 | 
						|
    {
 | 
						|
        return it->second;
 | 
						|
    }
 | 
						|
 | 
						|
    return PDFObject();
 | 
						|
}
 | 
						|
 | 
						|
QByteArrayList PDFAppeareanceStreams::getAppearanceStates(Appearance appearance) const
 | 
						|
{
 | 
						|
    QByteArrayList result;
 | 
						|
 | 
						|
    for (const auto& item : m_appearanceStreams)
 | 
						|
    {
 | 
						|
        if (item.first.first != appearance)
 | 
						|
        {
 | 
						|
            continue;
 | 
						|
        }
 | 
						|
 | 
						|
        result << item.first.second;
 | 
						|
    }
 | 
						|
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
std::vector<PDFAppeareanceStreams::Key> PDFAppeareanceStreams::getAppearanceKeys() const
 | 
						|
{
 | 
						|
    std::vector<Key> result;
 | 
						|
    std::transform(m_appearanceStreams.cbegin(), m_appearanceStreams.cend(), std::back_inserter(result), [](const auto& item) { return item.first; });
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
PDFAnnotation::PDFAnnotation() :
 | 
						|
    m_flags(),
 | 
						|
    m_structParent(0)
 | 
						|
{
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::draw(AnnotationDrawParameters& parameters) const
 | 
						|
{
 | 
						|
    Q_UNUSED(parameters);
 | 
						|
}
 | 
						|
 | 
						|
std::vector<PDFAppeareanceStreams::Key> PDFAnnotation::getDrawKeys(const PDFFormManager* formManager) const
 | 
						|
{
 | 
						|
    Q_UNUSED(formManager);
 | 
						|
 | 
						|
    return { PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Normal, QByteArray() } };
 | 
						|
}
 | 
						|
 | 
						|
QPainter::CompositionMode PDFAnnotation::getCompositionMode() const
 | 
						|
{
 | 
						|
    if (PDFBlendModeInfo::isSupportedByQt(getBlendMode()))
 | 
						|
    {
 | 
						|
        return PDFBlendModeInfo::getCompositionModeFromBlendMode(getBlendMode());
 | 
						|
    }
 | 
						|
 | 
						|
    return PDFBlendModeInfo::getCompositionModeFromBlendMode(BlendMode::Normal);
 | 
						|
}
 | 
						|
 | 
						|
QPainterPath PDFAnnotation::parsePath(const PDFObjectStorage* storage, const PDFDictionary* dictionary, bool closePath)
 | 
						|
{
 | 
						|
    QPainterPath path;
 | 
						|
 | 
						|
    PDFDocumentDataLoaderDecorator loader(storage);
 | 
						|
    PDFObject pathObject = storage->getObject(dictionary->get("Path"));
 | 
						|
    if (pathObject.isArray())
 | 
						|
    {
 | 
						|
        for (const PDFObject& pathItemObject : *pathObject.getArray())
 | 
						|
        {
 | 
						|
            std::vector<PDFReal> pathItem = loader.readNumberArray(pathItemObject);
 | 
						|
            switch (pathItem.size())
 | 
						|
            {
 | 
						|
                case 2:
 | 
						|
                {
 | 
						|
                    QPointF point(pathItem[0], pathItem[1]);
 | 
						|
                    if (path.isEmpty())
 | 
						|
                    {
 | 
						|
                        path.moveTo(point);
 | 
						|
                    }
 | 
						|
                    else
 | 
						|
                    {
 | 
						|
                        path.lineTo(point);
 | 
						|
                    }
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
 | 
						|
                case 4:
 | 
						|
                {
 | 
						|
                    if (path.isEmpty())
 | 
						|
                    {
 | 
						|
                        // First path item must be 'Move to' command
 | 
						|
                        continue;
 | 
						|
                    }
 | 
						|
 | 
						|
                    path.quadTo(pathItem[0], pathItem[1], pathItem[2], pathItem[3]);
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
 | 
						|
                case 6:
 | 
						|
                {
 | 
						|
                    if (path.isEmpty())
 | 
						|
                    {
 | 
						|
                        // First path item must be 'Move to' command
 | 
						|
                        continue;
 | 
						|
                    }
 | 
						|
 | 
						|
                    path.cubicTo(pathItem[0], pathItem[1], pathItem[2], pathItem[3], pathItem[4], pathItem[5]);
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
 | 
						|
                default:
 | 
						|
                    break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if (closePath)
 | 
						|
    {
 | 
						|
        path.closeSubpath();
 | 
						|
    }
 | 
						|
 | 
						|
    return path;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::setLanguage(const QString& language)
 | 
						|
{
 | 
						|
    m_language = language;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::setBlendMode(const BlendMode& blendMode)
 | 
						|
{
 | 
						|
    m_blendMode = blendMode;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::setStrokingOpacity(const PDFReal& strokingOpacity)
 | 
						|
{
 | 
						|
    m_strokingOpacity = strokingOpacity;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::setFillingOpacity(const PDFReal& fillingOpacity)
 | 
						|
{
 | 
						|
    m_fillingOpacity = fillingOpacity;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::setAssociatedFiles(const PDFObject& associatedFiles)
 | 
						|
{
 | 
						|
    m_associatedFiles = associatedFiles;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::setOptionalContentReference(const PDFObjectReference& optionalContentReference)
 | 
						|
{
 | 
						|
    m_optionalContentReference = optionalContentReference;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::setStructParent(const PDFInteger& structParent)
 | 
						|
{
 | 
						|
    m_structParent = structParent;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::setColor(const std::vector<PDFReal>& color)
 | 
						|
{
 | 
						|
    m_color = color;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::setAnnotationBorder(const PDFAnnotationBorder& annotationBorder)
 | 
						|
{
 | 
						|
    m_annotationBorder = annotationBorder;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::setAppearanceState(const QByteArray& appearanceState)
 | 
						|
{
 | 
						|
    m_appearanceState = appearanceState;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::setAppearanceStreams(const PDFAppeareanceStreams& appearanceStreams)
 | 
						|
{
 | 
						|
    m_appearanceStreams = appearanceStreams;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::setFlags(const Flags& flags)
 | 
						|
{
 | 
						|
    m_flags = flags;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::setLastModifiedString(const QString& lastModifiedString)
 | 
						|
{
 | 
						|
    m_lastModifiedString = lastModifiedString;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::setLastModified(const QDateTime& lastModified)
 | 
						|
{
 | 
						|
    m_lastModified = lastModified;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::setName(const QString& name)
 | 
						|
{
 | 
						|
    m_name = name;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::setPageReference(const PDFObjectReference& pageReference)
 | 
						|
{
 | 
						|
    m_pageReference = pageReference;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::setContents(const QString& contents)
 | 
						|
{
 | 
						|
    m_contents = contents;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::setRectangle(const QRectF& rectangle)
 | 
						|
{
 | 
						|
    m_rectangle = rectangle;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::setSelfReference(const PDFObjectReference& selfReference)
 | 
						|
{
 | 
						|
    m_selfReference = selfReference;
 | 
						|
}
 | 
						|
 | 
						|
PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference)
 | 
						|
{
 | 
						|
    PDFObject object = storage->getObjectByReference(reference);
 | 
						|
    PDFAnnotationPtr result;
 | 
						|
 | 
						|
    const PDFDictionary* dictionary = storage->getDictionaryFromObject(object);
 | 
						|
    if (!dictionary)
 | 
						|
    {
 | 
						|
        return result;
 | 
						|
    }
 | 
						|
 | 
						|
    PDFDocumentDataLoaderDecorator loader(storage);
 | 
						|
    QRectF annotationsRectangle = loader.readRectangle(dictionary->get("Rect"), QRectF());
 | 
						|
 | 
						|
    // Determine type of annotation
 | 
						|
    QByteArray subtype = loader.readNameFromDictionary(dictionary, "Subtype");
 | 
						|
    if (subtype == "Text")
 | 
						|
    {
 | 
						|
        PDFTextAnnotation* textAnnotation = new PDFTextAnnotation;
 | 
						|
        result.reset(textAnnotation);
 | 
						|
 | 
						|
        textAnnotation->m_open = loader.readBooleanFromDictionary(dictionary, "Open", false);
 | 
						|
        textAnnotation->m_iconName = loader.readNameFromDictionary(dictionary, "Name");
 | 
						|
        textAnnotation->m_state = loader.readTextStringFromDictionary(dictionary, "State", "Unmarked");
 | 
						|
        textAnnotation->m_stateModel = loader.readTextStringFromDictionary(dictionary, "StateModel", "Marked");
 | 
						|
    }
 | 
						|
    else if (subtype == "Link")
 | 
						|
    {
 | 
						|
        PDFLinkAnnotation* linkAnnotation = new PDFLinkAnnotation;
 | 
						|
        result.reset(linkAnnotation);
 | 
						|
 | 
						|
        linkAnnotation->m_action = PDFAction::parse(storage, dictionary->get("A"));
 | 
						|
        if (!linkAnnotation->m_action)
 | 
						|
        {
 | 
						|
            PDFDestination destination = PDFDestination::parse(storage, dictionary->get("Dest"));
 | 
						|
            linkAnnotation->m_action.reset(new PDFActionGoTo(destination, PDFDestination()));
 | 
						|
        }
 | 
						|
        linkAnnotation->m_previousAction = PDFAction::parse(storage, dictionary->get("PA"));
 | 
						|
 | 
						|
        constexpr const std::array<std::pair<const char*, LinkHighlightMode>, 4> highlightMode = {
 | 
						|
            std::pair<const char*, LinkHighlightMode>{ "N", LinkHighlightMode::None },
 | 
						|
            std::pair<const char*, LinkHighlightMode>{ "I", LinkHighlightMode::Invert },
 | 
						|
            std::pair<const char*, LinkHighlightMode>{ "O", LinkHighlightMode::Outline },
 | 
						|
            std::pair<const char*, LinkHighlightMode>{ "P", LinkHighlightMode::Push }
 | 
						|
        };
 | 
						|
 | 
						|
        linkAnnotation->m_highlightMode = loader.readEnumByName(dictionary->get("H"), highlightMode.begin(), highlightMode.end(), LinkHighlightMode::Invert);
 | 
						|
        linkAnnotation->m_activationRegion = parseQuadrilaterals(storage, dictionary->get("QuadPoints"), annotationsRectangle);
 | 
						|
    }
 | 
						|
    else if (subtype == "FreeText")
 | 
						|
    {
 | 
						|
        PDFFreeTextAnnotation* freeTextAnnotation = new PDFFreeTextAnnotation;
 | 
						|
        result.reset(freeTextAnnotation);
 | 
						|
 | 
						|
        constexpr const std::array<std::pair<const char*, PDFFreeTextAnnotation::Intent>, 2> intents = {
 | 
						|
            std::pair<const char*, PDFFreeTextAnnotation::Intent>{ "FreeTextCallout", PDFFreeTextAnnotation::Intent::Callout },
 | 
						|
            std::pair<const char*, PDFFreeTextAnnotation::Intent>{ "FreeTextTypeWriter", PDFFreeTextAnnotation::Intent::TypeWriter }
 | 
						|
        };
 | 
						|
 | 
						|
        freeTextAnnotation->m_defaultAppearance = loader.readStringFromDictionary(dictionary, "DA");
 | 
						|
        freeTextAnnotation->m_justification = static_cast<PDFFreeTextAnnotation::Justification>(loader.readIntegerFromDictionary(dictionary, "Q", 0));
 | 
						|
        freeTextAnnotation->m_defaultStyleString = loader.readTextStringFromDictionary(dictionary, "DS", QString());
 | 
						|
        freeTextAnnotation->m_calloutLine = PDFAnnotationCalloutLine::parse(storage, dictionary->get("CL"));
 | 
						|
        freeTextAnnotation->m_intent = loader.readEnumByName(dictionary->get("IT"), intents.begin(), intents.end(), PDFFreeTextAnnotation::Intent::None);
 | 
						|
        freeTextAnnotation->m_effect = PDFAnnotationBorderEffect::parse(storage, dictionary->get("BE"));
 | 
						|
 | 
						|
        std::vector<PDFReal> differenceRectangle = loader.readNumberArrayFromDictionary(dictionary, "RD");
 | 
						|
        if (differenceRectangle.size() == 4)
 | 
						|
        {
 | 
						|
            freeTextAnnotation->m_textRectangle = annotationsRectangle.adjusted(differenceRectangle[0], differenceRectangle[1], -differenceRectangle[2], -differenceRectangle[3]);
 | 
						|
            if (!freeTextAnnotation->m_textRectangle.isValid())
 | 
						|
            {
 | 
						|
                freeTextAnnotation->m_textRectangle = QRectF();
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        std::vector<QByteArray> lineEndings = loader.readNameArrayFromDictionary(dictionary, "LE");
 | 
						|
        if (lineEndings.size() == 2)
 | 
						|
        {
 | 
						|
            freeTextAnnotation->m_startLineEnding = convertNameToLineEnding(lineEndings[0]);
 | 
						|
            freeTextAnnotation->m_endLineEnding = convertNameToLineEnding(lineEndings[1]);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else if (subtype == "Line")
 | 
						|
    {
 | 
						|
        PDFLineAnnotation* lineAnnotation = new PDFLineAnnotation;
 | 
						|
        result.reset(lineAnnotation);
 | 
						|
 | 
						|
        std::vector<PDFReal> line = loader.readNumberArrayFromDictionary(dictionary, "L");
 | 
						|
        if (line.size() == 4)
 | 
						|
        {
 | 
						|
            lineAnnotation->m_line = QLineF(line[0], line[1], line[2], line[3]);
 | 
						|
        }
 | 
						|
 | 
						|
        std::vector<QByteArray> lineEndings = loader.readNameArrayFromDictionary(dictionary, "LE");
 | 
						|
        if (lineEndings.size() == 2)
 | 
						|
        {
 | 
						|
            lineAnnotation->m_startLineEnding = convertNameToLineEnding(lineEndings[0]);
 | 
						|
            lineAnnotation->m_endLineEnding = convertNameToLineEnding(lineEndings[1]);
 | 
						|
        }
 | 
						|
 | 
						|
        lineAnnotation->m_interiorColor = loader.readNumberArrayFromDictionary(dictionary, "IC");
 | 
						|
        lineAnnotation->m_leaderLineLength = loader.readNumberFromDictionary(dictionary, "LL", 0.0);
 | 
						|
        lineAnnotation->m_leaderLineExtension = loader.readNumberFromDictionary(dictionary, "LLE", 0.0);
 | 
						|
        lineAnnotation->m_leaderLineOffset = loader.readNumberFromDictionary(dictionary, "LLO", 0.0);
 | 
						|
        lineAnnotation->m_captionRendered = loader.readBooleanFromDictionary(dictionary, "Cap", false);
 | 
						|
        lineAnnotation->m_intent = (loader.readNameFromDictionary(dictionary, "IT") == "LineDimension") ? PDFLineAnnotation::Intent::Dimension : PDFLineAnnotation::Intent::Arrow;
 | 
						|
        lineAnnotation->m_captionPosition = (loader.readNameFromDictionary(dictionary, "CP") == "Top") ? PDFLineAnnotation::CaptionPosition::Top : PDFLineAnnotation::CaptionPosition::Inline;
 | 
						|
        lineAnnotation->m_measureDictionary = storage->getObject(dictionary->get("Measure"));
 | 
						|
 | 
						|
        std::vector<PDFReal> captionOffset = loader.readNumberArrayFromDictionary(dictionary, "CO");
 | 
						|
        if (captionOffset.size() == 2)
 | 
						|
        {
 | 
						|
            lineAnnotation->m_captionOffset == QPointF(captionOffset[0], captionOffset[1]);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else if (subtype == "Square" || subtype == "Circle")
 | 
						|
    {
 | 
						|
        PDFSimpleGeometryAnnotation* annotation = new PDFSimpleGeometryAnnotation((subtype == "Square") ? AnnotationType::Square : AnnotationType::Circle);
 | 
						|
        result.reset(annotation);
 | 
						|
 | 
						|
        annotation->m_interiorColor = loader.readNumberArrayFromDictionary(dictionary, "IC");
 | 
						|
        annotation->m_effect = PDFAnnotationBorderEffect::parse(storage, dictionary->get("BE"));
 | 
						|
 | 
						|
        std::vector<PDFReal> differenceRectangle = loader.readNumberArrayFromDictionary(dictionary, "RD");
 | 
						|
        if (differenceRectangle.size() == 4)
 | 
						|
        {
 | 
						|
            annotation->m_geometryRectangle = annotationsRectangle.adjusted(differenceRectangle[0], differenceRectangle[1], -differenceRectangle[2], -differenceRectangle[3]);
 | 
						|
            if (!annotation->m_geometryRectangle.isValid())
 | 
						|
            {
 | 
						|
                annotation->m_geometryRectangle = QRectF();
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else if (subtype == "Polygon" || subtype == "PolyLine")
 | 
						|
    {
 | 
						|
        PDFPolygonalGeometryAnnotation* annotation = new PDFPolygonalGeometryAnnotation((subtype == "Polygon") ? AnnotationType::Polygon : AnnotationType::Polyline);
 | 
						|
        result.reset(annotation);
 | 
						|
 | 
						|
        std::vector<PDFReal> vertices = loader.readNumberArrayFromDictionary(dictionary, "Vertices");
 | 
						|
        const size_t count = vertices.size() / 2;
 | 
						|
        annotation->m_vertices.reserve(count);
 | 
						|
        for (size_t i = 0; i < count; ++i)
 | 
						|
        {
 | 
						|
            annotation->m_vertices.emplace_back(vertices[2 * i], vertices[2 * i + 1]);
 | 
						|
        }
 | 
						|
 | 
						|
        std::vector<QByteArray> lineEndings = loader.readNameArrayFromDictionary(dictionary, "LE");
 | 
						|
        if (lineEndings.size() == 2)
 | 
						|
        {
 | 
						|
            annotation->m_startLineEnding = convertNameToLineEnding(lineEndings[0]);
 | 
						|
            annotation->m_endLineEnding = convertNameToLineEnding(lineEndings[1]);
 | 
						|
        }
 | 
						|
 | 
						|
        annotation->m_interiorColor = loader.readNumberArrayFromDictionary(dictionary, "IC");
 | 
						|
        annotation->m_effect = PDFAnnotationBorderEffect::parse(storage, dictionary->get("BE"));
 | 
						|
 | 
						|
        constexpr const std::array<std::pair<const char*, PDFPolygonalGeometryAnnotation::Intent>, 3> intents = {
 | 
						|
            std::pair<const char*, PDFPolygonalGeometryAnnotation::Intent>{ "PolygonCloud", PDFPolygonalGeometryAnnotation::Intent::Cloud },
 | 
						|
            std::pair<const char*, PDFPolygonalGeometryAnnotation::Intent>{ "PolyLineDimension", PDFPolygonalGeometryAnnotation::Intent::Dimension },
 | 
						|
            std::pair<const char*, PDFPolygonalGeometryAnnotation::Intent>{ "PolygonDimension", PDFPolygonalGeometryAnnotation::Intent::Dimension }
 | 
						|
        };
 | 
						|
 | 
						|
        annotation->m_intent = loader.readEnumByName(dictionary->get("IT"), intents.begin(), intents.end(), PDFPolygonalGeometryAnnotation::Intent::None);
 | 
						|
        annotation->m_measure = storage->getObject(dictionary->get("Measure"));
 | 
						|
        annotation->m_path = parsePath(storage, dictionary, subtype == "Polygon");
 | 
						|
    }
 | 
						|
    else if (subtype == "Highlight" ||
 | 
						|
             subtype == "Underline" ||
 | 
						|
             subtype == "Squiggly" ||
 | 
						|
             subtype == "StrikeOut")
 | 
						|
    {
 | 
						|
        AnnotationType type = AnnotationType::Highlight;
 | 
						|
        if (subtype == "Underline")
 | 
						|
        {
 | 
						|
            type = AnnotationType::Underline;
 | 
						|
        }
 | 
						|
        else if (subtype == "Squiggly")
 | 
						|
        {
 | 
						|
            type = AnnotationType::Squiggly;
 | 
						|
        }
 | 
						|
        else if (subtype == "StrikeOut")
 | 
						|
        {
 | 
						|
            type = AnnotationType::StrikeOut;
 | 
						|
        }
 | 
						|
 | 
						|
        PDFHighlightAnnotation* annotation = new PDFHighlightAnnotation(type);
 | 
						|
        result.reset(annotation);
 | 
						|
 | 
						|
        annotation->m_highlightArea = parseQuadrilaterals(storage, dictionary->get("QuadPoints"), annotationsRectangle);
 | 
						|
    }
 | 
						|
    else if (subtype == "Caret")
 | 
						|
    {
 | 
						|
        PDFCaretAnnotation* annotation = new PDFCaretAnnotation();
 | 
						|
        result.reset(annotation);
 | 
						|
 | 
						|
        std::vector<PDFReal> differenceRectangle = loader.readNumberArrayFromDictionary(dictionary, "RD");
 | 
						|
        if (differenceRectangle.size() == 4)
 | 
						|
        {
 | 
						|
            annotation->m_caretRectangle = annotationsRectangle.adjusted(differenceRectangle[0], differenceRectangle[1], -differenceRectangle[2], -differenceRectangle[3]);
 | 
						|
            if (!annotation->m_caretRectangle.isValid())
 | 
						|
            {
 | 
						|
                annotation->m_caretRectangle = QRectF();
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        annotation->m_symbol = (loader.readNameFromDictionary(dictionary, "Sy") == "P") ? PDFCaretAnnotation::Symbol::Paragraph : PDFCaretAnnotation::Symbol::None;
 | 
						|
    }
 | 
						|
    else if (subtype == "Stamp")
 | 
						|
    {
 | 
						|
        PDFStampAnnotation* annotation = new PDFStampAnnotation();
 | 
						|
        result.reset(annotation);
 | 
						|
 | 
						|
        constexpr const std::array stamps = {
 | 
						|
            std::pair<const char*, Stamp>{ "Approved", Stamp::Approved },
 | 
						|
            std::pair<const char*, Stamp>{ "AsIs", Stamp::AsIs },
 | 
						|
            std::pair<const char*, Stamp>{ "Confidential", Stamp::Confidential },
 | 
						|
            std::pair<const char*, Stamp>{ "Departmental", Stamp::Departmental },
 | 
						|
            std::pair<const char*, Stamp>{ "Draft", Stamp::Draft },
 | 
						|
            std::pair<const char*, Stamp>{ "Experimental", Stamp::Experimental },
 | 
						|
            std::pair<const char*, Stamp>{ "Expired", Stamp::Expired },
 | 
						|
            std::pair<const char*, Stamp>{ "Final", Stamp::Final },
 | 
						|
            std::pair<const char*, Stamp>{ "ForComment", Stamp::ForComment },
 | 
						|
            std::pair<const char*, Stamp>{ "ForPublicRelease", Stamp::ForPublicRelease },
 | 
						|
            std::pair<const char*, Stamp>{ "NotApproved", Stamp::NotApproved },
 | 
						|
            std::pair<const char*, Stamp>{ "NotForPublicRelease", Stamp::NotForPublicRelease },
 | 
						|
            std::pair<const char*, Stamp>{ "Sold", Stamp::Sold },
 | 
						|
            std::pair<const char*, Stamp>{ "TopSecret", Stamp::TopSecret }
 | 
						|
        };
 | 
						|
 | 
						|
        annotation->m_stamp = loader.readEnumByName(dictionary->get("Name"), stamps.begin(), stamps.end(), Stamp::Draft);
 | 
						|
 | 
						|
        constexpr const std::array stampsIntents = {
 | 
						|
            std::pair<const char*, StampIntent>{ "Stamp", StampIntent::Stamp },
 | 
						|
            std::pair<const char*, StampIntent>{ "StampImage", StampIntent::StampImage },
 | 
						|
            std::pair<const char*, StampIntent>{ "StampSnapshot", StampIntent::StampSnapshot },
 | 
						|
        };
 | 
						|
 | 
						|
        annotation->m_intent = loader.readEnumByName(dictionary->get("IT"), stampsIntents.begin(), stampsIntents.end(), StampIntent::Stamp);
 | 
						|
    }
 | 
						|
    else if (subtype == "Ink")
 | 
						|
    {
 | 
						|
        PDFInkAnnotation* annotation = new PDFInkAnnotation();
 | 
						|
        result.reset(annotation);
 | 
						|
 | 
						|
        annotation->m_inkPath = parsePath(storage, dictionary, false);
 | 
						|
        if (annotation->m_inkPath.isEmpty())
 | 
						|
        {
 | 
						|
            PDFObject inkList = storage->getObject(dictionary->get("InkList"));
 | 
						|
            if (inkList.isArray())
 | 
						|
            {
 | 
						|
                const PDFArray* inkListArray = inkList.getArray();
 | 
						|
                for (size_t i = 0, count = inkListArray->getCount(); i < count; ++i)
 | 
						|
                {
 | 
						|
                    std::vector<PDFReal> points = loader.readNumberArray(inkListArray->getItem(i));
 | 
						|
                    const size_t pointCount = points.size() / 2;
 | 
						|
 | 
						|
                    for (size_t j = 0; j < pointCount; ++j)
 | 
						|
                    {
 | 
						|
                        QPointF point(points[j * 2], points[j * 2 + 1]);
 | 
						|
 | 
						|
                        if (j == 0)
 | 
						|
                        {
 | 
						|
                            annotation->m_inkPath.moveTo(point);
 | 
						|
                        }
 | 
						|
                        else
 | 
						|
                        {
 | 
						|
                            annotation->m_inkPath.lineTo(point);
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else if (subtype == "Popup")
 | 
						|
    {
 | 
						|
        PDFPopupAnnotation* annotation = new PDFPopupAnnotation();
 | 
						|
        result.reset(annotation);
 | 
						|
 | 
						|
        annotation->m_opened = loader.readBooleanFromDictionary(dictionary, "Open", false);
 | 
						|
    }
 | 
						|
    else if (subtype == "FileAttachment")
 | 
						|
    {
 | 
						|
        PDFFileAttachmentAnnotation* annotation = new PDFFileAttachmentAnnotation();
 | 
						|
        result.reset(annotation);
 | 
						|
 | 
						|
        annotation->m_fileSpecification = PDFFileSpecification::parse(storage, dictionary->get("FS"));
 | 
						|
 | 
						|
        constexpr const std::array<std::pair<const char*, FileAttachmentIcon>, 4> icons = {
 | 
						|
            std::pair<const char*, FileAttachmentIcon>{ "Graph", FileAttachmentIcon::Graph },
 | 
						|
            std::pair<const char*, FileAttachmentIcon>{ "Paperclip", FileAttachmentIcon::Paperclip },
 | 
						|
            std::pair<const char*, FileAttachmentIcon>{ "PushPin", FileAttachmentIcon::PushPin },
 | 
						|
            std::pair<const char*, FileAttachmentIcon>{ "Tag", FileAttachmentIcon::Tag }
 | 
						|
        };
 | 
						|
 | 
						|
        annotation->m_icon = loader.readEnumByName(dictionary->get("Name"), icons.begin(), icons.end(), FileAttachmentIcon::PushPin);
 | 
						|
    }
 | 
						|
    else if (subtype == "Redact")
 | 
						|
    {
 | 
						|
        PDFRedactAnnotation* annotation = new PDFRedactAnnotation();
 | 
						|
        result.reset(annotation);
 | 
						|
 | 
						|
        annotation->m_redactionRegion = parseQuadrilaterals(storage, dictionary->get("QuadPoints"), annotationsRectangle);
 | 
						|
        annotation->m_interiorColor = loader.readNumberArrayFromDictionary(dictionary, "IC");
 | 
						|
        annotation->m_overlayForm = dictionary->get("RO");
 | 
						|
        annotation->m_overlayText = loader.readTextStringFromDictionary(dictionary, "OverlayText", QString());
 | 
						|
        annotation->m_repeat = loader.readBooleanFromDictionary(dictionary, "Repeat", false);
 | 
						|
        annotation->m_defaultAppearance = loader.readStringFromDictionary(dictionary, "DA");
 | 
						|
        annotation->m_justification = loader.readIntegerFromDictionary(dictionary, "Q", 0);
 | 
						|
    }
 | 
						|
    else if (subtype == "Sound")
 | 
						|
    {
 | 
						|
        PDFSoundAnnotation* annotation = new PDFSoundAnnotation();
 | 
						|
        result.reset(annotation);
 | 
						|
 | 
						|
        annotation->m_sound = PDFSound::parse(storage, dictionary->get("Sound"));
 | 
						|
 | 
						|
        constexpr const std::array<std::pair<const char*, PDFSoundAnnotation::Icon>, 2> icons = {
 | 
						|
            std::pair<const char*, PDFSoundAnnotation::Icon>{ "Speaker", PDFSoundAnnotation::Icon::Speaker },
 | 
						|
            std::pair<const char*, PDFSoundAnnotation::Icon>{ "Mic", PDFSoundAnnotation::Icon::Microphone }
 | 
						|
        };
 | 
						|
 | 
						|
        annotation->m_icon = loader.readEnumByName(dictionary->get("Name"), icons.begin(), icons.end(), PDFSoundAnnotation::Icon::Speaker);
 | 
						|
    }
 | 
						|
    else if (subtype == "Movie")
 | 
						|
    {
 | 
						|
        PDFMovieAnnotation* annotation = new PDFMovieAnnotation();
 | 
						|
        result.reset(annotation);
 | 
						|
 | 
						|
        annotation->m_movieTitle = loader.readStringFromDictionary(dictionary, "T");
 | 
						|
        annotation->m_movie = PDFMovie::parse(storage, dictionary->get("Movie"));
 | 
						|
 | 
						|
        PDFObject activation = storage->getObject(dictionary->get("A"));
 | 
						|
        if (activation.isBool())
 | 
						|
        {
 | 
						|
            annotation->m_playMovie = activation.getBool();
 | 
						|
        }
 | 
						|
        else if (activation.isDictionary())
 | 
						|
        {
 | 
						|
            annotation->m_playMovie = true;
 | 
						|
            annotation->m_movieActivation = PDFMovieActivation::parse(storage, activation);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else if (subtype == "Screen")
 | 
						|
    {
 | 
						|
        PDFScreenAnnotation* annotation = new PDFScreenAnnotation();
 | 
						|
        result.reset(annotation);
 | 
						|
 | 
						|
        annotation->m_screenTitle = loader.readTextStringFromDictionary(dictionary, "T", QString());
 | 
						|
        annotation->m_appearanceCharacteristics = PDFAnnotationAppearanceCharacteristics::parse(storage, dictionary->get("MK"));
 | 
						|
        annotation->m_action = PDFAction::parse(storage, dictionary->get("A"));
 | 
						|
        annotation->m_additionalActions = PDFAnnotationAdditionalActions::parse(storage, dictionary->get("AA"), dictionary->get("A"));
 | 
						|
    }
 | 
						|
    else if (subtype == "Widget")
 | 
						|
    {
 | 
						|
        PDFWidgetAnnotation* annotation = new PDFWidgetAnnotation();
 | 
						|
        result.reset(annotation);
 | 
						|
 | 
						|
        constexpr const std::array<std::pair<const char*, PDFWidgetAnnotation::HighlightMode>, 5> highlightModes = {
 | 
						|
            std::pair<const char*, PDFWidgetAnnotation::HighlightMode>{ "N", PDFWidgetAnnotation::HighlightMode::None },
 | 
						|
            std::pair<const char*, PDFWidgetAnnotation::HighlightMode>{ "I", PDFWidgetAnnotation::HighlightMode::Invert },
 | 
						|
            std::pair<const char*, PDFWidgetAnnotation::HighlightMode>{ "O", PDFWidgetAnnotation::HighlightMode::Outline },
 | 
						|
            std::pair<const char*, PDFWidgetAnnotation::HighlightMode>{ "P", PDFWidgetAnnotation::HighlightMode::Push },
 | 
						|
            std::pair<const char*, PDFWidgetAnnotation::HighlightMode>{ "T", PDFWidgetAnnotation::HighlightMode::Toggle }
 | 
						|
        };
 | 
						|
 | 
						|
        annotation->m_highlightMode = loader.readEnumByName(dictionary->get("H"), highlightModes.begin(), highlightModes.end(), PDFWidgetAnnotation::HighlightMode::Invert);
 | 
						|
        annotation->m_appearanceCharacteristics = PDFAnnotationAppearanceCharacteristics::parse(storage, dictionary->get("MK"));
 | 
						|
        annotation->m_action = PDFAction::parse(storage, dictionary->get("A"));
 | 
						|
        annotation->m_additionalActions = PDFAnnotationAdditionalActions::parse(storage, dictionary->get("AA"), dictionary->get("A"));
 | 
						|
    }
 | 
						|
    else if (subtype == "PrinterMark")
 | 
						|
    {
 | 
						|
        PDFPrinterMarkAnnotation* annotation = new PDFPrinterMarkAnnotation();
 | 
						|
        result.reset(annotation);
 | 
						|
    }
 | 
						|
    else if (subtype == "TrapNet")
 | 
						|
    {
 | 
						|
        PDFTrapNetworkAnnotation* annotation = new PDFTrapNetworkAnnotation();
 | 
						|
        result.reset(annotation);
 | 
						|
    }
 | 
						|
    else if (subtype == "Watermark")
 | 
						|
    {
 | 
						|
        PDFWatermarkAnnotation* annotation = new PDFWatermarkAnnotation();
 | 
						|
        result.reset(annotation);
 | 
						|
 | 
						|
        if (const PDFDictionary* fixedPrintDictionary = storage->getDictionaryFromObject(dictionary->get("FixedPrint")))
 | 
						|
        {
 | 
						|
            annotation->m_matrix = loader.readMatrixFromDictionary(fixedPrintDictionary, "Matrix", QMatrix());
 | 
						|
            annotation->m_relativeHorizontalOffset = loader.readNumberFromDictionary(fixedPrintDictionary, "H", 0.0);
 | 
						|
            annotation->m_relativeVerticalOffset = loader.readNumberFromDictionary(fixedPrintDictionary, "V", 0.0);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else if (subtype == "Projection")
 | 
						|
    {
 | 
						|
        PDFProjectionAnnotation* annotation = new PDFProjectionAnnotation();
 | 
						|
        result.reset(annotation);
 | 
						|
    }
 | 
						|
    else if (subtype == "3D")
 | 
						|
    {
 | 
						|
        PDF3DAnnotation* annotation = new PDF3DAnnotation();
 | 
						|
        result.reset(annotation);
 | 
						|
 | 
						|
        annotation->m_stream = PDF3DStream::parse(storage, dictionary->get("3DD"));
 | 
						|
 | 
						|
        const std::vector<PDF3DView>& views = annotation->getStream().getViews();
 | 
						|
        PDFObject defaultViewObject = storage->getObject(dictionary->get("DV"));
 | 
						|
        if (defaultViewObject.isDictionary())
 | 
						|
        {
 | 
						|
            annotation->m_defaultView = PDF3DView::parse(storage, defaultViewObject);
 | 
						|
        }
 | 
						|
        else if (defaultViewObject.isInt())
 | 
						|
        {
 | 
						|
            PDFInteger index = defaultViewObject.getInteger();
 | 
						|
            if (index >= 0 && index < PDFInteger(views.size()))
 | 
						|
            {
 | 
						|
                annotation->m_defaultView = views[index];
 | 
						|
            }
 | 
						|
        }
 | 
						|
        else if (defaultViewObject.isName() && !views.empty())
 | 
						|
        {
 | 
						|
            QByteArray name = defaultViewObject.getString();
 | 
						|
            if (name == "F")
 | 
						|
            {
 | 
						|
                annotation->m_defaultView = views.front();
 | 
						|
            }
 | 
						|
            else if (name == "L")
 | 
						|
            {
 | 
						|
                annotation->m_defaultView = views.back();
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        annotation->m_activation = PDF3DActivation::parse(storage, dictionary->get("3DA"));
 | 
						|
        annotation->m_interactive = loader.readBooleanFromDictionary(dictionary, "3DI", true);
 | 
						|
        annotation->m_viewBox = loader.readRectangle(dictionary->get("3DB"), QRectF());
 | 
						|
    }
 | 
						|
    else if (subtype == "RichMedia")
 | 
						|
    {
 | 
						|
        PDFRichMediaAnnotation* annotation = new PDFRichMediaAnnotation();
 | 
						|
        result.reset(annotation);
 | 
						|
 | 
						|
        annotation->m_content = PDFRichMediaContent::parse(storage, dictionary->get("RichMediaContent"));
 | 
						|
        annotation->m_settings = PDFRichMediaSettings::parse(storage, dictionary->get("RichMediaSettings"));
 | 
						|
    }
 | 
						|
 | 
						|
    if (!result)
 | 
						|
    {
 | 
						|
        // Invalid annotation type
 | 
						|
        return result;
 | 
						|
    }
 | 
						|
 | 
						|
    // Load common data for annotation
 | 
						|
    result->m_selfReference = reference;
 | 
						|
    result->m_rectangle = annotationsRectangle;
 | 
						|
    result->m_contents = loader.readTextStringFromDictionary(dictionary, "Contents", QString());
 | 
						|
    result->m_pageReference = loader.readReferenceFromDictionary(dictionary, "P");
 | 
						|
    result->m_name = loader.readTextStringFromDictionary(dictionary, "NM", QString());
 | 
						|
 | 
						|
    QByteArray string = loader.readStringFromDictionary(dictionary, "M");
 | 
						|
    result->m_lastModified = PDFEncoding::convertToDateTime(string);
 | 
						|
    if (!result->m_lastModified.isValid())
 | 
						|
    {
 | 
						|
        result->m_lastModifiedString = loader.readTextStringFromDictionary(dictionary, "M", QString());
 | 
						|
    }
 | 
						|
 | 
						|
    result->m_flags = Flags(loader.readIntegerFromDictionary(dictionary, "F", 0));
 | 
						|
    result->m_appearanceStreams = PDFAppeareanceStreams::parse(storage, dictionary->get("AP"));
 | 
						|
    result->m_appearanceState = loader.readNameFromDictionary(dictionary, "AS");
 | 
						|
 | 
						|
    result->m_annotationBorder = PDFAnnotationBorder::parseBS(storage, dictionary->get("BS"));
 | 
						|
    if (!result->m_annotationBorder.isValid())
 | 
						|
    {
 | 
						|
        result->m_annotationBorder = PDFAnnotationBorder::parseBorder(storage, dictionary->get("Border"));
 | 
						|
    }
 | 
						|
    result->m_color = loader.readNumberArrayFromDictionary(dictionary, "C");
 | 
						|
    result->m_structParent = loader.readIntegerFromDictionary(dictionary, "StructParent", 0);
 | 
						|
    result->m_optionalContentReference = loader.readReferenceFromDictionary(dictionary, "OC");
 | 
						|
    result->m_strokingOpacity = loader.readNumberFromDictionary(dictionary, "CA", 1.0);
 | 
						|
    result->m_fillingOpacity = loader.readNumberFromDictionary(dictionary, "ca", result->m_strokingOpacity);
 | 
						|
    result->m_blendMode = PDFBlendModeInfo::getBlendMode(loader.readNameFromDictionary(dictionary, "BM"));
 | 
						|
 | 
						|
    if (result->m_blendMode == BlendMode::Invalid)
 | 
						|
    {
 | 
						|
        result->m_blendMode = BlendMode::Normal;
 | 
						|
    }
 | 
						|
 | 
						|
    result->m_language = loader.readTextStringFromDictionary(dictionary, "Lang", QString());
 | 
						|
 | 
						|
    if (PDFMarkupAnnotation* markupAnnotation = result->asMarkupAnnotation())
 | 
						|
    {
 | 
						|
        markupAnnotation->m_windowTitle = loader.readTextStringFromDictionary(dictionary, "T", QString());
 | 
						|
        markupAnnotation->m_popupAnnotation = loader.readReferenceFromDictionary(dictionary, "Popup");
 | 
						|
        markupAnnotation->m_richTextString = loader.readTextStringFromDictionary(dictionary, "RC", QString());
 | 
						|
        markupAnnotation->m_creationDate = PDFEncoding::convertToDateTime(loader.readStringFromDictionary(dictionary, "CreationDate"));
 | 
						|
        markupAnnotation->m_inReplyTo = loader.readReferenceFromDictionary(dictionary, "IRT");
 | 
						|
        markupAnnotation->m_subject = loader.readTextStringFromDictionary(dictionary, "Subj", QString());
 | 
						|
        markupAnnotation->m_replyType = (loader.readNameFromDictionary(dictionary, "RT") == "Group") ? PDFMarkupAnnotation::ReplyType::Group : PDFMarkupAnnotation::ReplyType::Reply;
 | 
						|
        markupAnnotation->m_intent = loader.readNameFromDictionary(dictionary, "IT");
 | 
						|
        markupAnnotation->m_externalData = storage->getObject(dictionary->get("ExData"));
 | 
						|
    }
 | 
						|
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
PDFAnnotationQuadrilaterals PDFAnnotation::parseQuadrilaterals(const PDFObjectStorage* storage, PDFObject quadrilateralsObject, const QRectF annotationRect)
 | 
						|
{
 | 
						|
    QPainterPath path;
 | 
						|
    PDFAnnotationQuadrilaterals::Quadrilaterals quadrilaterals;
 | 
						|
 | 
						|
    PDFDocumentDataLoaderDecorator loader(storage);
 | 
						|
    std::vector<PDFReal> points = loader.readNumberArray(quadrilateralsObject);
 | 
						|
    const size_t quadrilateralCount = points.size() / 8;
 | 
						|
    path.reserve(int(quadrilateralCount) + 5);
 | 
						|
    quadrilaterals.reserve(quadrilateralCount);
 | 
						|
    for (size_t i = 0; i < quadrilateralCount; ++i)
 | 
						|
    {
 | 
						|
        const size_t offset = i * 8;
 | 
						|
        QPointF p1(points[offset + 0], points[offset + 1]);
 | 
						|
        QPointF p2(points[offset + 2], points[offset + 3]);
 | 
						|
        QPointF p3(points[offset + 4], points[offset + 5]);
 | 
						|
        QPointF p4(points[offset + 6], points[offset + 7]);
 | 
						|
 | 
						|
        path.moveTo(p1);
 | 
						|
        path.lineTo(p2);
 | 
						|
        path.lineTo(p4);
 | 
						|
        path.lineTo(p3);
 | 
						|
        path.closeSubpath();
 | 
						|
 | 
						|
        quadrilaterals.emplace_back(PDFAnnotationQuadrilaterals::Quadrilateral{ p1, p2, p3, p4 });
 | 
						|
    }
 | 
						|
 | 
						|
    if (path.isEmpty() && annotationRect.isValid())
 | 
						|
    {
 | 
						|
        // Jakub Melka: we are using points at the top, because PDF has inverted y axis
 | 
						|
        // against the Qt's y axis.
 | 
						|
        path.addRect(annotationRect);
 | 
						|
        quadrilaterals.emplace_back(PDFAnnotationQuadrilaterals::Quadrilateral{ annotationRect.topLeft(), annotationRect.topRight(), annotationRect.bottomLeft(), annotationRect.bottomRight() });
 | 
						|
    }
 | 
						|
 | 
						|
    return PDFAnnotationQuadrilaterals(qMove(path), qMove(quadrilaterals));
 | 
						|
}
 | 
						|
 | 
						|
constexpr const std::array<std::pair<AnnotationLineEnding, const char*>, 10> lineEndings = {
 | 
						|
    std::pair<AnnotationLineEnding, const char*>{ AnnotationLineEnding::None, "None" },
 | 
						|
    std::pair<AnnotationLineEnding, const char*>{ AnnotationLineEnding::Square, "Square" },
 | 
						|
    std::pair<AnnotationLineEnding, const char*>{ AnnotationLineEnding::Circle, "Circle" },
 | 
						|
    std::pair<AnnotationLineEnding, const char*>{ AnnotationLineEnding::Diamond, "Diamond" },
 | 
						|
    std::pair<AnnotationLineEnding, const char*>{ AnnotationLineEnding::OpenArrow, "OpenArrow" },
 | 
						|
    std::pair<AnnotationLineEnding, const char*>{ AnnotationLineEnding::ClosedArrow, "ClosedArrow" },
 | 
						|
    std::pair<AnnotationLineEnding, const char*>{ AnnotationLineEnding::Butt, "Butt" },
 | 
						|
    std::pair<AnnotationLineEnding, const char*>{ AnnotationLineEnding::ROpenArrow, "ROpenArrow" },
 | 
						|
    std::pair<AnnotationLineEnding, const char*>{ AnnotationLineEnding::RClosedArrow, "RClosedArrow" },
 | 
						|
    std::pair<AnnotationLineEnding, const char*>{ AnnotationLineEnding::Slash, "Slash" }
 | 
						|
};
 | 
						|
 | 
						|
AnnotationLineEnding PDFAnnotation::convertNameToLineEnding(const QByteArray& name)
 | 
						|
{
 | 
						|
    auto it = std::find_if(lineEndings.cbegin(), lineEndings.cend(), [&name](const auto& item) { return name == item.second; });
 | 
						|
    if (it != lineEndings.cend())
 | 
						|
    {
 | 
						|
        return it->first;
 | 
						|
    }
 | 
						|
 | 
						|
    return AnnotationLineEnding::None;
 | 
						|
}
 | 
						|
 | 
						|
QByteArray PDFAnnotation::convertLineEndingToName(AnnotationLineEnding lineEnding)
 | 
						|
{
 | 
						|
    auto it = std::find_if(lineEndings.cbegin(), lineEndings.cend(), [lineEnding](const auto& item) { return lineEnding == item.first; });
 | 
						|
    if (it != lineEndings.cend())
 | 
						|
    {
 | 
						|
        return it->second;
 | 
						|
    }
 | 
						|
 | 
						|
    return lineEndings.front().second;
 | 
						|
}
 | 
						|
 | 
						|
QColor PDFAnnotation::getStrokeColor() const
 | 
						|
{
 | 
						|
    return getDrawColorFromAnnotationColor(getColor(), getStrokeOpacity());
 | 
						|
}
 | 
						|
 | 
						|
QColor PDFAnnotation::getFillColor() const
 | 
						|
{
 | 
						|
    return QColor();
 | 
						|
}
 | 
						|
 | 
						|
QColor PDFAnnotation::getDrawColorFromAnnotationColor(const std::vector<PDFReal>& color, PDFReal opacity)
 | 
						|
{
 | 
						|
    switch (color.size())
 | 
						|
    {
 | 
						|
        case 1:
 | 
						|
        {
 | 
						|
            const PDFReal gray = color.back();
 | 
						|
            return QColor::fromRgbF(gray, gray, gray, opacity);
 | 
						|
        }
 | 
						|
 | 
						|
        case 3:
 | 
						|
        {
 | 
						|
            const PDFReal r = color[0];
 | 
						|
            const PDFReal g = color[1];
 | 
						|
            const PDFReal b = color[2];
 | 
						|
            return QColor::fromRgbF(r, g, b, opacity);
 | 
						|
        }
 | 
						|
 | 
						|
        case 4:
 | 
						|
        {
 | 
						|
            const PDFReal c = color[0];
 | 
						|
            const PDFReal m = color[1];
 | 
						|
            const PDFReal y = color[2];
 | 
						|
            const PDFReal k = color[3];
 | 
						|
            return QColor::fromCmykF(c, m, y, k, opacity);
 | 
						|
        }
 | 
						|
 | 
						|
        default:
 | 
						|
            break;
 | 
						|
    }
 | 
						|
 | 
						|
    QColor black(Qt::black);
 | 
						|
    black.setAlphaF(opacity);
 | 
						|
    return black;
 | 
						|
}
 | 
						|
 | 
						|
bool PDFAnnotation::isTypeEditable(AnnotationType type)
 | 
						|
{
 | 
						|
    switch (type)
 | 
						|
    {
 | 
						|
        case AnnotationType::Text:
 | 
						|
        case AnnotationType::Link:
 | 
						|
        case AnnotationType::FreeText:
 | 
						|
        case AnnotationType::Line:
 | 
						|
        case AnnotationType::Square:
 | 
						|
        case AnnotationType::Circle:
 | 
						|
        case AnnotationType::Polygon:
 | 
						|
        case AnnotationType::Polyline:
 | 
						|
        case AnnotationType::Highlight:
 | 
						|
        case AnnotationType::Underline:
 | 
						|
        case AnnotationType::Squiggly:
 | 
						|
        case AnnotationType::StrikeOut:
 | 
						|
        case AnnotationType::Stamp:
 | 
						|
        case AnnotationType::Caret:
 | 
						|
        case AnnotationType::Ink:
 | 
						|
        case AnnotationType::FileAttachment:
 | 
						|
        case AnnotationType::PrinterMark:
 | 
						|
        case AnnotationType::Watermark:
 | 
						|
        case AnnotationType::Redact:
 | 
						|
            return true;
 | 
						|
 | 
						|
        default:
 | 
						|
            break;
 | 
						|
    }
 | 
						|
 | 
						|
    return false;
 | 
						|
}
 | 
						|
 | 
						|
QPen PDFAnnotation::getPen() const
 | 
						|
{
 | 
						|
    QColor strokeColor = getStrokeColor();
 | 
						|
    const PDFAnnotationBorder& border = getBorder();
 | 
						|
 | 
						|
    if (qFuzzyIsNull(border.getWidth()))
 | 
						|
    {
 | 
						|
        // No border is drawn
 | 
						|
        return Qt::NoPen;
 | 
						|
    }
 | 
						|
 | 
						|
    QPen pen(strokeColor);
 | 
						|
    pen.setWidthF(border.getWidth());
 | 
						|
 | 
						|
    if (!border.getDashPattern().empty())
 | 
						|
    {
 | 
						|
        PDFLineDashPattern lineDashPattern(border.getDashPattern(), 0.0);
 | 
						|
        pen.setStyle(Qt::CustomDashLine);
 | 
						|
        pen.setDashPattern(QVector<PDFReal>::fromStdVector(lineDashPattern.getDashArray()));
 | 
						|
        pen.setDashOffset(lineDashPattern.getDashOffset());
 | 
						|
    }
 | 
						|
 | 
						|
    return pen;
 | 
						|
}
 | 
						|
 | 
						|
QBrush PDFAnnotation::getBrush() const
 | 
						|
{
 | 
						|
    QColor color = getFillColor();
 | 
						|
    if (color.isValid())
 | 
						|
    {
 | 
						|
        return QBrush(color, Qt::SolidPattern);
 | 
						|
    }
 | 
						|
 | 
						|
    return QBrush(Qt::NoBrush);
 | 
						|
}
 | 
						|
 | 
						|
PDFAnnotationCalloutLine PDFAnnotationCalloutLine::parse(const PDFObjectStorage* storage, PDFObject object)
 | 
						|
{
 | 
						|
    PDFDocumentDataLoaderDecorator loader(storage);
 | 
						|
    std::vector<PDFReal> points = loader.readNumberArray(object);
 | 
						|
 | 
						|
    switch (points.size())
 | 
						|
    {
 | 
						|
        case 4:
 | 
						|
            return PDFAnnotationCalloutLine(QPointF(points[0], points[1]), QPointF(points[2], points[3]));
 | 
						|
 | 
						|
        case 6:
 | 
						|
            return PDFAnnotationCalloutLine(QPointF(points[0], points[1]), QPointF(points[2], points[3]), QPointF(points[4], points[5]));
 | 
						|
 | 
						|
        default:
 | 
						|
            break;
 | 
						|
    }
 | 
						|
 | 
						|
    return PDFAnnotationCalloutLine();
 | 
						|
}
 | 
						|
 | 
						|
PDFAnnotationAppearanceCharacteristics PDFAnnotationAppearanceCharacteristics::parse(const PDFObjectStorage* storage, PDFObject object)
 | 
						|
{
 | 
						|
    PDFAnnotationAppearanceCharacteristics result;
 | 
						|
 | 
						|
    if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object))
 | 
						|
    {
 | 
						|
        PDFDocumentDataLoaderDecorator loader(storage);
 | 
						|
 | 
						|
        result.m_rotation = loader.readIntegerFromDictionary(dictionary, "R", 0);
 | 
						|
        result.m_borderColor = loader.readNumberArrayFromDictionary(dictionary, "BC");
 | 
						|
        result.m_backgroundColor = loader.readNumberArrayFromDictionary(dictionary, "BG");
 | 
						|
        result.m_normalCaption = loader.readTextStringFromDictionary(dictionary, "CA", QString());
 | 
						|
        result.m_rolloverCaption = loader.readTextStringFromDictionary(dictionary, "RC", QString());
 | 
						|
        result.m_downCaption = loader.readTextStringFromDictionary(dictionary, "AC", QString());
 | 
						|
        result.m_normalIcon = storage->getObject(dictionary->get("I"));
 | 
						|
        result.m_rolloverIcon = storage->getObject(dictionary->get("RI"));
 | 
						|
        result.m_downIcon = storage->getObject(dictionary->get("IX"));
 | 
						|
        result.m_iconFit = PDFAnnotationIconFitInfo::parse(storage, dictionary->get("IF"));
 | 
						|
        result.m_pushButtonMode = static_cast<PushButtonMode>(loader.readIntegerFromDictionary(dictionary, "TP", PDFInteger(PDFAnnotationAppearanceCharacteristics::PushButtonMode::NoIcon)));
 | 
						|
    }
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
PDFAnnotationIconFitInfo PDFAnnotationIconFitInfo::parse(const PDFObjectStorage* storage, PDFObject object)
 | 
						|
{
 | 
						|
    PDFAnnotationIconFitInfo info;
 | 
						|
 | 
						|
    if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object))
 | 
						|
    {
 | 
						|
        PDFDocumentDataLoaderDecorator loader(storage);
 | 
						|
 | 
						|
        constexpr const std::array<std::pair<const char*, PDFAnnotationIconFitInfo::ScaleCondition>, 4> scaleConditions = {
 | 
						|
            std::pair<const char*, PDFAnnotationIconFitInfo::ScaleCondition>{ "A", PDFAnnotationIconFitInfo::ScaleCondition::Always },
 | 
						|
            std::pair<const char*, PDFAnnotationIconFitInfo::ScaleCondition>{ "B", PDFAnnotationIconFitInfo::ScaleCondition::ScaleBigger },
 | 
						|
            std::pair<const char*, PDFAnnotationIconFitInfo::ScaleCondition>{ "S", PDFAnnotationIconFitInfo::ScaleCondition::ScaleSmaller },
 | 
						|
            std::pair<const char*, PDFAnnotationIconFitInfo::ScaleCondition>{ "N", PDFAnnotationIconFitInfo::ScaleCondition::Never }
 | 
						|
        };
 | 
						|
 | 
						|
        constexpr const std::array<std::pair<const char*, PDFAnnotationIconFitInfo::ScaleType>, 2> scaleTypes = {
 | 
						|
            std::pair<const char*, PDFAnnotationIconFitInfo::ScaleType>{ "A", PDFAnnotationIconFitInfo::ScaleType::Anamorphic },
 | 
						|
            std::pair<const char*, PDFAnnotationIconFitInfo::ScaleType>{ "P", PDFAnnotationIconFitInfo::ScaleType::Proportional }
 | 
						|
        };
 | 
						|
 | 
						|
        std::vector<PDFReal> point = loader.readNumberArrayFromDictionary(dictionary, "A");
 | 
						|
        if (point.size() != 2)
 | 
						|
        {
 | 
						|
            point.resize(2, 0.5);
 | 
						|
        }
 | 
						|
 | 
						|
        info.m_scaleCondition = loader.readEnumByName(dictionary->get("SW"), scaleConditions.begin(), scaleConditions.end(), PDFAnnotationIconFitInfo::ScaleCondition::Always);
 | 
						|
        info.m_scaleType = loader.readEnumByName(dictionary->get("S"), scaleTypes.begin(), scaleTypes.end(), PDFAnnotationIconFitInfo::ScaleType::Proportional);
 | 
						|
        info.m_relativeProportionalPosition = QPointF(point[0], point[1]);
 | 
						|
        info.m_fullBox = loader.readBooleanFromDictionary(dictionary, "FB", false);
 | 
						|
    }
 | 
						|
 | 
						|
    return info;
 | 
						|
}
 | 
						|
 | 
						|
PDFAnnotationAdditionalActions PDFAnnotationAdditionalActions::parse(const PDFObjectStorage* storage, PDFObject object, PDFObject defaultAction)
 | 
						|
{
 | 
						|
    PDFAnnotationAdditionalActions result;
 | 
						|
 | 
						|
    if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object))
 | 
						|
    {
 | 
						|
        result.m_actions[CursorEnter] = PDFAction::parse(storage, dictionary->get("E"));
 | 
						|
        result.m_actions[CursorLeave] = PDFAction::parse(storage, dictionary->get("X"));
 | 
						|
        result.m_actions[MousePressed] = PDFAction::parse(storage, dictionary->get("D"));
 | 
						|
        result.m_actions[MouseReleased] = PDFAction::parse(storage, dictionary->get("U"));
 | 
						|
        result.m_actions[FocusIn] = PDFAction::parse(storage, dictionary->get("Fo"));
 | 
						|
        result.m_actions[FocusOut] = PDFAction::parse(storage, dictionary->get("Bl"));
 | 
						|
        result.m_actions[PageOpened] = PDFAction::parse(storage, dictionary->get("PO"));
 | 
						|
        result.m_actions[PageClosed] = PDFAction::parse(storage, dictionary->get("PC"));
 | 
						|
        result.m_actions[PageShow] = PDFAction::parse(storage, dictionary->get("PV"));
 | 
						|
        result.m_actions[PageHide] = PDFAction::parse(storage, dictionary->get("PI"));
 | 
						|
        result.m_actions[FormFieldModified] = PDFAction::parse(storage, dictionary->get("K"));
 | 
						|
        result.m_actions[FormFieldFormatted] = PDFAction::parse(storage, dictionary->get("F"));
 | 
						|
        result.m_actions[FormFieldValidated] = PDFAction::parse(storage, dictionary->get("V"));
 | 
						|
        result.m_actions[FormFieldCalculated] = PDFAction::parse(storage, dictionary->get("C"));
 | 
						|
    }
 | 
						|
 | 
						|
    result.m_actions[Default] = PDFAction::parse(storage, defaultAction);
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
PDFAnnotationManager::PDFAnnotationManager(PDFFontCache* fontCache,
 | 
						|
                                           const PDFCMSManager* cmsManager,
 | 
						|
                                           const PDFOptionalContentActivity* optionalActivity,
 | 
						|
                                           PDFMeshQualitySettings meshQualitySettings,
 | 
						|
                                           PDFRenderer::Features features,
 | 
						|
                                           Target target,
 | 
						|
                                           QObject* parent) :
 | 
						|
    BaseClass(parent),
 | 
						|
    m_document(nullptr),
 | 
						|
    m_fontCache(fontCache),
 | 
						|
    m_cmsManager(cmsManager),
 | 
						|
    m_optionalActivity(optionalActivity),
 | 
						|
    m_formManager(nullptr),
 | 
						|
    m_meshQualitySettings(meshQualitySettings),
 | 
						|
    m_features(features),
 | 
						|
    m_target(target)
 | 
						|
{
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
PDFAnnotationManager::~PDFAnnotationManager()
 | 
						|
{
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
QMatrix PDFAnnotationManager::prepareTransformations(const QMatrix& pagePointToDevicePointMatrix,
 | 
						|
                                                     QPaintDevice* device,
 | 
						|
                                                     const PDFAnnotation::Flags annotationFlags,
 | 
						|
                                                     const PDFPage* page,
 | 
						|
                                                     QRectF& annotationRectangle) const
 | 
						|
{
 | 
						|
    // "Unrotate" user coordinate space, if NoRotate flag is set
 | 
						|
    QMatrix userSpaceToDeviceSpace = pagePointToDevicePointMatrix;
 | 
						|
    if (annotationFlags.testFlag(PDFAnnotation::NoRotate))
 | 
						|
    {
 | 
						|
        PDFReal rotationAngle = 0.0;
 | 
						|
        switch (page->getPageRotation())
 | 
						|
        {
 | 
						|
            case PageRotation::None:
 | 
						|
                break;
 | 
						|
 | 
						|
            case PageRotation::Rotate90:
 | 
						|
                rotationAngle = -90.0;
 | 
						|
                break;
 | 
						|
 | 
						|
            case PageRotation::Rotate180:
 | 
						|
                rotationAngle = -180.0;
 | 
						|
                break;
 | 
						|
 | 
						|
            case PageRotation::Rotate270:
 | 
						|
                rotationAngle = -270.0;
 | 
						|
                break;
 | 
						|
 | 
						|
            default:
 | 
						|
                Q_ASSERT(false);
 | 
						|
                break;
 | 
						|
        }
 | 
						|
 | 
						|
        QMatrix rotationMatrix;
 | 
						|
        rotationMatrix.rotate(-rotationAngle);
 | 
						|
        QPointF topLeft = annotationRectangle.bottomLeft(); // Do not forget, that y is upward instead of Qt
 | 
						|
        QPointF difference = topLeft - rotationMatrix.map(topLeft);
 | 
						|
 | 
						|
        QMatrix finalMatrix;
 | 
						|
        finalMatrix.translate(difference.x(), difference.y());
 | 
						|
        finalMatrix.rotate(-rotationAngle);
 | 
						|
        userSpaceToDeviceSpace = finalMatrix * userSpaceToDeviceSpace;
 | 
						|
    }
 | 
						|
 | 
						|
    if (annotationFlags.testFlag(PDFAnnotation::NoZoom))
 | 
						|
    {
 | 
						|
        // Jakub Melka: we must adjust annotation rectangle to disable zoom. We calculate
 | 
						|
        // inverse zoom as square root of absolute value of determinant of scale matrix.
 | 
						|
        // Determinant corresponds approximately to zoom squared, and if we will have
 | 
						|
        // unrotated matrix, and both axes are scaled by same value, then determinant will
 | 
						|
        // be exactly zoom squared. Also, we will adjust to target device logical DPI,
 | 
						|
        // if we, for example are using 4K, or 8K monitors.
 | 
						|
        qreal zoom = 1.0 / qSqrt(qAbs(pagePointToDevicePointMatrix.determinant()));
 | 
						|
        zoom = PDFWidgetUtils::scaleDPI_x(device, zoom);
 | 
						|
 | 
						|
        QRectF unzoomedRect(annotationRectangle.bottomLeft(), annotationRectangle.size() * zoom);
 | 
						|
        unzoomedRect.translate(0, -unzoomedRect.height());
 | 
						|
        annotationRectangle = unzoomedRect;
 | 
						|
    }
 | 
						|
 | 
						|
    return userSpaceToDeviceSpace;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotationManager::drawWidgetAnnotationHighlight(QRectF annotationRectangle,
 | 
						|
                                                         const PDFAnnotation* annotation,
 | 
						|
                                                         QPainter* painter,
 | 
						|
                                                         QMatrix userSpaceToDeviceSpace) const
 | 
						|
{
 | 
						|
    const bool isWidget = annotation->getType() == AnnotationType::Widget;
 | 
						|
    if (m_formManager && isWidget)
 | 
						|
    {
 | 
						|
        // Is it a form field?
 | 
						|
        const PDFFormManager::FormAppearanceFlags flags = m_formManager->getAppearanceFlags();
 | 
						|
        const bool isFocused = m_formManager->isFocused(annotation->getSelfReference());
 | 
						|
        if (isFocused || flags.testFlag(PDFFormManager::HighlightFields) || flags.testFlag(PDFFormManager::HighlightRequiredFields))
 | 
						|
        {
 | 
						|
            const PDFFormField* formField = m_formManager->getFormFieldForWidget(annotation->getSelfReference());
 | 
						|
            if (!formField)
 | 
						|
            {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            // Nekreslíme zvýraznění push buttonů
 | 
						|
            if (formField->getFieldType() == PDFFormField::FieldType::Button &&
 | 
						|
                formField->getFlags().testFlag(PDFFormField::PushButton))
 | 
						|
            {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            QColor color;
 | 
						|
            if (flags.testFlag(PDFFormManager::HighlightFields))
 | 
						|
            {
 | 
						|
                color = Qt::blue;
 | 
						|
            }
 | 
						|
            if (flags.testFlag(PDFFormManager::HighlightRequiredFields) && formField->getFlags().testFlag(PDFFormField::Required))
 | 
						|
            {
 | 
						|
                color = Qt::red;
 | 
						|
            }
 | 
						|
            if (isFocused)
 | 
						|
            {
 | 
						|
                color = Qt::yellow;
 | 
						|
            }
 | 
						|
 | 
						|
            if (color.isValid())
 | 
						|
            {
 | 
						|
                color.setAlphaF(0.2);
 | 
						|
 | 
						|
                // Draw annotation rectangle by highlight color
 | 
						|
                QPainterPath highlightArea;
 | 
						|
                highlightArea.addRect(annotationRectangle);
 | 
						|
                highlightArea = userSpaceToDeviceSpace.map(highlightArea);
 | 
						|
                painter->fillPath(highlightArea, color);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotationManager::drawPage(QPainter* painter,
 | 
						|
                                    PDFInteger pageIndex,
 | 
						|
                                    const PDFPrecompiledPage* compiledPage,
 | 
						|
                                    PDFTextLayoutGetter& layoutGetter,
 | 
						|
                                    const QMatrix& pagePointToDevicePointMatrix,
 | 
						|
                                    QList<PDFRenderError>& errors) const
 | 
						|
{
 | 
						|
    Q_UNUSED(compiledPage);
 | 
						|
    Q_UNUSED(layoutGetter);
 | 
						|
 | 
						|
    const PageAnnotations& annotations = getPageAnnotations(pageIndex);
 | 
						|
    if (!annotations.isEmpty())
 | 
						|
    {
 | 
						|
        const PDFPage* page = m_document->getCatalog()->getPage(pageIndex);
 | 
						|
        Q_ASSERT(page);
 | 
						|
 | 
						|
        PDFRenderer::Features features = m_features;
 | 
						|
        if (!features.testFlag(PDFRenderer::DisplayAnnotations))
 | 
						|
        {
 | 
						|
            // Annotation displaying is disabled
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        Q_ASSERT(m_fontCache);
 | 
						|
        Q_ASSERT(m_cmsManager);
 | 
						|
        Q_ASSERT(m_optionalActivity);
 | 
						|
 | 
						|
        int fontCacheLock = 0;
 | 
						|
        const PDFCMSPointer cms = m_cmsManager->getCurrentCMS();
 | 
						|
        m_fontCache->setCacheShrinkEnabled(&fontCacheLock, false);
 | 
						|
 | 
						|
        const PageAnnotation* annotationDrawnByEditor = nullptr;
 | 
						|
        for (const PageAnnotation& annotation : annotations.annotations)
 | 
						|
        {
 | 
						|
            // If annotation draw is not enabled, then skip it
 | 
						|
            if (!isAnnotationDrawEnabled(annotation))
 | 
						|
            {
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
 | 
						|
            if (isAnnotationDrawnByEditor(annotation))
 | 
						|
            {
 | 
						|
                Q_ASSERT(!annotationDrawnByEditor);
 | 
						|
                annotationDrawnByEditor = &annotation;
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
 | 
						|
            drawAnnotation(annotation, pagePointToDevicePointMatrix, page, cms.data(), false, errors, painter);
 | 
						|
        }
 | 
						|
 | 
						|
        if (annotationDrawnByEditor)
 | 
						|
        {
 | 
						|
            drawAnnotation(*annotationDrawnByEditor, pagePointToDevicePointMatrix, page, cms.data(), true, errors, painter);
 | 
						|
        }
 | 
						|
 | 
						|
        m_fontCache->setCacheShrinkEnabled(&fontCacheLock, true);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotationManager::drawAnnotation(const PageAnnotation& annotation,
 | 
						|
                                          const QMatrix& pagePointToDevicePointMatrix,
 | 
						|
                                          const PDFPage* page,
 | 
						|
                                          const PDFCMS* cms,
 | 
						|
                                          bool isEditorDrawEnabled,
 | 
						|
                                          QList<PDFRenderError>& errors,
 | 
						|
                                          QPainter* painter) const
 | 
						|
{
 | 
						|
    try
 | 
						|
    {
 | 
						|
        PDFObject appearanceStreamObject = m_document->getObject(getAppearanceStream(annotation));
 | 
						|
        if (!appearanceStreamObject.isStream() || isEditorDrawEnabled)
 | 
						|
        {
 | 
						|
            // Object is not valid appearance stream. We will try to draw default
 | 
						|
            // annotation appearance, but we must consider also optional content.
 | 
						|
            // We do not draw annotation, if it is not ignored and annotation
 | 
						|
            // has reference to optional content.
 | 
						|
 | 
						|
            drawAnnotationDirect(annotation, pagePointToDevicePointMatrix, page, cms, isEditorDrawEnabled, painter);
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            drawAnnotationUsingAppearanceStream(annotation, appearanceStreamObject, pagePointToDevicePointMatrix, page, cms, painter);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    catch (PDFException exception)
 | 
						|
    {
 | 
						|
        errors.push_back(PDFRenderError(RenderErrorType::Error, exception.getMessage()));
 | 
						|
    }
 | 
						|
    catch (PDFRendererException exception)
 | 
						|
    {
 | 
						|
        errors.push_back(exception.getError());
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
bool PDFAnnotationManager::isAnnotationDrawEnabled(const PageAnnotation& annotation) const
 | 
						|
{
 | 
						|
    const PDFAnnotation::Flags annotationFlags = annotation.annotation->getEffectiveFlags();
 | 
						|
    return !(annotationFlags.testFlag(PDFAnnotation::Hidden) || // Annotation is completely hidden
 | 
						|
       (m_target == Target::Print && !annotationFlags.testFlag(PDFAnnotation::Print)) || // Target is print and annotation is marked as not printed
 | 
						|
       (m_target == Target::View && annotationFlags.testFlag(PDFAnnotation::NoView)) ||  // Target is view, and annotation is disabled for screen
 | 
						|
        annotation.annotation->isReplyTo()); // Annotation is reply to another annotation, display just the first annotation
 | 
						|
}
 | 
						|
 | 
						|
bool PDFAnnotationManager::isAnnotationDrawnByEditor(const PageAnnotation& annotation) const
 | 
						|
{
 | 
						|
    if (!m_formManager)
 | 
						|
    {
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    const PDFFormFieldWidgetEditor* editor = nullptr;
 | 
						|
    if (annotation.annotation->getType() == AnnotationType::Widget)
 | 
						|
    {
 | 
						|
        editor = m_formManager->getEditor(m_formManager->getFormFieldForWidget(annotation.annotation->getSelfReference()));
 | 
						|
        return editor && editor->isEditorDrawEnabled();
 | 
						|
    }
 | 
						|
 | 
						|
    return false;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotationManager::drawAnnotationDirect(const PageAnnotation& annotation,
 | 
						|
                                                const QMatrix& pagePointToDevicePointMatrix,
 | 
						|
                                                const PDFPage* page,
 | 
						|
                                                const PDFCMS* cms,
 | 
						|
                                                bool isEditorDrawEnabled,
 | 
						|
                                                QPainter* painter) const
 | 
						|
{
 | 
						|
    if (!m_features.testFlag(PDFRenderer::IgnoreOptionalContent) &&
 | 
						|
        annotation.annotation->getOptionalContent().isValid())
 | 
						|
    {
 | 
						|
        PDFPainter pdfPainter(painter, m_features, pagePointToDevicePointMatrix, page, m_document, m_fontCache, cms, m_optionalActivity, m_meshQualitySettings);
 | 
						|
        if (pdfPainter.isContentSuppressedByOC(annotation.annotation->getOptionalContent()))
 | 
						|
        {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    QRectF annotationRectangle = annotation.annotation->getRectangle();
 | 
						|
    {
 | 
						|
        PDFPainterStateGuard guard(painter);
 | 
						|
        painter->setRenderHint(QPainter::Antialiasing, true);
 | 
						|
        painter->setWorldMatrix(pagePointToDevicePointMatrix, true);
 | 
						|
        AnnotationDrawParameters parameters;
 | 
						|
        parameters.painter = painter;
 | 
						|
        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);
 | 
						|
        annotation.annotation->draw(parameters);
 | 
						|
 | 
						|
        if (parameters.boundingRectangle.isValid())
 | 
						|
        {
 | 
						|
            annotationRectangle = parameters.boundingRectangle;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // Draw highlighting of fields, but only, if target is View,
 | 
						|
    // we do not want to render form field highlight, when we are
 | 
						|
    // printing to the printer.
 | 
						|
    if (m_target == Target::View && !isEditorDrawEnabled)
 | 
						|
    {
 | 
						|
        PDFPainterStateGuard guard(painter);
 | 
						|
        drawWidgetAnnotationHighlight(annotationRectangle, annotation.annotation.get(), painter, pagePointToDevicePointMatrix);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotationManager::drawAnnotationUsingAppearanceStream(const PageAnnotation& annotation,
 | 
						|
                                                               const PDFObject& appearanceStreamObject,
 | 
						|
                                                               const QMatrix& pagePointToDevicePointMatrix,
 | 
						|
                                                               const PDFPage* page,
 | 
						|
                                                               const PDFCMS* cms,
 | 
						|
                                                               QPainter* painter) const
 | 
						|
{
 | 
						|
    PDFDocumentDataLoaderDecorator loader(m_document);
 | 
						|
    const PDFStream* formStream = appearanceStreamObject.getStream();
 | 
						|
    const PDFDictionary* formDictionary = formStream->getDictionary();
 | 
						|
 | 
						|
    const PDFAnnotation::Flags annotationFlags = annotation.annotation->getEffectiveFlags();
 | 
						|
    QRectF annotationRectangle = annotation.annotation->getRectangle();
 | 
						|
    QRectF formBoundingBox = loader.readRectangle(formDictionary->get("BBox"), QRectF());
 | 
						|
    QMatrix formMatrix = loader.readMatrixFromDictionary(formDictionary, "Matrix", QMatrix());
 | 
						|
    QByteArray content = m_document->getDecodedStream(formStream);
 | 
						|
    PDFObject resources = m_document->getObject(formDictionary->get("Resources"));
 | 
						|
    PDFObject transparencyGroup = m_document->getObject(formDictionary->get("Group"));
 | 
						|
    const PDFInteger formStructuralParentKey = loader.readIntegerFromDictionary(formDictionary, "StructParent", page->getStructureParentKey());
 | 
						|
 | 
						|
    if (formBoundingBox.isEmpty() || annotationRectangle.isEmpty())
 | 
						|
    {
 | 
						|
        // Form bounding box is empty
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    QMatrix userSpaceToDeviceSpace = prepareTransformations(pagePointToDevicePointMatrix, painter->device(), annotationFlags, page, annotationRectangle);
 | 
						|
 | 
						|
    PDFRenderer::Features features = m_features;
 | 
						|
    if (annotationFlags.testFlag(PDFAnnotation::NoZoom))
 | 
						|
    {
 | 
						|
        features.setFlag(PDFRenderer::ClipToCropBox, false);
 | 
						|
    }
 | 
						|
 | 
						|
    // Jakub Melka: perform algorithm 8.1, defined in PDF 1.7 reference,
 | 
						|
    // chapter 8.4.4 Appearance streams.
 | 
						|
 | 
						|
    // Step 1) - calculate transformed appearance box
 | 
						|
    QRectF transformedAppearanceBox = formMatrix.mapRect(formBoundingBox);
 | 
						|
 | 
						|
    // Step 2) - calculate matrix A, which maps from form space to annotation space
 | 
						|
    //           Matrix A transforms from transformed appearance box space to the
 | 
						|
    //           annotation rectangle.
 | 
						|
    const PDFReal scaleX = annotationRectangle.width() / transformedAppearanceBox.width();
 | 
						|
    const PDFReal scaleY = annotationRectangle.height() / transformedAppearanceBox.height();
 | 
						|
    const PDFReal translateX = annotationRectangle.left() - transformedAppearanceBox.left() * scaleX;
 | 
						|
    const PDFReal translateY = annotationRectangle.bottom() - transformedAppearanceBox.bottom() * scaleY;
 | 
						|
    QMatrix A(scaleX, 0.0, 0.0, scaleY, translateX, translateY);
 | 
						|
 | 
						|
    // Step 3) - compute final matrix AA
 | 
						|
    QMatrix AA = formMatrix * A;
 | 
						|
 | 
						|
    bool isContentVisible = false;
 | 
						|
 | 
						|
    // Draw annotation
 | 
						|
    {
 | 
						|
        PDFPainterStateGuard guard(painter);
 | 
						|
        PDFPainter pdfPainter(painter, features, userSpaceToDeviceSpace, page, m_document, m_fontCache, cms, m_optionalActivity, m_meshQualitySettings);
 | 
						|
        pdfPainter.initializeProcessor();
 | 
						|
 | 
						|
        // Jakub Melka: we must check, that we do not display annotation disabled by optional content
 | 
						|
        PDFObjectReference oc = annotation.annotation->getOptionalContent();
 | 
						|
        isContentVisible = !oc.isValid() || !pdfPainter.isContentSuppressedByOC(oc);
 | 
						|
 | 
						|
        if (isContentVisible)
 | 
						|
        {
 | 
						|
            pdfPainter.processForm(AA, formBoundingBox, resources, transparencyGroup, content, formStructuralParentKey);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // Draw highlighting of fields, but only, if target is View,
 | 
						|
    // we do not want to render form field highlight, when we are
 | 
						|
    // printing to the printer.
 | 
						|
    if (isContentVisible && m_target == Target::View)
 | 
						|
    {
 | 
						|
        PDFPainterStateGuard guard(painter);
 | 
						|
        painter->resetMatrix();
 | 
						|
        drawWidgetAnnotationHighlight(annotationRectangle, annotation.annotation.get(), painter, userSpaceToDeviceSpace);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotationManager::setDocument(const PDFModifiedDocument& document)
 | 
						|
{
 | 
						|
    if (m_document != document)
 | 
						|
    {
 | 
						|
        m_document = document;
 | 
						|
        m_optionalActivity = document.getOptionalContentActivity();
 | 
						|
 | 
						|
        if (document.hasReset() || document.hasFlag(PDFModifiedDocument::Annotation))
 | 
						|
        {
 | 
						|
            m_pageAnnotations.clear();
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
PDFObject PDFAnnotationManager::getAppearanceStream(const PageAnnotation& pageAnnotation) const
 | 
						|
{
 | 
						|
    auto getAppearanceStream = [&pageAnnotation] (void) -> PDFObject
 | 
						|
    {
 | 
						|
        return pageAnnotation.annotation->getAppearanceStreams().getAppearance(pageAnnotation.appearance, pageAnnotation.annotation->getAppearanceState());
 | 
						|
    };
 | 
						|
 | 
						|
    QMutexLocker lock(&m_mutex);
 | 
						|
    return pageAnnotation.appearanceStream.get(getAppearanceStream);
 | 
						|
}
 | 
						|
 | 
						|
const PDFAnnotationManager::PageAnnotations& PDFAnnotationManager::getPageAnnotations(PDFInteger pageIndex) const
 | 
						|
{
 | 
						|
    return const_cast<PDFAnnotationManager*>(this)->getPageAnnotations(pageIndex);
 | 
						|
}
 | 
						|
 | 
						|
PDFAnnotationManager::PageAnnotations& PDFAnnotationManager::getPageAnnotations(PDFInteger pageIndex)
 | 
						|
{
 | 
						|
    Q_ASSERT(m_document);
 | 
						|
 | 
						|
    QMutexLocker lock(&m_mutex);
 | 
						|
    auto it = m_pageAnnotations.find(pageIndex);
 | 
						|
    if (it == m_pageAnnotations.cend())
 | 
						|
    {
 | 
						|
        // Create page annotations
 | 
						|
        const PDFPage* page = m_document->getCatalog()->getPage(pageIndex);
 | 
						|
        Q_ASSERT(page);
 | 
						|
 | 
						|
        PageAnnotations annotations;
 | 
						|
 | 
						|
        const std::vector<PDFObjectReference>& pageAnnotations = page->getAnnotations();
 | 
						|
        annotations.annotations.reserve(pageAnnotations.size());
 | 
						|
        for (PDFObjectReference annotationReference : pageAnnotations)
 | 
						|
        {
 | 
						|
            PDFAnnotationPtr annotationPtr = PDFAnnotation::parse(&m_document->getStorage(), annotationReference);
 | 
						|
            if (annotationPtr)
 | 
						|
            {
 | 
						|
                PageAnnotation annotation;
 | 
						|
                annotation.annotation = qMove(annotationPtr);
 | 
						|
                annotations.annotations.emplace_back(qMove(annotation));
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        it = m_pageAnnotations.insert(std::make_pair(pageIndex, qMove(annotations))).first;
 | 
						|
    }
 | 
						|
 | 
						|
    return it->second;
 | 
						|
}
 | 
						|
 | 
						|
bool PDFAnnotationManager::hasAnnotation(PDFInteger pageIndex) const
 | 
						|
{
 | 
						|
    return !getPageAnnotations(pageIndex).isEmpty();
 | 
						|
}
 | 
						|
 | 
						|
bool PDFAnnotationManager::hasAnyPageAnnotation(const std::vector<PDFInteger>& pageIndices) const
 | 
						|
{
 | 
						|
    return std::any_of(pageIndices.cbegin(), pageIndices.cend(), std::bind(&PDFAnnotationManager::hasAnnotation, this, std::placeholders::_1));
 | 
						|
}
 | 
						|
 | 
						|
PDFFormManager* PDFAnnotationManager::getFormManager() const
 | 
						|
{
 | 
						|
    return m_formManager;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotationManager::setFormManager(PDFFormManager* formManager)
 | 
						|
{
 | 
						|
    m_formManager = formManager;
 | 
						|
}
 | 
						|
 | 
						|
PDFRenderer::Features PDFAnnotationManager::getFeatures() const
 | 
						|
{
 | 
						|
    return m_features;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotationManager::setFeatures(PDFRenderer::Features features)
 | 
						|
{
 | 
						|
    m_features = features;
 | 
						|
}
 | 
						|
 | 
						|
PDFMeshQualitySettings PDFAnnotationManager::getMeshQualitySettings() const
 | 
						|
{
 | 
						|
    return m_meshQualitySettings;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotationManager::setMeshQualitySettings(const PDFMeshQualitySettings& meshQualitySettings)
 | 
						|
{
 | 
						|
    m_meshQualitySettings = meshQualitySettings;
 | 
						|
}
 | 
						|
 | 
						|
PDFFontCache* PDFAnnotationManager::getFontCache() const
 | 
						|
{
 | 
						|
    return m_fontCache;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotationManager::setFontCache(PDFFontCache* fontCache)
 | 
						|
{
 | 
						|
    m_fontCache = fontCache;
 | 
						|
}
 | 
						|
 | 
						|
const PDFOptionalContentActivity* PDFAnnotationManager::getOptionalActivity() const
 | 
						|
{
 | 
						|
    return m_optionalActivity;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotationManager::setOptionalActivity(const PDFOptionalContentActivity* optionalActivity)
 | 
						|
{
 | 
						|
    m_optionalActivity = optionalActivity;
 | 
						|
}
 | 
						|
 | 
						|
PDFAnnotationManager::Target PDFAnnotationManager::getTarget() const
 | 
						|
{
 | 
						|
    return m_target;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotationManager::setTarget(Target target)
 | 
						|
{
 | 
						|
    m_target = target;
 | 
						|
}
 | 
						|
 | 
						|
PDFWidgetAnnotationManager::PDFWidgetAnnotationManager(PDFDrawWidgetProxy* proxy, QObject* parent) :
 | 
						|
    BaseClass(proxy->getFontCache(), proxy->getCMSManager(), proxy->getOptionalContentActivity(), proxy->getMeshQualitySettings(), proxy->getFeatures(), Target::View, parent),
 | 
						|
    m_proxy(proxy)
 | 
						|
{
 | 
						|
    Q_ASSERT(proxy);
 | 
						|
    m_proxy->registerDrawInterface(this);
 | 
						|
}
 | 
						|
 | 
						|
PDFWidgetAnnotationManager::~PDFWidgetAnnotationManager()
 | 
						|
{
 | 
						|
    m_proxy->unregisterDrawInterface(this);
 | 
						|
}
 | 
						|
 | 
						|
void PDFWidgetAnnotationManager::setDocument(const PDFModifiedDocument& document)
 | 
						|
{
 | 
						|
    BaseClass::setDocument(document);
 | 
						|
 | 
						|
    if (document.hasReset() || document.getFlags().testFlag(PDFModifiedDocument::Annotation))
 | 
						|
    {
 | 
						|
        m_editableAnnotation = PDFObjectReference();
 | 
						|
        m_editableAnnotationPage = PDFObjectReference();
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void PDFWidgetAnnotationManager::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event)
 | 
						|
{
 | 
						|
    Q_UNUSED(widget);
 | 
						|
    Q_UNUSED(event);
 | 
						|
}
 | 
						|
 | 
						|
void PDFWidgetAnnotationManager::keyPressEvent(QWidget* widget, QKeyEvent* event)
 | 
						|
{
 | 
						|
    Q_UNUSED(widget);
 | 
						|
    Q_UNUSED(event);
 | 
						|
}
 | 
						|
 | 
						|
void PDFWidgetAnnotationManager::keyReleaseEvent(QWidget* widget, QKeyEvent* event)
 | 
						|
{
 | 
						|
    Q_UNUSED(widget);
 | 
						|
    Q_UNUSED(event);
 | 
						|
}
 | 
						|
 | 
						|
void PDFWidgetAnnotationManager::mousePressEvent(QWidget* widget, QMouseEvent* event)
 | 
						|
{
 | 
						|
    Q_UNUSED(widget);
 | 
						|
 | 
						|
    updateFromMouseEvent(event);
 | 
						|
 | 
						|
    // Show context menu?
 | 
						|
    if (event->button() == Qt::RightButton)
 | 
						|
    {
 | 
						|
        PDFWidget* widget = m_proxy->getWidget();
 | 
						|
        std::vector<PDFInteger> currentPages = widget->getDrawWidget()->getCurrentPages();
 | 
						|
 | 
						|
        if (!hasAnyPageAnnotation(currentPages))
 | 
						|
        {
 | 
						|
            // All pages doesn't have annotation
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        m_editableAnnotation = PDFObjectReference();
 | 
						|
        m_editableAnnotationPage = PDFObjectReference();
 | 
						|
        for (PDFInteger pageIndex : currentPages)
 | 
						|
        {
 | 
						|
            PageAnnotations& pageAnnotations = getPageAnnotations(pageIndex);
 | 
						|
            for (PageAnnotation& pageAnnotation : pageAnnotations.annotations)
 | 
						|
            {
 | 
						|
                if (!pageAnnotation.isHovered)
 | 
						|
                {
 | 
						|
                    continue;
 | 
						|
                }
 | 
						|
 | 
						|
                if (!PDFAnnotation::isTypeEditable(pageAnnotation.annotation->getType()))
 | 
						|
                {
 | 
						|
                    continue;
 | 
						|
                }
 | 
						|
 | 
						|
                m_editableAnnotation = pageAnnotation.annotation->getSelfReference();
 | 
						|
                m_editableAnnotationPage = pageAnnotation.annotation->getPageReference();
 | 
						|
 | 
						|
                if (!m_editableAnnotationPage.isValid())
 | 
						|
                {
 | 
						|
                    m_editableAnnotationPage = m_document->getCatalog()->getPage(pageIndex)->getPageReference();
 | 
						|
                }
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if (m_editableAnnotation.isValid())
 | 
						|
        {
 | 
						|
            QMenu menu(tr("Annotation"), widget);
 | 
						|
            QAction* showPopupAction = menu.addAction(tr("Show Popup Window"));
 | 
						|
            QAction* copyAction = menu.addAction(tr("Copy to Multiple Pages"));
 | 
						|
            QAction* editAction = menu.addAction(tr("Edit"));
 | 
						|
            QAction* deleteAction = menu.addAction(tr("Delete"));
 | 
						|
            connect(showPopupAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onShowPopupAnnotation);
 | 
						|
            connect(copyAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onCopyAnnotation);
 | 
						|
            connect(editAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onEditAnnotation);
 | 
						|
            connect(deleteAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onDeleteAnnotation);
 | 
						|
 | 
						|
            m_editableAnnotationGlobalPosition = widget->mapToGlobal(event->pos());
 | 
						|
            menu.exec(m_editableAnnotationGlobalPosition);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void PDFWidgetAnnotationManager::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event)
 | 
						|
{
 | 
						|
    Q_UNUSED(widget);
 | 
						|
    Q_UNUSED(event);
 | 
						|
}
 | 
						|
 | 
						|
void PDFWidgetAnnotationManager::mouseReleaseEvent(QWidget* widget, QMouseEvent* event)
 | 
						|
{
 | 
						|
    Q_UNUSED(widget);
 | 
						|
 | 
						|
    updateFromMouseEvent(event);
 | 
						|
}
 | 
						|
 | 
						|
void PDFWidgetAnnotationManager::mouseMoveEvent(QWidget* widget, QMouseEvent* event)
 | 
						|
{
 | 
						|
    Q_UNUSED(widget);
 | 
						|
 | 
						|
    updateFromMouseEvent(event);
 | 
						|
}
 | 
						|
 | 
						|
void PDFWidgetAnnotationManager::wheelEvent(QWidget* widget, QWheelEvent* event)
 | 
						|
{
 | 
						|
    Q_UNUSED(widget);
 | 
						|
    Q_UNUSED(event);
 | 
						|
}
 | 
						|
 | 
						|
void PDFWidgetAnnotationManager::updateFromMouseEvent(QMouseEvent* event)
 | 
						|
{
 | 
						|
    PDFWidget* widget = m_proxy->getWidget();
 | 
						|
    std::vector<PDFInteger> currentPages = widget->getDrawWidget()->getCurrentPages();
 | 
						|
 | 
						|
    if (!hasAnyPageAnnotation(currentPages))
 | 
						|
    {
 | 
						|
        // All pages doesn't have annotation
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    m_tooltip = QString();
 | 
						|
    m_cursor = std::nullopt;
 | 
						|
    bool appearanceChanged = false;
 | 
						|
 | 
						|
    // We must update appearance states, and update tooltip
 | 
						|
    PDFWidgetSnapshot snapshot = m_proxy->getSnapshot();
 | 
						|
    const bool isDown = event->buttons().testFlag(Qt::LeftButton);
 | 
						|
    const PDFAppeareanceStreams::Appearance hoverAppearance = isDown ? PDFAppeareanceStreams::Appearance::Down : PDFAppeareanceStreams::Appearance::Rollover;
 | 
						|
 | 
						|
    for (const PDFWidgetSnapshot::SnapshotItem& snapshotItem : snapshot.items)
 | 
						|
    {
 | 
						|
        PageAnnotations& pageAnnotations = getPageAnnotations(snapshotItem.pageIndex);
 | 
						|
        for (PageAnnotation& pageAnnotation : pageAnnotations.annotations)
 | 
						|
        {
 | 
						|
            if (pageAnnotation.annotation->isReplyTo())
 | 
						|
            {
 | 
						|
                // Annotation is reply to another annotation, do not interact with it
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
 | 
						|
            const PDFAppeareanceStreams::Appearance oldAppearance = pageAnnotation.appearance;
 | 
						|
            QRectF annotationRect = pageAnnotation.annotation->getRectangle();
 | 
						|
            QMatrix matrix = prepareTransformations(snapshotItem.pageToDeviceMatrix, widget, pageAnnotation.annotation->getEffectiveFlags(), m_document->getCatalog()->getPage(snapshotItem.pageIndex), annotationRect);
 | 
						|
            QPainterPath path;
 | 
						|
            path.addRect(annotationRect);
 | 
						|
            path = matrix.map(path);
 | 
						|
 | 
						|
            if (path.contains(event->pos()))
 | 
						|
            {
 | 
						|
                pageAnnotation.appearance = hoverAppearance;
 | 
						|
                pageAnnotation.isHovered = true;
 | 
						|
 | 
						|
                // Generate tooltip
 | 
						|
                if (m_tooltip.isEmpty())
 | 
						|
                {
 | 
						|
                    const PDFMarkupAnnotation* markupAnnotation = pageAnnotation.annotation->asMarkupAnnotation();
 | 
						|
                    if (markupAnnotation)
 | 
						|
                    {
 | 
						|
                        QString title = markupAnnotation->getWindowTitle();
 | 
						|
                        if (title.isEmpty())
 | 
						|
                        {
 | 
						|
                            title = markupAnnotation->getSubject();
 | 
						|
                        }
 | 
						|
                        if (title.isEmpty())
 | 
						|
                        {
 | 
						|
                            title = PDFTranslationContext::tr("Info");
 | 
						|
                        }
 | 
						|
 | 
						|
                        const size_t repliesCount = pageAnnotations.getReplies(pageAnnotation).size();
 | 
						|
                        if (repliesCount > 0)
 | 
						|
                        {
 | 
						|
                            title = PDFTranslationContext::tr("%1 (%2 replies)").arg(title).arg(repliesCount);
 | 
						|
                        }
 | 
						|
 | 
						|
                        m_tooltip = QString("<p><b>%1</b></p><p>%2</p>").arg(title, markupAnnotation->getContents());
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                const PDFAction* linkAction = nullptr;
 | 
						|
                const AnnotationType annotationType = pageAnnotation.annotation->getType();
 | 
						|
                if (annotationType == AnnotationType::Link)
 | 
						|
                {
 | 
						|
                    const PDFLinkAnnotation* linkAnnotation = dynamic_cast<const PDFLinkAnnotation*>(pageAnnotation.annotation.data());
 | 
						|
                    Q_ASSERT(linkAnnotation);
 | 
						|
 | 
						|
                    // We must check, if user clicked to the link area
 | 
						|
                    QPainterPath activationPath = linkAnnotation->getActivationRegion().getPath();
 | 
						|
                    activationPath = snapshotItem.pageToDeviceMatrix.map(activationPath);
 | 
						|
                    if (activationPath.contains(event->pos()) && linkAnnotation->getAction())
 | 
						|
                    {
 | 
						|
                        m_cursor = QCursor(Qt::PointingHandCursor);
 | 
						|
                        linkAction = linkAnnotation->getAction();
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                if (annotationType == AnnotationType::Widget)
 | 
						|
                {
 | 
						|
                    if (m_formManager && m_formManager->hasFormFieldWidgetText(pageAnnotation.annotation->getSelfReference()))
 | 
						|
                    {
 | 
						|
                        m_cursor = QCursor(Qt::IBeamCursor);
 | 
						|
                    }
 | 
						|
                    else
 | 
						|
                    {
 | 
						|
                        m_cursor = QCursor(Qt::ArrowCursor);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                // Generate popup window
 | 
						|
                if (event->type() == QEvent::MouseButtonPress && event->button() == Qt::LeftButton)
 | 
						|
                {
 | 
						|
                    const PDFMarkupAnnotation* markupAnnotation = pageAnnotation.annotation->asMarkupAnnotation();
 | 
						|
                    if (markupAnnotation)
 | 
						|
                    {
 | 
						|
                        QDialog* dialog = createDialogForMarkupAnnotations(widget, pageAnnotation, pageAnnotations);
 | 
						|
 | 
						|
                        // Set proper dialog position - according to the popup annotation. If we
 | 
						|
                        // do not have popup annotation, then try to use annotations rectangle.
 | 
						|
                        if (const PageAnnotation* popupAnnotation = pageAnnotations.getPopupAnnotation(pageAnnotation))
 | 
						|
                        {
 | 
						|
                            QPoint popupPoint = snapshotItem.pageToDeviceMatrix.map(popupAnnotation->annotation->getRectangle().bottomLeft()).toPoint();
 | 
						|
                            popupPoint = widget->mapToGlobal(popupPoint);
 | 
						|
                            dialog->move(popupPoint);
 | 
						|
                        }
 | 
						|
                        else if (markupAnnotation->getRectangle().isValid())
 | 
						|
                        {
 | 
						|
                            QPoint popupPoint = snapshotItem.pageToDeviceMatrix.map(markupAnnotation->getRectangle().bottomRight()).toPoint();
 | 
						|
                            popupPoint = widget->mapToGlobal(popupPoint);
 | 
						|
                            dialog->move(popupPoint);
 | 
						|
                        }
 | 
						|
 | 
						|
                        dialog->exec();
 | 
						|
                    }
 | 
						|
 | 
						|
                    if (linkAction)
 | 
						|
                    {
 | 
						|
                        emit actionTriggered(linkAction);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                pageAnnotation.appearance = PDFAppeareanceStreams::Appearance::Normal;
 | 
						|
                pageAnnotation.isHovered = false;
 | 
						|
            }
 | 
						|
 | 
						|
            const bool currentAppearanceChanged = oldAppearance != pageAnnotation.appearance;
 | 
						|
            if (currentAppearanceChanged)
 | 
						|
            {
 | 
						|
                // We have changed appearance - we must mark stream as dirty
 | 
						|
                pageAnnotation.appearanceStream.dirty();
 | 
						|
                appearanceChanged = true;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // If appearance has changed, then we must redraw the page
 | 
						|
    if (appearanceChanged)
 | 
						|
    {
 | 
						|
        emit widget->getDrawWidgetProxy()->repaintNeeded();
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void PDFWidgetAnnotationManager::onShowPopupAnnotation()
 | 
						|
{
 | 
						|
    PDFWidgetSnapshot snapshot = m_proxy->getSnapshot();
 | 
						|
    for (const PDFWidgetSnapshot::SnapshotItem& snapshotItem : snapshot.items)
 | 
						|
    {
 | 
						|
        PageAnnotations& pageAnnotations = getPageAnnotations(snapshotItem.pageIndex);
 | 
						|
        for (PageAnnotation& pageAnnotation : pageAnnotations.annotations)
 | 
						|
        {
 | 
						|
            if (pageAnnotation.annotation->isReplyTo())
 | 
						|
            {
 | 
						|
                // Annotation is reply to another annotation, do not interact with it
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
 | 
						|
            if (pageAnnotation.annotation->getSelfReference() == m_editableAnnotation)
 | 
						|
            {
 | 
						|
                QDialog* dialog = createDialogForMarkupAnnotations(m_proxy->getWidget(), pageAnnotation, pageAnnotations);
 | 
						|
                dialog->move(m_editableAnnotationGlobalPosition);
 | 
						|
                dialog->exec();
 | 
						|
                return;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void PDFWidgetAnnotationManager::onCopyAnnotation()
 | 
						|
{
 | 
						|
    pdf::PDFSelectPagesDialog dialog(tr("Copy Annotation"), tr("Copy Annotation onto Multiple Pages"),
 | 
						|
                                     m_document->getCatalog()->getPageCount(), m_proxy->getWidget()->getDrawWidget()->getCurrentPages(), m_proxy->getWidget());
 | 
						|
    if (dialog.exec() == QDialog::Accepted)
 | 
						|
    {
 | 
						|
        std::vector<PDFInteger> pages = dialog.getSelectedPages();
 | 
						|
        const PDFInteger currentPageIndex = m_document->getCatalog()->getPageIndexFromPageReference(m_editableAnnotationPage);
 | 
						|
 | 
						|
        for (PDFInteger& pageIndex : pages)
 | 
						|
        {
 | 
						|
            --pageIndex;
 | 
						|
        }
 | 
						|
 | 
						|
        auto it = std::find(pages.begin(), pages.end(), currentPageIndex);
 | 
						|
        if (it != pages.end())
 | 
						|
        {
 | 
						|
            pages.erase(it);
 | 
						|
        }
 | 
						|
 | 
						|
        if (pages.empty())
 | 
						|
        {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        PDFDocumentModifier modifier(m_document);
 | 
						|
        modifier.markAnnotationsChanged();
 | 
						|
 | 
						|
        for (const PDFInteger pageIndex : pages)
 | 
						|
        {
 | 
						|
            modifier.getBuilder()->copyAnnotation(m_document->getCatalog()->getPage(pageIndex)->getPageReference(), m_editableAnnotation);
 | 
						|
        }
 | 
						|
 | 
						|
        if (modifier.finalize())
 | 
						|
        {
 | 
						|
            emit documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags()));
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void PDFWidgetAnnotationManager::onEditAnnotation()
 | 
						|
{
 | 
						|
    PDFEditObjectDialog dialog(EditObjectType::Annotation, m_proxy->getWidget());
 | 
						|
 | 
						|
    PDFObject originalObject = m_document->getObjectByReference(m_editableAnnotation);
 | 
						|
    dialog.setObject(originalObject);
 | 
						|
 | 
						|
    if (dialog.exec() == PDFEditObjectDialog::Accepted)
 | 
						|
    {
 | 
						|
        PDFObject object = dialog.getObject();
 | 
						|
        if (object != originalObject)
 | 
						|
        {
 | 
						|
            PDFDocumentModifier modifier(m_document);
 | 
						|
            modifier.markAnnotationsChanged();
 | 
						|
            modifier.getBuilder()->setObject(m_editableAnnotation, object);
 | 
						|
            modifier.getBuilder()->updateAnnotationAppearanceStreams(m_editableAnnotation);
 | 
						|
 | 
						|
            if (modifier.finalize())
 | 
						|
            {
 | 
						|
                emit documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags()));
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void PDFWidgetAnnotationManager::onDeleteAnnotation()
 | 
						|
{
 | 
						|
    if (m_editableAnnotation.isValid())
 | 
						|
    {
 | 
						|
        PDFDocumentModifier modifier(m_document);
 | 
						|
        modifier.markAnnotationsChanged();
 | 
						|
        modifier.getBuilder()->removeAnnotation(m_editableAnnotationPage, m_editableAnnotation);
 | 
						|
 | 
						|
        if (modifier.finalize())
 | 
						|
        {
 | 
						|
            emit documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags()));
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
QDialog* PDFWidgetAnnotationManager::createDialogForMarkupAnnotations(PDFWidget* widget,
 | 
						|
                                                                      const PageAnnotation& pageAnnotation,
 | 
						|
                                                                      const PageAnnotations& pageAnnotations)
 | 
						|
{
 | 
						|
    QDialog* dialog = new QDialog(widget->getDrawWidget()->getWidget(), Qt::Popup);
 | 
						|
    dialog->setAttribute(Qt::WA_DeleteOnClose, true);
 | 
						|
    createWidgetsForMarkupAnnotations(dialog, pageAnnotation, pageAnnotations);
 | 
						|
    return dialog;
 | 
						|
}
 | 
						|
 | 
						|
void PDFWidgetAnnotationManager::createWidgetsForMarkupAnnotations(QWidget* parentWidget,
 | 
						|
                                                                   const PageAnnotation& pageAnnotation,
 | 
						|
                                                                   const PageAnnotations& pageAnnotations)
 | 
						|
{
 | 
						|
    std::vector<const PageAnnotation*> replies = pageAnnotations.getReplies(pageAnnotation);
 | 
						|
    replies.insert(replies.begin(), &pageAnnotation);
 | 
						|
 | 
						|
    QScrollArea* scrollArea = new QScrollArea(parentWidget);
 | 
						|
    scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
 | 
						|
    scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
 | 
						|
 | 
						|
    QVBoxLayout* layout = new QVBoxLayout(parentWidget);
 | 
						|
    layout->addWidget(scrollArea);
 | 
						|
    layout->setMargin(0);
 | 
						|
    layout->setContentsMargins(QMargins());
 | 
						|
 | 
						|
    QWidget* frameWidget = new QWidget(scrollArea);
 | 
						|
    QVBoxLayout* frameLayout = new QVBoxLayout(frameWidget);
 | 
						|
    frameLayout->setMargin(0);
 | 
						|
    frameLayout->setSpacing(0);
 | 
						|
    scrollArea->setWidget(frameWidget);
 | 
						|
 | 
						|
    const PDFMarkupAnnotation* markupMainAnnotation = pageAnnotation.annotation->asMarkupAnnotation();
 | 
						|
    QColor color = markupMainAnnotation->getDrawColorFromAnnotationColor(markupMainAnnotation->getColor(), 1.0);
 | 
						|
    QColor titleColor = QColor::fromHslF(color.hueF(), color.saturationF(), 0.2, 1.0);
 | 
						|
    QColor backgroundColor = QColor::fromHslF(color.hueF(), color.saturationF(), 0.9, 1.0);
 | 
						|
 | 
						|
    QString style = "QGroupBox { "
 | 
						|
                    "border: 2px solid black; "
 | 
						|
                    "border-color: rgb(%4, %5, %6); "
 | 
						|
                    "margin-top: 3ex; "
 | 
						|
                    "background-color: rgb(%1, %2, %3); "
 | 
						|
                    "}"
 | 
						|
                    "QGroupBox::title { "
 | 
						|
                    "subcontrol-origin: margin; "
 | 
						|
                    "subcontrol-position: top center; "
 | 
						|
                    "padding: 0px 8192px; "
 | 
						|
                    "background-color: rgb(%4, %5, %6); "
 | 
						|
                    "color: #FFFFFF;"
 | 
						|
                    "}";
 | 
						|
    style = style.arg(backgroundColor.red()).arg(backgroundColor.green()).arg(backgroundColor.blue()).arg(titleColor.red()).arg(titleColor.green()).arg(titleColor.blue());
 | 
						|
 | 
						|
    for (const PageAnnotation* annotation : replies)
 | 
						|
    {
 | 
						|
        const PDFMarkupAnnotation* markupAnnotation = annotation->annotation->asMarkupAnnotation();
 | 
						|
 | 
						|
        if (!markupAnnotation)
 | 
						|
        {
 | 
						|
            // This should not happen...
 | 
						|
            continue;
 | 
						|
        }
 | 
						|
 | 
						|
        QGroupBox* groupBox = new QGroupBox(scrollArea);
 | 
						|
        frameLayout->addWidget(groupBox);
 | 
						|
 | 
						|
        QString title = markupAnnotation->getWindowTitle();
 | 
						|
        if (title.isEmpty())
 | 
						|
        {
 | 
						|
            title = markupAnnotation->getSubject();
 | 
						|
        }
 | 
						|
 | 
						|
        QString dateTimeString = markupAnnotation->getCreationDate().toLocalTime().toString(Qt::SystemLocaleLongDate);
 | 
						|
        title = QString("%1 (%2)").arg(title, dateTimeString).trimmed();
 | 
						|
 | 
						|
        groupBox->setStyleSheet(style);
 | 
						|
        groupBox->setTitle(title);
 | 
						|
        QVBoxLayout* groupBoxLayout = new QVBoxLayout(groupBox);
 | 
						|
 | 
						|
        QLabel* label = new QLabel(groupBox);
 | 
						|
        label->setTextInteractionFlags(Qt::TextBrowserInteraction);
 | 
						|
        label->setWordWrap(true);
 | 
						|
        label->setText(markupAnnotation->getContents());
 | 
						|
        label->setFixedWidth(PDFWidgetUtils::scaleDPI_x(label, 250));
 | 
						|
        label->setMinimumHeight(label->sizeHint().height());
 | 
						|
        groupBoxLayout->addWidget(label);
 | 
						|
    }
 | 
						|
 | 
						|
    frameWidget->setFixedSize(frameWidget->minimumSizeHint());
 | 
						|
    parentWidget->setFixedSize(scrollArea->sizeHint());
 | 
						|
}
 | 
						|
 | 
						|
void PDFSimpleGeometryAnnotation::draw(AnnotationDrawParameters& parameters) const
 | 
						|
{
 | 
						|
    Q_ASSERT(parameters.painter);
 | 
						|
    parameters.boundingRectangle = getRectangle();
 | 
						|
 | 
						|
    QPainter& painter = *parameters.painter;
 | 
						|
    painter.setPen(getPen());
 | 
						|
    painter.setBrush(getBrush());
 | 
						|
    painter.setCompositionMode(getCompositionMode());
 | 
						|
 | 
						|
    switch (getType())
 | 
						|
    {
 | 
						|
        case AnnotationType::Square:
 | 
						|
        {
 | 
						|
            const PDFAnnotationBorder& border = getBorder();
 | 
						|
 | 
						|
            const PDFReal hCornerRadius = border.getHorizontalCornerRadius();
 | 
						|
            const PDFReal vCornerRadius = border.getVerticalCornerRadius();
 | 
						|
            const bool isRounded = !qFuzzyIsNull(hCornerRadius) || !qFuzzyIsNull(vCornerRadius);
 | 
						|
 | 
						|
            if (isRounded)
 | 
						|
            {
 | 
						|
                painter.drawRoundedRect(getRectangle(), hCornerRadius, vCornerRadius, Qt::AbsoluteSize);
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                painter.drawRect(getRectangle());
 | 
						|
            }
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        case AnnotationType::Circle:
 | 
						|
        {
 | 
						|
            const PDFAnnotationBorder& border = getBorder();
 | 
						|
            const PDFReal width = border.getWidth();
 | 
						|
            QRectF rectangle = getRectangle();
 | 
						|
            rectangle.adjust(width, width, -width, -width);
 | 
						|
            painter.drawEllipse(rectangle);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        default:
 | 
						|
        {
 | 
						|
            Q_ASSERT(false);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
QColor PDFSimpleGeometryAnnotation::getFillColor() const
 | 
						|
{
 | 
						|
    return getDrawColorFromAnnotationColor(getInteriorColor(), getFillOpacity());
 | 
						|
}
 | 
						|
 | 
						|
bool PDFMarkupAnnotation::isReplyTo() const
 | 
						|
{
 | 
						|
    return m_inReplyTo.isValid() && m_replyType == ReplyType::Reply;
 | 
						|
}
 | 
						|
 | 
						|
void PDFMarkupAnnotation::setWindowTitle(const QString& windowTitle)
 | 
						|
{
 | 
						|
    m_windowTitle = windowTitle;
 | 
						|
}
 | 
						|
 | 
						|
void PDFMarkupAnnotation::setPopupAnnotation(const PDFObjectReference& popupAnnotation)
 | 
						|
{
 | 
						|
    m_popupAnnotation = popupAnnotation;
 | 
						|
}
 | 
						|
 | 
						|
void PDFMarkupAnnotation::setRichTextString(const QString& richTextString)
 | 
						|
{
 | 
						|
    m_richTextString = richTextString;
 | 
						|
}
 | 
						|
 | 
						|
void PDFMarkupAnnotation::setCreationDate(const QDateTime& creationDate)
 | 
						|
{
 | 
						|
    m_creationDate = creationDate;
 | 
						|
}
 | 
						|
 | 
						|
void PDFMarkupAnnotation::setInReplyTo(PDFObjectReference inReplyTo)
 | 
						|
{
 | 
						|
    m_inReplyTo = inReplyTo;
 | 
						|
}
 | 
						|
 | 
						|
void PDFMarkupAnnotation::setSubject(const QString& subject)
 | 
						|
{
 | 
						|
    m_subject = subject;
 | 
						|
}
 | 
						|
 | 
						|
void PDFMarkupAnnotation::setIntent(const QByteArray& intent)
 | 
						|
{
 | 
						|
    m_intent = intent;
 | 
						|
}
 | 
						|
 | 
						|
void PDFMarkupAnnotation::setExternalData(const PDFObject& externalData)
 | 
						|
{
 | 
						|
    m_externalData = externalData;
 | 
						|
}
 | 
						|
 | 
						|
void PDFMarkupAnnotation::setReplyType(ReplyType replyType)
 | 
						|
{
 | 
						|
    m_replyType = replyType;
 | 
						|
}
 | 
						|
 | 
						|
std::vector<PDFAppeareanceStreams::Key> PDFTextAnnotation::getDrawKeys(const PDFFormManager* formManager) const
 | 
						|
{
 | 
						|
    Q_UNUSED(formManager);
 | 
						|
 | 
						|
    return { PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Normal, QByteArray() },
 | 
						|
             PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Rollover, QByteArray() },
 | 
						|
             PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Down, QByteArray() } };
 | 
						|
}
 | 
						|
 | 
						|
void PDFTextAnnotation::draw(AnnotationDrawParameters& parameters) const
 | 
						|
{
 | 
						|
    QColor strokeColor = QColor::fromRgbF(0.0, 0.0, 0.0, getStrokeOpacity());
 | 
						|
    QColor fillColor = (parameters.key.first == PDFAppeareanceStreams::Appearance::Normal) ? QColor::fromRgbF(1.0, 1.0, 0.0, getFillOpacity()) :
 | 
						|
                                                                                             QColor::fromRgbF(1.0, 0.0, 0.0, getFillOpacity());
 | 
						|
 | 
						|
    constexpr const PDFReal rectSize = 32.0;
 | 
						|
    constexpr const PDFReal penWidth = 2.0;
 | 
						|
 | 
						|
    QPainter& painter = *parameters.painter;
 | 
						|
    painter.setCompositionMode(getCompositionMode());
 | 
						|
    QRectF rectangle = getRectangle();
 | 
						|
    rectangle.setSize(QSizeF(rectSize, rectSize));
 | 
						|
 | 
						|
    QPen pen(strokeColor);
 | 
						|
    pen.setWidthF(penWidth);
 | 
						|
    painter.setPen(pen);
 | 
						|
 | 
						|
    painter.setBrush(QBrush(fillColor, Qt::SolidPattern));
 | 
						|
 | 
						|
    QRectF ellipseRectangle = rectangle;
 | 
						|
    ellipseRectangle.adjust(penWidth, penWidth, -penWidth, -penWidth);
 | 
						|
 | 
						|
    // Draw the ellipse
 | 
						|
    painter.drawEllipse(ellipseRectangle);
 | 
						|
 | 
						|
    QFont font = QApplication::font();
 | 
						|
    font.setPixelSize(16.0);
 | 
						|
 | 
						|
    QString text = getTextForIcon(m_iconName);
 | 
						|
 | 
						|
    QPainterPath textPath;
 | 
						|
    textPath.addText(0.0, 0.0, font, text);
 | 
						|
    textPath = QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0).map(textPath);
 | 
						|
    QRectF textBoundingRect = textPath.boundingRect();
 | 
						|
    QPointF offset = rectangle.center() - textBoundingRect.center();
 | 
						|
    textPath.translate(offset);
 | 
						|
    painter.fillPath(textPath, QBrush(strokeColor, Qt::SolidPattern));
 | 
						|
 | 
						|
    parameters.boundingRectangle = rectangle;
 | 
						|
}
 | 
						|
 | 
						|
PDFTextAnnotation::Flags PDFTextAnnotation::getEffectiveFlags() const
 | 
						|
{
 | 
						|
    return getFlags() | NoZoom | NoRotate;
 | 
						|
}
 | 
						|
 | 
						|
QIcon PDFTextAnnotation::createIcon(QString key, QSize size)
 | 
						|
{
 | 
						|
    QIcon icon;
 | 
						|
 | 
						|
    QPixmap pixmap(size);
 | 
						|
    pixmap.fill(Qt::transparent);
 | 
						|
 | 
						|
    QRect rectangle(QPoint(0, 0), size);
 | 
						|
    rectangle.adjust(1, 1, -1, -1);
 | 
						|
 | 
						|
    QPainter painter(&pixmap);
 | 
						|
    painter.setRenderHint(QPainter::Antialiasing);
 | 
						|
    painter.setRenderHint(QPainter::TextAntialiasing);
 | 
						|
 | 
						|
    QString text = getTextForIcon(key);
 | 
						|
 | 
						|
    QFont font = QApplication::font();
 | 
						|
    font.setPixelSize(size.height() * 0.75);
 | 
						|
 | 
						|
    QPainterPath textPath;
 | 
						|
    textPath.addText(0.0, 0.0, font, text);
 | 
						|
    QRectF textBoundingRect = textPath.boundingRect();
 | 
						|
    QPointF offset = rectangle.center() - textBoundingRect.center();
 | 
						|
    textPath.translate(offset);
 | 
						|
    painter.fillPath(textPath, QBrush(Qt::black, Qt::SolidPattern));
 | 
						|
    painter.end();
 | 
						|
 | 
						|
    icon.addPixmap(qMove(pixmap));
 | 
						|
    return icon;
 | 
						|
}
 | 
						|
 | 
						|
QString PDFTextAnnotation::getTextForIcon(const QString& key)
 | 
						|
{
 | 
						|
    QString text = "?";
 | 
						|
    if (key == "Comment")
 | 
						|
    {
 | 
						|
        text = QString::fromUtf16(u"\U0001F4AC");
 | 
						|
    }
 | 
						|
    else if (key == "Help")
 | 
						|
    {
 | 
						|
        text = "?";
 | 
						|
    }
 | 
						|
    else if (key == "Insert")
 | 
						|
    {
 | 
						|
        text = QString::fromUtf16(u"\u2380");
 | 
						|
    }
 | 
						|
    else if (key == "Key")
 | 
						|
    {
 | 
						|
        text = QString::fromUtf16(u"\U0001F511");
 | 
						|
    }
 | 
						|
    else if (key == "NewParagraph")
 | 
						|
    {
 | 
						|
        text = QString::fromUtf16(u"\u2606");
 | 
						|
    }
 | 
						|
    else if (key == "Note")
 | 
						|
    {
 | 
						|
        text = QString::fromUtf16(u"\u266A");
 | 
						|
    }
 | 
						|
    else if (key == "Paragraph")
 | 
						|
    {
 | 
						|
        text = QString::fromUtf16(u"\u00B6");
 | 
						|
    }
 | 
						|
    return text;
 | 
						|
}
 | 
						|
 | 
						|
void PDFLineAnnotation::draw(AnnotationDrawParameters& parameters) const
 | 
						|
{
 | 
						|
    QLineF line = getLine();
 | 
						|
    if (line.isNull())
 | 
						|
    {
 | 
						|
        // Jakub Melka: do not draw empty lines
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    QPainter& painter = *parameters.painter;
 | 
						|
    painter.setCompositionMode(getCompositionMode());
 | 
						|
    painter.setPen(getPen());
 | 
						|
    painter.setBrush(getBrush());
 | 
						|
 | 
						|
    QPainterPath boundingPath;
 | 
						|
    boundingPath.moveTo(line.p1());
 | 
						|
    boundingPath.lineTo(line.p2());
 | 
						|
 | 
						|
    LineGeometryInfo info = LineGeometryInfo::create(line);
 | 
						|
 | 
						|
    const PDFReal leaderLineLength = getLeaderLineLength();
 | 
						|
    const PDFReal coefficientSigned = (leaderLineLength >= 0.0) ? 1.0 : -1.0;
 | 
						|
    const PDFReal leaderLineOffset = getLeaderLineOffset() * coefficientSigned;
 | 
						|
    const PDFReal leaderLineExtension = getLeaderLineExtension() * coefficientSigned;
 | 
						|
    const PDFReal lineEndingSize = qMin(painter.pen().widthF() * 5.0, line.length() * 0.5);
 | 
						|
    const bool hasLeaderLine = !qFuzzyIsNull(leaderLineLength) || !qFuzzyIsNull(leaderLineExtension);
 | 
						|
 | 
						|
    QLineF normalLine = info.transformedLine.normalVector().unitVector();
 | 
						|
    QPointF normalVector = normalLine.p1() - normalLine.p2();
 | 
						|
 | 
						|
    QLineF lineToPaint = info.transformedLine;
 | 
						|
    if (hasLeaderLine)
 | 
						|
    {
 | 
						|
        // We will draw leader lines at both start/end
 | 
						|
        QPointF p1llStart = info.transformedLine.p1() + normalVector * leaderLineOffset;
 | 
						|
        QPointF p1llEnd = info.transformedLine.p1() + normalVector * (leaderLineOffset + leaderLineLength + leaderLineExtension);
 | 
						|
 | 
						|
        QLineF llStart(p1llStart, p1llEnd);
 | 
						|
        llStart = info.LCStoGCS.map(llStart);
 | 
						|
 | 
						|
        boundingPath.moveTo(llStart.p1());
 | 
						|
        boundingPath.lineTo(llStart.p2());
 | 
						|
        painter.drawLine(llStart);
 | 
						|
 | 
						|
        QPointF p2llStart = info.transformedLine.p2() + normalVector * leaderLineOffset;
 | 
						|
        QPointF p2llEnd = info.transformedLine.p2() + normalVector * (leaderLineOffset + leaderLineLength + leaderLineExtension);
 | 
						|
 | 
						|
        QLineF llEnd(p2llStart, p2llEnd);
 | 
						|
        llEnd = info.LCStoGCS.map(llEnd);
 | 
						|
 | 
						|
        boundingPath.moveTo(llEnd.p1());
 | 
						|
        boundingPath.lineTo(llEnd.p2());
 | 
						|
        painter.drawLine(llEnd);
 | 
						|
 | 
						|
        lineToPaint.translate(normalVector * (leaderLineOffset + leaderLineLength));
 | 
						|
    }
 | 
						|
 | 
						|
    QLineF lineToPaintOrig = info.LCStoGCS.map(lineToPaint);
 | 
						|
    info = LineGeometryInfo::create(lineToPaintOrig);
 | 
						|
    drawLine(info, painter, lineEndingSize, getStartLineEnding(), getEndLineEnding(), boundingPath, getCaptionOffset(), getContents(), getCaptionPosition() == CaptionPosition::Top);
 | 
						|
 | 
						|
    parameters.boundingRectangle = boundingPath.boundingRect();
 | 
						|
    parameters.boundingRectangle.adjust(-lineEndingSize, -lineEndingSize, lineEndingSize, lineEndingSize);
 | 
						|
}
 | 
						|
 | 
						|
QColor PDFLineAnnotation::getFillColor() const
 | 
						|
{
 | 
						|
    return getDrawColorFromAnnotationColor(getInteriorColor(), getFillOpacity());
 | 
						|
}
 | 
						|
 | 
						|
void PDFPolygonalGeometryAnnotation::draw(AnnotationDrawParameters& parameters) const
 | 
						|
{
 | 
						|
    if (m_vertices.empty())
 | 
						|
    {
 | 
						|
        // Jakub Melka: do not draw empty lines
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    QPainter& painter = *parameters.painter;
 | 
						|
    painter.setCompositionMode(getCompositionMode());
 | 
						|
    painter.setPen(getPen());
 | 
						|
    painter.setBrush(getBrush());
 | 
						|
 | 
						|
    const PDFReal penWidth = painter.pen().widthF();
 | 
						|
    switch (m_type)
 | 
						|
    {
 | 
						|
        case AnnotationType::Polygon:
 | 
						|
        {
 | 
						|
            if (m_path.isEmpty())
 | 
						|
            {
 | 
						|
                QPolygonF polygon;
 | 
						|
                polygon.reserve(int(m_vertices.size() + 1));
 | 
						|
                for (const QPointF& point : m_vertices)
 | 
						|
                {
 | 
						|
                    polygon << point;
 | 
						|
                }
 | 
						|
                if (!polygon.isClosed())
 | 
						|
                {
 | 
						|
                    polygon << m_vertices.front();
 | 
						|
                }
 | 
						|
 | 
						|
                painter.drawPolygon(polygon, Qt::OddEvenFill);
 | 
						|
                parameters.boundingRectangle = polygon.boundingRect();
 | 
						|
                parameters.boundingRectangle.adjust(-penWidth, -penWidth, penWidth, penWidth);
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                painter.drawPath(m_path);
 | 
						|
                parameters.boundingRectangle = m_path.boundingRect();
 | 
						|
                parameters.boundingRectangle.adjust(-penWidth, -penWidth, penWidth, penWidth);
 | 
						|
            }
 | 
						|
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        case AnnotationType::Polyline:
 | 
						|
        {
 | 
						|
            const PDFReal lineEndingSize = painter.pen().widthF() * 5.0;
 | 
						|
            QPainterPath boundingPath;
 | 
						|
 | 
						|
            if (m_path.isEmpty())
 | 
						|
            {
 | 
						|
                const size_t pointCount = m_vertices.size();
 | 
						|
                const size_t lastPoint = pointCount - 1;
 | 
						|
                for (size_t i = 1; i < pointCount; ++i)
 | 
						|
                {
 | 
						|
                    if (i == 1)
 | 
						|
                    {
 | 
						|
                        // We draw first line
 | 
						|
                        drawLine(LineGeometryInfo::create(QLineF(m_vertices[i - 1], m_vertices[i])), painter, lineEndingSize, getStartLineEnding(), AnnotationLineEnding::None, boundingPath, QPointF(), QString(), true);
 | 
						|
                    }
 | 
						|
                    else if (i == lastPoint)
 | 
						|
                    {
 | 
						|
                        // We draw last line
 | 
						|
                        drawLine(LineGeometryInfo::create(QLineF(m_vertices[i - 1], m_vertices[i])), painter, lineEndingSize, AnnotationLineEnding::None, getEndLineEnding(), boundingPath, QPointF(), QString(), true);
 | 
						|
                    }
 | 
						|
                    else
 | 
						|
                    {
 | 
						|
                        QLineF line(m_vertices[i - 1], m_vertices[i]);
 | 
						|
                        boundingPath.moveTo(line.p1());
 | 
						|
                        boundingPath.lineTo(line.p2());
 | 
						|
                        painter.drawLine(line);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                const PDFReal angle = 30;
 | 
						|
                const PDFReal lineEndingHalfSize = lineEndingSize * 0.5;
 | 
						|
                const PDFReal arrowAxisLength = lineEndingHalfSize / qTan(qDegreesToRadians(angle));
 | 
						|
 | 
						|
                boundingPath = m_path;
 | 
						|
                painter.drawPath(m_path);
 | 
						|
 | 
						|
                QMatrix LCStoGCS_start = LineGeometryInfo::create(QLineF(m_path.pointAtPercent(0.00), m_path.pointAtPercent(0.01))).LCStoGCS;
 | 
						|
                QMatrix LCStoGCS_end = LineGeometryInfo::create(QLineF(m_path.pointAtPercent(0.99), m_path.pointAtPercent(1.00))).LCStoGCS;
 | 
						|
 | 
						|
                drawLineEnding(&painter, m_path.pointAtPercent(0), lineEndingSize, arrowAxisLength, getStartLineEnding(), false, LCStoGCS_start, boundingPath);
 | 
						|
                drawLineEnding(&painter, m_path.pointAtPercent(1), lineEndingSize, arrowAxisLength, getEndLineEnding(), true, LCStoGCS_end, boundingPath);
 | 
						|
            }
 | 
						|
 | 
						|
            parameters.boundingRectangle = boundingPath.boundingRect();
 | 
						|
            parameters.boundingRectangle.adjust(-lineEndingSize, -lineEndingSize, lineEndingSize, lineEndingSize);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        default:
 | 
						|
            Q_ASSERT(false);
 | 
						|
            break;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
QColor PDFPolygonalGeometryAnnotation::getFillColor() const
 | 
						|
{
 | 
						|
    return getDrawColorFromAnnotationColor(getInteriorColor(), getFillOpacity());
 | 
						|
}
 | 
						|
 | 
						|
PDFAnnotation::LineGeometryInfo PDFAnnotation::LineGeometryInfo::create(QLineF line)
 | 
						|
{
 | 
						|
    LineGeometryInfo result;
 | 
						|
    result.originalLine = line;
 | 
						|
    result.transformedLine = QLineF(QPointF(0, 0), QPointF(line.length(), 0));
 | 
						|
 | 
						|
    // Strategy: for simplification, we rotate the line clockwise so we will
 | 
						|
    // get the line axis equal to the x-axis.
 | 
						|
    const double angle = line.angleTo(QLineF(0, 0, 1, 0));
 | 
						|
 | 
						|
    QPointF p1 = line.p1();
 | 
						|
 | 
						|
    // Matrix LCStoGCS is local coordinate system of line line. It transforms
 | 
						|
    // points on the line to the global coordinate system. So, point (0, 0) will
 | 
						|
    // map onto p1 and point (length(p1-p2), 0) will map onto p2.
 | 
						|
    result.LCStoGCS = QMatrix();
 | 
						|
    result.LCStoGCS.translate(p1.x(), p1.y());
 | 
						|
    result.LCStoGCS.rotate(angle);
 | 
						|
    result.GCStoLCS = result.LCStoGCS.inverted();
 | 
						|
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::drawLineEnding(QPainter* painter,
 | 
						|
                                   QPointF point,
 | 
						|
                                   PDFReal lineEndingSize,
 | 
						|
                                   PDFReal arrowAxisLength,
 | 
						|
                                   AnnotationLineEnding ending,
 | 
						|
                                   bool flipAxis,
 | 
						|
                                   const QMatrix& LCStoGCS,
 | 
						|
                                   QPainterPath& boundingPath) const
 | 
						|
{
 | 
						|
    QPainterPath path;
 | 
						|
    const PDFReal lineEndingHalfSize = lineEndingSize * 0.5;
 | 
						|
 | 
						|
    switch (ending)
 | 
						|
    {
 | 
						|
        case AnnotationLineEnding::None:
 | 
						|
            break;
 | 
						|
 | 
						|
        case AnnotationLineEnding::Square:
 | 
						|
        {
 | 
						|
            path.addRect(-lineEndingHalfSize, -lineEndingHalfSize, lineEndingSize, lineEndingSize);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        case AnnotationLineEnding::Circle:
 | 
						|
        {
 | 
						|
            path.addEllipse(QPointF(0, 0), lineEndingHalfSize, lineEndingHalfSize);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        case AnnotationLineEnding::Diamond:
 | 
						|
        {
 | 
						|
            path.moveTo(0.0, -lineEndingHalfSize);
 | 
						|
            path.lineTo(lineEndingHalfSize, 0.0);
 | 
						|
            path.lineTo(0.0, +lineEndingHalfSize);
 | 
						|
            path.lineTo(-lineEndingHalfSize, 0.0);
 | 
						|
            path.closeSubpath();
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        case AnnotationLineEnding::OpenArrow:
 | 
						|
        {
 | 
						|
            path.moveTo(0.0, 0.0);
 | 
						|
            path.lineTo(arrowAxisLength, lineEndingHalfSize);
 | 
						|
            path.moveTo(0.0, 0.0);
 | 
						|
            path.lineTo(arrowAxisLength, -lineEndingHalfSize);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        case AnnotationLineEnding::ClosedArrow:
 | 
						|
        {
 | 
						|
            path.moveTo(0.0, 0.0);
 | 
						|
            path.lineTo(arrowAxisLength, lineEndingHalfSize);
 | 
						|
            path.lineTo(arrowAxisLength, -lineEndingHalfSize);
 | 
						|
            path.closeSubpath();
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        case AnnotationLineEnding::Butt:
 | 
						|
        {
 | 
						|
            path.moveTo(0.0, -lineEndingHalfSize);
 | 
						|
            path.lineTo(0.0, lineEndingHalfSize);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        case AnnotationLineEnding::ROpenArrow:
 | 
						|
        {
 | 
						|
            path.moveTo(0.0, 0.0);
 | 
						|
            path.lineTo(-arrowAxisLength, lineEndingHalfSize);
 | 
						|
            path.moveTo(0.0, 0.0);
 | 
						|
            path.lineTo(-arrowAxisLength, -lineEndingHalfSize);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        case AnnotationLineEnding::RClosedArrow:
 | 
						|
        {
 | 
						|
            path.moveTo(0.0, 0.0);
 | 
						|
            path.lineTo(-arrowAxisLength, lineEndingHalfSize);
 | 
						|
            path.lineTo(-arrowAxisLength, -lineEndingHalfSize);
 | 
						|
            path.closeSubpath();
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        case AnnotationLineEnding::Slash:
 | 
						|
        {
 | 
						|
            const PDFReal angle = 60;
 | 
						|
            const PDFReal lineEndingHalfSizeForSlash = lineEndingSize * 0.5;
 | 
						|
            const PDFReal slashAxisLength = lineEndingHalfSizeForSlash / qTan(qDegreesToRadians(angle));
 | 
						|
 | 
						|
            path.moveTo(-slashAxisLength, -lineEndingHalfSizeForSlash);
 | 
						|
            path.lineTo(slashAxisLength, lineEndingHalfSizeForSlash);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        default:
 | 
						|
            break;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!path.isEmpty())
 | 
						|
    {
 | 
						|
        // Flip the x-axis (we are drawing endpoint)
 | 
						|
        if (flipAxis && ending != AnnotationLineEnding::Slash)
 | 
						|
        {
 | 
						|
            QMatrix matrix;
 | 
						|
            matrix.scale(-1.0, 1.0);
 | 
						|
            path = matrix.map(path);
 | 
						|
        }
 | 
						|
 | 
						|
        path.translate(point);
 | 
						|
        path = LCStoGCS.map(path);
 | 
						|
        painter->drawPath(path);
 | 
						|
        boundingPath.addPath(path);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::drawLine(const PDFAnnotation::LineGeometryInfo& info,
 | 
						|
                             QPainter& painter,
 | 
						|
                             PDFReal lineEndingSize,
 | 
						|
                             AnnotationLineEnding p1Ending,
 | 
						|
                             AnnotationLineEnding p2Ending,
 | 
						|
                             QPainterPath& boundingPath,
 | 
						|
                             QPointF textOffset,
 | 
						|
                             const QString& text,
 | 
						|
                             bool textIsAboveLine) const
 | 
						|
{
 | 
						|
    const PDFReal angle = 30;
 | 
						|
    const PDFReal lineEndingHalfSize = lineEndingSize * 0.5;
 | 
						|
    const PDFReal arrowAxisLength = lineEndingHalfSize / qTan(qDegreesToRadians(angle));
 | 
						|
 | 
						|
    auto getOffsetFromLineEnding = [lineEndingHalfSize, arrowAxisLength](AnnotationLineEnding ending)
 | 
						|
    {
 | 
						|
        switch (ending)
 | 
						|
        {
 | 
						|
            case AnnotationLineEnding::Square:
 | 
						|
            case AnnotationLineEnding::Circle:
 | 
						|
            case AnnotationLineEnding::Diamond:
 | 
						|
                return lineEndingHalfSize;
 | 
						|
            case AnnotationLineEnding::ClosedArrow:
 | 
						|
                return arrowAxisLength;
 | 
						|
 | 
						|
            default:
 | 
						|
                break;
 | 
						|
        }
 | 
						|
 | 
						|
        return 0.0;
 | 
						|
    };
 | 
						|
 | 
						|
    // Remove the offset from start/end
 | 
						|
    const PDFReal startOffset = getOffsetFromLineEnding(p1Ending);
 | 
						|
    const PDFReal endOffset = getOffsetFromLineEnding(p2Ending);
 | 
						|
 | 
						|
    QLineF adjustedLine = info.transformedLine;
 | 
						|
    adjustedLine.setP1(adjustedLine.p1() + QPointF(startOffset, 0));
 | 
						|
    adjustedLine.setP2(adjustedLine.p2() - QPointF(endOffset, 0));
 | 
						|
 | 
						|
    int textVerticalOffset = 0;
 | 
						|
    const bool drawText = !text.isEmpty();
 | 
						|
    QPainterPath textPath;
 | 
						|
 | 
						|
    if (drawText)
 | 
						|
    {
 | 
						|
        QFont font = QApplication::font();
 | 
						|
        font.setPixelSize(12.0);
 | 
						|
 | 
						|
        QFontMetricsF fontMetrics(font, painter.device());
 | 
						|
        textVerticalOffset = fontMetrics.descent() + fontMetrics.leading();
 | 
						|
 | 
						|
        textPath.addText(0, 0, font, text);
 | 
						|
        textPath = QMatrix(1, 0, 0, -1, 0, 0).map(textPath);
 | 
						|
    }
 | 
						|
 | 
						|
    drawLineEnding(&painter, info.transformedLine.p1(), lineEndingSize, arrowAxisLength, p1Ending, false, info.LCStoGCS, boundingPath);
 | 
						|
    drawLineEnding(&painter, info.transformedLine.p2(), lineEndingSize, arrowAxisLength, p2Ending, true, info.LCStoGCS, boundingPath);
 | 
						|
 | 
						|
    if (drawText && !textIsAboveLine)
 | 
						|
    {
 | 
						|
        // We will draw text in the line
 | 
						|
        QRectF textBoundingRect = textPath.controlPointRect();
 | 
						|
        QPointF center = textBoundingRect.center();
 | 
						|
        const qreal offsetY = center.y();
 | 
						|
        const qreal offsetX = center.x();
 | 
						|
        textPath.translate(textOffset + adjustedLine.center() - QPointF(offsetX, offsetY));
 | 
						|
        textBoundingRect = textPath.controlPointRect();
 | 
						|
 | 
						|
        const qreal textPadding = 3.0;
 | 
						|
        const qreal textStart = textBoundingRect.left() - textPadding;
 | 
						|
        const qreal textEnd = textBoundingRect.right() + textPadding;
 | 
						|
 | 
						|
        textPath = info.LCStoGCS.map(textPath);
 | 
						|
        painter.fillPath(textPath, QBrush(painter.pen().color(), Qt::SolidPattern));
 | 
						|
        boundingPath.addPath(textPath);
 | 
						|
 | 
						|
        if (textStart > adjustedLine.p1().x())
 | 
						|
        {
 | 
						|
            QLineF leftLine(adjustedLine.p1(), QPointF(textStart, adjustedLine.p2().y()));
 | 
						|
            painter.drawLine(info.LCStoGCS.map(leftLine));
 | 
						|
        }
 | 
						|
 | 
						|
        if (textEnd < adjustedLine.p2().x())
 | 
						|
        {
 | 
						|
            QLineF rightLine(QPointF(textEnd, adjustedLine.p2().y()), adjustedLine.p2());
 | 
						|
            painter.drawLine(info.LCStoGCS.map(rightLine));
 | 
						|
        }
 | 
						|
 | 
						|
        // Include whole line to the bounding path
 | 
						|
        adjustedLine = info.LCStoGCS.map(adjustedLine);
 | 
						|
        boundingPath.moveTo(adjustedLine.p1());
 | 
						|
        boundingPath.lineTo(adjustedLine.p2());
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        if (drawText)
 | 
						|
        {
 | 
						|
            // We will draw text above the line
 | 
						|
            QRectF textBoundingRect = textPath.controlPointRect();
 | 
						|
            const qreal offsetY = textBoundingRect.top() - textVerticalOffset;
 | 
						|
            const qreal offsetX = textBoundingRect.center().x();
 | 
						|
            textPath.translate(textOffset + adjustedLine.center() - QPointF(offsetX, offsetY));
 | 
						|
 | 
						|
            textPath = info.LCStoGCS.map(textPath);
 | 
						|
            painter.fillPath(textPath, QBrush(painter.pen().color(), Qt::SolidPattern));
 | 
						|
            boundingPath.addPath(textPath);
 | 
						|
        }
 | 
						|
 | 
						|
        adjustedLine = info.LCStoGCS.map(adjustedLine);
 | 
						|
        painter.drawLine(adjustedLine);
 | 
						|
 | 
						|
        boundingPath.moveTo(adjustedLine.p1());
 | 
						|
        boundingPath.lineTo(adjustedLine.p2());
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void PDFHighlightAnnotation::draw(AnnotationDrawParameters& parameters) const
 | 
						|
{
 | 
						|
    if (m_highlightArea.isEmpty())
 | 
						|
    {
 | 
						|
        // Jakub Melka: do not draw empty highlight area
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    QPainter& painter = *parameters.painter;
 | 
						|
    painter.setCompositionMode(getCompositionMode());
 | 
						|
    parameters.boundingRectangle = m_highlightArea.getPath().boundingRect();
 | 
						|
 | 
						|
    painter.setPen(getPen());
 | 
						|
    painter.setBrush(getBrush());
 | 
						|
    switch (m_type)
 | 
						|
    {
 | 
						|
        case AnnotationType::Highlight:
 | 
						|
        {
 | 
						|
            painter.setCompositionMode(QPainter::CompositionMode_Multiply);
 | 
						|
            painter.fillPath(m_highlightArea.getPath(), QBrush(getStrokeColor(), Qt::SolidPattern));
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        case AnnotationType::Underline:
 | 
						|
        {
 | 
						|
            for (const PDFAnnotationQuadrilaterals::Quadrilateral& quadrilateral : m_highlightArea.getQuadrilaterals())
 | 
						|
            {
 | 
						|
                QPointF p1 = quadrilateral[0];
 | 
						|
                QPointF p2 = quadrilateral[1];
 | 
						|
                QLineF line(p1, p2);
 | 
						|
                painter.drawLine(line);
 | 
						|
            }
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        case AnnotationType::Squiggly:
 | 
						|
        {
 | 
						|
            // Jakub Melka: Squiggly underline
 | 
						|
            for (const PDFAnnotationQuadrilaterals::Quadrilateral& quadrilateral : m_highlightArea.getQuadrilaterals())
 | 
						|
            {
 | 
						|
                QPointF p1 = quadrilateral[0];
 | 
						|
                QPointF p2 = quadrilateral[1];
 | 
						|
 | 
						|
                // Calculate length (height) of quadrilateral
 | 
						|
                const PDFReal height = (QLineF(quadrilateral[0], quadrilateral[2]).length() + QLineF(quadrilateral[1], quadrilateral[3]).length()) * 0.5;
 | 
						|
                const PDFReal markSize = height / 7.0;
 | 
						|
 | 
						|
                // We can't assume, that line is horizontal. For example, rotated text with 45° degrees
 | 
						|
                // counterclockwise, if it is highlighted with squiggly underline, it is not horizontal line.
 | 
						|
                // So, we must calculate line geometry and transform it.
 | 
						|
                QLineF line(p1, p2);
 | 
						|
                LineGeometryInfo lineGeometryInfo = LineGeometryInfo::create(line);
 | 
						|
 | 
						|
                bool leadingEdge = true;
 | 
						|
                for (PDFReal x = lineGeometryInfo.transformedLine.p1().x(); x < lineGeometryInfo.transformedLine.p2().x(); x+= markSize)
 | 
						|
                {
 | 
						|
                    QLineF line;
 | 
						|
                    if (leadingEdge)
 | 
						|
                    {
 | 
						|
                        line = QLineF(x, 0.0, x + markSize, markSize);
 | 
						|
                    }
 | 
						|
                    else
 | 
						|
                    {
 | 
						|
                        // Falling edge
 | 
						|
                        line = QLineF(x, markSize, x + markSize, 0.0);
 | 
						|
                    }
 | 
						|
 | 
						|
                    QLineF transformedLine = lineGeometryInfo.LCStoGCS.map(line);
 | 
						|
                    painter.drawLine(transformedLine);
 | 
						|
                    leadingEdge = !leadingEdge;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        case AnnotationType::StrikeOut:
 | 
						|
        {
 | 
						|
            for (const PDFAnnotationQuadrilaterals::Quadrilateral& quadrilateral : m_highlightArea.getQuadrilaterals())
 | 
						|
            {
 | 
						|
                QPointF p1 = (quadrilateral[0] + quadrilateral[2]) * 0.5;
 | 
						|
                QPointF p2 = (quadrilateral[1] + quadrilateral[3]) * 0.5;
 | 
						|
                QLineF line(p1, p2);
 | 
						|
                painter.drawLine(line);
 | 
						|
            }
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        default:
 | 
						|
            break;
 | 
						|
    }
 | 
						|
 | 
						|
    const qreal penWidth = painter.pen().widthF();
 | 
						|
    parameters.boundingRectangle.adjust(-penWidth, -penWidth, penWidth, penWidth);
 | 
						|
}
 | 
						|
 | 
						|
std::vector<PDFAppeareanceStreams::Key> PDFLinkAnnotation::getDrawKeys(const PDFFormManager* formManager) const
 | 
						|
{
 | 
						|
    Q_UNUSED(formManager);
 | 
						|
 | 
						|
    return { PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Down, QByteArray() } };
 | 
						|
}
 | 
						|
 | 
						|
void PDFLinkAnnotation::draw(AnnotationDrawParameters& parameters) const
 | 
						|
{
 | 
						|
    if (parameters.key.first != PDFAppeareanceStreams::Appearance::Down ||
 | 
						|
        m_activationRegion.isEmpty() ||
 | 
						|
        m_highlightMode == LinkHighlightMode::None)
 | 
						|
    {
 | 
						|
        // Nothing to draw
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    QPainter& painter = *parameters.painter;
 | 
						|
    parameters.boundingRectangle = getRectangle();
 | 
						|
 | 
						|
    switch (m_highlightMode)
 | 
						|
    {
 | 
						|
        case LinkHighlightMode::Invert:
 | 
						|
        {
 | 
						|
            // Invert all
 | 
						|
            painter.setCompositionMode(QPainter::CompositionMode_Difference);
 | 
						|
            painter.fillPath(m_activationRegion.getPath(), QBrush(Qt::white, Qt::SolidPattern));
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        case LinkHighlightMode::Outline:
 | 
						|
        {
 | 
						|
            // Invert the border
 | 
						|
            painter.setCompositionMode(QPainter::CompositionMode_Difference);
 | 
						|
            QPen pen = getPen();
 | 
						|
            pen.setColor(Qt::white);
 | 
						|
            painter.setPen(pen);
 | 
						|
            painter.setBrush(Qt::NoBrush);
 | 
						|
            painter.drawPath(m_activationRegion.getPath());
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        case LinkHighlightMode::Push:
 | 
						|
        {
 | 
						|
            // Draw border
 | 
						|
            painter.setCompositionMode(getCompositionMode());
 | 
						|
            painter.setPen(getPen());
 | 
						|
            painter.setBrush(Qt::NoBrush);
 | 
						|
            painter.drawPath(m_activationRegion.getPath());
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        default:
 | 
						|
            Q_ASSERT(false);
 | 
						|
            break;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
PDFAnnotationDefaultAppearance PDFAnnotationDefaultAppearance::parse(const QByteArray& string)
 | 
						|
{
 | 
						|
    PDFAnnotationDefaultAppearance result;
 | 
						|
    PDFLexicalAnalyzer analyzer(string.constData(), string.constData() + string.size());
 | 
						|
 | 
						|
    std::vector<PDFLexicalAnalyzer::Token> tokens;
 | 
						|
 | 
						|
    for (PDFLexicalAnalyzer::Token token = analyzer.fetch(); token.type != PDFLexicalAnalyzer::TokenType::EndOfFile; token = analyzer.fetch())
 | 
						|
    {
 | 
						|
        tokens.push_back(qMove(token));
 | 
						|
    }
 | 
						|
 | 
						|
    auto readNumber = [&tokens](size_t index) -> PDFReal
 | 
						|
    {
 | 
						|
        Q_ASSERT(index >= 0 && index < tokens.size());
 | 
						|
        const PDFLexicalAnalyzer::Token& token = tokens[index];
 | 
						|
        if (token.type == PDFLexicalAnalyzer::TokenType::Real ||
 | 
						|
            token.type == PDFLexicalAnalyzer::TokenType::Integer)
 | 
						|
        {
 | 
						|
            return token.data.toDouble();
 | 
						|
        }
 | 
						|
 | 
						|
        return 0.0;
 | 
						|
    };
 | 
						|
 | 
						|
    for (size_t i = 0; i < tokens.size(); ++i)
 | 
						|
    {
 | 
						|
        const PDFLexicalAnalyzer::Token& token = tokens[i];
 | 
						|
        if (token.type == PDFLexicalAnalyzer::TokenType::Command)
 | 
						|
        {
 | 
						|
            QByteArray command = token.data.toByteArray();
 | 
						|
            if (command == "Tf")
 | 
						|
            {
 | 
						|
                if (i >= 1)
 | 
						|
                {
 | 
						|
                    result.m_fontSize = readNumber(i - 1);
 | 
						|
                }
 | 
						|
                if (i >= 2)
 | 
						|
                {
 | 
						|
                    result.m_fontName = tokens[i - 2].data.toByteArray();
 | 
						|
                }
 | 
						|
            }
 | 
						|
            else if (command == "g" && i >= 1)
 | 
						|
            {
 | 
						|
                result.m_fontColor = PDFAnnotation::getDrawColorFromAnnotationColor({ readNumber(i - 1) }, 1.0);
 | 
						|
            }
 | 
						|
            else if (command == "rg" && i >= 3)
 | 
						|
            {
 | 
						|
                result.m_fontColor = PDFAnnotation::getDrawColorFromAnnotationColor({ readNumber(i - 3), readNumber(i - 2), readNumber(i - 1) }, 1.0);
 | 
						|
            }
 | 
						|
            else if (command == "k" && i >= 4)
 | 
						|
            {
 | 
						|
                result.m_fontColor = PDFAnnotation::getDrawColorFromAnnotationColor({ readNumber(i - 4), readNumber(i - 3), readNumber(i - 2), readNumber(i - 1) }, 1.0);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
void PDFFreeTextAnnotation::draw(AnnotationDrawParameters& parameters) const
 | 
						|
{
 | 
						|
    QPainter& painter = *parameters.painter;
 | 
						|
    painter.setCompositionMode(getCompositionMode());
 | 
						|
    parameters.boundingRectangle = getRectangle();
 | 
						|
 | 
						|
    painter.setPen(getPen());
 | 
						|
    painter.setBrush(getBrush());
 | 
						|
 | 
						|
    // Draw callout line
 | 
						|
    const PDFAnnotationCalloutLine& calloutLine = getCalloutLine();
 | 
						|
 | 
						|
    switch (calloutLine.getType())
 | 
						|
    {
 | 
						|
        case PDFAnnotationCalloutLine::Type::Invalid:
 | 
						|
        {
 | 
						|
            // Annotation doesn't have callout line
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        case PDFAnnotationCalloutLine::Type::StartEnd:
 | 
						|
        {
 | 
						|
            QPainterPath boundingPath;
 | 
						|
            QLineF line(calloutLine.getPoint(0), calloutLine.getPoint(1));
 | 
						|
            const PDFReal lineEndingSize = qMin(painter.pen().widthF() * 5.0, line.length() * 0.5);
 | 
						|
            drawLine(LineGeometryInfo::create(line), painter, lineEndingSize,
 | 
						|
                     getStartLineEnding(), getEndLineEnding(), boundingPath,
 | 
						|
                     QPointF(), QString(), true);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        case PDFAnnotationCalloutLine::Type::StartKneeEnd:
 | 
						|
        {
 | 
						|
            QPainterPath boundingPath;
 | 
						|
 | 
						|
            QLineF lineStart(calloutLine.getPoint(0), calloutLine.getPoint(1));
 | 
						|
            QLineF lineEnd(calloutLine.getPoint(1), calloutLine.getPoint(2));
 | 
						|
 | 
						|
            PDFReal preferredLineEndingSize = painter.pen().widthF() * 5.0;
 | 
						|
            PDFReal lineStartEndingSize = qMin(preferredLineEndingSize, lineStart.length() * 0.5);
 | 
						|
            drawLine(LineGeometryInfo::create(lineStart), painter, lineStartEndingSize,
 | 
						|
                     getStartLineEnding(), AnnotationLineEnding::None, boundingPath,
 | 
						|
                     QPointF(), QString(), true);
 | 
						|
 | 
						|
 | 
						|
            PDFReal lineEndEndingSize = qMin(preferredLineEndingSize, lineEnd.length() * 0.5);
 | 
						|
            drawLine(LineGeometryInfo::create(lineEnd), painter, lineEndEndingSize,
 | 
						|
                     AnnotationLineEnding::None, getEndLineEnding() , boundingPath,
 | 
						|
                     QPointF(), QString(), true);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        default:
 | 
						|
        {
 | 
						|
            Q_ASSERT(false);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    QRectF textRect = getTextRectangle();
 | 
						|
    if (!textRect.isValid())
 | 
						|
    {
 | 
						|
        textRect = getRectangle();
 | 
						|
    }
 | 
						|
 | 
						|
    painter.drawRect(textRect);
 | 
						|
 | 
						|
    // Draw text
 | 
						|
    PDFAnnotationDefaultAppearance defaultAppearance = PDFAnnotationDefaultAppearance::parse(getDefaultAppearance());
 | 
						|
 | 
						|
    QFont font(defaultAppearance.getFontName());
 | 
						|
    font.setPixelSize(defaultAppearance.getFontSize());
 | 
						|
    painter.setPen(defaultAppearance.getFontColor());
 | 
						|
 | 
						|
    Qt::Alignment alignment = Qt::AlignTop;
 | 
						|
    switch (getJustification())
 | 
						|
    {
 | 
						|
        case PDFFreeTextAnnotation::Justification::Left:
 | 
						|
            alignment |= Qt::AlignLeft;
 | 
						|
            break;
 | 
						|
 | 
						|
        case PDFFreeTextAnnotation::Justification::Centered:
 | 
						|
            alignment |= Qt::AlignHCenter;
 | 
						|
            break;
 | 
						|
 | 
						|
        case PDFFreeTextAnnotation::Justification::Right:
 | 
						|
            alignment |= Qt::AlignRight;
 | 
						|
            break;
 | 
						|
 | 
						|
        default:
 | 
						|
            alignment |= Qt::AlignLeft;
 | 
						|
            Q_ASSERT(false);
 | 
						|
            break;
 | 
						|
    }
 | 
						|
 | 
						|
    QTextOption option(alignment);
 | 
						|
    option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
 | 
						|
    option.setUseDesignMetrics(true);
 | 
						|
 | 
						|
    painter.translate(textRect.left(), textRect.bottom());
 | 
						|
    painter.scale(1.0, -1.0);
 | 
						|
    painter.drawText(QRectF(QPointF(0, 0), textRect.size()), getContents(), option);
 | 
						|
}
 | 
						|
 | 
						|
void PDFCaretAnnotation::draw(AnnotationDrawParameters& parameters) const
 | 
						|
{
 | 
						|
    QPainter& painter = *parameters.painter;
 | 
						|
    painter.setCompositionMode(getCompositionMode());
 | 
						|
    parameters.boundingRectangle = getRectangle();
 | 
						|
 | 
						|
    QRectF caretRect = getCaretRectangle();
 | 
						|
    if (caretRect.isEmpty())
 | 
						|
    {
 | 
						|
        caretRect = getRectangle();
 | 
						|
    }
 | 
						|
 | 
						|
    QPointF controlPoint(caretRect.center());
 | 
						|
    controlPoint.setY(caretRect.top());
 | 
						|
 | 
						|
    QPointF topPoint = controlPoint;
 | 
						|
    topPoint.setY(caretRect.bottom());
 | 
						|
 | 
						|
    QPainterPath path;
 | 
						|
    path.moveTo(caretRect.topLeft());
 | 
						|
    path.quadTo(controlPoint, topPoint);
 | 
						|
    path.quadTo(controlPoint, caretRect.topRight());
 | 
						|
    path.lineTo(caretRect.topLeft());
 | 
						|
    path.closeSubpath();
 | 
						|
 | 
						|
    painter.fillPath(path, QBrush(getStrokeColor(), Qt::SolidPattern));
 | 
						|
}
 | 
						|
 | 
						|
void PDFInkAnnotation::draw(AnnotationDrawParameters& parameters) const
 | 
						|
{
 | 
						|
    QPainter& painter = *parameters.painter;
 | 
						|
    QPainterPath path = getInkPath();
 | 
						|
 | 
						|
    painter.setPen(getPen());
 | 
						|
    painter.setBrush(getBrush());
 | 
						|
    painter.setCompositionMode(getCompositionMode());
 | 
						|
 | 
						|
    QPainterPath boundingPath;
 | 
						|
    QPainterPath currentPath;
 | 
						|
    const int elementCount = path.elementCount();
 | 
						|
    bool hasSpline = false;
 | 
						|
    for (int i = 0; i < elementCount; ++i)
 | 
						|
    {
 | 
						|
        QPainterPath::Element element = path.elementAt(i);
 | 
						|
        switch (element.type)
 | 
						|
        {
 | 
						|
            case QPainterPath::MoveToElement:
 | 
						|
            {
 | 
						|
                // Reset the path
 | 
						|
                if (!currentPath.isEmpty())
 | 
						|
                {
 | 
						|
                    boundingPath.addPath(currentPath);
 | 
						|
                    painter.drawPath(currentPath);
 | 
						|
                    currentPath.clear();
 | 
						|
                }
 | 
						|
                currentPath.moveTo(element.x, element.y);
 | 
						|
                break;
 | 
						|
            }
 | 
						|
 | 
						|
            case QPainterPath::LineToElement:
 | 
						|
            {
 | 
						|
                const QPointF p0 = currentPath.currentPosition();
 | 
						|
                const QPointF p1(element.x, element.y);
 | 
						|
 | 
						|
                QLineF line(p0, p1);
 | 
						|
                QPointF normal = line.normalVector().p2() - p0;
 | 
						|
 | 
						|
                // Jakub Melka: This computation should be clarified. We use second order Bezier curves.
 | 
						|
                // We must calculate single control point. Let B(t) is bezier curve of second order.
 | 
						|
                // Then second derivation is B''(t) = 2(p0 - 2*Pc + p1). Second derivation curve is its
 | 
						|
                // normal. So, we calculate normal vector of the line (which has norm equal to line length),
 | 
						|
                // then we get following formula:
 | 
						|
                //
 | 
						|
                // Pc = (p0 + p1) / 2 - B''(t) / 4
 | 
						|
                //
 | 
						|
                // So, for bezier curves of second order, second derivative is constant (which is not surprising,
 | 
						|
                // because second derivative of polynomial of order 2 is also a constant). Control point is then
 | 
						|
                // used to paint the path.
 | 
						|
                QPointF controlPoint = -normal * 0.25 + (p0 + p1) * 0.5;
 | 
						|
                currentPath.quadTo(controlPoint, p1);
 | 
						|
                break;
 | 
						|
            }
 | 
						|
 | 
						|
            case QPainterPath::CurveToElement:
 | 
						|
            case QPainterPath::CurveToDataElement:
 | 
						|
                hasSpline = true;
 | 
						|
                break;
 | 
						|
 | 
						|
            default:
 | 
						|
                Q_ASSERT(false);
 | 
						|
                break;
 | 
						|
        }
 | 
						|
 | 
						|
        // Jakub Melka: If we have a spline, then we don't do anything...
 | 
						|
        // Just copy the spline path.
 | 
						|
        if (hasSpline)
 | 
						|
        {
 | 
						|
            currentPath = path;
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // Reset the path
 | 
						|
    if (!currentPath.isEmpty())
 | 
						|
    {
 | 
						|
        boundingPath.addPath(currentPath);
 | 
						|
        painter.drawPath(currentPath);
 | 
						|
        currentPath.clear();
 | 
						|
    }
 | 
						|
 | 
						|
    const qreal penWidth = painter.pen().widthF();
 | 
						|
    parameters.boundingRectangle = boundingPath.boundingRect();
 | 
						|
    parameters.boundingRectangle.adjust(-penWidth, -penWidth, penWidth, penWidth);
 | 
						|
}
 | 
						|
 | 
						|
void PDFStampAnnotation::draw(AnnotationDrawParameters& parameters) const
 | 
						|
{
 | 
						|
    QPainter& painter = *parameters.painter;
 | 
						|
    painter.setCompositionMode(getCompositionMode());
 | 
						|
 | 
						|
    QString text = getText(m_stamp);
 | 
						|
    QColor color(Qt::red);
 | 
						|
 | 
						|
    switch (m_stamp)
 | 
						|
    {
 | 
						|
        case Stamp::Approved:
 | 
						|
            color = Qt::green;
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::AsIs:
 | 
						|
        case Stamp::Confidential:
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::Departmental:
 | 
						|
            color = Qt::blue;
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::Draft:
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::Experimental:
 | 
						|
            color = Qt::blue;
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::Expired:
 | 
						|
        case Stamp::Final:
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::ForComment:
 | 
						|
            color = Qt::green;
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::ForPublicRelease:
 | 
						|
            color = Qt::green;
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::NotApproved:
 | 
						|
        case Stamp::NotForPublicRelease:
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::Sold:
 | 
						|
            color = Qt::blue;
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::TopSecret:
 | 
						|
            break;
 | 
						|
 | 
						|
        default:
 | 
						|
            Q_ASSERT(false);
 | 
						|
            break;
 | 
						|
    }
 | 
						|
 | 
						|
    color.setAlphaF(getFillOpacity());
 | 
						|
 | 
						|
    const PDFReal textHeight = 16;
 | 
						|
    QFont font("Courier New");
 | 
						|
    font.setBold(true);
 | 
						|
    font.setPixelSize(textHeight);
 | 
						|
 | 
						|
    QFontMetricsF fontMetrics(font, painter.device());
 | 
						|
    const qreal textWidth = fontMetrics.width(text);
 | 
						|
    const qreal rectangleWidth = textWidth + 10;
 | 
						|
    const qreal rectangleHeight = textHeight * 1.2;
 | 
						|
    const qreal penWidth = 2.0;
 | 
						|
 | 
						|
    QRectF rectangle = getRectangle();
 | 
						|
    rectangle.setSize(QSizeF(rectangleWidth, rectangleHeight));
 | 
						|
 | 
						|
    QPen pen(color);
 | 
						|
    pen.setWidthF(penWidth);
 | 
						|
    painter.setPen(pen);
 | 
						|
    painter.setBrush(Qt::NoBrush);
 | 
						|
 | 
						|
    painter.drawRoundedRect(rectangle, 5, 5, Qt::AbsoluteSize);
 | 
						|
 | 
						|
    // Draw text
 | 
						|
    QPainterPath textPath;
 | 
						|
    textPath.addText(0, 0, font, text);
 | 
						|
    textPath = QMatrix(1, 0, 0, -1, 0, 0).map(textPath);
 | 
						|
 | 
						|
    QPointF center = textPath.boundingRect().center();
 | 
						|
    textPath.translate(rectangle.center() - center);
 | 
						|
    painter.fillPath(textPath, QBrush(color, Qt::SolidPattern));
 | 
						|
 | 
						|
    parameters.boundingRectangle = rectangle;
 | 
						|
    parameters.boundingRectangle.adjust(-penWidth, -penWidth, penWidth, penWidth);
 | 
						|
}
 | 
						|
 | 
						|
QString PDFStampAnnotation::getText(Stamp stamp)
 | 
						|
{
 | 
						|
    QString text;
 | 
						|
 | 
						|
    switch (stamp)
 | 
						|
    {
 | 
						|
        case Stamp::Approved:
 | 
						|
            text = PDFTranslationContext::tr("APPROVED");
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::AsIs:
 | 
						|
            text = PDFTranslationContext::tr("AS IS");
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::Confidential:
 | 
						|
            text = PDFTranslationContext::tr("CONFIDENTIAL");
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::Departmental:
 | 
						|
            text = PDFTranslationContext::tr("DEPARTMENTAL");
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::Draft:
 | 
						|
            text = PDFTranslationContext::tr("DRAFT");
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::Experimental:
 | 
						|
            text = PDFTranslationContext::tr("EXPERIMENTAL");
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::Expired:
 | 
						|
            text = PDFTranslationContext::tr("EXPIRED");
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::Final:
 | 
						|
            text = PDFTranslationContext::tr("FINAL");
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::ForComment:
 | 
						|
            text = PDFTranslationContext::tr("FOR COMMENT");
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::ForPublicRelease:
 | 
						|
            text = PDFTranslationContext::tr("FOR PUBLIC RELEASE");
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::NotApproved:
 | 
						|
            text = PDFTranslationContext::tr("NOT APPROVED");
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::NotForPublicRelease:
 | 
						|
            text = PDFTranslationContext::tr("NOT FOR PUBLIC RELEASE");
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::Sold:
 | 
						|
            text = PDFTranslationContext::tr("SOLD");
 | 
						|
            break;
 | 
						|
 | 
						|
        case Stamp::TopSecret:
 | 
						|
            text = PDFTranslationContext::tr("TOP SECRET");
 | 
						|
            break;
 | 
						|
 | 
						|
        default:
 | 
						|
            Q_ASSERT(false);
 | 
						|
            break;
 | 
						|
    }
 | 
						|
 | 
						|
    return text;
 | 
						|
}
 | 
						|
 | 
						|
void PDFStampAnnotation::setStamp(const Stamp& stamp)
 | 
						|
{
 | 
						|
    m_stamp = stamp;
 | 
						|
}
 | 
						|
 | 
						|
void PDFStampAnnotation::setIntent(const StampIntent& intent)
 | 
						|
{
 | 
						|
    m_intent = intent;
 | 
						|
}
 | 
						|
 | 
						|
void PDFAnnotation::drawCharacterSymbol(QString text, PDFReal opacity, AnnotationDrawParameters& parameters) const
 | 
						|
{
 | 
						|
    QColor strokeColor = QColor::fromRgbF(0.0, 0.0, 0.0, opacity);
 | 
						|
 | 
						|
    constexpr const PDFReal rectSize = 24.0;
 | 
						|
    QPainter& painter = *parameters.painter;
 | 
						|
    QRectF rectangle = getRectangle();
 | 
						|
    rectangle.setSize(QSizeF(rectSize, rectSize));
 | 
						|
 | 
						|
    QFont font = QApplication::font();
 | 
						|
    font.setPixelSize(16.0);
 | 
						|
 | 
						|
    QPainterPath textPath;
 | 
						|
    textPath.addText(0.0, 0.0, font, text);
 | 
						|
    textPath = QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0).map(textPath);
 | 
						|
    QRectF textBoundingRect = textPath.boundingRect();
 | 
						|
    QPointF offset = rectangle.center() - textBoundingRect.center();
 | 
						|
    textPath.translate(offset);
 | 
						|
    painter.fillPath(textPath, QBrush(strokeColor, Qt::SolidPattern));
 | 
						|
 | 
						|
    parameters.boundingRectangle = rectangle;
 | 
						|
}
 | 
						|
 | 
						|
void PDFFileAttachmentAnnotation::draw(AnnotationDrawParameters& parameters) const
 | 
						|
{
 | 
						|
    QString text = "?";
 | 
						|
    switch (getIcon())
 | 
						|
    {
 | 
						|
        case FileAttachmentIcon::Graph:
 | 
						|
            text = QString::fromUtf16(u"\U0001F4C8");
 | 
						|
            break;
 | 
						|
        case FileAttachmentIcon::Paperclip:
 | 
						|
            text = QString::fromUtf16(u"\U0001F4CE");
 | 
						|
            break;
 | 
						|
        case FileAttachmentIcon::PushPin:
 | 
						|
            text = QString::fromUtf16(u"\U0001F4CC");
 | 
						|
            break;
 | 
						|
        case FileAttachmentIcon::Tag:
 | 
						|
            text = QString::fromUtf16(u"\U0001F3F7");
 | 
						|
            break;
 | 
						|
 | 
						|
        default:
 | 
						|
            Q_ASSERT(false);
 | 
						|
            break;
 | 
						|
    }
 | 
						|
 | 
						|
    parameters.painter->setCompositionMode(getCompositionMode());
 | 
						|
    drawCharacterSymbol(text, getStrokeOpacity(), parameters);
 | 
						|
}
 | 
						|
 | 
						|
void PDFSoundAnnotation::draw(AnnotationDrawParameters& parameters) const
 | 
						|
{
 | 
						|
    QString text = "?";
 | 
						|
    switch (getIcon())
 | 
						|
    {
 | 
						|
        case Icon::Speaker:
 | 
						|
            text = QString::fromUtf16(u"\U0001F508");
 | 
						|
            break;
 | 
						|
        case Icon::Microphone:
 | 
						|
            text = QString::fromUtf16(u"\U0001F3A4");
 | 
						|
            break;
 | 
						|
 | 
						|
        default:
 | 
						|
            Q_ASSERT(false);
 | 
						|
            break;
 | 
						|
    }
 | 
						|
 | 
						|
    parameters.painter->setCompositionMode(getCompositionMode());
 | 
						|
    drawCharacterSymbol(text, getStrokeOpacity(), parameters);
 | 
						|
}
 | 
						|
 | 
						|
const PDFAnnotationManager::PageAnnotation* PDFAnnotationManager::PageAnnotations::getPopupAnnotation(const PageAnnotation& pageAnnotation) const
 | 
						|
{
 | 
						|
    const PDFMarkupAnnotation* markupAnnotation = pageAnnotation.annotation->asMarkupAnnotation();
 | 
						|
    if (markupAnnotation)
 | 
						|
    {
 | 
						|
        const PDFObjectReference popupAnnotation = markupAnnotation->getPopupAnnotation();
 | 
						|
        auto it = std::find_if(annotations.cbegin(), annotations.cend(), [this, popupAnnotation](const PageAnnotation& pa) { return pa.annotation->getSelfReference() == popupAnnotation; });
 | 
						|
        if (it != annotations.cend())
 | 
						|
        {
 | 
						|
            return &*it;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return nullptr;
 | 
						|
}
 | 
						|
 | 
						|
std::vector<const PDFAnnotationManager::PageAnnotation*> PDFAnnotationManager::PageAnnotations::getReplies(const PageAnnotation& pageAnnotation) const
 | 
						|
{
 | 
						|
    std::vector<const PageAnnotation*> result;
 | 
						|
 | 
						|
    const PDFObjectReference reference = pageAnnotation.annotation->getSelfReference();
 | 
						|
    for (size_t i = 0, count = annotations.size(); i < count; ++i)
 | 
						|
    {
 | 
						|
        const PageAnnotation& currentAnnotation = annotations[i];
 | 
						|
        if (currentAnnotation.annotation->isReplyTo())
 | 
						|
        {
 | 
						|
            const PDFMarkupAnnotation* markupAnnotation = currentAnnotation.annotation->asMarkupAnnotation();
 | 
						|
            Q_ASSERT(markupAnnotation);
 | 
						|
 | 
						|
            if (markupAnnotation->getInReplyTo() == reference)
 | 
						|
            {
 | 
						|
                result.push_back(¤tAnnotation);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    auto comparator = [](const PageAnnotation* l, const PageAnnotation* r)
 | 
						|
    {
 | 
						|
        QDateTime leftDateTime = l->annotation->getLastModifiedDateTime();
 | 
						|
        QDateTime rightDateTime = r->annotation->getLastModifiedDateTime();
 | 
						|
 | 
						|
        if (const PDFMarkupAnnotation* markupL = l->annotation->asMarkupAnnotation())
 | 
						|
        {
 | 
						|
            leftDateTime = markupL->getCreationDate();
 | 
						|
        }
 | 
						|
 | 
						|
        if (const PDFMarkupAnnotation* markupR = r->annotation->asMarkupAnnotation())
 | 
						|
        {
 | 
						|
            rightDateTime = markupR->getCreationDate();
 | 
						|
        }
 | 
						|
 | 
						|
        return leftDateTime < rightDateTime;
 | 
						|
    };
 | 
						|
    std::sort(result.begin(), result.end(), comparator);
 | 
						|
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
void PDFWidgetAnnotation::draw(AnnotationDrawParameters& parameters) const
 | 
						|
{
 | 
						|
    // Do not draw without form manager
 | 
						|
    if (!parameters.formManager)
 | 
						|
    {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    // Do not draw without form field
 | 
						|
    const PDFFormField* formField = parameters.formManager->getFormFieldForWidget(getSelfReference());
 | 
						|
    if (!formField)
 | 
						|
    {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    PDFPainterStateGuard guard(parameters.painter);
 | 
						|
    parameters.painter->setCompositionMode(getCompositionMode());
 | 
						|
 | 
						|
    const PDFFormFieldWidgetEditor* editor = parameters.formManager->getEditor(formField);
 | 
						|
    if (editor && editor->isEditorDrawEnabled())
 | 
						|
    {
 | 
						|
        editor->draw(parameters, true);
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        switch (formField->getFieldType())
 | 
						|
        {
 | 
						|
            case PDFFormField::FieldType::Text:
 | 
						|
            case PDFFormField::FieldType::Choice:
 | 
						|
            {
 | 
						|
                editor->draw(parameters, false);
 | 
						|
                break;
 | 
						|
            }
 | 
						|
 | 
						|
            case PDFFormField::FieldType::Button:
 | 
						|
            {
 | 
						|
                const PDFFormFieldButton* button = dynamic_cast<const PDFFormFieldButton*>(formField);
 | 
						|
                switch (button->getButtonType())
 | 
						|
                {
 | 
						|
                    case PDFFormFieldButton::ButtonType::PushButton:
 | 
						|
                    {
 | 
						|
                        QRectF rectangle = getRectangle();
 | 
						|
 | 
						|
                        if (!getContents().isEmpty())
 | 
						|
                        {
 | 
						|
                            QByteArray defaultAppearance = parameters.formManager->getForm()->getDefaultAppearance().value_or(QByteArray());
 | 
						|
                            PDFAnnotationDefaultAppearance appearance = PDFAnnotationDefaultAppearance::parse(defaultAppearance);
 | 
						|
 | 
						|
                            qreal fontSize = appearance.getFontSize();
 | 
						|
                            if (qFuzzyIsNull(fontSize))
 | 
						|
                            {
 | 
						|
                                fontSize = rectangle.height() * 0.6;
 | 
						|
                            }
 | 
						|
 | 
						|
                            QFont font(appearance.getFontName());
 | 
						|
                            font.setHintingPreference(QFont::PreferNoHinting);
 | 
						|
                            font.setPixelSize(qCeil(fontSize));
 | 
						|
                            font.setStyleStrategy(QFont::ForceOutline);
 | 
						|
 | 
						|
                            QFontMetrics fontMetrics(font);
 | 
						|
 | 
						|
                            QPainter* painter = parameters.painter;
 | 
						|
                            painter->translate(rectangle.bottomLeft());
 | 
						|
                            painter->scale(1.0, -1.0);
 | 
						|
                            painter->setFont(font);
 | 
						|
 | 
						|
                            QStyleOptionButton option;
 | 
						|
                            option.state = QStyle::State_Enabled;
 | 
						|
                            option.rect = QRect(0, 0, qFloor(rectangle.width()), qFloor(rectangle.height()));
 | 
						|
                            option.palette = QApplication::palette();
 | 
						|
 | 
						|
                            if (parameters.key.first == PDFAppeareanceStreams::Appearance::Rollover)
 | 
						|
                            {
 | 
						|
                                option.state |= QStyle::State_MouseOver;
 | 
						|
                            }
 | 
						|
 | 
						|
                            if (parameters.key.first == PDFAppeareanceStreams::Appearance::Down)
 | 
						|
                            {
 | 
						|
                                option.state |= QStyle::State_Sunken;
 | 
						|
                            }
 | 
						|
 | 
						|
                            option.features = QStyleOptionButton::None;
 | 
						|
                            option.text = getContents();
 | 
						|
                            option.fontMetrics = fontMetrics;
 | 
						|
 | 
						|
                            QApplication::style()->drawControl(QStyle::CE_PushButton, &option, painter, nullptr);
 | 
						|
                        }
 | 
						|
                        else
 | 
						|
                        {
 | 
						|
                            // This is push button without text. Just mark the area as highlighted.
 | 
						|
                            QPainter* painter = parameters.painter;
 | 
						|
 | 
						|
                            if (parameters.key.first == PDFAppeareanceStreams::Appearance::Rollover ||
 | 
						|
                                parameters.key.first == PDFAppeareanceStreams::Appearance::Down)
 | 
						|
                            {
 | 
						|
                                switch (m_highlightMode)
 | 
						|
                                {
 | 
						|
                                    case HighlightMode::Invert:
 | 
						|
                                    {
 | 
						|
                                        // Invert all
 | 
						|
                                        painter->setCompositionMode(QPainter::CompositionMode_Difference);
 | 
						|
                                        painter->fillRect(rectangle, QBrush(Qt::white, Qt::SolidPattern));
 | 
						|
                                        break;
 | 
						|
                                    }
 | 
						|
 | 
						|
                                    case HighlightMode::Outline:
 | 
						|
                                    {
 | 
						|
                                        // Invert the border
 | 
						|
                                        painter->setCompositionMode(QPainter::CompositionMode_Difference);
 | 
						|
                                        QPen pen = getPen();
 | 
						|
                                        pen.setColor(Qt::white);
 | 
						|
                                        painter->setPen(pen);
 | 
						|
                                        painter->setBrush(Qt::NoBrush);
 | 
						|
                                        painter->drawRect(rectangle);
 | 
						|
                                        break;
 | 
						|
                                    }
 | 
						|
 | 
						|
                                    case HighlightMode::Push:
 | 
						|
                                    {
 | 
						|
                                        // Draw border
 | 
						|
                                        painter->setCompositionMode(getCompositionMode());
 | 
						|
                                        painter->setPen(getPen());
 | 
						|
                                        painter->setBrush(Qt::NoBrush);
 | 
						|
                                        painter->drawRect(rectangle);
 | 
						|
                                        break;
 | 
						|
                                    }
 | 
						|
 | 
						|
                                    default:
 | 
						|
                                        Q_ASSERT(false);
 | 
						|
                                        break;
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
 | 
						|
                    case PDFFormFieldButton::ButtonType::RadioButton:
 | 
						|
                    case PDFFormFieldButton::ButtonType::CheckBox:
 | 
						|
                    {
 | 
						|
                        QRectF rectangle = getRectangle();
 | 
						|
                        QPainter* painter = parameters.painter;
 | 
						|
                        painter->translate(rectangle.bottomLeft());
 | 
						|
                        painter->scale(1.0, -1.0);
 | 
						|
 | 
						|
                        QStyleOptionButton option;
 | 
						|
                        option.state = QStyle::State_Enabled;
 | 
						|
                        option.rect = QRect(0, 0, qFloor(rectangle.width()), qFloor(rectangle.height()));
 | 
						|
                        option.palette = QApplication::palette();
 | 
						|
 | 
						|
                        if (parameters.key.first == PDFAppeareanceStreams::Appearance::Rollover)
 | 
						|
                        {
 | 
						|
                            option.state |= QStyle::State_MouseOver;
 | 
						|
                        }
 | 
						|
 | 
						|
                        if (parameters.key.first == PDFAppeareanceStreams::Appearance::Down)
 | 
						|
                        {
 | 
						|
                            option.state |= QStyle::State_Sunken;
 | 
						|
                        }
 | 
						|
 | 
						|
                        if (parameters.key.second != "Off")
 | 
						|
                        {
 | 
						|
                            option.state |= QStyle::State_On;
 | 
						|
                        }
 | 
						|
                        else
 | 
						|
                        {
 | 
						|
                            option.state |= QStyle::State_Off;
 | 
						|
                        }
 | 
						|
 | 
						|
                        option.features = QStyleOptionButton::None;
 | 
						|
                        option.text = QString();
 | 
						|
 | 
						|
                        QStyle::PrimitiveElement element = (button->getButtonType() == PDFFormFieldButton::ButtonType::CheckBox) ? QStyle::PE_IndicatorCheckBox : QStyle::PE_IndicatorRadioButton;
 | 
						|
                        QApplication::style()->drawPrimitive(element, &option, painter, nullptr);
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
 | 
						|
                    default:
 | 
						|
                    {
 | 
						|
                        Q_ASSERT(false);
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                break;
 | 
						|
            }
 | 
						|
 | 
						|
            case PDFFormField::FieldType::Invalid:
 | 
						|
            case PDFFormField::FieldType::Signature:
 | 
						|
                break;
 | 
						|
 | 
						|
            default:
 | 
						|
            {
 | 
						|
                Q_ASSERT(false);
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
std::vector<pdf::PDFAppeareanceStreams::Key> PDFWidgetAnnotation::getDrawKeys(const PDFFormManager* formManager) const
 | 
						|
{
 | 
						|
    if (!formManager)
 | 
						|
    {
 | 
						|
        return PDFAnnotation::getDrawKeys(formManager);
 | 
						|
    }
 | 
						|
 | 
						|
    std::vector<pdf::PDFAppeareanceStreams::Key> result;
 | 
						|
 | 
						|
    // Try get the form field, if we find it, then determine from form field type
 | 
						|
    // the list of appearance states.
 | 
						|
    const PDFFormField* formField = formManager->getFormFieldForWidget(getSelfReference());
 | 
						|
    if (!formField)
 | 
						|
    {
 | 
						|
        return PDFAnnotation::getDrawKeys(formManager);
 | 
						|
    }
 | 
						|
 | 
						|
    switch (formField->getFieldType())
 | 
						|
    {
 | 
						|
        case PDFFormField::FieldType::Invalid:
 | 
						|
            break;
 | 
						|
 | 
						|
        case PDFFormField::FieldType::Button:
 | 
						|
        {
 | 
						|
            const PDFFormFieldButton* button = dynamic_cast<const PDFFormFieldButton*>(formField);
 | 
						|
            switch (button->getButtonType())
 | 
						|
            {
 | 
						|
                case PDFFormFieldButton::ButtonType::PushButton:
 | 
						|
                {
 | 
						|
                    result = { PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Normal, QByteArray() },
 | 
						|
                               PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Rollover, QByteArray() },
 | 
						|
                               PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Down, QByteArray() } };
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
 | 
						|
                case PDFFormFieldButton::ButtonType::RadioButton:
 | 
						|
                case PDFFormFieldButton::ButtonType::CheckBox:
 | 
						|
                {
 | 
						|
                    result = getAppearanceStreams().getAppearanceKeys();
 | 
						|
                    PDFAppeareanceStreams::Key offKey{ PDFAppeareanceStreams::Appearance::Normal, QByteArray() };
 | 
						|
                    if (std::find(result.cbegin(), result.cend(), offKey) == result.cend())
 | 
						|
                    {
 | 
						|
                        result.push_back(qMove(offKey));
 | 
						|
                    }
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
 | 
						|
                default:
 | 
						|
                {
 | 
						|
                    Q_ASSERT(false);
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        case PDFFormField::FieldType::Text:
 | 
						|
            // Text has only default appearance
 | 
						|
            break;
 | 
						|
 | 
						|
        case PDFFormField::FieldType::Choice:
 | 
						|
            // Choices have always default appearance
 | 
						|
            break;
 | 
						|
 | 
						|
        case PDFFormField::FieldType::Signature:
 | 
						|
            // Signatures have always default appearance
 | 
						|
            break;
 | 
						|
    }
 | 
						|
 | 
						|
    if (result.empty())
 | 
						|
    {
 | 
						|
        result = PDFAnnotation::getDrawKeys(formManager);
 | 
						|
    }
 | 
						|
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
void PDFRedactAnnotation::draw(AnnotationDrawParameters& parameters) const
 | 
						|
{
 | 
						|
    if (m_redactionRegion.isEmpty())
 | 
						|
    {
 | 
						|
        // Jakub Melka: do not draw empty redact area
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    QPainter& painter = *parameters.painter;
 | 
						|
    painter.setCompositionMode(getCompositionMode());
 | 
						|
    parameters.boundingRectangle = m_redactionRegion.getPath().boundingRect();
 | 
						|
 | 
						|
    painter.setPen(getPen());
 | 
						|
    painter.setBrush(getBrush());
 | 
						|
    painter.drawPath(m_redactionRegion.getPath());
 | 
						|
 | 
						|
    const qreal penWidth = painter.pen().widthF();
 | 
						|
    parameters.boundingRectangle.adjust(-penWidth, -penWidth, penWidth, penWidth);
 | 
						|
}
 | 
						|
 | 
						|
QColor PDFRedactAnnotation::getFillColor() const
 | 
						|
{
 | 
						|
    return getDrawColorFromAnnotationColor(getInteriorColor(), getFillOpacity());
 | 
						|
}
 | 
						|
 | 
						|
}   // namespace pdf
 |