diff --git a/core/area.cpp b/core/area.cpp index 3cf1f5ba9..09b735b3b 100644 --- a/core/area.cpp +++ b/core/area.cpp @@ -1,489 +1,502 @@ /*************************************************************************** * Copyright (C) 2004-05 by Enrico Ros * * Copyright (C) 2005 by Piotr Szymanski * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "area.h" #include #include #include #include #include "action.h" #include "annotations.h" #include "annotations_p.h" #include "debug_p.h" #include "sourcereference.h" using namespace Okular; /** class NormalizedPoint **/ NormalizedPoint::NormalizedPoint() : x( 0.0 ), y( 0.0 ) {} NormalizedPoint::NormalizedPoint( double dX, double dY ) : x( dX ), y( dY ) {} NormalizedPoint::NormalizedPoint( int iX, int iY, int xScale, int yScale ) : x( (double)iX / (double)xScale ), y( (double)iY / (double)yScale ) {} NormalizedPoint& NormalizedPoint::operator=( const NormalizedPoint & p ) = default; NormalizedPoint::NormalizedPoint( const NormalizedPoint& ) = default; NormalizedPoint::~NormalizedPoint() = default; void NormalizedPoint::transform( const QTransform &matrix ) { qreal tmp_x = (qreal)x; qreal tmp_y = (qreal)y; matrix.map( tmp_x, tmp_y, &tmp_x, &tmp_y ); x = tmp_x; y = tmp_y; } double NormalizedPoint::distanceSqr( double x, double y, double xScale, double yScale ) const { return pow( (this->x - x) * xScale, 2 ) + pow( (this->y - y) * yScale, 2 ); } /** * Returns a vector from the given points @p a and @p b * @internal */ NormalizedPoint operator-( const NormalizedPoint& a, const NormalizedPoint& b ) { return NormalizedPoint( a.x - b.x, a.y - b.y ); } /** * @brief Calculates distance of the point @p x @p y @p xScale @p yScale to the line segment from @p start to @p end */ double NormalizedPoint::distanceSqr( double x, double y, double xScale, double yScale, const NormalizedPoint& start, const NormalizedPoint& end ) { NormalizedPoint point( x, y ); double thisDistance; NormalizedPoint lineSegment( end - start ); const double lengthSqr = pow( lineSegment.x, 2 ) + pow( lineSegment.y, 2 ); //if the length of the current segment is null, we can just //measure the distance to either end point if ( lengthSqr == 0.0 ) { thisDistance = end.distanceSqr( x, y, xScale, yScale ); } else { //vector from the start point of the current line segment to the measurement point NormalizedPoint a = point - start; //vector from the same start point to the end point of the current line segment NormalizedPoint b = end - start; //we're using a * b (dot product) := |a| * |b| * cos(phi) and the knowledge //that cos(phi) is adjacent side / hypotenuse (hypotenuse = |b|) //therefore, t becomes the length of the vector that represents the projection of //the point p onto the current line segment //(hint: if this is still unclear, draw it!) float t = (a.x * b.x + a.y * b.y) / lengthSqr; if ( t < 0 ) { //projection falls outside the line segment on the side of "start" thisDistance = point.distanceSqr( start.x, start.y, xScale, yScale ); } else if ( t > 1 ) { //projection falls outside the line segment on the side of the current point thisDistance = point.distanceSqr( end.x, end.y, xScale, yScale ); } else { //projection is within [start, *i]; //determine the length of the perpendicular distance from the projection to the actual point NormalizedPoint direction = end - start; NormalizedPoint projection = start - NormalizedPoint( -t * direction.x, -t * direction.y ); thisDistance = projection.distanceSqr( x, y, xScale, yScale ); } } return thisDistance; } QDebug operator<<( QDebug str, const Okular::NormalizedPoint& p ) { str.nospace() << "NormPt(" << p.x << "," << p.y << ")"; return str.space(); } /** class NormalizedRect **/ NormalizedRect::NormalizedRect() : left( 0.0 ), top( 0.0 ), right( 0.0 ), bottom( 0.0 ) {} NormalizedRect::NormalizedRect( double l, double t, double r, double b ) // note: check for swapping coords? : left( l ), top( t ), right( r ), bottom( b ) {} NormalizedRect::NormalizedRect( const QRect & r, double xScale, double yScale ) : left( (double)r.left() / xScale ), top( (double)r.top() / yScale ), right( (double)r.right() / xScale ), bottom( (double)r.bottom() / yScale ) {} NormalizedRect::NormalizedRect( const NormalizedRect & rect ) = default; NormalizedRect NormalizedRect::fromQRectF( const QRectF &rect ) { QRectF nrect = rect.normalized(); NormalizedRect ret; ret.left = nrect.left(); ret.top = nrect.top(); ret.right = nrect.right(); ret.bottom = nrect.bottom(); return ret; } bool NormalizedRect::isNull() const { return left == 0 && top== 0 && right == 0 && bottom == 0; } bool NormalizedRect::contains( double x, double y ) const { return x >= left && x <= right && y >= top && y <= bottom; } bool NormalizedRect::intersects( const NormalizedRect & r ) const { return (r.left <= right) && (r.right >= left) && (r.top <= bottom) && (r.bottom >= top); } bool NormalizedRect::intersects( const NormalizedRect * r ) const { return (r->left <= right) && (r->right >= left) && (r->top <= bottom) && (r->bottom >= top); } bool NormalizedRect::intersects( double l, double t, double r, double b ) const { return (l <= right) && (r >= left) && (t <= bottom) && (b >= top); } NormalizedRect NormalizedRect::operator| (const NormalizedRect & r) const { NormalizedRect ret; // todo ! ret.left=qMin(left,r.left); ret.top=qMin(top,r.top); ret.bottom=qMax(bottom,r.bottom); ret.right=qMax(right,r.right); return ret; } NormalizedRect& NormalizedRect::operator|= (const NormalizedRect & r) { left = qMin( left, r.left ); top = qMin( top, r.top ); bottom = qMax( bottom, r.bottom ); right = qMax( right, r.right ); return *this; } NormalizedRect NormalizedRect::operator&( const NormalizedRect & r ) const { if ( isNull() || r.isNull() ) return NormalizedRect(); NormalizedRect ret; ret.left = qMax( left, r.left ); ret.top = qMax( top, r.top ); ret.bottom = qMin( bottom, r.bottom ); ret.right = qMin( right, r.right ); return ret; } NormalizedRect & NormalizedRect::operator=( const NormalizedRect & r ) = default; NormalizedRect::~NormalizedRect() = default; bool NormalizedRect::operator==( const NormalizedRect & r ) const { return ( isNull() && r.isNull() ) || ( fabs( left - r.left ) < 1e-4 && fabs( right - r.right ) < 1e-4 && fabs( top - r.top ) < 1e-4 && fabs( bottom - r.bottom ) < 1e-4 ); } NormalizedPoint NormalizedRect::center() const { return NormalizedPoint((left+right)/2.0, (top+bottom)/2.0); } /* QDebug operator << (QDebug str , const NormalizedRect &r) { str << "[" <(), d( nullptr ) { } RegularAreaRect::RegularAreaRect( const RegularAreaRect& rar ) : RegularArea< NormalizedRect, QRect >( rar ), d( nullptr ) { } RegularAreaRect::~RegularAreaRect() { } RegularAreaRect& RegularAreaRect::operator=( const RegularAreaRect& rar ) { RegularArea< NormalizedRect, QRect >::operator=( rar ); return *this; } HighlightAreaRect::HighlightAreaRect( const RegularAreaRect *area ) : RegularAreaRect(), s_id( -1 ) { if ( area ) { RegularAreaRect::ConstIterator it = area->begin(); RegularAreaRect::ConstIterator itEnd = area->end(); for ( ; it != itEnd; ++it ) { append( NormalizedRect( *it ) ); } } } /** class ObjectRect **/ ObjectRect::ObjectRect( double l, double t, double r, double b, bool ellipse, ObjectType type, void * pnt ) : m_objectType( type ), m_object( pnt ) { // assign coordinates swapping them if negative width or height QRectF rect( r > l ? l : r, b > t ? t : b, fabs( r - l ), fabs( b - t ) ); if ( ellipse ) m_path.addEllipse( rect ); else m_path.addRect( rect ); m_transformedPath = m_path; } ObjectRect::ObjectRect( const NormalizedRect& x, bool ellipse, ObjectType type, void * pnt ) : m_objectType( type ), m_object( pnt ) { QRectF rect( x.left, x.top, fabs( x.right - x.left ), fabs( x.bottom - x.top ) ); if ( ellipse ) m_path.addEllipse( rect ); else m_path.addRect( rect ); m_transformedPath = m_path; } ObjectRect::ObjectRect( const QPolygonF &poly, ObjectType type, void * pnt ) : m_objectType( type ), m_object( pnt ) { m_path.addPolygon( poly ); m_transformedPath = m_path; } ObjectRect::ObjectType ObjectRect::objectType() const { return m_objectType; } const void * ObjectRect::object() const { return m_object; } const QPainterPath &ObjectRect::region() const { return m_transformedPath; } QRect ObjectRect::boundingRect( double xScale, double yScale ) const { const QRectF &br = m_transformedPath.boundingRect(); return QRect( (int)( br.left() * xScale ), (int)( br.top() * yScale ), (int)( br.width() * xScale ), (int)( br.height() * yScale ) ); } bool ObjectRect::contains( double x, double y, double, double ) const { return m_transformedPath.contains( QPointF( x, y ) ); } void ObjectRect::transform( const QTransform &matrix ) { m_transformedPath = matrix.map( m_path ); } double ObjectRect::distanceSqr( double x, double y, double xScale, double yScale ) const { switch ( m_objectType ) { case Action: case Image: { const QRectF& rect( m_transformedPath.boundingRect() ); return NormalizedRect( rect.x(), rect.y(), rect.right(), rect.bottom() ).distanceSqr( x, y, xScale, yScale ); } case OAnnotation: { return static_cast(m_object)->d_func()->distanceSqr( x, y, xScale, yScale ); } case SourceRef: { const SourceRefObjectRect * sr = static_cast< const SourceRefObjectRect * >( this ); const NormalizedPoint& point = sr->m_point; if ( point.x == -1.0 ) { return pow( ( y - point.y ) * yScale, 2 ); } else if ( point.y == -1.0 ) { return pow( ( x - point.x ) * xScale, 2 ); } else { return pow( ( x - point.x ) * xScale, 2 ) + pow( ( y - point.y ) * yScale, 2 ); } } } return 0.0; } ObjectRect::~ObjectRect() { if ( !m_object ) return; if ( m_objectType == Action ) delete static_cast( m_object ); else if ( m_objectType == SourceRef ) delete static_cast( m_object ); else qCDebug(OkularCoreDebug).nospace() << "Object deletion not implemented for type '" << m_objectType << "'."; } /** class AnnotationObjectRect **/ AnnotationObjectRect::AnnotationObjectRect( Annotation * annotation ) : ObjectRect( QPolygonF(), OAnnotation, annotation ), m_annotation( annotation ) { } Annotation *AnnotationObjectRect::annotation() const { return m_annotation; } QRect AnnotationObjectRect::boundingRect( double xScale, double yScale ) const { const QRect annotRect = AnnotationUtils::annotationGeometry( m_annotation, xScale, yScale ); const QPoint center = annotRect.center(); // Make sure that the rectangle has a minimum size, so that it's possible // to click on it const int minSize = 14; const QRect minRect( center.x()-minSize/2, center.y()-minSize/2, minSize, minSize ); return annotRect | minRect; } bool AnnotationObjectRect::contains( double x, double y, double xScale, double yScale ) const { return boundingRect( xScale, yScale ).contains( (int)( x * xScale ), (int)( y * yScale ), false ); } AnnotationObjectRect::~AnnotationObjectRect() { // the annotation pointer is kept elsewehere (in Page, most probably), // so just release its pointer m_object = nullptr; } void AnnotationObjectRect::transform( const QTransform &matrix ) { m_annotation->d_func()->annotationTransform( matrix ); } /** class SourceRefObjectRect **/ SourceRefObjectRect::SourceRefObjectRect( const NormalizedPoint& point, void * srcRef ) : ObjectRect( point.x, point.y, .0, .0, false, SourceRef, srcRef ), m_point( point ) { const double x = m_point.x < 0.0 ? 0.5 : m_point.x; const double y = m_point.y < 0.0 ? 0.5 : m_point.y; const QRectF rect( x - 2, y - 2, 5, 5 ); m_path.addRect( rect ); m_transformedPath = m_path; } QRect SourceRefObjectRect::boundingRect( double xScale, double yScale ) const { const double x = m_point.x < 0.0 ? 0.5 : m_point.x; const double y = m_point.y < 0.0 ? 0.5 : m_point.y; return QRect( x * xScale, y * yScale, 1, 1 ); } bool SourceRefObjectRect::contains( double x, double y, double xScale, double yScale ) const { return distanceSqr( x, y, xScale, yScale ) < ( pow( 7.0 / xScale, 2 ) + pow( 7.0 / yScale, 2 ) ); } + +/** class NonOwningObjectRect **/ + +NonOwningObjectRect::NonOwningObjectRect( double left, double top, double right, double bottom, bool ellipse, ObjectType type, void *object ) + : ObjectRect( left, top, right, bottom, ellipse, type, object ) +{ +} + +NonOwningObjectRect::~NonOwningObjectRect() +{ + // Set m_object so that ~ObjectRect() doesn't delete it + m_object = nullptr; +} diff --git a/core/area.h b/core/area.h index 4c4535d74..b22a3ed83 100644 --- a/core/area.h +++ b/core/area.h @@ -1,865 +1,876 @@ /*************************************************************************** * Copyright (C) 2004-05 by Enrico Ros * * Copyright (C) 2005 by Piotr Szymanski * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #ifndef _OKULAR_AREA_H_ #define _OKULAR_AREA_H_ #include #include #include #include #include #include #include "global.h" #include "okularcore_export.h" class QPolygonF; class QRect; namespace Okular { class Annotation; class Action; class NormalizedShape; /** * NormalizedPoint is a helper class which stores the coordinates * of a normalized point. Normalized means that the coordinates are * between 0 and 1 so that it is page size independent. * * Example: * The normalized point is (0.5, 0.3) * * If you want to draw it on a 800x600 page, just multiply the x coordinate (0.5) with * the page width (800) and the y coordinate (0.3) with the page height (600), so * the point will be drawn on the page at (400, 180). * * That allows you to zoom the page by just multiplying the normalized points with the * zoomed page size. */ class OKULARCORE_EXPORT NormalizedPoint { public: /** * Creates a new empty normalized point. */ NormalizedPoint(); /** * Creates a new normalized point with the normalized coordinates (@p x, @p y ). */ NormalizedPoint( double x, double y ); /** * Creates a new normalized point with the coordinates (@p x, @p y) which are normalized * by the scaling factors @p xScale and @p yScale. */ NormalizedPoint( int x, int y, int xScale, int yScale ); /** * @internal */ NormalizedPoint& operator=( const NormalizedPoint& ); NormalizedPoint( const NormalizedPoint& ); ~NormalizedPoint(); /** * Transforms the normalized point with the operations defined by @p matrix. */ void transform( const QTransform &matrix ); /** * Returns squared distance to point @p x @p y @p xScale @p yScale * @since 0.17 (KDE 4.11) */ double distanceSqr( double x, double y, double xScale, double yScale ) const; /** * @brief Calculates distance of the point @p x @p y @p xScale @p yScale to the line segment from @p start to @p end * @since 0.17 (KDE 4.11) */ static double distanceSqr( double x, double y, double xScale, double yScale, const NormalizedPoint& start, const NormalizedPoint& end ); /** * The normalized x coordinate. */ double x; /** * The normalized y coordinate. */ double y; }; /** * NormalizedRect is a helper class which stores the coordinates * of a normalized rect, which is a rectangle of @see NormalizedPoints. */ class OKULARCORE_EXPORT NormalizedRect { public: /** * Creates a null normalized rectangle. * @see isNull() */ NormalizedRect(); /** * Creates a normalized rectangle with the normalized coordinates * @p left, @p top, @p right, @p bottom. * * If you need the x, y, width and height coordinates use the * following formulas: * * @li x = left * @li y = top * @li width = right - left * @li height = bottom - top */ NormalizedRect( double left, double top, double right, double bottom ); /** * Creates a normalized rectangle of the given @p rectangle which is normalized * by the scaling factors @p xScale and @p yScale. */ NormalizedRect( const QRect &rectangle, double xScale, double yScale ); /** * @internal */ NormalizedRect( const NormalizedRect& ); /** * @internal */ NormalizedRect& operator=( const NormalizedRect &other ); ~NormalizedRect(); /** * Build a normalized rect from a QRectF. */ static NormalizedRect fromQRectF( const QRectF &rect ); /** * Returns whether this normalized rectangle is a null normalized rect. */ bool isNull() const; /** * Returns whether the normalized rectangle contains the normalized coordinates * @p x and @p y. */ bool contains( double x, double y ) const; /** * Returns whether the normalized rectangle intersects the @p other normalized * rectangle. */ bool intersects( const NormalizedRect &other ) const; /** * This is an overloaded member function, provided for convenience. It behaves essentially * like the above function. */ bool intersects( const NormalizedRect *other ) const; /** * Returns whether the normalized rectangle intersects an other normalized * rectangle, which is defined by @p left, @p top, @p right and @p bottom. */ bool intersects( double left, double top, double right, double bottom ) const; /** * Returns the rectangle that accrues when the normalized rectangle is multiplyed * with the scaling @p xScale and @p yScale. */ QRect geometry( int xScale, int yScale ) const; /** * Same functionality as geometry, but the output is now rounded before typecasting to int * @since 0.14 (KDE 4.8) */ QRect roundedGeometry( int xScale, int yScale ) const; /** * Returns the normalized bounding rectangle of the normalized rectangle * combined with the @p other normalized rectangle. */ NormalizedRect operator|( const NormalizedRect &other ) const; /** * Sets the normalized rectangle to the normalized bounding rectangle * of itself combined with the @p other normalized rectangle. */ NormalizedRect& operator|=( const NormalizedRect &other ); /** * Returns the intersection of this normalized rectangle with the specified * @p other. If the rects do not intersect then the result is null. * * @since 0.7 (KDE 4.1) */ NormalizedRect operator&( const NormalizedRect &other ) const; /** * Returns whether the normalized rectangle is equal to the @p other * normalized rectangle. */ bool operator==( const NormalizedRect &other ) const; /** * Returns the center of the rectangle * @since 0.10 (KDE 4.4) */ NormalizedPoint center() const; /** * Transforms the normalized rectangle with the operations defined by @p matrix. */ void transform( const QTransform &matrix ); /** * Returns true if the point pt is located to the bottom of the rectangle * @since 0.14 (KDE 4.8) */ bool isBottom(const NormalizedPoint& pt) const { return bottom < pt.y; } /** * Returns true if the point pt is located on the top of the rectangle * @since 0.14 (KDE 4.8) */ bool isTop(const NormalizedPoint& pt) const { return top > pt.y; } /** * Returns true if the point pt is located under the top of the rectangle * @since 0.14 (KDE 4.8) */ bool isBottomOrLevel(const NormalizedPoint& pt) const { return top < pt.y; } /** * Returns true if the point pt is located above the bottom of the rectangle * @since 0.14 (KDE 4.8) */ bool isTopOrLevel(const NormalizedPoint& pt) const { return bottom > pt.y; } /** * Returns true if the point pt is located to the right of the left arm of rectangle * @since 0.14 (KDE 4.8) */ bool isLeft(const NormalizedPoint& pt) const { return left < pt.x; } /** * Returns true if the point pt is located to the left of the right arm of rectangle * @since 0.14 (KDE 4.8) */ bool isRight(const NormalizedPoint& pt) const { return right > pt.x; } /** * Returns the distance of the point @p x @p y @p xScale @p yScale to the closest * edge or 0 if the point is within the rectangle * @since 0.17 (KDE 4.11) */ double distanceSqr(double x, double y, double xScale, double yScale) const { double distX = 0; if ( x < left ) distX = left - x; else if ( x > right ) distX = x - right; double distY = 0; if ( top > y ) distY = top - y; else if (bottom < y) distY = y - bottom; return pow( distX * xScale, 2 ) + pow( distY * yScale, 2 ); } /// @since 1.4 double width() const { return right - left; } /// @since 1.4 double height() const { return bottom - top; } /** * The normalized left coordinate. */ double left; /** * The normalized top coordinate. */ double top; /** * The normalized right coordinate. */ double right; /** * The normalized bottom coordinate. */ double bottom; }; //KDE_DUMMY_QHASH_FUNCTION(NormalizedRect) /** * @short NormalizedRect that contains a reference to an object. * * These rects contains a pointer to a okular object (such as an action or something * like that). The pointer is read and stored as 'void pointer' so cast is * performed by accessors based on the value returned by objectType(). Objects * are reparented to this class. * * Type / Class correspondence tab: * - Action : class Action: description of an action * - Image : class Image : description of an image (n/a) * - Annotation: class Annotation: description of an annotation */ class OKULARCORE_EXPORT ObjectRect { public: /** * Describes the type of storable object. */ enum ObjectType { Action, ///< An action Image, ///< An image OAnnotation, ///< An annotation SourceRef ///< A source reference }; /** * Creates a new object rectangle. * * @param left The left coordinate of the rectangle. * @param top The top coordinate of the rectangle. * @param right The right coordinate of the rectangle. * @param bottom The bottom coordinate of the rectangle. * @param ellipse If true the rectangle describes an ellipse. * @param type The type of the storable object @see ObjectType. * @param object The pointer to the storable object. */ ObjectRect( double left, double top, double right, double bottom, bool ellipse, ObjectType type, void *object ); /** * This is an overloaded member function, provided for convenience. */ ObjectRect( const NormalizedRect &rect, bool ellipse, ObjectType type, void *object ); /** * This is an overloaded member function, provided for convenience. */ ObjectRect( const QPolygonF &poly, ObjectType type, void *object ); /** * Destroys the object rectangle. */ virtual ~ObjectRect(); /** * Returns the object type of the object rectangle. * @see ObjectType */ ObjectType objectType() const; /** * Returns the storable object of the object rectangle. */ const void *object() const; /** * Returns the region that is covered by the object rectangle. */ const QPainterPath ®ion() const; /** * Returns the bounding rect of the object rectangle for the * scaling factor @p xScale and @p yScale. */ virtual QRect boundingRect( double xScale, double yScale ) const; /** * Returns whether the object rectangle contains the point @p x, @p y for the * scaling factor @p xScale and @p yScale. */ virtual bool contains( double x, double y, double xScale, double yScale ) const; /** * Transforms the object rectangle with the operations defined by @p matrix. */ virtual void transform( const QTransform &matrix ); /** * Returns the square of the distance between the object and the point @p x, @p y * for the scaling factor @p xScale and @p yScale. * * @since 0.8.2 (KDE 4.2.2) */ // FIXME this should most probably be a virtual method double distanceSqr( double x, double y, double xScale, double yScale ) const; protected: ObjectType m_objectType; void * m_object; QPainterPath m_path; QPainterPath m_transformedPath; }; /** * This class describes the object rectangle for an annotation. */ class OKULARCORE_EXPORT AnnotationObjectRect : public ObjectRect { public: /** * Creates a new annotation object rectangle with the * given @p annotation. */ explicit AnnotationObjectRect( Annotation *annotation ); /** * Destroys the annotation object rectangle. */ virtual ~AnnotationObjectRect(); /** * Returns the annotation object of the annotation object rectangle. */ Annotation *annotation() const; /** * Returns the bounding rect of the annotation object rectangle for the * scaling factor @p xScale and @p yScale. */ QRect boundingRect( double xScale, double yScale ) const override; /** * Returns whether the annotation object rectangle contains the point @p x, @p y for the * scaling factor @p xScale and @p yScale. */ bool contains( double x, double y, double xScale, double yScale ) const override; /** * Transforms the annotation object rectangle with the operations defined by @p matrix. */ void transform( const QTransform &matrix ) override; private: Annotation * m_annotation; }; /** * This class describes the object rectangle for a source reference. */ class OKULARCORE_EXPORT SourceRefObjectRect : public ObjectRect { friend class ObjectRect; public: /** * Creates a new source reference object rectangle. * * @param point The point of the source reference. * @param reference The storable source reference object. */ SourceRefObjectRect( const NormalizedPoint& point, void *reference ); /** * Returns the bounding rect of the source reference object rectangle for the * scaling factor @p xScale and @p yScale. */ QRect boundingRect( double xScale, double yScale ) const override; /** * Returns whether the source reference object rectangle contains the point @p x, @p y for the * scaling factor @p xScale and @p yScale. */ bool contains( double x, double y, double xScale, double yScale ) const override; private: NormalizedPoint m_point; }; +/** + * This class is an object rect that doesn't own the given pointer, i.e. won't delete it on destruction + * @since 1.7 + */ +class OKULARCORE_EXPORT NonOwningObjectRect : public ObjectRect +{ + public: + NonOwningObjectRect( double left, double top, double right, double bottom, bool ellipse, ObjectType type, void *object ); + ~NonOwningObjectRect(); +}; + /// @cond PRIVATE /** @internal */ /** @internal */ template T* givePtr( T& t ) { return &t; } /** @internal */ template T& deref( T& t ) { return t; } /// @endcond /** * @short A regular area of NormalizedShape which normalizes a Shape * * Class NormalizedShape \b must have the following functions/operators defined: * - bool contains( double, double ) * - bool intersects( NormalizedShape ) * - bool isNull() * - Shape geometry( int, int ) * - operator|=( NormalizedShape ) which unite two NormalizedShape's */ template class RegularArea : public QList { public: /** * Returns whether the regular area contains the * normalized point @p x, @p y. */ bool contains( double x, double y ) const; /** * Returns whether the regular area contains the * given @p shape. */ bool contains( const NormalizedShape& shape ) const; /** * Returns whether the regular area intersects with the given @p area. */ bool intersects( const RegularArea *area ) const; /** * Returns whether the regular area intersects with the given @p shape. */ bool intersects( const NormalizedShape& shape ) const; /** * Appends the given @p area to the regular area. */ void appendArea( const RegularArea *area ); /** * Appends the given @p shape to the regular area. */ void appendShape( const NormalizedShape& shape, MergeSide side = MergeAll ); /** * Simplifies the regular area by merging its intersecting subareas. */ void simplify(); /** * Returns whether the regular area is a null area. */ bool isNull() const; /** * Returns the subareas of the regular areas as shapes for the given scaling factor * @p xScale and @p yScale, translated by @p dx and @p dy. */ QList geometry( int xScale, int yScale, int dx = 0, int dy = 0 ) const; /** * Transforms the regular area with the operations defined by @p matrix. */ void transform( const QTransform &matrix ); }; template void RegularArea::simplify() { #ifdef DEBUG_REGULARAREA int prev_end = this->count(); #endif int end = this->count() - 1, x = 0; for ( int i = 0; i < end; ++i ) { if ( givePtr( (*this)[x] )->intersects( deref( (*this)[i+1] ) ) ) { deref((*this)[x]) |= deref((*this)[i+1]); this->removeAt( i + 1 ); --end; --i; } else { x=i+1; } } #ifdef DEBUG_REGULARAREA qCDebug(OkularCoreDebug) << "from" << prev_end << "to" << this->count(); #endif } template bool RegularArea::isNull() const { if ( this->isEmpty() ) return true; typename QList::const_iterator it = this->begin(), itEnd = this->end(); for ( ; it != itEnd; ++it ) if ( !givePtr( *it )->isNull() ) return false; return true; } template bool RegularArea::intersects( const NormalizedShape& rect ) const { if ( this->isEmpty() ) return false; typename QList::const_iterator it = this->begin(), itEnd = this->end(); for ( ; it != itEnd; ++it ) if ( !givePtr( *it )->isNull() && givePtr( *it )->intersects( rect ) ) return true; return false; } template bool RegularArea::intersects( const RegularArea *area ) const { if ( this->isEmpty() ) return false; typename QList::const_iterator it = this->begin(), itEnd = this->end(); for ( ; it != itEnd; ++it ) { typename QList::const_iterator areaIt = area->begin(), areaItEnd = area->end(); for ( ; areaIt != areaItEnd; ++areaIt ) { if ( !( *it ).isNull() && ( *it ).intersects( *areaIt ) ) return true; } } return false; } template void RegularArea::appendArea( const RegularArea *area ) { typename QList::const_iterator areaIt = area->begin(), areaItEnd = area->end(); for ( ; areaIt != areaItEnd; ++areaIt ) this->append( *areaIt ); } template void RegularArea::appendShape( const NormalizedShape& shape, MergeSide side ) { int size = this->count(); // if the list is empty, adds the shape normally if ( size == 0 ) { this->append( shape ); } else { bool intersection = false; NormalizedShape& last = (*this)[size - 1]; #define O_LAST givePtr( last ) # define O_LAST_R O_LAST->right # define O_LAST_L O_LAST->left # define O_LAST_T O_LAST->top # define O_LAST_B O_LAST->bottom #define O_NEW givePtr( shape ) # define O_NEW_R O_NEW->right # define O_NEW_L O_NEW->left # define O_NEW_T O_NEW->top # define O_NEW_B O_NEW->bottom switch ( side ) { case MergeRight: intersection = ( O_LAST_R >= O_NEW_L ) && ( O_LAST_L <= O_NEW_R ) && ( ( O_LAST_T <= O_NEW_T && O_LAST_B >= O_NEW_B ) || ( O_LAST_T >= O_NEW_T && O_LAST_B <= O_NEW_B ) ); break; case MergeBottom: intersection = ( O_LAST_B >= O_NEW_T ) && ( O_LAST_T <= O_NEW_B ) && ( ( O_LAST_R <= O_NEW_R && O_LAST_L >= O_NEW_L ) || ( O_LAST_R >= O_NEW_R && O_LAST_L <= O_NEW_L ) ); break; case MergeLeft: intersection = ( O_LAST_L <= O_NEW_R ) && ( O_LAST_R >= O_NEW_L ) && ( ( O_LAST_T <= O_NEW_T && O_LAST_B >= O_NEW_B ) || ( O_LAST_T >= O_NEW_T && O_LAST_B <= O_NEW_B ) ); break; case MergeTop: intersection = ( O_LAST_T <= O_NEW_B ) && ( O_LAST_B >= O_NEW_T ) && ( ( O_LAST_R <= O_NEW_R && O_LAST_L >= O_NEW_L ) || ( O_LAST_R >= O_NEW_R && O_LAST_L <= O_NEW_L ) ); break; case MergeAll: intersection = O_LAST->intersects( shape ); break; } #undef O_LAST # undef O_LAST_R # undef O_LAST_L # undef O_LAST_T # undef O_LAST_B #undef O_NEW # undef O_NEW_R # undef O_NEW_L # undef O_NEW_T # undef O_NEW_B // if the new shape intersects with the last shape in the list, then // merge it with that and delete the shape if ( intersection ) { deref((*this)[size - 1]) |= deref( shape ); } else this->append( shape ); } } template bool RegularArea::contains( double x, double y ) const { if ( this->isEmpty() ) return false; typename QList::const_iterator it = this->begin(), itEnd = this->end(); for ( ; it != itEnd; ++it ) if ( ( *it ).contains( x, y ) ) return true; return false; } template bool RegularArea::contains( const NormalizedShape& shape ) const { if ( this->isEmpty() ) return false; return QList::contains( shape ); } template QList RegularArea::geometry( int xScale, int yScale, int dx, int dy ) const { if ( this->isEmpty() ) return QList(); QList ret; Shape t; typename QList::const_iterator it = this->begin(), itEnd = this->end(); for ( ; it != itEnd; ++it ) { t = givePtr( *it )->geometry( xScale, yScale ); t.translate( dx, dy ); ret.append( t ); } return ret; } template void RegularArea::transform( const QTransform &matrix ) { if ( this->isEmpty() ) return; for ( int i = 0; i < this->count(); ++i ) givePtr( (*this)[i] )->transform( matrix ); } class OKULARCORE_EXPORT RegularAreaRect : public RegularArea< NormalizedRect, QRect > { public: RegularAreaRect(); RegularAreaRect( const RegularAreaRect& rar ); ~RegularAreaRect(); RegularAreaRect& operator=( const RegularAreaRect& rar ); private: class Private; Private * const d; }; /** * This class stores the coordinates of a highlighting area * together with the id of the highlight owner and the color. */ class HighlightAreaRect : public RegularAreaRect { public: /** * Creates a new highlight area rect with the coordinates of * the given @p area. */ explicit HighlightAreaRect( const RegularAreaRect *area = nullptr ); /** * The search ID of the highlight owner. */ int s_id; /** * The color of the highlight. */ QColor color; }; } uint qHash(const Okular::NormalizedRect& r, uint seed = 0); #ifndef QT_NO_DEBUG_STREAM /** * Debug operator for normalized @p point. */ OKULARCORE_EXPORT QDebug operator<<( QDebug str, const Okular::NormalizedPoint &point ); /** * Debug operator for normalized @p rect. */ OKULARCORE_EXPORT QDebug operator<<( QDebug str, const Okular::NormalizedRect &rect ); #endif #endif diff --git a/core/textdocumentgenerator.cpp b/core/textdocumentgenerator.cpp index c764809a8..a21a6fde1 100644 --- a/core/textdocumentgenerator.cpp +++ b/core/textdocumentgenerator.cpp @@ -1,556 +1,565 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #include "textdocumentgenerator.h" #include "textdocumentgenerator_p.h" #include #include #include #include #include #include #include #include #include #include #include "action.h" #include "annotations.h" #include "page.h" #include "textpage.h" #include "document.h" using namespace Okular; /** * Generic Converter Implementation */ TextDocumentConverter::TextDocumentConverter() : QObject( nullptr ), d_ptr( new TextDocumentConverterPrivate ) { } TextDocumentConverter::~TextDocumentConverter() { delete d_ptr; } QTextDocument *TextDocumentConverter::convert( const QString & ) { return nullptr; } Document::OpenResult TextDocumentConverter::convertWithPassword( const QString &fileName, const QString & ) { QTextDocument *doc = convert( fileName ); setDocument( doc ); return doc != nullptr ? Document::OpenSuccess : Document::OpenError; } QTextDocument *TextDocumentConverter::document() { return d_ptr->mDocument; } void TextDocumentConverter::setDocument( QTextDocument *document ) { d_ptr->mDocument = document; } DocumentViewport TextDocumentConverter::calculateViewport( QTextDocument *document, const QTextBlock &block ) { return TextDocumentUtils::calculateViewport( document, block ); } TextDocumentGenerator* TextDocumentConverter::generator() const { return d_ptr->mParent ? d_ptr->mParent->q_func() : nullptr; } /** * Generic Generator Implementation */ Okular::TextPage* TextDocumentGeneratorPrivate::createTextPage( int pageNumber ) const { #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING Q_Q( const TextDocumentGenerator ); #endif Okular::TextPage *textPage = new Okular::TextPage; int start, end; #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING q->userMutex()->lock(); #endif TextDocumentUtils::calculatePositions( mDocument, pageNumber, start, end ); { QTextCursor cursor( mDocument ); for ( int i = start; i < end - 1; ++i ) { cursor.setPosition( i ); cursor.setPosition( i + 1, QTextCursor::KeepAnchor ); QString text = cursor.selectedText(); if ( text.length() == 1 ) { QRectF rect; TextDocumentUtils::calculateBoundingRect( mDocument, i, i + 1, rect, pageNumber ); if ( pageNumber == -1 ) text = QStringLiteral("\n"); textPage->append( text, new Okular::NormalizedRect( rect.left(), rect.top(), rect.right(), rect.bottom() ) ); } } } #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING q->userMutex()->unlock(); #endif return textPage; } void TextDocumentGeneratorPrivate::addAction( Action *action, int cursorBegin, int cursorEnd ) { if ( !action ) return; LinkPosition position; position.link = action; position.startPosition = cursorBegin; position.endPosition = cursorEnd; mLinkPositions.append( position ); } void TextDocumentGeneratorPrivate::addAnnotation( Annotation *annotation, int cursorBegin, int cursorEnd ) { if ( !annotation ) return; annotation->setFlags( annotation->flags() | Okular::Annotation::External ); AnnotationPosition position; position.annotation = annotation; position.startPosition = cursorBegin; position.endPosition = cursorEnd; mAnnotationPositions.append( position ); } void TextDocumentGeneratorPrivate::addTitle( int level, const QString &title, const QTextBlock &block ) { TitlePosition position; position.level = level; position.title = title; position.block = block; mTitlePositions.append( position ); } void TextDocumentGeneratorPrivate::addMetaData( const QString &key, const QString &value, const QString &title ) { mDocumentInfo.set( key, value, title ); } void TextDocumentGeneratorPrivate::addMetaData( DocumentInfo::Key key, const QString &value ) { mDocumentInfo.set( key, value ); } QList TextDocumentGeneratorPrivate::generateLinkInfos() const { QList result; for ( int i = 0; i < mLinkPositions.count(); ++i ) { const LinkPosition &linkPosition = mLinkPositions[ i ]; - LinkInfo info; - info.link = linkPosition.link; + const QVector rects = TextDocumentUtils::calculateBoundingRects( mDocument, linkPosition.startPosition, linkPosition.endPosition ); - TextDocumentUtils::calculateBoundingRect( mDocument, linkPosition.startPosition, linkPosition.endPosition, - info.boundingRect, info.page ); + for ( int i = 0; i < rects.count(); ++i) { + const QRectF &rect = rects[ i ]; - if ( info.page >= 0 ) + LinkInfo info; + info.link = linkPosition.link; + info.ownsLink = i == 0; + info.page = std::floor( rect.y() ); + info.boundingRect = QRectF( rect.x(), rect.y() - info.page, rect.width(), rect.height() ); result.append( info ); + } } return result; } QList TextDocumentGeneratorPrivate::generateAnnotationInfos() const { QList result; for ( int i = 0; i < mAnnotationPositions.count(); ++i ) { const AnnotationPosition &annotationPosition = mAnnotationPositions[ i ]; AnnotationInfo info; info.annotation = annotationPosition.annotation; TextDocumentUtils::calculateBoundingRect( mDocument, annotationPosition.startPosition, annotationPosition.endPosition, info.boundingRect, info.page ); if ( info.page >= 0 ) result.append( info ); } return result; } void TextDocumentGeneratorPrivate::generateTitleInfos() { QStack< QPair > parentNodeStack; QDomNode parentNode = mDocumentSynopsis; parentNodeStack.push( qMakePair( 0, parentNode ) ); for ( int i = 0; i < mTitlePositions.count(); ++i ) { const TitlePosition &position = mTitlePositions[ i ]; Okular::DocumentViewport viewport = TextDocumentUtils::calculateViewport( mDocument, position.block ); QDomElement item = mDocumentSynopsis.createElement( position.title ); item.setAttribute( QStringLiteral("Viewport"), viewport.toString() ); int headingLevel = position.level; // we need a parent, which has to be at a higher heading level than this heading level // so we just work through the stack while ( ! parentNodeStack.isEmpty() ) { int parentLevel = parentNodeStack.top().first; if ( parentLevel < headingLevel ) { // this is OK as a parent parentNode = parentNodeStack.top().second; break; } else { // we'll need to be further into the stack parentNodeStack.pop(); } } parentNode.appendChild( item ); parentNodeStack.push( qMakePair( headingLevel, QDomNode(item) ) ); } } void TextDocumentGeneratorPrivate::initializeGenerator() { Q_Q( TextDocumentGenerator ); mConverter->d_ptr->mParent = q->d_func(); if ( mGeneralSettings ) { mFont = mGeneralSettings->font(); } q->setFeature( Generator::TextExtraction ); q->setFeature( Generator::PrintNative ); q->setFeature( Generator::PrintToFile ); #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING if ( QFontDatabase::supportsThreadedFontRendering() ) q->setFeature( Generator::Threaded ); #endif QObject::connect( mConverter, SIGNAL(addAction(Action*,int,int)), q, SLOT(addAction(Action*,int,int)) ); QObject::connect( mConverter, SIGNAL(addAnnotation(Annotation*,int,int)), q, SLOT(addAnnotation(Annotation*,int,int)) ); QObject::connect( mConverter, SIGNAL(addTitle(int,QString,QTextBlock)), q, SLOT(addTitle(int,QString,QTextBlock)) ); QObject::connect( mConverter, SIGNAL(addMetaData(QString,QString,QString)), q, SLOT(addMetaData(QString,QString,QString)) ); QObject::connect( mConverter, SIGNAL(addMetaData(DocumentInfo::Key,QString)), q, SLOT(addMetaData(DocumentInfo::Key,QString)) ); QObject::connect( mConverter, &TextDocumentConverter::error, q, &Generator::error ); QObject::connect( mConverter, &TextDocumentConverter::warning, q, &Generator::warning ); QObject::connect( mConverter, &TextDocumentConverter::notice, q, &Generator::notice ); } TextDocumentGenerator::TextDocumentGenerator(TextDocumentConverter *converter, const QString& configName , QObject *parent, const QVariantList &args) : Okular::Generator( *new TextDocumentGeneratorPrivate( converter ), parent, args ) { Q_D( TextDocumentGenerator ); d->mGeneralSettings = new TextDocumentSettings( configName, this ); d->initializeGenerator(); } TextDocumentGenerator::~TextDocumentGenerator() { } Document::OpenResult TextDocumentGenerator::loadDocumentWithPassword( const QString & fileName, QVector & pagesVector, const QString &password ) { Q_D( TextDocumentGenerator ); const Document::OpenResult openResult = d->mConverter->convertWithPassword( fileName, password ); if ( openResult != Document::OpenSuccess ) { d->mDocument = nullptr; // loading failed, cleanup all the stuff eventually gathered from the converter d->mTitlePositions.clear(); Q_FOREACH ( const TextDocumentGeneratorPrivate::LinkPosition &linkPos, d->mLinkPositions ) { delete linkPos.link; } d->mLinkPositions.clear(); Q_FOREACH ( const TextDocumentGeneratorPrivate::AnnotationPosition &annPos, d->mAnnotationPositions ) { delete annPos.annotation; } d->mAnnotationPositions.clear(); return openResult; } d->mDocument = d->mConverter->document(); d->generateTitleInfos(); const QList linkInfos = d->generateLinkInfos(); const QList annotationInfos = d->generateAnnotationInfos(); pagesVector.resize( d->mDocument->pageCount() ); const QSize size = d->mDocument->pageSize().toSize(); QVector< QLinkedList > objects( d->mDocument->pageCount() ); for ( const TextDocumentGeneratorPrivate::LinkInfo &info : linkInfos ) { // in case that the converter report bogus link info data, do not assert here if ( info.page >= objects.count() ) continue; const QRectF rect = info.boundingRect; - objects[ info.page ].append( new Okular::ObjectRect( rect.left(), rect.top(), rect.right(), rect.bottom(), false, - Okular::ObjectRect::Action, info.link ) ); + if ( info.ownsLink ) { + objects[ info.page ].append( new Okular::ObjectRect( rect.left(), rect.top(), rect.right(), rect.bottom(), false, + Okular::ObjectRect::Action, info.link ) ); + } else { + objects[ info.page ].append( new Okular::NonOwningObjectRect( rect.left(), rect.top(), rect.right(), rect.bottom(), false, + Okular::ObjectRect::Action, info.link ) ); + } } QVector< QLinkedList > annots( d->mDocument->pageCount() ); for ( const TextDocumentGeneratorPrivate::AnnotationInfo &info : annotationInfos ) { annots[ info.page ].append( info.annotation ); } for ( int i = 0; i < d->mDocument->pageCount(); ++i ) { Okular::Page * page = new Okular::Page( i, size.width(), size.height(), Okular::Rotation0 ); pagesVector[ i ] = page; if ( !objects.at( i ).isEmpty() ) { page->setObjectRects( objects.at( i ) ); } QLinkedList::ConstIterator annIt = annots.at( i ).begin(), annEnd = annots.at( i ).end(); for ( ; annIt != annEnd; ++annIt ) { page->addAnnotation( *annIt ); } } return openResult; } bool TextDocumentGenerator::doCloseDocument() { Q_D( TextDocumentGenerator ); delete d->mDocument; d->mDocument = nullptr; d->mTitlePositions.clear(); d->mLinkPositions.clear(); d->mAnnotationPositions.clear(); // do not use clear() for the following two, otherwise they change type d->mDocumentInfo = Okular::DocumentInfo(); d->mDocumentSynopsis = Okular::DocumentSynopsis(); return true; } bool TextDocumentGenerator::canGeneratePixmap() const { return Generator::canGeneratePixmap(); } void TextDocumentGenerator::generatePixmap( Okular::PixmapRequest * request ) { Generator::generatePixmap( request ); } QImage TextDocumentGeneratorPrivate::image( PixmapRequest * request ) { if ( !mDocument ) return QImage(); #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING Q_Q( TextDocumentGenerator ); #endif QImage image( request->width(), request->height(), QImage::Format_ARGB32 ); image.fill( Qt::white ); QPainter p; p.begin( &image ); qreal width = request->width(); qreal height = request->height(); const QSize size = mDocument->pageSize().toSize(); p.scale( width / (qreal)size.width(), height / (qreal)size.height() ); QRect rect; rect = QRect( 0, request->pageNumber() * size.height(), size.width(), size.height() ); p.translate( QPoint( 0, request->pageNumber() * size.height() * -1 ) ); p.setClipRect( rect ); #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING q->userMutex()->lock(); #endif QAbstractTextDocumentLayout::PaintContext context; context.palette.setColor( QPalette::Text, Qt::black ); // FIXME Fix Qt, this doesn't work, we have horrible hacks // in the generators that return html, remove that code // if Qt ever gets fixed // context.palette.setColor( QPalette::Link, Qt::blue ); context.clip = rect; mDocument->setDefaultFont( mFont ); mDocument->documentLayout()->draw( &p, context ); #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING q->userMutex()->unlock(); #endif p.end(); return image; } Okular::TextPage* TextDocumentGenerator::textPage( Okular::TextRequest * request ) { Q_D( TextDocumentGenerator ); return d->createTextPage( request->page()->number() ); } bool TextDocumentGenerator::print( QPrinter& printer ) { Q_D( TextDocumentGenerator ); if ( !d->mDocument ) return false; d->mDocument->print( &printer ); return true; } Okular::DocumentInfo TextDocumentGenerator::generateDocumentInfo( const QSet & /*keys*/ ) const { Q_D( const TextDocumentGenerator ); return d->mDocumentInfo; } const Okular::DocumentSynopsis* TextDocumentGenerator::generateDocumentSynopsis() { Q_D( TextDocumentGenerator ); if ( !d->mDocumentSynopsis.hasChildNodes() ) return nullptr; else return &d->mDocumentSynopsis; } QVariant TextDocumentGeneratorPrivate::metaData( const QString &key, const QVariant &option ) const { Q_UNUSED( option ) if ( key == QLatin1String("DocumentTitle") ) { return mDocumentInfo.get( DocumentInfo::Title ); } return QVariant(); } Okular::ExportFormat::List TextDocumentGenerator::exportFormats( ) const { static Okular::ExportFormat::List formats; if ( formats.isEmpty() ) { formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText ) ); formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PDF ) ); if ( QTextDocumentWriter::supportedDocumentFormats().contains( "ODF" ) ) { formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::OpenDocumentText ) ); } if ( QTextDocumentWriter::supportedDocumentFormats().contains( "HTML" ) ) { formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::HTML ) ); } } return formats; } bool TextDocumentGenerator::exportTo( const QString &fileName, const Okular::ExportFormat &format ) { Q_D( TextDocumentGenerator ); if ( !d->mDocument ) return false; if ( format.mimeType().name() == QLatin1String( "application/pdf" ) ) { QFile file( fileName ); if ( !file.open( QIODevice::WriteOnly ) ) return false; QPrinter printer( QPrinter::HighResolution ); printer.setOutputFormat( QPrinter::PdfFormat ); printer.setOutputFileName( fileName ); d->mDocument->print( &printer ); return true; } else if ( format.mimeType().name() == QLatin1String( "text/plain" ) ) { QFile file( fileName ); if ( !file.open( QIODevice::WriteOnly ) ) return false; QTextStream out( &file ); out << d->mDocument->toPlainText(); return true; } else if ( format.mimeType().name() == QLatin1String( "application/vnd.oasis.opendocument.text" ) ) { QTextDocumentWriter odfWriter( fileName, "odf" ); return odfWriter.write( d->mDocument ); } else if ( format.mimeType().name() == QLatin1String( "text/html" ) ) { QTextDocumentWriter odfWriter( fileName, "html" ); return odfWriter.write( d->mDocument ); } return false; } bool TextDocumentGenerator::reparseConfig() { Q_D( TextDocumentGenerator ); const QFont newFont = d->mGeneralSettings->font(); if ( newFont != d->mFont ) { d->mFont = newFont; return true; } return false; } void TextDocumentGenerator::addPages( KConfigDialog* /*dlg*/ ) { qCWarning(OkularCoreDebug) << "You forgot to reimplement addPages in your TextDocumentGenerator"; return; } TextDocumentSettings* TextDocumentGenerator::generalSettings() { Q_D( TextDocumentGenerator ); return d->mGeneralSettings; } #include "moc_textdocumentgenerator.cpp" diff --git a/core/textdocumentgenerator_p.h b/core/textdocumentgenerator_p.h index 052860b8f..284996d07 100644 --- a/core/textdocumentgenerator_p.h +++ b/core/textdocumentgenerator_p.h @@ -1,201 +1,262 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ #ifndef _OKULAR_TEXTDOCUMENTGENERATOR_P_H_ #define _OKULAR_TEXTDOCUMENTGENERATOR_P_H_ #include #include #include #include "action.h" #include "document.h" #include "generator_p.h" #include "textdocumentgenerator.h" #include "debug_p.h" namespace Okular { namespace TextDocumentUtils { static void calculateBoundingRect( QTextDocument *document, int startPosition, int endPosition, QRectF &rect, int &page ) { const QSizeF pageSize = document->pageSize(); const QTextBlock startBlock = document->findBlock( startPosition ); const QRectF startBoundingRect = document->documentLayout()->blockBoundingRect( startBlock ); const QTextBlock endBlock = document->findBlock( endPosition ); const QRectF endBoundingRect = document->documentLayout()->blockBoundingRect( endBlock ); const QTextLayout *startLayout = startBlock.layout(); const QTextLayout *endLayout = endBlock.layout(); if (!startLayout || !endLayout) { qCWarning(OkularCoreDebug) << "Start or end layout not found" << startLayout << endLayout; page = -1; return; } const int startPos = startPosition - startBlock.position(); const int endPos = endPosition - endBlock.position(); const QTextLine startLine = startLayout->lineForTextPosition( startPos ); const QTextLine endLine = endLayout->lineForTextPosition( endPos ); const double x = startBoundingRect.x() + startLine.cursorToX( startPos ); const double y = startBoundingRect.y() + startLine.y(); const double r = endBoundingRect.x() + endLine.cursorToX( endPos ); const double b = endBoundingRect.y() + endLine.y() + endLine.height(); const int offset = qRound( y ) % qRound( pageSize.height() ); if ( x > r ) { // line break, so return a pseudo character on the start line rect = QRectF( x / pageSize.width(), offset / pageSize.height(), 3 / pageSize.width(), startLine.height() / pageSize.height() ); page = -1; return; } page = qRound( y ) / qRound( pageSize.height() ); rect = QRectF( x / pageSize.width(), offset / pageSize.height(), (r - x) / pageSize.width(), (b - y) / pageSize.height() ); } + static QVector calculateBoundingRects( QTextDocument *document, int startPosition, int endPosition ) + { + QVector result; + + const QSizeF pageSize = document->pageSize(); + + const QTextBlock startBlock = document->findBlock( startPosition ); + const QRectF startBoundingRect = document->documentLayout()->blockBoundingRect( startBlock ); + + const QTextBlock endBlock = document->findBlock( endPosition ); + const QRectF endBoundingRect = document->documentLayout()->blockBoundingRect( endBlock ); + + const QTextLayout *startLayout = startBlock.layout(); + const QTextLayout *endLayout = endBlock.layout(); + if (!startLayout || !endLayout) { + qCWarning(OkularCoreDebug) << "Start or end layout not found" << startLayout << endLayout; + return {}; + } + + const int startPos = startPosition - startBlock.position(); + const int endPos = endPosition - endBlock.position(); + const QTextLine startLine = startLayout->lineForTextPosition( startPos ); + const QTextLine endLine = endLayout->lineForTextPosition( endPos ); + + // This only works if both start and end layout are the same + if (startLayout == endLayout) { + Q_ASSERT( startBoundingRect == endBoundingRect ); + for (int i = startLine.lineNumber(); i < endLine.lineNumber(); ++i) { + const QTextLine line = startLayout->lineAt( i ); + // using startPos and endPos is fine, if the pos is out of bounds for that line, it'll return beginning and end of line respectively + const double x = endBoundingRect.x() + line.cursorToX( startPos ); + const double y = endBoundingRect.y() + line.y(); + const double r = endBoundingRect.x() + line.cursorToX( endPos ); + const double b = endBoundingRect.y() + line.y() + endLine.height(); + + result.append( QRectF( x / pageSize.width(), y / pageSize.height(), + (r - x) / pageSize.width(), (b - y) / pageSize.height() ) ); + } + + // The last line + const double x = endBoundingRect.x() + endLine.cursorToX( startPos ); + const double y = endBoundingRect.y() + endLine.y(); + const double r = endBoundingRect.x() + endLine.cursorToX( endPos ); + const double b = endBoundingRect.y() + endLine.y() + endLine.height(); + + result.append( QRectF( x / pageSize.width(), y / pageSize.height(), + (r - x) / pageSize.width(), (b - y) / pageSize.height() ) ); + } else { + const double x = startBoundingRect.x() + startLine.cursorToX( startPos ); + const double y = startBoundingRect.y() + startLine.y(); + const double r = endBoundingRect.x() + endLine.cursorToX( endPos ); + const double b = endBoundingRect.y() + endLine.y() + endLine.height(); + + result.append( QRectF( x / pageSize.width(), y / pageSize.height(), + (r - x) / pageSize.width(), (b - y) / pageSize.height() ) ); + } + + return result; + } + static void calculatePositions( QTextDocument *document, int page, int &start, int &end ) { const QAbstractTextDocumentLayout *layout = document->documentLayout(); const QSizeF pageSize = document->pageSize(); const double margin = document->rootFrame()->frameFormat().margin(); /** * Take the upper left and lower left corner including the margin */ start = layout->hitTest( QPointF( margin, (page * pageSize.height()) + margin ), Qt::FuzzyHit ); end = layout->hitTest( QPointF( margin, ((page + 1) * pageSize.height()) - margin ), Qt::FuzzyHit ); } static Okular::DocumentViewport calculateViewport( QTextDocument *document, const QTextBlock &block ) { const QSizeF pageSize = document->pageSize(); const QRectF rect = document->documentLayout()->blockBoundingRect( block ); const int page = qRound( rect.y() ) / qRound( pageSize.height() ); const int offset = qRound( rect.y() ) % qRound( pageSize.height() ); Okular::DocumentViewport viewport( page ); viewport.rePos.normalizedX = (double)rect.x() / (double)pageSize.width(); viewport.rePos.normalizedY = (double)offset / (double)pageSize.height(); viewport.rePos.enabled = true; viewport.rePos.pos = Okular::DocumentViewport::Center; return viewport; } } class TextDocumentConverterPrivate { public: TextDocumentConverterPrivate() : mParent( nullptr ) { } TextDocumentGeneratorPrivate *mParent; QTextDocument *mDocument; }; class TextDocumentGeneratorPrivate : public GeneratorPrivate { friend class TextDocumentConverter; public: explicit TextDocumentGeneratorPrivate( TextDocumentConverter *converter ) : mConverter( converter ), mDocument( nullptr ), mGeneralSettings( nullptr ) { } virtual ~TextDocumentGeneratorPrivate() { delete mConverter; delete mDocument; } void initializeGenerator(); struct LinkInfo { int page; QRectF boundingRect; Action *link; + bool ownsLink; }; struct AnnotationInfo { int page; QRectF boundingRect; Annotation *annotation; }; Q_DECLARE_PUBLIC( TextDocumentGenerator ) /* reimp */ QVariant metaData( const QString &key, const QVariant &option ) const override; /* reimp */ QImage image( PixmapRequest * ) override; void calculateBoundingRect( int startPosition, int endPosition, QRectF &rect, int &page ) const; void calculatePositions( int page, int &start, int &end ) const; Okular::TextPage* createTextPage( int ) const; void addAction( Action *action, int cursorBegin, int cursorEnd ); void addAnnotation( Annotation *annotation, int cursorBegin, int cursorEnd ); void addTitle( int level, const QString &title, const QTextBlock &position ); void addMetaData( const QString &key, const QString &value, const QString &title ); void addMetaData( DocumentInfo::Key, const QString &value ); QList generateLinkInfos() const; QList generateAnnotationInfos() const; void generateTitleInfos(); TextDocumentConverter *mConverter; QTextDocument *mDocument; Okular::DocumentInfo mDocumentInfo; Okular::DocumentSynopsis mDocumentSynopsis; struct TitlePosition { int level; QString title; QTextBlock block; }; QList mTitlePositions; struct LinkPosition { int startPosition; int endPosition; Action *link; }; QList mLinkPositions; struct AnnotationPosition { int startPosition; int endPosition; Annotation *annotation; }; QList mAnnotationPositions; TextDocumentSettings *mGeneralSettings; QFont mFont; }; } #endif