2022-01-30 18:23:25 +01:00
// Copyright (C) 2019-2022 Jakub Melka
2021-09-27 11:14:20 +02:00
//
// This file is part of PDF4QT.
//
// PDF4QT is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// with the written consent of the copyright owner, any later version.
//
// PDF4QT is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with PDF4QT. If not, see <https://www.gnu.org/licenses/>.
# include "pdfpainter.h"
# include "pdfpattern.h"
# include "pdfcms.h"
2022-01-30 18:23:25 +01:00
# include "pdfdbgheap.h"
2021-09-27 11:14:20 +02:00
# include <QPainter>
2021-09-08 20:33:32 +02:00
# include <QCryptographicHash>
2021-09-27 11:14:20 +02:00
namespace pdf
{
PDFPainterBase : : PDFPainterBase ( PDFRenderer : : Features features ,
const PDFPage * page ,
const PDFDocument * document ,
const PDFFontCache * fontCache ,
const PDFCMS * cms ,
const PDFOptionalContentActivity * optionalContentActivity ,
2022-08-20 17:43:33 +02:00
QTransform pagePointToDevicePointMatrix ,
2021-09-27 11:14:20 +02:00
const PDFMeshQualitySettings & meshQualitySettings ) :
BaseClass ( page , document , fontCache , cms , optionalContentActivity , pagePointToDevicePointMatrix , meshQualitySettings ) ,
m_features ( features )
{
}
void PDFPainterBase : : performUpdateGraphicsState ( const PDFPageContentProcessorState & state )
{
const PDFPageContentProcessorState : : StateFlags flags = state . getStateFlags ( ) ;
// If current transformation matrix has changed, then update it
if ( flags . testFlag ( PDFPageContentProcessorState : : StateCurrentTransformationMatrix ) )
{
setWorldMatrix ( getCurrentWorldMatrix ( ) ) ;
}
if ( flags . testFlag ( PDFPageContentProcessorState : : StateStrokeColor ) | |
flags . testFlag ( PDFPageContentProcessorState : : StateLineWidth ) | |
flags . testFlag ( PDFPageContentProcessorState : : StateLineCapStyle ) | |
flags . testFlag ( PDFPageContentProcessorState : : StateLineJoinStyle ) | |
flags . testFlag ( PDFPageContentProcessorState : : StateMitterLimit ) | |
flags . testFlag ( PDFPageContentProcessorState : : StateLineDashPattern ) | |
flags . testFlag ( PDFPageContentProcessorState : : StateAlphaStroking ) )
{
m_currentPen . dirty ( ) ;
}
if ( flags . testFlag ( PDFPageContentProcessorState : : StateFillColor ) | |
flags . testFlag ( PDFPageContentProcessorState : : StateAlphaFilling ) )
{
m_currentBrush . dirty ( ) ;
}
// If current blend mode has changed, then update it
if ( flags . testFlag ( PDFPageContentProcessorState : : StateBlendMode ) )
{
// Try to simulate transparency groups. Use only first composition mode,
// outside the transparency groups (so we are on pages main transparency
// groups).
const BlendMode blendMode = state . getBlendMode ( ) ;
if ( canSetBlendMode ( blendMode ) )
{
if ( ! PDFBlendModeInfo : : isSupportedByQt ( blendMode ) )
{
reportRenderErrorOnce ( RenderErrorType : : NotSupported , PDFTranslationContext : : tr ( " Blend mode '%1' not supported. " ) . arg ( PDFBlendModeInfo : : getBlendModeName ( blendMode ) ) ) ;
}
const QPainter : : CompositionMode compositionMode = PDFBlendModeInfo : : getCompositionModeFromBlendMode ( blendMode ) ;
setCompositionMode ( compositionMode ) ;
}
else if ( blendMode ! = BlendMode : : Normal & & blendMode ! = BlendMode : : Compatible )
{
reportRenderErrorOnce ( RenderErrorType : : NotSupported , PDFTranslationContext : : tr ( " Blend mode '%1' is in transparency group, which is not supported. " ) . arg ( PDFBlendModeInfo : : getBlendModeName ( blendMode ) ) ) ;
}
}
if ( flags . testFlag ( PDFPageContentProcessorState : : StateSoftMask ) & & state . getSoftMask ( ) )
{
reportRenderErrorOnce ( RenderErrorType : : NotSupported , PDFTranslationContext : : tr ( " Soft masks not supported. " ) ) ;
}
BaseClass : : performUpdateGraphicsState ( state ) ;
}
bool PDFPainterBase : : isContentSuppressedByOC ( PDFObjectReference ocgOrOcmd )
{
if ( m_features . testFlag ( PDFRenderer : : IgnoreOptionalContent ) )
{
return false ;
}
return PDFPageContentProcessor : : isContentSuppressedByOC ( ocgOrOcmd ) ;
}
QPen PDFPainterBase : : getCurrentPenImpl ( ) const
{
const PDFPageContentProcessorState * graphicState = getGraphicState ( ) ;
QColor color = graphicState - > getStrokeColor ( ) ;
if ( color . isValid ( ) )
{
color . setAlphaF ( getEffectiveStrokingAlpha ( ) ) ;
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 ) ;
2023-07-25 18:26:57 +02:00
pen . setDashPattern ( lineDashPattern . createForQPen ( pen . widthF ( ) ) ) ;
2021-09-27 11:14:20 +02:00
pen . setDashOffset ( lineDashPattern . getDashOffset ( ) ) ;
}
return pen ;
}
else
{
return QPen ( Qt : : NoPen ) ;
}
}
QBrush PDFPainterBase : : getCurrentBrushImpl ( ) const
{
const PDFPageContentProcessorState * graphicState = getGraphicState ( ) ;
QColor color = graphicState - > getFillColor ( ) ;
if ( color . isValid ( ) )
{
color . setAlphaF ( getEffectiveFillingAlpha ( ) ) ;
return QBrush ( color , Qt : : SolidPattern ) ;
}
else
{
return QBrush ( Qt : : NoBrush ) ;
}
}
PDFReal PDFPainterBase : : getEffectiveStrokingAlpha ( ) const
{
PDFReal alpha = getGraphicState ( ) - > getAlphaStroking ( ) ;
auto it = m_transparencyGroupDataStack . crbegin ( ) ;
auto itEnd = m_transparencyGroupDataStack . crend ( ) ;
for ( ; it ! = itEnd ; + + it )
{
const PDFTransparencyGroupPainterData & transparencyGroup = * it ;
alpha * = transparencyGroup . alphaStroke ;
if ( transparencyGroup . group . isolated )
{
break ;
}
}
return alpha ;
}
PDFReal PDFPainterBase : : getEffectiveFillingAlpha ( ) const
{
PDFReal alpha = getGraphicState ( ) - > getAlphaFilling ( ) ;
auto it = m_transparencyGroupDataStack . crbegin ( ) ;
auto itEnd = m_transparencyGroupDataStack . crend ( ) ;
for ( ; it ! = itEnd ; + + it )
{
const PDFTransparencyGroupPainterData & transparencyGroup = * it ;
alpha * = transparencyGroup . alphaFill ;
if ( transparencyGroup . group . isolated )
{
break ;
}
}
return alpha ;
}
bool PDFPainterBase : : canSetBlendMode ( BlendMode mode ) const
{
// We will assume, that we can set blend mode, when
// all other blend modes on transparency stack are normal,
// or compatible. It should work.
Q_UNUSED ( mode ) ;
return std : : all_of ( m_transparencyGroupDataStack . cbegin ( ) , m_transparencyGroupDataStack . cend ( ) , [ ] ( const PDFTransparencyGroupPainterData & group ) { return group . blendMode = = BlendMode : : Normal | | group . blendMode = = BlendMode : : Compatible ; } ) ;
}
void PDFPainterBase : : performBeginTransparencyGroup ( ProcessOrder order , const PDFTransparencyGroup & transparencyGroup )
{
if ( order = = ProcessOrder : : BeforeOperation )
{
PDFTransparencyGroupPainterData data ;
data . group = transparencyGroup ;
data . alphaFill = getGraphicState ( ) - > getAlphaFilling ( ) ;
data . alphaStroke = getGraphicState ( ) - > getAlphaStroking ( ) ;
data . blendMode = getGraphicState ( ) - > getBlendMode ( ) ;
m_transparencyGroupDataStack . emplace_back ( qMove ( data ) ) ;
}
}
void PDFPainterBase : : performEndTransparencyGroup ( ProcessOrder order , const PDFTransparencyGroup & transparencyGroup )
{
Q_UNUSED ( transparencyGroup ) ;
if ( order = = ProcessOrder : : AfterOperation )
{
m_transparencyGroupDataStack . pop_back ( ) ;
}
}
PDFPainter : : PDFPainter ( QPainter * painter ,
PDFRenderer : : Features features ,
2022-08-20 17:43:33 +02:00
QTransform pagePointToDevicePointMatrix ,
2021-09-27 11:14:20 +02:00
const PDFPage * page ,
const PDFDocument * document ,
const PDFFontCache * fontCache ,
const PDFCMS * cms ,
const PDFOptionalContentActivity * optionalContentActivity ,
const PDFMeshQualitySettings & meshQualitySettings ) :
BaseClass ( features , page , document , fontCache , cms , optionalContentActivity , pagePointToDevicePointMatrix , meshQualitySettings ) ,
m_painter ( painter )
{
Q_ASSERT ( painter ) ;
Q_ASSERT ( pagePointToDevicePointMatrix . isInvertible ( ) ) ;
m_painter - > save ( ) ;
if ( features . testFlag ( PDFRenderer : : ClipToCropBox ) )
{
QRectF cropBox = page - > getCropBox ( ) ;
if ( cropBox . isValid ( ) )
{
QPainterPath path ;
path . addPolygon ( pagePointToDevicePointMatrix . map ( cropBox ) ) ;
m_painter - > setClipPath ( path , Qt : : IntersectClip ) ;
}
}
m_painter - > setRenderHint ( QPainter : : SmoothPixmapTransform , features . testFlag ( PDFRenderer : : SmoothImages ) ) ;
}
PDFPainter : : ~ PDFPainter ( )
{
m_painter - > restore ( ) ;
}
void PDFPainter : : performPathPainting ( const QPainterPath & path , bool stroke , bool fill , bool text , Qt : : FillRule fillRule )
{
Q_ASSERT ( stroke | | fill ) ;
// Set antialiasing
const bool antialiasing = ( text & & hasFeature ( PDFRenderer : : TextAntialiasing ) ) | | ( ! text & & hasFeature ( PDFRenderer : : Antialiasing ) ) ;
m_painter - > setRenderHint ( QPainter : : Antialiasing , antialiasing ) ;
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 ) ;
m_painter - > setClipPath ( path , Qt : : IntersectClip ) ;
}
void PDFPainter : : performImagePainting ( const QImage & image )
{
if ( isContentSuppressed ( ) )
{
// Content is suppressed, do not paint anything
return ;
}
m_painter - > save ( ) ;
QImage adjustedImage = image ;
if ( hasFeature ( 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) We are shrinking the image
2022-07-31 18:32:57 +02:00
QTransform transform = m_painter - > worldTransform ( ) ;
QLineF mappedWidthVector = transform . map ( QLineF ( 0 , 0 , 1 , 0 ) ) ;
QLineF mappedHeightVector = transform . map ( QLineF ( 0 , 0 , 0 , 1 ) ) ;
2021-09-27 11:14:20 +02:00
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 )
{
QSize size = adjustedImage . size ( ) ;
QSize adjustedImageSize = size . scaled ( newWidth , newHeight , Qt : : KeepAspectRatio ) ;
adjustedImage = adjustedImage . scaled ( adjustedImageSize , Qt : : KeepAspectRatio , Qt : : SmoothTransformation ) ;
}
}
}
2022-07-31 18:32:57 +02:00
QTransform imageTransform ( 1.0 / adjustedImage . width ( ) , 0 , 0 , 1.0 / adjustedImage . height ( ) , 0 , 0 ) ;
QTransform worldTransform = imageTransform * m_painter - > worldTransform ( ) ;
2021-09-27 11:14:20 +02:00
// Because Qt uses opposite axis direction than PDF, then we must transform the y-axis
// to the opposite (so the image is then unchanged)
2022-07-31 18:32:57 +02:00
worldTransform . translate ( 0 , adjustedImage . height ( ) ) ;
worldTransform . scale ( 1 , - 1 ) ;
2021-09-27 11:14:20 +02:00
2022-07-31 18:32:57 +02:00
m_painter - > setWorldTransform ( worldTransform ) ;
2021-09-27 11:14:20 +02:00
m_painter - > drawImage ( 0 , 0 , adjustedImage ) ;
m_painter - > restore ( ) ;
}
void PDFPainter : : performMeshPainting ( const PDFMesh & mesh )
{
m_painter - > save ( ) ;
2022-07-31 18:32:57 +02:00
m_painter - > setWorldTransform ( QTransform ( ) ) ;
2021-09-27 11:14:20 +02:00
mesh . paint ( m_painter , getEffectiveFillingAlpha ( ) ) ;
m_painter - > restore ( ) ;
}
void PDFPainter : : performSaveGraphicState ( ProcessOrder order )
{
if ( order = = ProcessOrder : : AfterOperation )
{
m_painter - > save ( ) ;
}
}
void PDFPainter : : performRestoreGraphicState ( ProcessOrder order )
{
if ( order = = ProcessOrder : : BeforeOperation )
{
m_painter - > restore ( ) ;
}
}
2022-08-20 17:43:33 +02:00
void PDFPainter : : setWorldMatrix ( const QTransform & matrix )
2021-09-27 11:14:20 +02:00
{
2022-07-31 18:32:57 +02:00
m_painter - > setWorldTransform ( QTransform ( matrix ) , false ) ;
2021-09-27 11:14:20 +02:00
}
void PDFPainter : : setCompositionMode ( QPainter : : CompositionMode mode )
{
m_painter - > setCompositionMode ( mode ) ;
}
PDFPrecompiledPageGenerator : : PDFPrecompiledPageGenerator ( PDFPrecompiledPage * precompiledPage ,
PDFRenderer : : Features features ,
const PDFPage * page ,
const PDFDocument * document ,
const PDFFontCache * fontCache ,
const PDFCMS * cms ,
const PDFOptionalContentActivity * optionalContentActivity ,
const PDFMeshQualitySettings & meshQualitySettings ) :
2022-08-20 17:43:33 +02:00
BaseClass ( features , page , document , fontCache , cms , optionalContentActivity , QTransform ( ) , meshQualitySettings ) ,
2021-09-27 11:14:20 +02:00
m_precompiledPage ( precompiledPage )
{
m_precompiledPage - > setPaperColor ( cms - > getPaperColor ( ) ) ;
m_precompiledPage - > getSnapInfo ( ) - > addPageMediaBox ( page - > getRotatedMediaBox ( ) ) ;
}
void PDFPrecompiledPageGenerator : : performPathPainting ( const QPainterPath & path , bool stroke , bool fill , bool text , Qt : : FillRule fillRule )
{
Q_ASSERT ( stroke | | fill ) ;
Q_ASSERT ( path . fillRule ( ) = = fillRule ) ;
QPen pen = stroke ? getCurrentPen ( ) : QPen ( Qt : : NoPen ) ;
QBrush brush = fill ? getCurrentBrush ( ) : QBrush ( Qt : : NoBrush ) ;
m_precompiledPage - > addPath ( qMove ( pen ) , qMove ( brush ) , path , text ) ;
}
void PDFPrecompiledPageGenerator : : performClipping ( const QPainterPath & path , Qt : : FillRule fillRule )
{
Q_ASSERT ( path . fillRule ( ) = = fillRule ) ;
m_precompiledPage - > addClip ( path ) ;
}
void PDFPrecompiledPageGenerator : : performImagePainting ( const QImage & image )
{
if ( isContentSuppressed ( ) )
{
// Content is suppressed, do not paint anything
return ;
}
// Add snap info for image to the snapper
2022-08-20 17:43:33 +02:00
QTransform matrix = getCurrentWorldMatrix ( ) ;
2021-09-27 11:14:20 +02:00
PDFSnapInfo * snapInfo = m_precompiledPage - > getSnapInfo ( ) ;
snapInfo - > addImage ( {
matrix . map ( QPointF ( 0.0 , 0.0 ) ) ,
matrix . map ( QPointF ( 1.0 , 0.0 ) ) ,
matrix . map ( QPointF ( 1.0 , 1.0 ) ) ,
matrix . map ( QPointF ( 0.0 , 1.0 ) ) ,
matrix . map ( QPointF ( 0.5 , 0.5 ) ) ,
} , image ) ;
if ( isTransparencyGroupActive ( ) )
{
PDFReal alpha = getEffectiveFillingAlpha ( ) ;
if ( alpha ! = 1.0 )
{
// Try to approximate transparency group using alpha channel
QImage imageWithAlpha = image ;
QImage alphaChannel = imageWithAlpha . convertToFormat ( QImage : : Format_Alpha8 ) ;
uchar * bits = alphaChannel . bits ( ) ;
for ( qsizetype i = 0 , sizeInBytes = alphaChannel . sizeInBytes ( ) ; i < sizeInBytes ; + + i )
{
bits [ i ] * = alpha ;
}
imageWithAlpha . setAlphaChannel ( alphaChannel ) ;
m_precompiledPage - > addImage ( imageWithAlpha ) ;
return ;
}
}
m_precompiledPage - > addImage ( image ) ;
}
void PDFPrecompiledPageGenerator : : performMeshPainting ( const PDFMesh & mesh )
{
m_precompiledPage - > addMesh ( mesh , getEffectiveFillingAlpha ( ) ) ;
}
void PDFPrecompiledPageGenerator : : performSaveGraphicState ( PDFPageContentProcessor : : ProcessOrder order )
{
if ( order = = ProcessOrder : : AfterOperation )
{
m_precompiledPage - > addSaveGraphicState ( ) ;
}
}
void PDFPrecompiledPageGenerator : : performRestoreGraphicState ( PDFPageContentProcessor : : ProcessOrder order )
{
if ( order = = ProcessOrder : : BeforeOperation )
{
m_precompiledPage - > addRestoreGraphicState ( ) ;
}
}
2022-08-20 17:43:33 +02:00
void PDFPrecompiledPageGenerator : : setWorldMatrix ( const QTransform & matrix )
2021-09-27 11:14:20 +02:00
{
m_precompiledPage - > addSetWorldMatrix ( matrix ) ;
}
void PDFPrecompiledPageGenerator : : setCompositionMode ( QPainter : : CompositionMode mode )
{
m_precompiledPage - > addSetCompositionMode ( mode ) ;
}
2021-10-18 16:14:58 +02:00
void PDFPrecompiledPage : : draw ( QPainter * painter ,
const QRectF & cropBox ,
2022-08-20 17:43:33 +02:00
const QTransform & pagePointToDevicePointMatrix ,
2021-10-18 16:14:58 +02:00
PDFRenderer : : Features features ,
PDFReal opacity ) const
2021-09-27 11:14:20 +02:00
{
Q_ASSERT ( painter ) ;
Q_ASSERT ( pagePointToDevicePointMatrix . isInvertible ( ) ) ;
painter - > save ( ) ;
2022-07-31 18:32:57 +02:00
painter - > setWorldTransform ( QTransform ( ) ) ;
2021-10-18 16:14:58 +02:00
painter - > setOpacity ( opacity ) ;
2021-09-27 11:14:20 +02:00
if ( features . testFlag ( PDFRenderer : : ClipToCropBox ) )
{
if ( cropBox . isValid ( ) )
{
QPainterPath path ;
path . addPolygon ( pagePointToDevicePointMatrix . map ( cropBox ) ) ;
painter - > setClipPath ( path , Qt : : IntersectClip ) ;
}
}
painter - > setRenderHint ( QPainter : : SmoothPixmapTransform , features . testFlag ( PDFRenderer : : SmoothImages ) ) ;
// Process all instructions
for ( const Instruction & instruction : m_instructions )
{
switch ( instruction . type )
{
case InstructionType : : DrawPath :
{
const PathPaintData & data = m_paths [ instruction . dataIndex ] ;
// Set antialiasing
const bool antialiasing = ( data . isText & & features . testFlag ( PDFRenderer : : TextAntialiasing ) ) | | ( ! data . isText & & features . testFlag ( PDFRenderer : : Antialiasing ) ) ;
painter - > setRenderHint ( QPainter : : Antialiasing , antialiasing ) ;
painter - > setPen ( data . pen ) ;
painter - > setBrush ( data . brush ) ;
painter - > drawPath ( data . path ) ;
break ;
}
case InstructionType : : DrawImage :
{
const ImageData & data = m_images [ instruction . dataIndex ] ;
const QImage & image = data . image ;
painter - > save ( ) ;
2022-07-31 18:32:57 +02:00
QTransform imageTransform ( 1.0 / image . width ( ) , 0 , 0 , 1.0 / image . height ( ) , 0 , 0 ) ;
QTransform worldTransform = imageTransform * painter - > worldTransform ( ) ;
2021-09-27 11:14:20 +02:00
// Jakub Melka: Because Qt uses opposite axis direction than PDF, then we must transform the y-axis
// to the opposite (so the image is then unchanged)
2022-07-31 18:32:57 +02:00
worldTransform . translate ( 0 , image . height ( ) ) ;
worldTransform . scale ( 1 , - 1 ) ;
2021-09-27 11:14:20 +02:00
2022-07-31 18:32:57 +02:00
painter - > setWorldTransform ( worldTransform ) ;
2021-09-27 11:14:20 +02:00
painter - > drawImage ( 0 , 0 , image ) ;
painter - > restore ( ) ;
break ;
}
case InstructionType : : DrawMesh :
{
const MeshPaintData & data = m_meshes [ instruction . dataIndex ] ;
painter - > save ( ) ;
2022-07-31 18:32:57 +02:00
painter - > setWorldTransform ( QTransform ( pagePointToDevicePointMatrix ) ) ;
2021-09-27 11:14:20 +02:00
data . mesh . paint ( painter , data . alpha ) ;
painter - > restore ( ) ;
break ;
}
case InstructionType : : Clip :
{
painter - > setClipPath ( m_clips [ instruction . dataIndex ] . clipPath , Qt : : IntersectClip ) ;
break ;
}
case InstructionType : : SaveGraphicState :
{
painter - > save ( ) ;
break ;
}
case InstructionType : : RestoreGraphicState :
{
painter - > restore ( ) ;
break ;
}
case InstructionType : : SetWorldMatrix :
{
2022-07-31 18:32:57 +02:00
painter - > setWorldTransform ( QTransform ( m_matrices [ instruction . dataIndex ] * pagePointToDevicePointMatrix ) ) ;
2021-09-27 11:14:20 +02:00
break ;
}
case InstructionType : : SetCompositionMode :
{
painter - > setCompositionMode ( m_compositionModes [ instruction . dataIndex ] ) ;
break ;
}
default :
{
Q_ASSERT ( false ) ;
break ;
}
}
}
painter - > restore ( ) ;
}
2022-08-20 17:43:33 +02:00
void PDFPrecompiledPage : : redact ( QPainterPath redactPath , const QTransform & matrix , QColor color )
2021-09-27 11:14:20 +02:00
{
if ( redactPath . isEmpty ( ) )
{
// Nothing to be redacted
return ;
}
2022-08-20 17:43:33 +02:00
std : : stack < QTransform > worldMatrixStack ;
2021-09-27 11:14:20 +02:00
worldMatrixStack . push ( matrix ) ;
if ( color . isValid ( ) )
{
m_instructions . insert ( m_instructions . begin ( ) , Instruction ( InstructionType : : SaveGraphicState , 0 ) ) ;
}
// Process all instructions
for ( const Instruction & instruction : m_instructions )
{
switch ( instruction . type )
{
case InstructionType : : DrawPath :
{
2022-08-20 17:43:33 +02:00
QTransform currentMatrix = worldMatrixStack . top ( ) . inverted ( ) ;
2022-07-31 18:32:57 +02:00
QPainterPath mappedRedactPath = currentMatrix . map ( redactPath ) ;
2021-09-27 11:14:20 +02:00
PathPaintData & path = m_paths [ instruction . dataIndex ] ;
path . path = path . path . subtracted ( mappedRedactPath ) ;
break ;
}
case InstructionType : : DrawImage :
{
ImageData & data = m_images [ instruction . dataIndex ] ;
QImage & image = data . image ;
2022-07-31 18:32:57 +02:00
QTransform imageTransform ( 1.0 / image . width ( ) , 0 , 0 , 1.0 / image . height ( ) , 0 , 0 ) ;
QTransform worldTransform = imageTransform * QTransform ( worldMatrixStack . top ( ) ) ;
2021-09-27 11:14:20 +02:00
// Jakub Melka: Because Qt uses opposite axis direction than PDF, then we must transform the y-axis
// to the opposite (so the image is then unchanged)
2022-07-31 18:32:57 +02:00
worldTransform . translate ( 0 , image . height ( ) ) ;
worldTransform . scale ( 1 , - 1 ) ;
2021-09-27 11:14:20 +02:00
QPainter painter ( & image ) ;
2022-07-31 18:32:57 +02:00
painter . setWorldTransform ( worldTransform . inverted ( ) ) ;
2021-09-27 11:14:20 +02:00
painter . drawPath ( redactPath ) ;
painter . end ( ) ;
break ;
}
case InstructionType : : DrawMesh :
// We do not redact mesh
break ;
case InstructionType : : Clip :
{
2022-08-20 17:43:33 +02:00
QTransform currentMatrix = worldMatrixStack . top ( ) . inverted ( ) ;
2022-07-31 18:32:57 +02:00
QPainterPath mappedRedactPath = currentMatrix . map ( redactPath ) ;
2021-09-27 11:14:20 +02:00
m_clips [ instruction . dataIndex ] . clipPath = m_clips [ instruction . dataIndex ] . clipPath . subtracted ( mappedRedactPath ) ;
break ;
}
case InstructionType : : SaveGraphicState :
worldMatrixStack . push ( worldMatrixStack . top ( ) ) ;
break ;
case InstructionType : : RestoreGraphicState :
worldMatrixStack . pop ( ) ;
break ;
case InstructionType : : SetWorldMatrix :
worldMatrixStack . top ( ) = m_matrices [ instruction . dataIndex ] ;
break ;
case InstructionType : : SetCompositionMode :
break ;
default :
{
Q_ASSERT ( false ) ;
break ;
}
}
}
if ( color . isValid ( ) )
{
addRestoreGraphicState ( ) ;
addPath ( Qt : : NoPen , QBrush ( color ) , matrix . map ( redactPath ) , false ) ;
}
}
void PDFPrecompiledPage : : addPath ( QPen pen , QBrush brush , QPainterPath path , bool isText )
{
m_instructions . emplace_back ( InstructionType : : DrawPath , m_paths . size ( ) ) ;
m_paths . emplace_back ( qMove ( pen ) , qMove ( brush ) , qMove ( path ) , isText ) ;
}
void PDFPrecompiledPage : : addClip ( QPainterPath path )
{
m_instructions . emplace_back ( InstructionType : : Clip , m_clips . size ( ) ) ;
m_clips . emplace_back ( qMove ( path ) ) ;
}
void PDFPrecompiledPage : : addImage ( QImage image )
{
// Convert the image into format Format_ARGB32_Premultiplied for fast drawing.
// If this format is used, then no image conversion is performed while drawing.
if ( image . format ( ) ! = QImage : : Format_ARGB32_Premultiplied )
{
image . convertTo ( QImage : : Format_ARGB32_Premultiplied ) ;
}
m_instructions . emplace_back ( InstructionType : : DrawImage , m_images . size ( ) ) ;
m_images . emplace_back ( qMove ( image ) ) ;
}
void PDFPrecompiledPage : : addMesh ( PDFMesh mesh , PDFReal alpha )
{
m_instructions . emplace_back ( InstructionType : : DrawMesh , m_meshes . size ( ) ) ;
m_meshes . emplace_back ( qMove ( mesh ) , alpha ) ;
}
2022-08-20 17:43:33 +02:00
void PDFPrecompiledPage : : addSetWorldMatrix ( const QTransform & matrix )
2021-09-27 11:14:20 +02:00
{
m_instructions . emplace_back ( InstructionType : : SetWorldMatrix , m_matrices . size ( ) ) ;
m_matrices . push_back ( matrix ) ;
}
void PDFPrecompiledPage : : addSetCompositionMode ( QPainter : : CompositionMode compositionMode )
{
m_instructions . emplace_back ( InstructionType : : SetCompositionMode , m_compositionModes . size ( ) ) ;
m_compositionModes . push_back ( compositionMode ) ;
}
void PDFPrecompiledPage : : optimize ( )
{
m_instructions . shrink_to_fit ( ) ;
m_paths . shrink_to_fit ( ) ;
m_clips . shrink_to_fit ( ) ;
m_images . shrink_to_fit ( ) ;
m_meshes . shrink_to_fit ( ) ;
m_matrices . shrink_to_fit ( ) ;
m_compositionModes . shrink_to_fit ( ) ;
}
void PDFPrecompiledPage : : invertColors ( )
{
// Jakub Melka: we must invert colors in following areas:
// - painter paths
// - images
// - meshes
for ( PathPaintData & pathData : m_paths )
{
if ( pathData . pen . style ( ) ! = Qt : : NoPen )
{
pathData . pen . setColor ( invertColor ( pathData . pen . color ( ) ) ) ;
}
if ( pathData . brush . style ( ) = = Qt : : SolidPattern )
{
pathData . brush . setColor ( invertColor ( pathData . brush . color ( ) ) ) ;
}
}
for ( ImageData & imageData : m_images )
{
imageData . image . invertPixels ( QImage : : InvertRgb ) ;
}
for ( MeshPaintData & meshPaintData : m_meshes )
{
meshPaintData . mesh . invertColors ( ) ;
}
m_paperColor = invertColor ( m_paperColor ) ;
}
void PDFPrecompiledPage : : finalize ( qint64 compilingTimeNS , QList < PDFRenderError > errors )
{
m_compilingTimeNS = compilingTimeNS ;
m_errors = qMove ( errors ) ;
// Determine memory consumption
m_memoryConsumptionEstimate = sizeof ( * this ) ;
m_memoryConsumptionEstimate + = sizeof ( Instruction ) * m_instructions . capacity ( ) ;
m_memoryConsumptionEstimate + = sizeof ( PathPaintData ) * m_paths . capacity ( ) ;
m_memoryConsumptionEstimate + = sizeof ( ClipData ) * m_clips . capacity ( ) ;
m_memoryConsumptionEstimate + = sizeof ( ImageData ) * m_images . capacity ( ) ;
m_memoryConsumptionEstimate + = sizeof ( MeshPaintData ) * m_meshes . capacity ( ) ;
2022-08-20 17:43:33 +02:00
m_memoryConsumptionEstimate + = sizeof ( QTransform ) * m_matrices . capacity ( ) ;
2021-09-27 11:14:20 +02:00
m_memoryConsumptionEstimate + = sizeof ( QPainter : : CompositionMode ) * m_compositionModes . capacity ( ) ;
m_memoryConsumptionEstimate + = sizeof ( PDFRenderError ) * m_errors . size ( ) ;
auto calculateQPathMemoryConsumption = [ ] ( const QPainterPath & path )
{
return sizeof ( QPainterPath : : Element ) * path . capacity ( ) ;
} ;
for ( const PathPaintData & data : m_paths )
{
2022-01-26 20:06:23 +01:00
m_memoryConsumptionEstimate + = calculateQPathMemoryConsumption ( data . path ) ;
2021-09-27 11:14:20 +02:00
}
for ( const ClipData & data : m_clips )
{
m_memoryConsumptionEstimate + = calculateQPathMemoryConsumption ( data . clipPath ) ;
}
for ( const ImageData & data : m_images )
{
m_memoryConsumptionEstimate + = data . image . sizeInBytes ( ) ;
}
for ( const MeshPaintData & data : m_meshes )
{
m_memoryConsumptionEstimate + = data . mesh . getMemoryConsumptionEstimate ( ) ;
}
}
2021-09-09 18:28:55 +02:00
PDFPrecompiledPage : : GraphicPieceInfos PDFPrecompiledPage : : calculateGraphicPieceInfos ( QRectF mediaBox ,
PDFReal epsilon ) const
2021-09-08 20:33:32 +02:00
{
GraphicPieceInfos infos ;
struct State
{
2022-08-20 17:43:33 +02:00
QTransform matrix ;
2021-09-08 20:33:32 +02:00
} ;
std : : stack < State > stateStack ;
stateStack . emplace ( ) ;
// Check, if epsilon is not too small
if ( qFuzzyIsNull ( epsilon ) )
{
epsilon = 0.000001 ;
}
PDFReal factor = 1.0 / epsilon ;
2021-09-09 18:28:55 +02:00
QImage shadingTestImage ;
2021-09-08 20:33:32 +02:00
// Process all instructions
for ( const Instruction & instruction : m_instructions )
{
switch ( instruction . type )
{
case InstructionType : : DrawPath :
{
const PathPaintData & data = m_paths [ instruction . dataIndex ] ;
GraphicPieceInfo info ;
QByteArray serializedPath ;
// Serialize data
if ( true )
{
QDataStream stream ( & serializedPath , QIODevice : : WriteOnly ) ;
stream < < data . isText ;
stream < < data . pen ;
stream < < data . brush ;
// Translate map to page coordinates
QPainterPath pagePath = stateStack . top ( ) . matrix . map ( data . path ) ;
info . type = data . isText ? GraphicPieceInfo : : Type : : Text : GraphicPieceInfo : : Type : : VectorGraphics ;
info . boundingRect = pagePath . controlPointRect ( ) ;
2021-09-11 18:17:38 +02:00
info . pagePath = pagePath ;
2021-09-08 20:33:32 +02:00
const int elementCount = pagePath . elementCount ( ) ;
for ( int i = 0 ; i < elementCount ; + + i )
{
QPainterPath : : Element element = pagePath . elementAt ( i ) ;
2021-09-11 18:17:38 +02:00
PDFReal roundedX = qFloor ( element . x * factor ) ;
PDFReal roundedY = qFloor ( element . y * factor ) ;
2021-09-08 20:33:32 +02:00
stream < < roundedX ;
stream < < roundedY ;
stream < < element . type ;
}
}
QByteArray hash = QCryptographicHash : : hash ( serializedPath , QCryptographicHash : : Sha512 ) ;
Q_ASSERT ( QCryptographicHash : : hashLength ( QCryptographicHash : : Sha512 ) = = 64 ) ;
size_t size = qMin < size_t > ( hash . length ( ) , info . hash . size ( ) ) ;
std : : copy ( hash . data ( ) , hash . data ( ) + size , info . hash . data ( ) ) ;
infos . emplace_back ( std : : move ( info ) ) ;
break ;
}
case InstructionType : : DrawImage :
{
2021-09-09 18:28:55 +02:00
const ImageData & data = m_images [ instruction . dataIndex ] ;
2021-09-08 20:33:32 +02:00
const QImage & image = data . image ;
2021-09-09 18:28:55 +02:00
GraphicPieceInfo info ;
QByteArray serializedPath ;
2021-09-11 18:17:38 +02:00
QByteArray serializedImage ;
2021-09-08 20:33:32 +02:00
2021-09-09 18:28:55 +02:00
// Serialize data
if ( true )
{
QDataStream stream ( & serializedPath , QIODevice : : WriteOnly ) ;
2021-09-11 18:17:38 +02:00
QDataStream streamImage ( & serializedImage , QIODevice : : WriteOnly ) ;
2021-09-08 20:33:32 +02:00
2021-09-09 18:28:55 +02:00
// Jakub Melka: serialize image position
2022-08-20 17:43:33 +02:00
QTransform worldMatrix = stateStack . top ( ) . matrix ;
2021-09-08 20:33:32 +02:00
2021-09-09 18:28:55 +02:00
QPainterPath pagePath ;
pagePath . addRect ( 0 , 0 , 1 , 1 ) ;
pagePath = worldMatrix . map ( pagePath ) ;
info . type = GraphicPieceInfo : : Type : : Image ;
info . boundingRect = pagePath . controlPointRect ( ) ;
2021-09-11 18:17:38 +02:00
info . pagePath = pagePath ;
2021-09-09 18:28:55 +02:00
const int elementCount = pagePath . elementCount ( ) ;
for ( int i = 0 ; i < elementCount ; + + i )
{
QPainterPath : : Element element = pagePath . elementAt ( i ) ;
PDFReal roundedX = qRound ( element . x * factor ) ;
PDFReal roundedY = qRound ( element . y * factor ) ;
stream < < roundedX ;
stream < < roundedY ;
stream < < element . type ;
}
// serialize image data
stream . writeBytes ( reinterpret_cast < const char * > ( image . bits ( ) ) , image . sizeInBytes ( ) ) ;
2021-09-11 18:17:38 +02:00
streamImage . writeBytes ( reinterpret_cast < const char * > ( image . bits ( ) ) , image . sizeInBytes ( ) ) ;
2021-09-09 18:28:55 +02:00
}
QByteArray hash = QCryptographicHash : : hash ( serializedPath , QCryptographicHash : : Sha512 ) ;
Q_ASSERT ( QCryptographicHash : : hashLength ( QCryptographicHash : : Sha512 ) = = 64 ) ;
2021-09-11 18:17:38 +02:00
QByteArray imageHash = QCryptographicHash : : hash ( serializedImage , QCryptographicHash : : Sha512 ) ;
2021-09-09 18:28:55 +02:00
size_t size = qMin < size_t > ( hash . length ( ) , info . hash . size ( ) ) ;
std : : copy ( hash . data ( ) , hash . data ( ) + size , info . hash . data ( ) ) ;
2021-09-11 18:17:38 +02:00
size_t sizeImage = qMin < size_t > ( imageHash . length ( ) , info . imageHash . size ( ) ) ;
std : : copy ( imageHash . data ( ) , imageHash . data ( ) + sizeImage , info . imageHash . data ( ) ) ;
2021-09-09 18:28:55 +02:00
infos . emplace_back ( std : : move ( info ) ) ;
2021-09-08 20:33:32 +02:00
break ;
}
case InstructionType : : DrawMesh :
{
2021-09-09 18:28:55 +02:00
const MeshPaintData & data = m_meshes [ instruction . dataIndex ] ;
2021-09-08 20:33:32 +02:00
2021-09-09 18:28:55 +02:00
if ( shadingTestImage . isNull ( ) )
{
QSizeF mediaBoxSize = mediaBox . size ( ) ;
mediaBoxSize = mediaBoxSize . scaled ( 256 , 256 , Qt : : KeepAspectRatio ) ;
QSize imageSize = mediaBoxSize . toSize ( ) ;
shadingTestImage = QImage ( imageSize , QImage : : Format_ARGB32 ) ;
}
shadingTestImage . fill ( Qt : : transparent ) ;
2022-07-31 18:32:57 +02:00
QTransform pagePointToDevicePointMatrix ;
2021-09-09 18:28:55 +02:00
pagePointToDevicePointMatrix . scale ( shadingTestImage . width ( ) / mediaBox . width ( ) , - shadingTestImage . height ( ) / mediaBox . height ( ) ) ;
{
QPainter painter ( & shadingTestImage ) ;
2022-07-31 18:32:57 +02:00
painter . setWorldTransform ( pagePointToDevicePointMatrix ) ;
2021-09-09 18:28:55 +02:00
data . mesh . paint ( & painter , data . alpha ) ;
}
GraphicPieceInfo info ;
QByteArray serializedMesh ;
// Serialize data
if ( true )
{
QDataStream stream ( & serializedMesh , QIODevice : : WriteOnly ) ;
// serialize image data
stream . writeBytes ( reinterpret_cast < const char * > ( shadingTestImage . bits ( ) ) , shadingTestImage . sizeInBytes ( ) ) ;
}
QByteArray hash = QCryptographicHash : : hash ( serializedMesh , QCryptographicHash : : Sha512 ) ;
Q_ASSERT ( QCryptographicHash : : hashLength ( QCryptographicHash : : Sha512 ) = = 64 ) ;
size_t size = qMin < size_t > ( hash . length ( ) , info . hash . size ( ) ) ;
std : : copy ( hash . data ( ) , hash . data ( ) + size , info . hash . data ( ) ) ;
info . boundingRect = QRectF ( ) ;
info . type = GraphicPieceInfo : : Type : : Shading ;
infos . emplace_back ( std : : move ( info ) ) ;
2021-09-08 20:33:32 +02:00
break ;
}
case InstructionType : : Clip :
{
// Do nothing, we are just collecting information
break ;
}
case InstructionType : : SaveGraphicState :
{
stateStack . push ( stateStack . top ( ) ) ;
break ;
}
case InstructionType : : RestoreGraphicState :
{
stateStack . pop ( ) ;
break ;
}
case InstructionType : : SetWorldMatrix :
{
stateStack . top ( ) . matrix = m_matrices [ instruction . dataIndex ] ;
break ;
}
case InstructionType : : SetCompositionMode :
{
// Do nothing, we are just collecting information
break ;
}
default :
{
Q_ASSERT ( false ) ;
break ;
}
}
}
return infos ;
}
2021-09-27 11:14:20 +02:00
} // namespace pdf