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 }