diff --git a/ui/pagepainter.h b/ui/pagepainter.h --- a/ui/pagepainter.h +++ b/ui/pagepainter.h @@ -72,6 +72,8 @@ RasterOperation op = Normal //float antiAliasRadius = 1.0 ); + + friend class LineAnnotPainter; }; #endif diff --git a/ui/pagepainter.cpp b/ui/pagepainter.cpp --- a/ui/pagepainter.cpp +++ b/ui/pagepainter.cpp @@ -52,6 +52,211 @@ return p; } +class LineAnnotPainter { +public: + LineAnnotPainter( const Okular::LineAnnotation * a, const Okular::Page * page, double pageScale, const std::vector &&paintMatrix ) + : la { a } + , linePen { buildPen( a, a->style().width(), a->style().color() ) } + , pageWidth { page->width() } + , pageHeight { page->height() } + , aspectRatio { page->height() / page->width() } + , pageScale { pageScale } + , paintMatrix { paintMatrix } + { + if ( ( la->lineClosed() || la->transformedLinePoints().count() == 2 ) && + la->lineInnerColor().isValid() ) + { + fillBrush = QBrush( la->lineInnerColor() ); + } + } + + void draw( QImage &backImage ) + { + const Okular::NormalizedPoint delta { + la->transformedLinePoints().last().x - la->transformedLinePoints().first().x, + la->transformedLinePoints().first().y - la->transformedLinePoints().last().y + }; + const double deaspectedY = delta.y * aspectRatio; + const double angle = atan2( delta.y * aspectRatio, delta.x ); + const double cosA = cos( -angle ); + const double sinA = sin( -angle ); + + /* tmpMtx allows us to construct line endings as if the main segment would be a simple horizontal line */ + const std::vector &tmpMtx = matrixMultiply( { + cosA, sinA / aspectRatio, + -sinA, cosA / aspectRatio, + la->transformedLinePoints().first().x, + la->transformedLinePoints().first().y + }, paintMatrix ); + + const double mainSegmentLength = sqrt( delta.x * delta.x + deaspectedY * deaspectedY ); + drawMainLine( backImage ); + if ( la->transformedLinePoints().count() == 2 ) + { + drawLineEnds( mainSegmentLength, backImage, tmpMtx ); + drawLeaderLine( mainSegmentLength, backImage, tmpMtx ); + } + } + +private: + void drawMainLine( QImage &backImage ) + { + // draw the line as normalized path into image + PagePainter::drawShapeOnImage( backImage, matrixTransformPath( + la->transformedLinePoints(), paintMatrix ), la->lineClosed(), + linePen, fillBrush, pageScale, PagePainter::Multiply ); + } + + void drawLineEnds( double mainSegmentLength, QImage &backImage, const std::vector& tmpMatrix ) + { + double lineendSize = std::min( 6. * la->style().width() / pageWidth, mainSegmentLength / 2. ); + + switch ( la->lineStartStyle() ) { + case Okular::LineAnnotation::Square: + drawLineEndRect( 0., -lineendSize, tmpMatrix, backImage ); + break; + case Okular::LineAnnotation::Circle: + case Okular::LineAnnotation::Diamond: + drawLineEndDiamond( 0., -lineendSize, tmpMatrix, backImage ); + break; + case Okular::LineAnnotation::OpenArrow: + drawLineEndArrow( 0., -lineendSize, -1., false, tmpMatrix, backImage ); + break; + case Okular::LineAnnotation::ClosedArrow: + drawLineEndArrow( 0., -lineendSize, -1., true, tmpMatrix, backImage ); + break; + case Okular::LineAnnotation::None: + case Okular::LineAnnotation::Butt: + case Okular::LineAnnotation::ROpenArrow: + drawLineEndArrow( 0., -lineendSize, 1., false, tmpMatrix, backImage ); + break; + case Okular::LineAnnotation::RClosedArrow: + drawLineEndArrow( 0., -lineendSize, 1., true, tmpMatrix, backImage ); + break; + case Okular::LineAnnotation::Slash: + break; + } + switch ( la->lineEndStyle() ) { + case Okular::LineAnnotation::Square: + drawLineEndRect( mainSegmentLength, lineendSize, tmpMatrix, backImage ); + break; + case Okular::LineAnnotation::Circle: + case Okular::LineAnnotation::Diamond: + drawLineEndDiamond( mainSegmentLength, lineendSize, tmpMatrix, backImage ); + break; + case Okular::LineAnnotation::OpenArrow: + drawLineEndArrow( mainSegmentLength, lineendSize, 1., false, tmpMatrix, backImage ); + break; + case Okular::LineAnnotation::ClosedArrow: + drawLineEndArrow( mainSegmentLength, lineendSize, 1., true, tmpMatrix, backImage ); + break; + case Okular::LineAnnotation::None: + case Okular::LineAnnotation::Butt: + case Okular::LineAnnotation::ROpenArrow: + drawLineEndArrow( mainSegmentLength, lineendSize, -1., false, tmpMatrix, backImage ); + break; + case Okular::LineAnnotation::RClosedArrow: + drawLineEndArrow( mainSegmentLength, lineendSize, -1., true, tmpMatrix, backImage ); + break; + case Okular::LineAnnotation::Slash: + break; + } + } + + std::vector matrixMultiply(const std::vector &matrixA, const std::vector &matrixB) + { + return { + matrixA[0] * matrixB[0] + matrixA[1] * matrixB[2], + matrixA[0] * matrixB[1] + matrixA[1] * matrixB[3], + matrixA[2] * matrixB[0] + matrixA[3] * matrixB[2], + matrixA[2] * matrixB[1] + matrixA[3] * matrixB[3], + matrixA[4] * matrixB[0] + matrixA[5] * matrixB[2] + matrixB[4], + matrixA[4] * matrixB[1] + matrixA[5] * matrixB[3] + matrixB[5], + }; + } + + template QList matrixTransformPath(const T &path, const std::vector &matrix) + { + QList transformed; + for( const Okular::NormalizedPoint &item : path ) + { + Okular::NormalizedPoint p; + p.x = matrix[0] * item.x + matrix[2] * item.y + matrix[4]; + p.y = matrix[1] * item.x + matrix[3] * item.y + matrix[5]; + transformed.append(p); + } + return transformed; + } + + void drawLineEndArrow(double xEndPos, double size, double flipX, bool close, const std::vector &matrix, QImage &backImage ) + { + const double halfSize = size / 2.; + const QList lineend { + { xEndPos - halfSize * flipX - halfSize, size / 2. }, + { xEndPos + halfSize * flipX - halfSize, 0. }, + { xEndPos - halfSize * flipX - halfSize, -size / 2. }, + }; + PagePainter::drawShapeOnImage( backImage, matrixTransformPath(lineend, matrix), + close, linePen, fillBrush, pageScale, PagePainter::Multiply); + } + + void drawLineEndRect(double xEndPos, double size, const std::vector &matrix, QImage &backImage ) + { + const QList lineend { + { xEndPos, size / 2. }, + { xEndPos - size, size / 2. }, + { xEndPos - size, -size / 2. }, + { xEndPos, -size / 2. } + }; + PagePainter::drawShapeOnImage( backImage, matrixTransformPath(lineend, matrix), + true, linePen, fillBrush, pageScale, PagePainter::Multiply); + } + + void drawLineEndDiamond(double xEndPos, double size, const std::vector &matrix, QImage &backImage ) + { + const QList lineend { + { xEndPos, 0. }, + { xEndPos - size / 2., size / 2. }, + { xEndPos - size, 0. }, + { xEndPos - size / 2., -size / 2. } + }; + PagePainter::drawShapeOnImage( backImage, matrixTransformPath(lineend, matrix), + true, linePen, fillBrush, pageScale, PagePainter::Multiply); + } + + void drawLeaderLine(double xEndPos, QImage &backImage, const std::vector &matrix ) + { + const double ll = la->lineLeadingForwardPoint() / pageHeight; + const double lle = la->lineLeadingBackwardPoint() / pageHeight; + QList lineend; + + int sign = ll > 0.0 ? 1 : -1; + if ( fabs( ll ) > 0.1 ) { + lineend.append( { xEndPos, ll } ); + // do we have the extension on the "back"? + if ( fabs( lle ) > 0.1 ) + { + lineend.append( { xEndPos, sign * lle } ); + } else { + lineend.append( { xEndPos, 0. } ); + } + } + + PagePainter::drawShapeOnImage( backImage, matrixTransformPath(lineend, matrix), false, + linePen, fillBrush, pageScale, PagePainter::Multiply); + } + +private: + const Okular::LineAnnotation* la; + const QPen linePen; + double pageWidth; + double pageHeight; + double aspectRatio; + double pageScale; + std::vector paintMatrix; + QBrush fillBrush; +}; + void PagePainter::paintPageOnPainter( QPainter * destPainter, const Okular::Page * page, Okular::DocumentObserver *observer, int flags, int scaledWidth, int scaledHeight, const QRect &limits ) { @@ -448,75 +653,9 @@ // draw LineAnnotation MISSING: all if ( type == Okular::Annotation::ALine ) { - // get the annotation - Okular::LineAnnotation * la = (Okular::LineAnnotation *) a; - - NormalizedPath path; - // normalize page point to image - const QLinkedList points = la->transformedLinePoints(); - QLinkedList::const_iterator it = points.constBegin(); - QLinkedList::const_iterator itEnd = points.constEnd(); - for ( ; it != itEnd; ++it ) - { - Okular::NormalizedPoint point; - point.x = ( (*it).x - xOffset) * xScale; - point.y = ( (*it).y - yOffset) * yScale; - path.append( point ); - } - - const QPen linePen = buildPen( a, a->style().width(), a->style().color() ); - QBrush fillBrush; - - if ( la->lineClosed() && la->lineInnerColor().isValid() ) - fillBrush = QBrush( la->lineInnerColor() ); - - // draw the line as normalized path into image - drawShapeOnImage( backImage, path, la->lineClosed(), - linePen, - fillBrush, pageScale ,Multiply); - - if ( path.count() == 2 && fabs( la->lineLeadingForwardPoint() ) > 0.1 ) - { - Okular::NormalizedPoint delta( la->transformedLinePoints().last().x - la->transformedLinePoints().first().x, la->transformedLinePoints().first().y - la->transformedLinePoints().last().y ); - double angle = atan2( delta.y * page->height(), delta.x * page->width() ); - if ( delta.y < 0 ) - angle += 2 * M_PI; - - int sign = la->lineLeadingForwardPoint() > 0.0 ? 1 : -1; - double LLx = fabs( la->lineLeadingForwardPoint() ) * cos( angle + sign * M_PI_2 + 2 * M_PI ) / page->width(); - double LLy = fabs( la->lineLeadingForwardPoint() ) * sin( angle + sign * M_PI_2 + 2 * M_PI ) / page->height(); - - NormalizedPath path2; - NormalizedPath path3; - - Okular::NormalizedPoint point; - point.x = ( la->transformedLinePoints().first().x + LLx - xOffset ) * xScale; - point.y = ( la->transformedLinePoints().first().y - LLy - yOffset ) * yScale; - path2.append( point ); - point.x = ( la->transformedLinePoints().last().x + LLx - xOffset ) * xScale; - point.y = ( la->transformedLinePoints().last().y - LLy - yOffset ) * yScale; - path3.append( point ); - // do we have the extension on the "back"? - if ( fabs( la->lineLeadingBackwardPoint() ) > 0.1 ) - { - double LLEx = la->lineLeadingBackwardPoint() * cos( angle - sign * M_PI_2 + 2 * M_PI ) / page->width(); - double LLEy = la->lineLeadingBackwardPoint() * sin( angle - sign * M_PI_2 + 2 * M_PI ) / page->height(); - point.x = ( la->transformedLinePoints().first().x + LLEx - xOffset ) * xScale; - point.y = ( la->transformedLinePoints().first().y - LLEy - yOffset ) * yScale; - path2.append( point ); - point.x = ( la->transformedLinePoints().last().x + LLEx - xOffset ) * xScale; - point.y = ( la->transformedLinePoints().last().y - LLEy - yOffset ) * yScale; - path3.append( point ); - } - else - { - path2.append( path[0] ); - path3.append( path[1] ); - } - - drawShapeOnImage( backImage, path2, false, linePen, QBrush(), pageScale, Multiply ); - drawShapeOnImage( backImage, path3, false, linePen, QBrush(), pageScale, Multiply ); - } + LineAnnotPainter linepainter { (Okular::LineAnnotation *) a, page, pageScale, + { xScale, 0., 0., yScale, -xOffset * xScale, -yOffset * yScale } }; + linepainter.draw( backImage ); } // draw HighlightAnnotation MISSING: under/strike width, feather, capping else if ( type == Okular::Annotation::AHighlight )