1
0
mirror of https://github.com/JakubMelka/PDF4QT.git synced 2025-02-25 07:57:48 +01:00

Refactoring of annotation drawing

This commit is contained in:
Jakub Melka 2020-05-24 18:42:11 +02:00
parent 81f27e87ac
commit 00e141129a
2 changed files with 257 additions and 139 deletions

@ -1084,157 +1084,222 @@ void PDFAnnotationManager::drawPage(QPainter* painter,
Q_ASSERT(m_cmsManager);
Q_ASSERT(m_optionalActivity);
int fontCacheLock = 0;
const PDFCMSPointer cms = m_cmsManager->getCurrentCMS();
m_fontCache->setCacheShrinkEnabled(this, false);
m_fontCache->setCacheShrinkEnabled(&fontCacheLock, false);
const PageAnnotation* annotationDrawnByEditor = nullptr;
for (const PageAnnotation& annotation : annotations.annotations)
{
try
// If annotation draw is not enabled, then skip it
if (!isAnnotationDrawEnabled(annotation))
{
const PDFAnnotation::Flags annotationFlags = annotation.annotation->getEffectiveFlags();
if (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
{
continue;
}
const PDFFormFieldWidgetEditor* editor = nullptr;
const bool isWidget = annotation.annotation->getType() == AnnotationType::Widget;
if (isWidget && m_formManager)
{
editor = m_formManager->getEditor(m_formManager->getFormFieldForWidget(annotation.annotation->getSelfReference()));
}
const bool isEditorDrawEnabled = editor && editor->isEditorDrawEnabled();
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.
if (!m_features.testFlag(PDFRenderer::IgnoreOptionalContent) &&
annotation.annotation->getOptionalContent().isValid())
{
continue;
}
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);
}
continue;
}
PDFDocumentDataLoaderDecorator loader(m_document);
const PDFStream* formStream = appearanceStreamObject.getStream();
const PDFDictionary* formDictionary = formStream->getDictionary();
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"));
if (formBoundingBox.isEmpty() || annotationRectangle.isEmpty())
{
// Form bounding box is empty
continue;
}
QMatrix userSpaceToDeviceSpace = prepareTransformations(pagePointToDevicePointMatrix, painter->device(), annotationFlags, page, annotationRectangle);
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.get(), 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);
}
}
// 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);
}
}
catch (PDFException exception)
{
errors.push_back(PDFRenderError(RenderErrorType::Error, exception.getMessage()));
continue;
}
catch (PDFRendererException exception)
if (isAnnotationDrawnByEditor(annotation))
{
errors.push_back(exception.getError());
Q_ASSERT(!annotationDrawnByEditor);
annotationDrawnByEditor = &annotation;
continue;
}
drawAnnotation(annotation, pagePointToDevicePointMatrix, page, cms.data(), false, errors, painter);
}
m_fontCache->setCacheShrinkEnabled(this, true);
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"));
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);
}
}
// 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);
}
}

@ -1400,6 +1400,59 @@ protected:
QPainter* painter,
QMatrix userSpaceToDeviceSpace) const;
/// Returns true, if given annotation should be drawn
/// \param annotation Annotation
bool isAnnotationDrawEnabled(const PageAnnotation& annotation) const;
/// Returns true, if annotation is drawn by editor (from form widget)
/// \param annotation Annotation
bool isAnnotationDrawnByEditor(const PageAnnotation& annotation) const;
/// Draws annotation
/// \param pageAnnotation Page annotation
/// \param pagePointToDevicePointMatrix Page point to device point matrix
/// \param page Page
/// \param cms Color management system
/// \param isEditorDrawEnabled Is editor draw enabled?
/// \param errors Errors list (where draw errors are stored)
/// \param painter Painter
void drawAnnotation(const PageAnnotation& annotation,
const QMatrix& pagePointToDevicePointMatrix,
const PDFPage* page,
const PDFCMS* cms,
bool isEditorDrawEnabled,
QList<PDFRenderError>& errors,
QPainter* painter) const;
/// Draws annotation by direct drawing, not using annotation's
/// appearance stream.
/// \param pageAnnotation Page annotation
/// \param pagePointToDevicePointMatrix Page point to device point matrix
/// \param page Page
/// \param cms Color management system
/// \param isEditorDrawEnabled Is annotation drawn by form widget editor?
/// \param painter Painter
void drawAnnotationDirect(const PageAnnotation& annotation,
const QMatrix& pagePointToDevicePointMatrix,
const PDFPage* page,
const PDFCMS* cms,
bool isEditorDrawEnabled,
QPainter* painter) const;
/// Draws annotation using annotation's appearance stream.
/// \param pageAnnotation Page annotation
/// \param appearanceStreamObject Object with appearance stream
/// \param pagePointToDevicePointMatrix Page point to device point matrix
/// \param page Page
/// \param cms Color management system
/// \param painter Painter
void drawAnnotationUsingAppearanceStream(const PageAnnotation& annotation,
const PDFObject& appearanceStreamObject,
const QMatrix& pagePointToDevicePointMatrix,
const PDFPage* page,
const PDFCMS* cms,
QPainter* painter) const;
const PDFDocument* m_document;
PDFFontCache* m_fontCache;