32 QTransform precisionTF = QTransform::fromScale(scale, scale);
40 QPainterPath subtract;
41 Q_FOREACH(
const KoShape *shape, shapesSubtract) {
45 p.setFillRule(path->fillRule());
47 if (shapeMargin > 0) {
49 Q_FOREACH(QPolygonF subPath,
p.toSubpathPolygons()) {
50 subpathPolygons.append(precisionTF.map(subPath).toPolygon());
54 Q_FOREACH (
const QPolygon poly, subpathPolygons) {
58 p = precisionTF.map(
p);
66 Q_FOREACH(
const KoShape *shape, shapesInside) {
70 p.setFillRule(path->fillRule());
72 p2.setFillRule(path->fillRule());
75 Q_FOREACH(QPolygonF subPath,
p.toSubpathPolygons()) {
76 subpathPolygons.append(precisionTF.map(subPath).toPolygon());
80 for (
int i=0; i < subpathPolygons.size(); i++) {
81 QPolygonF subpathPoly = subpathPolygons.at(i);
82 Q_FOREACH(QPolygonF subtractPoly, subtract.toSubpathPolygons()) {
83 if (subpathPoly.intersects(subtractPoly)) {
84 subpathPoly = subpathPoly.subtracted(subtractPoly);
87 p2.addPolygon(subpathPoly);
115 if (wordBox.isEmpty()) {
119 wordBox.setHeight(1e-3);
121 wordBox.setWidth(1e-3);
129 QRectF word = wordBox.normalized();
130 word.translate(-wordBox.topLeft());
134 QPointF terminatorAdjusted = terminator;
135 Q_FOREACH(
const QPolygonF polygon,
p.toFillPolygons()) {
137 for(
int i = 0; i < polygon.size()-1; i++) {
139 line.setP1(polygon.at(i));
140 line.setP2(polygon.at(i+1));
142 ||
pastTerminator(line.p2(), terminatorAdjusted, writingMode)))
continue;
144 if (line.angle() == 0.0 || line.angle() == 180.0) {
145 qreal offset = word.center().y();
146 offsetPoly.append(line.translated(0, offset));
147 offsetPoly.append(line.translated(0, -offset));
148 }
else if (line.angle() == 90.0 || line.angle() == 270.0) {
149 qreal offset = word.center().x();
150 offsetPoly.append(line.translated(offset, 0));
151 offsetPoly.append(line.translated(-offset, 0));
153 qreal tAngle = fmod(line.angle(), 180.0);
154 QPointF cPos = tAngle > 90? line.center() + QPointF(-word.center().x(), word.center().y()): line.center() + word.center();
156 const QPointF vectorT(qCos(qDegreesToRadians(tAngle)), -qSin(qDegreesToRadians(tAngle)));
157 QPointF vectorN(-vectorT.y(), vectorT.x());
158 QPointF offsetP = QPointF() - (0.0 * vectorT) + (offset * vectorN);
159 offsetPoly.append(line.translated(offsetP));
160 offsetPoly.append(line.translated(-offsetP));
164 terminatorAdjusted = terminator + word.center();
165 QLineF top(polygon.boundingRect().topLeft(), polygon.boundingRect().topRight());
166 offsetPoly.append(top.translated(0, terminatorAdjusted.y()));
168 terminatorAdjusted = terminator - word.center();
169 QLineF top(terminatorAdjusted.x(), polygon.boundingRect().top(),
170 terminatorAdjusted.x(), polygon.boundingRect().bottom());
171 offsetPoly.append(top);
173 terminatorAdjusted = terminator + word.center();
174 QLineF top(terminatorAdjusted.x(), polygon.boundingRect().top(),
175 terminatorAdjusted.x(), polygon.boundingRect().bottom());
176 offsetPoly.append(top);
178 for (
int i=0; i < offsetPoly.size(); i++) {
179 QLineF line = offsetPoly.at(i);
180 for (
int j=i; j< offsetPoly.size(); j++){
181 QLineF line2 = offsetPoly.at(j);
182 QPointF intersectPoint;
183 QLineF::IntersectType intersect = line.intersects(line2, &intersectPoint);
184 if (intersect != QLineF::NoIntersection) {
186 if (!
p.contains(intersectPoint)) {
189 if(!
p.contains(word.translated(intersectPoint-word.center()))) {
192 if (!candidatePositions.contains(intersectPoint)) {
193 candidatePositions.append(intersectPoint);
199 if (candidatePositions.isEmpty()) {
203 QPointF firstPointC = writingMode ==
KoSvgText::VerticalRL?
p.boundingRect().bottomLeft():
p.boundingRect().bottomRight();
204 Q_FOREACH(
const QPointF candidate, candidatePositions) {
209 firstPointC = candidate;
212 if (candidate.x() < firstPointC.x()) {
213 firstPointC = candidate;
216 if (candidate.x() > firstPointC.x()) {
217 firstPointC = candidate;
226 firstPointC = candidate;
229 if (candidate.y() < firstPointC.y()) {
230 firstPointC = candidate;
233 if (candidate.y() > firstPointC.y()) {
234 firstPointC = candidate;
243 firstPointC = candidate;
246 if (candidate.y() < firstPointC.y()) {
247 firstPointC = candidate;
250 if (candidate.y() > firstPointC.y()) {
251 firstPointC = candidate;
258 if (!
p.contains(firstPointC)) {
261 firstPointC -= word.center();
262 firstPointC -= wordBox.topLeft();
263 firstPoint = firstPointC;
286 QRectF word = wordBox.normalized();
290 baseLine = QLineF(shape.boundingRect().left()-5, firstPos.y(), shape.boundingRect().right()+5, firstPos.y());
291 lineTop = QPointF(0, word.top());
292 lineBottom = QPointF(0, word.bottom());
294 baseLine = QLineF(firstPos.x(), shape.boundingRect().top()-5, firstPos.x(), shape.boundingRect().bottom()+5);
296 lineTop = QPointF(word.left(), 0);
297 lineBottom = QPointF(word.right(), 0);
299 lineTop = QPointF(word.right(), 0);
300 lineBottom = QPointF(word.left(), 0);
304 QPolygonF polygon = shape.toFillPolygon();
306 QLineF topLine = baseLine.translated(lineTop);
307 QLineF bottomLine = baseLine.translated(lineBottom);
308 for(
int i = 0; i < polygon.size()-1; i++) {
309 QLineF line(polygon.at(i), polygon.at(i+1));
314 if (topLine.intersects(line, &intersect) == QLineF::BoundedIntersection) {
315 intersectA = intersect-lineTop;
316 intersects.append(intersectA);
319 if (bottomLine.intersects(line, &intersect) == QLineF::BoundedIntersection) {
320 intersectB = intersect-lineBottom;
321 if (intersectA != intersectB || !addedA) {
322 intersects.append(intersectB);
326 if (!intersects.isEmpty()) {
327 intersects.append(baseLine.p1());
328 intersects.append(baseLine.p2());
331 std::sort(intersects.begin(), intersects.end(),
pointLessThan);
337 for (
int i = 0; i< intersects.size()-1; i++) {
338 QLineF line(intersects.at(i), intersects.at(i+1));
340 if (!(shape.contains(line.translated(lineTop).center())
341 && shape.contains(line.translated(lineBottom).center()))
342 || line.length() == 0) {
346 QRectF lineBox = QRectF(line.p1() + lineTop, line.p2() + lineBottom).normalized();
349 for(
int i = 0; i < polygon.size()-1; i++) {
351 if (lineBox.contains(polygon.at(i))) {
352 relevant.append(polygon.at(i));
357 for(
int j = 0; j < relevant.size(); j++) {
358 QPointF current = relevant.at(j);
361 if (current.x() < line.center().x()) {
362 start = qMax(current.x(), start);
363 }
else if (current.x() > line.center().x()) {
364 end = qMin(current.x(), end);
367 if (current.y() < line.center().y()) {
368 start = qMax(current.y(), start);
369 }
else if (current.y() > line.center().y()) {
370 end = qMin(current.y(), end);
376 QLineF newLine(start, line.p1().y(), end, line.p2().y());
377 if (!lines.isEmpty()) {
378 if (lines.last().p2() == intersects.at(i)) {
379 newLine.setP1(lines.last().p1());
383 lines.append(newLine);
385 QLineF newLine(line.p1().x(), start, line.p2().x(), end);
386 if (!lines.isEmpty()) {
387 if (lines.last().p2() == intersects.at(i)) {
388 newLine.setP1(lines.last().p1());
392 lines.append(newLine);
460 const QMap<int, int> &logicalToVisual,
484 bool indentLine =
true;
487 : shapes.first().boundingRect().topLeft();
488 QPointF lineOffset = currentPos;
490 QListIterator<int> it(logicalToVisual.keys());
491 QListIterator<QPainterPath> shapesIt(shapes);
492 if (shapes.isEmpty()) {
498 QRectF wordBox = isHorizontal? QRectF(0, fontSize * -0.8,
SHAPE_PRECISION, fontSize)
500 getFirstPosition(startPos, shapes.first(), wordBox, currentPos, writingMode, ltr);
502 QPainterPath currentShape;
503 while (it.hasNext()) {
504 int index = it.next();
505 result[index].calculateAndApplyTabsize(wordAdvance + currentPos, isHorizontal, resHandler);
511 bool softBreak =
false;
512 bool doNotCountAdvance =
514 && !(currentLine.
isEmpty() && wordIndices.isEmpty()));
515 if (!doNotCountAdvance) {
516 if (wordIndices.isEmpty()) {
518 wordAdvance = charResult.
advance;
521 wordAdvance += charResult.
advance;
524 wordIndices.append(index);
525 currentLine.
lastLine = !it.hasNext();
540 QLineF line = currentLine.
chunks.value(i).length;
541 qreal lineLength = isHorizontal ? (currentPos - line.p1() + wordAdvance).x()
542 : (currentPos - line.p1() + wordAdvance).y();
543 if (qRound((abs(lineLength) - line.length())) > 0) {
544 if (i == currentLine.
chunks.size()-1) {
548 QLineF nextLine = currentLine.
chunks.value(i+1).length;
550 currentPos.setX(ltr? qMax(nextLine.p1().x(), currentPos.x()):
551 qMin(nextLine.p1().x(), currentPos.x()));
553 currentPos.setY(nextLine.p1().y());
558 addWordToLine(result, currentPos, wordIndices, currentLine, ltr, isHorizontal);
566 finalizeLine(result, currentPos, currentLine, lineOffset, anchor, writingMode, ltr,
true,
true, resHandler);
567 lineBoxes.append(currentLine);
573 bool ind = textIndentInfo.
hanging? !indentLine: indentLine;
574 QPointF indent = ind? textIndent: QPointF();
575 bool foundFirst =
false;
576 bool needNewLine =
true;
578 if (!currentShape.isEmpty()) {
585 qreal
length = isHorizontal? wordBox.width(): wordBox.height();
586 for (
int i = 0; i < currentLine.
chunks.size(); i++) {
605 foundFirst =
getFirstPosition(currentPos, currentShape, wordBox, lineOffset, writingMode, ltr);
606 if (foundFirst || !shapesIt.hasNext()) {
609 currentShape = shapesIt.next();
613 qreal textIdentValue = textIndentInfo.
length.
value;
616 textIdentValue *= currentShape.boundingRect().width();
618 textIndent = resHandler.
adjust(QPointF(textIdentValue, 0));
621 textIdentValue *= currentShape.boundingRect().height();
623 textIndent = resHandler.
adjust(QPointF(0, textIdentValue));
625 bool ind = textIndentInfo.
hanging? !indentLine: indentLine;
626 indent = ind? textIndent: QPointF();
627 currentPos = writingMode ==
KoSvgText::VerticalRL? currentShape.boundingRect().topRight(): currentShape.boundingRect().topLeft();
628 lineOffset = currentPos;
631 bool lastDitch =
false;
632 if (!foundFirst && lineBoxes.isEmpty() && !wordIndices.isEmpty() && !currentShape.isEmpty()) {
635 wordBox = result[wordIndices.first()].lineHeightBox().translated(result[wordIndices.first()].totalBaselineOffset());
636 foundFirst =
getFirstPosition(currentPos, currentShape, wordBox, lineOffset, writingMode, ltr);
648 const qreal expectedLineT = isHorizontal? wordBox.top():
651 currentLine.
firstLine = lineBoxes.isEmpty();
655 lineOffset = currentPos;
656 currentPos += needNewLine? currentLine.
textIndent: QPointF();
660 QPointF advance = currentPos;
661 Q_FOREACH(
const int i, wordIndices) {
662 advance += result[i].advance;
663 if (currentShape.contains(advance)) {
666 result[i].hidden =
true;
667 result[i].cssPosition = advance-result[i].advance;
669 wordIndices = wordNew;
672 addWordToLine(result, currentPos, wordIndices, currentLine, ltr, isHorizontal);
675 QPointF advance = currentPos;
676 Q_FOREACH (
const int j, wordIndices) {
677 result[j].cssPosition = advance;
678 advance += result[j].advance;
679 result[j].hidden =
true;
685 finalizeLine(result, currentPos, currentLine, lineOffset, anchor, writingMode, ltr,
true,
true, resHandler);
686 lineBoxes.append(currentLine);
691 finalizeLine(result, currentPos, currentLine, lineOffset, anchor, writingMode, ltr,
true,
true, resHandler);
692 lineBoxes.append(currentLine);
constexpr qreal SHAPE_PRECISION
Value that indicates the precision for testing coordinates for text-in-shape layout.
void calculateLineHeight(CharacterResult cr, double &ascent, double &descent, bool isHorizontal, bool compare=false)
calculateLineHeight calculate the total ascent and descent (including baseline-offset) of a charResul...
static bool getFirstPosition(QPointF &firstPoint, QPainterPath p, QRectF wordBox, QPointF terminator, KoSvgText::WritingMode writingMode, bool ltr)
QVector< LineBox > flowTextInShapes(const KoSvgTextProperties &properties, const QMap< int, int > &logicalToVisual, QVector< CharacterResult > &result, QList< QPainterPath > shapes, QPointF &startPos, const KoSvgText::ResolutionHandler &resHandler)
static QVector< QLineF > findLineBoxesForFirstPos(QPainterPath shape, QPointF firstPos, const QRectF wordBox, KoSvgText::WritingMode writingMode)
void finalizeLine(QVector< CharacterResult > &result, QPointF ¤tPos, LineBox ¤tLine, QPointF &lineOffset, const KoSvgText::TextAnchor anchor, const KoSvgText::WritingMode writingMode, const bool ltr, const bool inlineSize, const bool textInShape, const KoSvgText::ResolutionHandler &resHandler)
QList< QPainterPath > getShapes(QList< KoShape * > shapesInside, QList< KoShape * > shapesSubtract, const KoSvgTextProperties &properties)
static void getEstimatedHeight(QVector< CharacterResult > &result, const int index, QRectF &wordBox, const QRectF boundingBox, KoSvgText::WritingMode writingMode)
getEstimatedHeight Adjust the wordbox with the estimated height.
void addWordToLine(QVector< CharacterResult > &result, QPointF ¤tPos, QVector< int > &wordIndices, LineBox ¤tLine, bool ltr, bool isHorizontal)
addWordToLine Small function used in break lines to quickly add a 'word' to the current line....
TextAnchor
Where the text is anchored for SVG 1.1 text and 'inline-size'.
QPointF adjust(const QPointF point) const
Adjusts the point to rounded pixel values, based on whether roundToPixelHorizontal or roundToPixelVer...