2019-02-24 17:48:37 +01:00
// Copyright (C) 2019 Jakub Melka
//
// This file is part of PdfForQt.
//
// PdfForQt 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.
//
// PdfForQt 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 PDFForQt. If not, see <https://www.gnu.org/licenses/>.
# include "pdfpainter.h"
2019-08-31 14:37:18 +02:00
# include "pdfpattern.h"
2019-02-24 17:48:37 +01:00
# include <QPainter>
namespace pdf
{
2019-07-04 17:52:38 +02:00
PDFPainter : : PDFPainter ( QPainter * painter ,
PDFRenderer : : Features features ,
QMatrix pagePointToDevicePointMatrix ,
const PDFPage * page ,
const PDFDocument * document ,
const PDFFontCache * fontCache ,
2019-09-28 18:26:31 +02:00
const PDFOptionalContentActivity * optionalContentActivity ,
const PDFMeshQualitySettings & meshQualitySettings ) :
PDFPageContentProcessor ( page , document , fontCache , optionalContentActivity , pagePointToDevicePointMatrix , meshQualitySettings ) ,
2019-02-24 17:48:37 +01:00
m_painter ( painter ) ,
2019-08-31 14:37:18 +02:00
m_features ( features )
2019-02-24 17:48:37 +01:00
{
Q_ASSERT ( painter ) ;
Q_ASSERT ( pagePointToDevicePointMatrix . isInvertible ( ) ) ;
m_painter - > save ( ) ;
2019-09-01 15:44:22 +02:00
if ( features . testFlag ( PDFRenderer : : ClipToCropBox ) )
{
QRectF cropBox = page - > getRotatedCropBox ( ) ;
if ( cropBox . isValid ( ) )
{
QPainterPath path ;
path . addPolygon ( pagePointToDevicePointMatrix . map ( cropBox ) ) ;
m_painter - > setClipPath ( path , Qt : : IntersectClip ) ;
}
}
2019-09-26 19:14:04 +02:00
m_painter - > setRenderHint ( QPainter : : SmoothPixmapTransform , features . testFlag ( PDFRenderer : : SmoothImages ) ) ;
2019-02-24 17:48:37 +01:00
}
PDFPainter : : ~ PDFPainter ( )
{
m_painter - > restore ( ) ;
}
2019-05-04 18:22:40 +02:00
void PDFPainter : : performPathPainting ( const QPainterPath & path , bool stroke , bool fill , bool text , Qt : : FillRule fillRule )
2019-02-24 17:48:37 +01:00
{
2019-08-31 15:55:59 +02:00
Q_ASSERT ( stroke | | fill ) ;
2019-08-31 14:37:18 +02:00
2019-05-04 18:22:40 +02:00
// Set antialiasing
const bool antialiasing = ( text & & m_features . testFlag ( PDFRenderer : : TextAntialiasing ) ) | | ( ! text & & m_features . testFlag ( PDFRenderer : : Antialiasing ) ) ;
m_painter - > setRenderHint ( QPainter : : Antialiasing , antialiasing ) ;
2019-02-24 17:48:37 +01:00
if ( stroke )
{
m_painter - > setPen ( getCurrentPen ( ) ) ;
}
else
{
m_painter - > setPen ( Qt : : NoPen ) ;
}
if ( fill )
{
m_painter - > setBrush ( getCurrentBrush ( ) ) ;
}
else
{
m_painter - > setBrush ( Qt : : NoBrush ) ;
}
Q_ASSERT ( path . fillRule ( ) = = fillRule ) ;
m_painter - > drawPath ( path ) ;
}
void PDFPainter : : performClipping ( const QPainterPath & path , Qt : : FillRule fillRule )
{
Q_ASSERT ( path . fillRule ( ) = = fillRule ) ;
2019-03-17 16:12:36 +01:00
m_painter - > setClipPath ( path , Qt : : IntersectClip ) ;
2019-02-24 17:48:37 +01:00
}
2019-05-07 18:21:22 +02:00
void PDFPainter : : performImagePainting ( const QImage & image )
{
2019-07-04 17:52:38 +02:00
if ( isContentSuppressed ( ) )
{
// Content is suppressed, do not paint anything
return ;
}
2019-05-07 18:21:22 +02:00
m_painter - > save ( ) ;
2019-07-06 16:27:36 +02:00
QImage adjustedImage = image ;
if ( m_features . testFlag ( PDFRenderer : : SmoothImages ) )
{
// Test, if we can use smooth images. We can use them under following conditions:
// 1) Transformed rectangle is not skewed or deformed (so vectors (0, 1) and (1, 0) are orthogonal)
// 2) Image enlargement is not too big (so we doesn't run out of memory)
QMatrix matrix = m_painter - > worldMatrix ( ) ;
QLineF mappedWidthVector = matrix . map ( QLineF ( 0 , 0 , 1 , 0 ) ) ;
QLineF mappedHeightVector = matrix . map ( QLineF ( 0 , 0 , 0 , 1 ) ) ;
qreal angle = mappedWidthVector . angleTo ( mappedHeightVector ) ;
if ( qFuzzyCompare ( angle , 90.0 ) )
{
// Image is not skewed, so we test enlargement factor
const int newWidth = mappedWidthVector . length ( ) ;
const int newHeight = mappedHeightVector . length ( ) ;
const int newPixels = newWidth * newHeight ;
const int oldPixels = image . width ( ) * image . height ( ) ;
if ( newPixels < oldPixels * 8 )
{
QSize size = adjustedImage . size ( ) ;
QSize adjustedImageSize = size . scaled ( newWidth , newHeight , Qt : : KeepAspectRatio ) ;
adjustedImage = adjustedImage . scaled ( adjustedImageSize , Qt : : KeepAspectRatio , Qt : : SmoothTransformation ) ;
}
}
}
QMatrix imageTransform ( 1.0 / adjustedImage . width ( ) , 0 , 0 , 1.0 / adjustedImage . height ( ) , 0 , 0 ) ;
2019-05-07 18:21:22 +02:00
QMatrix worldMatrix = imageTransform * m_painter - > worldMatrix ( ) ;
// Because Qt uses opposite axis direction than PDF, then we must transform the y-axis
// to the opposite (so the image is then unchanged)
2019-07-06 16:27:36 +02:00
worldMatrix . translate ( 0 , adjustedImage . height ( ) ) ;
2019-05-07 18:21:22 +02:00
worldMatrix . scale ( 1 , - 1 ) ;
m_painter - > setWorldMatrix ( worldMatrix ) ;
2019-07-06 16:27:36 +02:00
m_painter - > drawImage ( 0 , 0 , adjustedImage ) ;
2019-05-07 18:21:22 +02:00
m_painter - > restore ( ) ;
}
2019-08-31 15:55:59 +02:00
void PDFPainter : : performMeshPainting ( const PDFMesh & mesh )
{
m_painter - > save ( ) ;
m_painter - > setWorldMatrix ( QMatrix ( ) ) ;
2019-09-29 15:44:35 +02:00
mesh . paint ( m_painter , getGraphicState ( ) - > getAlphaFilling ( ) ) ;
2019-08-31 15:55:59 +02:00
m_painter - > restore ( ) ;
}
2019-02-24 17:48:37 +01:00
void PDFPainter : : performUpdateGraphicsState ( const PDFPageContentProcessorState & state )
{
const PDFPageContentProcessorState : : StateFlags flags = state . getStateFlags ( ) ;
// If current transformation matrix has changed, then update it
if ( flags . testFlag ( PDFPageContentProcessorState : : StateCurrentTransformationMatrix ) )
{
2019-08-31 14:37:18 +02:00
m_painter - > setWorldMatrix ( getCurrentWorldMatrix ( ) , false ) ;
2019-02-24 17:48:37 +01:00
}
if ( flags . testFlag ( PDFPageContentProcessorState : : StateStrokeColor ) | |
flags . testFlag ( PDFPageContentProcessorState : : StateLineWidth ) | |
flags . testFlag ( PDFPageContentProcessorState : : StateLineCapStyle ) | |
flags . testFlag ( PDFPageContentProcessorState : : StateLineJoinStyle ) | |
flags . testFlag ( PDFPageContentProcessorState : : StateMitterLimit ) | |
2019-09-29 15:44:35 +02:00
flags . testFlag ( PDFPageContentProcessorState : : StateLineDashPattern ) | |
flags . testFlag ( PDFPageContentProcessorState : : StateAlphaStroking ) )
2019-02-24 17:48:37 +01:00
{
m_currentPen . dirty ( ) ;
}
2019-09-29 15:44:35 +02:00
if ( flags . testFlag ( PDFPageContentProcessorState : : StateFillColor ) | |
flags . testFlag ( PDFPageContentProcessorState : : StateAlphaFilling ) )
2019-02-24 17:48:37 +01:00
{
m_currentBrush . dirty ( ) ;
}
2019-03-30 18:45:30 +01:00
2019-09-29 15:44:35 +02:00
// If current blend mode has changed, then update it
if ( flags . testFlag ( PDFPageContentProcessorState : : StateBlendMode ) )
{
const BlendMode blendMode = state . getBlendMode ( ) ;
const QPainter : : CompositionMode compositionMode = PDFBlendModeInfo : : getCompositionModeFromBlendMode ( blendMode ) ;
if ( ! PDFBlendModeInfo : : isSupportedByQt ( blendMode ) )
{
reportRenderError ( RenderErrorType : : NotImplemented , PDFTranslationContext : : tr ( " Blend mode '%1' not supported. " ) . arg ( PDFBlendModeInfo : : getBlendModeName ( blendMode ) ) ) ;
}
m_painter - > setCompositionMode ( compositionMode ) ;
}
2019-03-30 18:45:30 +01:00
PDFPageContentProcessor : : performUpdateGraphicsState ( state ) ;
2019-02-24 17:48:37 +01:00
}
void PDFPainter : : performSaveGraphicState ( ProcessOrder order )
{
if ( order = = ProcessOrder : : AfterOperation )
{
m_painter - > save ( ) ;
}
}
void PDFPainter : : performRestoreGraphicState ( ProcessOrder order )
{
if ( order = = ProcessOrder : : BeforeOperation )
{
m_painter - > restore ( ) ;
}
}
2019-07-06 15:55:37 +02:00
bool PDFPainter : : isContentSuppressedByOC ( PDFObjectReference ocgOrOcmd )
{
if ( m_features . testFlag ( PDFRenderer : : IgnoreOptionalContent ) )
{
return false ;
}
return PDFPageContentProcessor : : isContentSuppressedByOC ( ocgOrOcmd ) ;
}
2019-02-24 17:48:37 +01:00
QPen PDFPainter : : getCurrentPenImpl ( ) const
{
const PDFPageContentProcessorState * graphicState = getGraphicState ( ) ;
2019-09-29 15:44:35 +02:00
const QColor & color = graphicState - > getStrokeColorWithAlpha ( ) ;
2019-02-24 17:48:37 +01:00
if ( color . isValid ( ) )
{
const PDFReal lineWidth = graphicState - > getLineWidth ( ) ;
Qt : : PenCapStyle penCapStyle = graphicState - > getLineCapStyle ( ) ;
Qt : : PenJoinStyle penJoinStyle = graphicState - > getLineJoinStyle ( ) ;
const PDFLineDashPattern & lineDashPattern = graphicState - > getLineDashPattern ( ) ;
const PDFReal mitterLimit = graphicState - > getMitterLimit ( ) ;
QPen pen ( color ) ;
pen . setWidthF ( lineWidth ) ;
pen . setCapStyle ( penCapStyle ) ;
pen . setJoinStyle ( penJoinStyle ) ;
pen . setMiterLimit ( mitterLimit ) ;
if ( lineDashPattern . isSolid ( ) )
{
pen . setStyle ( Qt : : SolidLine ) ;
}
else
{
pen . setStyle ( Qt : : CustomDashLine ) ;
pen . setDashPattern ( QVector < PDFReal > : : fromStdVector ( lineDashPattern . getDashArray ( ) ) ) ;
pen . setDashOffset ( lineDashPattern . getDashOffset ( ) ) ;
}
return pen ;
}
else
{
return QPen ( Qt : : NoPen ) ;
}
}
QBrush PDFPainter : : getCurrentBrushImpl ( ) const
{
const PDFPageContentProcessorState * graphicState = getGraphicState ( ) ;
2019-09-29 15:44:35 +02:00
const QColor & color = graphicState - > getFillColorWithAlpha ( ) ;
2019-02-24 17:48:37 +01:00
if ( color . isValid ( ) )
{
return QBrush ( color , Qt : : SolidPattern ) ;
}
else
{
return QBrush ( Qt : : NoBrush ) ;
}
}
} // namespace pdf