Warning, file /office/calligra/libs/textlayout/KoTextLayoutObstruction.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /* This file is part of the KDE project
0002  * Copyright (C) 2006-2007, 2010 Thomas Zander <zander@kde.org>
0003  * Copyright (C) 2010 Ko Gmbh <cbo@kogmbh.com>
0004  *
0005  * This library is free software; you can redistribute it and/or
0006  * modify it under the terms of the GNU Library General Public
0007  * License as published by the Free Software Foundation; either
0008  * version 2 of the License, or (at your option) any later version.
0009  *
0010  * This library is distributed in the hope that it will be useful,
0011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013  * Library General Public License for more details.
0014  *
0015  * You should have received a copy of the GNU Library General Public License
0016  * along with this library; see the file COPYING.LIB.  If not, write to
0017  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018  * Boston, MA 02110-1301, USA.
0019  */
0020 
0021 #include "KoTextLayoutObstruction.h"
0022 
0023 #include <KoShapeStrokeModel.h>
0024 #include <KoShapeShadow.h>
0025 #include <KoShapeGroup.h>
0026 #include <KoClipPath.h>
0027 #include <KoInsets.h>
0028 
0029 #include <QTransform>
0030 #include <QPainterPath>
0031 
0032 KoTextLayoutObstruction::KoTextLayoutObstruction(KoShape *shape, const QTransform &matrix)
0033     : m_side(None),
0034     m_polygon(QPolygonF()),
0035     m_line(QRectF()),
0036     m_shape(shape),
0037     m_runAroundThreshold(0)
0038 {
0039     qreal borderHalfWidth;
0040     QPainterPath path = decoratedOutline(m_shape, borderHalfWidth);
0041 
0042     //TODO check if path is convex. otherwise do triangulation and create more convex obstructions
0043     init(matrix, path, shape->textRunAroundDistanceLeft(), shape->textRunAroundDistanceTop(), shape->textRunAroundDistanceRight(), shape->textRunAroundDistanceBottom(), borderHalfWidth);
0044 
0045     if (shape->textRunAroundSide() == KoShape::NoRunAround) {
0046         // make the shape take the full width of the text area
0047         m_side = Empty;
0048     } else if (shape->textRunAroundSide() == KoShape::RunThrough) {
0049         m_distanceLeft = 0;
0050         m_distanceTop = 0;
0051         m_distanceRight = 0;
0052         m_distanceBottom = 0;
0053         // We don't exist.
0054         return;
0055     } else if (shape->textRunAroundSide() == KoShape::LeftRunAroundSide) {
0056         m_side = Left;
0057     } else if (shape->textRunAroundSide() == KoShape::RightRunAroundSide) {
0058         m_side = Right;
0059     } else if (shape->textRunAroundSide() == KoShape::BothRunAroundSide) {
0060         m_side = Both;
0061     } else if (shape->textRunAroundSide() == KoShape::BiggestRunAroundSide) {
0062         m_side = Bigger;
0063     } else if (shape->textRunAroundSide() == KoShape::EnoughRunAroundSide) {
0064         m_side = Enough;
0065         m_runAroundThreshold = shape->textRunAroundThreshold();
0066     }
0067 }
0068 
0069 KoTextLayoutObstruction::KoTextLayoutObstruction(const QRectF &rect, bool rtl)
0070     : m_side(None),
0071     m_polygon(QPolygonF()),
0072     m_line(QRectF()),
0073     m_shape(0),
0074     m_runAroundThreshold(0)
0075 {
0076     qreal borderHalfWidth = 0;
0077     qreal textRunAroundDistance = 1;
0078 
0079     QPainterPath path;
0080     path.addRect(rect);
0081 
0082     init(QTransform(), path, textRunAroundDistance, 0.0, textRunAroundDistance, 0.0,  borderHalfWidth);
0083     if (rtl) {
0084         m_side = Right;
0085     } else {
0086         m_side = Left;
0087     }
0088 }
0089 
0090 QPainterPath KoTextLayoutObstruction::decoratedOutline(const KoShape *shape, qreal &borderHalfWidth) const
0091 {
0092     const KoShapeGroup *shapeGroup = dynamic_cast<const KoShapeGroup *>(shape);
0093     if (shapeGroup) {
0094         QPainterPath groupPath;
0095 
0096         foreach (const KoShape *child, shapeGroup->shapes()) {
0097             groupPath += decoratedOutline(child, borderHalfWidth);
0098         }
0099         return groupPath;
0100     }
0101 
0102     QPainterPath path;
0103     if (shape->textRunAroundContour() != KoShape::ContourBox) {
0104         KoClipPath *clipPath = shape->clipPath();
0105         if (clipPath) {
0106             path = clipPath->pathForSize(shape->size());
0107         } else {
0108             path = shape->outline();
0109         }
0110     } else {
0111         path.addRect(shape->outlineRect());
0112     }
0113 
0114     QRectF bb = shape->outlineRect();
0115     borderHalfWidth = 0;
0116  
0117     if (shape->stroke()) {
0118         KoInsets insets;
0119         shape->stroke()->strokeInsets(shape, insets);
0120         /*
0121         bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom);
0122         path = QPainterPath();
0123         path.addRect(bb);
0124         */
0125         borderHalfWidth = qMax(qMax(insets.left, insets.top),qMax(insets.right, insets.bottom));
0126     }
0127 
0128     if (shape->shadow() && shape->shadow()->isVisible()) {
0129         QTransform transform = shape->absoluteTransformation(0);
0130         bb = transform.mapRect(bb);
0131         KoInsets insets;
0132         shape->shadow()->insets(insets);
0133         bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom);
0134         path = QPainterPath();
0135         path.addRect(bb);
0136         path = transform.inverted().map(path);
0137     }
0138 
0139     return path;
0140 }
0141 
0142 void KoTextLayoutObstruction::init(const QTransform &matrix, const QPainterPath &obstruction, qreal distanceLeft, qreal distanceTop, qreal distanceRight, qreal distanceBottom, qreal borderHalfWidth)
0143 {
0144     m_distanceLeft = distanceLeft;
0145     m_distanceTop = distanceTop;
0146     m_distanceRight = distanceRight;
0147     m_distanceBottom = distanceBottom;
0148     QPainterPath path =  matrix.map(obstruction);
0149     distanceLeft += borderHalfWidth;
0150     distanceTop += borderHalfWidth;
0151     distanceRight += borderHalfWidth;
0152     distanceBottom += borderHalfWidth;
0153 
0154     qreal extraWidth = distanceLeft + distanceRight;
0155     qreal extraHeight = distanceTop + distanceBottom;
0156     if (extraWidth != 0.0 || extraHeight != 0.0) {
0157         // Let's extend the outline with at least the border half width in all directions.
0158         // However since the distance can be express in 4 directions and QPainterPathStroker only
0159         // handles a penWidth we do some tricks to get it working.
0160         //
0161         // Explanation in one dimension only: we sum the distances top and below and use that as the
0162         // penWidth. afterwards we translate the result so it is distributed correctly by top and bottom
0163         // Now by doing that we would also implicitly set the left+right size of the pen which is no good,
0164         // so in order to set that to a minimal value (we choose 1, as 0 would give division by 0) we do
0165         // the following:. We scale the original path by sumX, stroke the path with penwidth=sumY, then
0166         // scale it back. Effectively we have now stroked with a pen sized 1 x sumY.
0167         //
0168         // The math to do both x an y in one go becomes a little more complex, but only a little.
0169         extraWidth = qMax(qreal(0.1), extraWidth);
0170         extraHeight = qMax(qreal(0.1), extraHeight);
0171 
0172         QPainterPathStroker stroker;
0173         stroker.setWidth(extraWidth);
0174         stroker.setJoinStyle(Qt::MiterJoin);
0175         stroker.setCapStyle(Qt::SquareCap);
0176         QPainterPath bigPath = stroker.createStroke(QTransform().scale(1.0, extraWidth / extraHeight).map(path));
0177         bigPath = QTransform().scale(1.0, extraHeight / extraWidth). map(bigPath);
0178         path += bigPath.translated(extraWidth / 2 - distanceLeft, extraHeight / 2 - distanceTop);
0179     }
0180 
0181     m_bounds = path.boundingRect();
0182 
0183     // Now we need to change the path into a polygon for easier handling later on
0184     m_polygon = path.toFillPolygon();
0185     QPointF &prev(m_polygon.first());
0186     // There exists a problem on msvc with for(each) and QVector<QPointF>
0187     for (int i = 0; i < m_polygon.count(); ++i) {
0188         const QPointF &vtx(m_polygon[i]);
0189         if (vtx.x() == prev.x() && vtx.y() == prev.y())
0190             continue;
0191         QLineF line;
0192         if (prev.y() < vtx.y()) // Make sure the vector lines all point downwards.
0193             line = QLineF(prev, vtx);
0194         else
0195             line = QLineF(vtx, prev);
0196         m_edges.insert(line.y1(), line);
0197         prev = vtx;
0198     }
0199 }
0200 
0201 qreal KoTextLayoutObstruction::xAtY(const QLineF &line, qreal y)
0202 {
0203     if (line.dx() == 0)
0204         return line.x1();
0205     return line.x1() + (y - line.y1()) / line.dy() * line.dx();
0206 }
0207 
0208 void KoTextLayoutObstruction::changeMatrix(const QTransform &matrix)
0209 {
0210     m_edges.clear();
0211 
0212     qreal borderHalfWidth;
0213     QPainterPath path = decoratedOutline(m_shape, borderHalfWidth);
0214 
0215     init(matrix, path, m_distanceLeft, m_distanceTop, m_distanceRight, m_distanceBottom, borderHalfWidth);
0216 }
0217 
0218 QRectF KoTextLayoutObstruction::cropToLine(const QRectF &lineRect)
0219 {
0220     if (m_bounds.intersects(lineRect)) {
0221         m_line = lineRect;
0222         bool untilFirst = true;
0223         //check inner points
0224         // There exists a problem on msvc with for(each) and QVector<QPointF>
0225         for (int i = 0; i < m_polygon.count(); ++i) {
0226             const QPointF point(m_polygon[i]);
0227             if (lineRect.contains(point)) {
0228                 if (untilFirst) {
0229                     m_line.setLeft(point.x());
0230                     m_line.setRight(point.x());
0231                     untilFirst = false;
0232                 } else {
0233                     if (point.x() < m_line.left()) {
0234                         m_line.setLeft(point.x());
0235                     } else if (point.x() > m_line.right()) {
0236                         m_line.setRight(point.x());
0237                     }
0238                 }
0239             }
0240         }
0241         //check edges
0242         qreal points[2] = { lineRect.top(), lineRect.bottom() };
0243         for (int i = 0; i < 2; i++) {
0244             const qreal y = points[i];
0245             QMap<qreal, QLineF>::const_iterator iter = m_edges.constBegin();
0246             for (;iter != m_edges.constEnd(); ++iter) {
0247                 QLineF line = iter.value();
0248                 if (line.y2() < y) // not a section that will intersect with ou Y yet
0249                     continue;
0250                 if (line.y1() > y) // section is below our Y, so abort loop
0251                     //break;
0252                     continue;
0253                 if (qAbs(line.dy()) < 1E-10)  // horizontal lines don't concern us.
0254                     continue;
0255 
0256                 qreal intersect = xAtY(iter.value(), y);
0257                 if (untilFirst) {
0258                     m_line.setLeft(intersect);
0259                     m_line.setRight(intersect);
0260                     untilFirst = false;
0261                 } else {
0262                     if (intersect < m_line.left()) {
0263                         m_line.setLeft(intersect);
0264                     } else if (intersect > m_line.right()) {
0265                         m_line.setRight(intersect);
0266                     }
0267                 }
0268             }
0269         }
0270     } else {
0271         m_line = QRectF();
0272     }
0273     return m_line;
0274 }
0275 
0276 QRectF KoTextLayoutObstruction::getLeftLinePart(const QRectF &lineRect) const
0277 {
0278     QRectF leftLinePart = lineRect;
0279     leftLinePart.setRight(m_line.left());
0280     return leftLinePart;
0281 }
0282 
0283 QRectF KoTextLayoutObstruction::getRightLinePart(const QRectF &lineRect) const
0284 {
0285     QRectF rightLinePart = lineRect;
0286     if (m_line.right() > rightLinePart.left()) {
0287         rightLinePart.setLeft(m_line.right());
0288     }
0289     return rightLinePart;
0290 }
0291 
0292 bool KoTextLayoutObstruction::textOnLeft() const
0293 {    
0294     return  m_side == Left;
0295 }
0296 
0297 bool KoTextLayoutObstruction::textOnRight() const
0298 {
0299     return m_side == Right;
0300 }
0301 
0302 bool KoTextLayoutObstruction::textOnBiggerSide() const
0303 {
0304     return m_side == Bigger;
0305 }
0306 
0307 bool KoTextLayoutObstruction::textOnEnoughSides() const
0308 {
0309     return m_side == Enough;
0310 }
0311 
0312 qreal KoTextLayoutObstruction::runAroundThreshold() const
0313 {
0314     return m_runAroundThreshold;
0315 }
0316 
0317 bool KoTextLayoutObstruction::noTextAround() const
0318 {
0319     return m_side == Empty;
0320 }
0321 
0322 bool KoTextLayoutObstruction::compareRectLeft(KoTextLayoutObstruction *o1, KoTextLayoutObstruction *o2)
0323 {
0324     return o1->m_line.left() < o2->m_line.left();
0325 }