Krita Source Code Documentation
Loading...
Searching...
No Matches
KoSvgTextShapeLayoutFunc_inShape.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2017 Dmitry Kazakov <dimula73@gmail.com>
3 * SPDX-FileCopyrightText: 2022 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
9
10#include "KoPolygonUtils.h"
11#include "KoSvgTextProperties.h"
12
13#include <KoClipMaskPainter.h>
14#include <KoColorBackground.h>
15#include <KoPathShape.h>
16#include <KoShapeStroke.h>
17
18#include <kis_global.h>
19
20#include <QPainter>
21#include <QtMath>
22
24
26getShapes(QList<KoShape *> shapesInside, QList<KoShape *> shapesSubtract, const KoSvgTextProperties &properties)
27{
28 // the boost polygon method requires (and gives best result) on a inter-based polygon,
29 // so we need to scale up. The scale selected here is the size freetype coordinates give to a single pixel.
30 qreal scale = 64.0;
31 QTransform precisionTF = QTransform::fromScale(scale, scale);
32
33 qreal shapePadding = scale * properties.propertyOrDefault(KoSvgTextProperties::ShapePaddingId).toReal();
34 qreal shapeMargin = scale * properties.propertyOrDefault(KoSvgTextProperties::ShapeMarginId).toReal();
35
36 QPainterPath subtract;
37 Q_FOREACH(const KoShape *shape, shapesSubtract) {
38 const KoPathShape *path = dynamic_cast<const KoPathShape*>(shape);
39 if (path) {
40 QPainterPath p = path->transformation().map(path->outline());
41 p.setFillRule(path->fillRule());
42 // grow each polygon here with the shape margin size.
43 if (shapeMargin > 0) {
44 QList<QPolygon> subpathPolygons;
45 Q_FOREACH(QPolygonF subPath, p.toSubpathPolygons()) {
46 subpathPolygons.append(precisionTF.map(subPath).toPolygon());
47 }
48 subpathPolygons = KoPolygonUtils::offsetPolygons(subpathPolygons, shapeMargin);
49 p.clear();
50 Q_FOREACH (const QPolygon poly, subpathPolygons) {
51 p.addPolygon(poly);
52 }
53 } else {
54 p = precisionTF.map(p);
55 }
56 subtract.addPath(p);
57 }
58 }
59
61 Q_FOREACH(const KoShape *shape, shapesInside) {
62 const KoPathShape *path = dynamic_cast<const KoPathShape*>(shape);
63 if (path) {
64 QPainterPath p = path->transformation().map(path->outline());
65 p.setFillRule(path->fillRule());
66 QPainterPath p2;
67 p2.setFillRule(path->fillRule());
68
69 QList<QPolygon> subpathPolygons;
70 Q_FOREACH(QPolygonF subPath, p.toSubpathPolygons()) {
71 subpathPolygons.append(precisionTF.map(subPath).toPolygon());
72 }
73 subpathPolygons = KoPolygonUtils::offsetPolygons(subpathPolygons, -shapePadding);
74
75 for (int i=0; i < subpathPolygons.size(); i++) {
76 QPolygonF subpathPoly = subpathPolygons.at(i);
77 Q_FOREACH(QPolygonF subtractPoly, subtract.toSubpathPolygons()) {
78 if (subpathPoly.intersects(subtractPoly)) {
79 subpathPoly = subpathPoly.subtracted(subtractPoly);
80 }
81 }
82 p2.addPolygon(subpathPoly);
83 }
84 shapes.append(precisionTF.inverted().map(p2));
85 }
86 }
87 return shapes;
88}
89
90static bool getFirstPosition(QPointF &firstPoint,
91 QPainterPath p,
92 QRectF wordBox,
93 QPointF terminator,
94 KoSvgText::WritingMode writingMode,
95 bool ltr)
96{
97 if (wordBox.isEmpty()) {
98 // line-height: 0 will give us empty rects, make them slightly tall/wide
99 // to avoid issues with complex QRectF operations.
100 if (writingMode == KoSvgText::HorizontalTB) {
101 wordBox.setHeight(1e-3);
102 } else {
103 wordBox.setWidth(1e-3);
104 }
105 }
106
109
110 QVector<QPointF> candidatePositions;
111 QRectF word = wordBox.normalized();
112 word.translate(-wordBox.topLeft());
113 // Slightly shrink the box to account for precision error.
115
116 QPointF terminatorAdjusted = terminator;
117 Q_FOREACH(const QPolygonF polygon, p.toFillPolygons()) {
118 QVector<QLineF> offsetPoly;
119 for(int i = 0; i < polygon.size()-1; i++) {
120 QLineF line;
121 line.setP1(polygon.at(i));
122 line.setP2(polygon.at(i+1));
123
124 if (line.angle() == 0.0 || line.angle() == 180.0) {
125 qreal offset = word.center().y();
126 offsetPoly.append(line.translated(0, offset));
127 offsetPoly.append(line.translated(0, -offset));
128 } else if (line.angle() == 90.0 || line.angle() == 270.0) {
129 qreal offset = word.center().x();
130 offsetPoly.append(line.translated(offset, 0));
131 offsetPoly.append(line.translated(-offset, 0));
132 } else {
133 qreal tAngle = fmod(line.angle(), 180.0);
134 QPointF cPos = tAngle > 90? line.center() + QPointF(-word.center().x(), word.center().y()): line.center() + word.center();
135 qreal offset = kisDistanceToLine(cPos, line);
136 const QPointF vectorT(qCos(qDegreesToRadians(tAngle)), -qSin(qDegreesToRadians(tAngle)));
137 QPointF vectorN(-vectorT.y(), vectorT.x());
138 QPointF offsetP = QPointF() - (0.0 * vectorT) + (offset * vectorN);
139 offsetPoly.append(line.translated(offsetP));
140 offsetPoly.append(line.translated(-offsetP));
141 }
142 }
143 if (writingMode == KoSvgText::HorizontalTB) {
144 terminatorAdjusted = terminator + word.center();
145 QLineF top(polygon.boundingRect().topLeft(), polygon.boundingRect().topRight());
146 offsetPoly.append(top.translated(0, terminatorAdjusted.y()));
147 } else if (writingMode == KoSvgText::VerticalRL) {
148 terminatorAdjusted = terminator - word.center();
149 QLineF top(terminatorAdjusted.x(), polygon.boundingRect().top(),
150 terminatorAdjusted.x(), polygon.boundingRect().bottom());
151 offsetPoly.append(top);
152 } else{
153 terminatorAdjusted = terminator + word.center();
154 QLineF top(terminatorAdjusted.x(), polygon.boundingRect().top(),
155 terminatorAdjusted.x(), polygon.boundingRect().bottom());
156 offsetPoly.append(top);
157 }
158 for (int i=0; i < offsetPoly.size(); i++) {
159 QLineF line = offsetPoly.at(i);
160 for (int j=i; j< offsetPoly.size(); j++){
161 QLineF line2 = offsetPoly.at(j);
162 QPointF intersectPoint;
163 QLineF::IntersectType intersect = line.intersects(line2, &intersectPoint);
164 if (intersect != QLineF::NoIntersection) {
165 // should proly handle 'reflex' vertices better.
166 if (!p.contains(intersectPoint)) {
167 continue;
168 }
169 if(!p.contains(word.translated(intersectPoint-word.center()))) {
170 continue;
171 }
172 if (!candidatePositions.contains(intersectPoint)) {
173 candidatePositions.append(intersectPoint);
174 }
175 }
176 }
177 }
178 }
179 if (candidatePositions.isEmpty()) {
180 return false;
181 }
182
183 QPointF firstPointC = writingMode == KoSvgText::VerticalRL? p.boundingRect().bottomLeft(): p.boundingRect().bottomRight();
184 Q_FOREACH(const QPointF candidate, candidatePositions) {
185 if (writingMode == KoSvgText::HorizontalTB) {
186 if (terminatorAdjusted.y() - candidate.y() < SHAPE_PRECISION) {
187
188 if (firstPointC.y() - candidate.y() > SHAPE_PRECISION) {
189 firstPointC = candidate;
190 } else if (firstPointC.y() - candidate.y() > -SHAPE_PRECISION) {
191 if (ltr) {
192 if (candidate.x() < firstPointC.x()) {
193 firstPointC = candidate;
194 }
195 } else {
196 if (candidate.x() > firstPointC.x()) {
197 firstPointC = candidate;
198 }
199 }
200 }
201 }
202 } else if (writingMode == KoSvgText::VerticalRL) {
203 if (terminatorAdjusted.x() - candidate.x() >= -SHAPE_PRECISION) {
204
205 if (firstPointC.x() - candidate.x() < -SHAPE_PRECISION) {
206 firstPointC = candidate;
207 } else if (firstPointC.x() - candidate.x() < SHAPE_PRECISION) {
208 if (ltr) {
209 if (candidate.y() < firstPointC.y()) {
210 firstPointC = candidate;
211 }
212 } else {
213 if (candidate.y() > firstPointC.y()) {
214 firstPointC = candidate;
215 }
216 }
217 }
218 }
219 } else {
220 if (terminatorAdjusted.x() - candidate.x() < SHAPE_PRECISION) {
221
222 if (firstPointC.x() - candidate.x() > SHAPE_PRECISION) {
223 firstPointC = candidate;
224 } else if (firstPointC.x() - candidate.x() > -SHAPE_PRECISION) {
225 if (ltr) {
226 if (candidate.y() < firstPointC.y()) {
227 firstPointC = candidate;
228 }
229 } else {
230 if (candidate.y() > firstPointC.y()) {
231 firstPointC = candidate;
232 }
233 }
234 }
235 }
236 }
237 }
238 if (!p.contains(firstPointC)) {
239 return false;
240 }
241 firstPointC -= word.center();
242 firstPointC -= wordBox.topLeft();
243 firstPoint = firstPointC;
244
245 return true;
246}
247
248static bool pointLessThan(const QPointF &a, const QPointF &b)
249{
250 return a.x() < b.x();
251}
252
253static bool pointLessThanVertical(const QPointF &a, const QPointF &b)
254{
255 return a.y() < b.y();
256}
257
258static QVector<QLineF>
259findLineBoxesForFirstPos(QPainterPath shape, QPointF firstPos, const QRectF wordBox, KoSvgText::WritingMode writingMode)
260{
261 QVector<QLineF> lines;
262
263 QLineF baseLine;
264 QPointF lineTop;
265 QPointF lineBottom;
266 QRectF word = wordBox.normalized();
268
269 if (writingMode == KoSvgText::HorizontalTB) {
270 baseLine = QLineF(shape.boundingRect().left()-5, firstPos.y(), shape.boundingRect().right()+5, firstPos.y());
271 lineTop = QPointF(0, word.top());
272 lineBottom = QPointF(0, word.bottom());
273 } else {
274 baseLine = QLineF(firstPos.x(), shape.boundingRect().top()-5, firstPos.x(), shape.boundingRect().bottom()+5);
275 if (writingMode == KoSvgText::VerticalRL) {
276 lineTop = QPointF(word.left(), 0);
277 lineBottom = QPointF(word.right(), 0);
278 } else {
279 lineTop = QPointF(word.right(), 0);
280 lineBottom = QPointF(word.left(), 0);
281 }
282 }
283
284 QPolygonF polygon = shape.toFillPolygon();
285 QList<QPointF> intersects;
286 QLineF topLine = baseLine.translated(lineTop);
287 QLineF bottomLine = baseLine.translated(lineBottom);
288 for(int i = 0; i < polygon.size()-1; i++) {
289 QLineF line(polygon.at(i), polygon.at(i+1));
290 bool addedA = false;
291 QPointF intersectA;
292 QPointF intersectB;
293 QPointF intersect;
294 if (topLine.intersects(line, &intersect) == QLineF::BoundedIntersection) {
295 intersectA = intersect-lineTop;
296 intersects.append(intersectA);
297 addedA = true;
298 }
299 if (bottomLine.intersects(line, &intersect) == QLineF::BoundedIntersection) {
300 intersectB = intersect-lineBottom;
301 if (intersectA != intersectB || !addedA) {
302 intersects.append(intersectB);
303 }
304 }
305 }
306 if (!intersects.isEmpty()) {
307 intersects.append(baseLine.p1());
308 intersects.append(baseLine.p2());
309 }
310 if (writingMode == KoSvgText::HorizontalTB) {
311 std::sort(intersects.begin(), intersects.end(), pointLessThan);
312 } else {
313 std::sort(intersects.begin(), intersects.end(), pointLessThanVertical);
314 }
315
316
317 for (int i = 0; i< intersects.size()-1; i++) {
318 QLineF line(intersects.at(i), intersects.at(i+1));
319
320 if (!(shape.contains(line.translated(lineTop).center())
321 && shape.contains(line.translated(lineBottom).center()))
322 || line.length() == 0) {
323 continue;
324 }
325
326 QRectF lineBox = QRectF(line.p1() + lineTop, line.p2() + lineBottom).normalized();
327
328 QVector<QPointF> relevant;
329 for(int i = 0; i < polygon.size()-1; i++) {
330
331 QLineF edgeLine(polygon.at(i), polygon.at(i+1));
332
333 if (lineBox.contains(polygon.at(i))) {
334 relevant.append(polygon.at(i));
335 }
336 }
337 qreal start = writingMode == KoSvgText::HorizontalTB? lineBox.left(): lineBox.top();
338 qreal end = writingMode == KoSvgText::HorizontalTB? lineBox.right(): lineBox.bottom();
339 for(int j = 0; j < relevant.size(); j++) {
340 QPointF current = relevant.at(j);
341
342 if (writingMode == KoSvgText::HorizontalTB) {
343 if (current.x() < line.center().x()) {
344 start = qMax(current.x(), start);
345 } else if (current.x() > line.center().x()) {
346 end = qMin(current.x(), end);
347 }
348 } else {
349 if (current.y() < line.center().y()) {
350 start = qMax(current.y(), start);
351 } else if (current.y() > line.center().y()) {
352 end = qMin(current.y(), end);
353 }
354 }
355 }
356 if (writingMode == KoSvgText::HorizontalTB) {
357
358 QLineF newLine(start, line.p1().y(), end, line.p2().y());
359 if (!lines.isEmpty()) {
360 if (lines.last().p2() == intersects.at(i)) {
361 newLine.setP1(lines.last().p1());
362 lines.removeLast();
363 }
364 }
365 lines.append(newLine);
366 } else {
367 QLineF newLine(line.p1().x(), start, line.p2().x(), end);
368 if (!lines.isEmpty()) {
369 if (lines.last().p2() == intersects.at(i)) {
370 newLine.setP1(lines.last().p1());
371 lines.removeLast();
372 }
373 }
374 lines.append(newLine);
375 }
376 }
377
378 return lines;
379}
380
387 const int index,
388 QRectF &wordBox,
389 const QRectF boundingBox,
390 KoSvgText::WritingMode writingMode)
391{
392 bool isHorizontal = writingMode == KoSvgText::HorizontalTB;
393 QPointF totalAdvance = wordBox.bottomRight() - wordBox.topLeft();
394 qreal maxAscent = isHorizontal? wordBox.top(): wordBox.right();
395 qreal maxDescent = isHorizontal? wordBox.bottom(): wordBox.left();
396
397 for (int i=index; i<result.size(); i++) {
398 if (!result.at(i).addressable || result.at(i).hidden) {
399 continue;
400 }
401 totalAdvance += result.at(i).advance;
402 if ((totalAdvance.x() > boundingBox.width() && isHorizontal) ||
403 (totalAdvance.y() > boundingBox.height() && !isHorizontal)) {
404 break;
405 }
406 calculateLineHeight(result.at(i), maxAscent, maxDescent, isHorizontal, true);
407 }
408 if (writingMode == KoSvgText::HorizontalTB) {
409 wordBox.setTop(maxAscent);
410 wordBox.setBottom(maxDescent);
411 } else {
412 // vertical lr has top at the right even though block flow is also to the right.
413 wordBox.setRight(maxAscent);
414 wordBox.setLeft(maxDescent);
415 }
416}
417
420{
421 KoSvgText::TextAlign compare = align;
422 if (align == KoSvgText::AlignJustify) {
423 compare = alignLast;
424 }
425 if (compare == KoSvgText::AlignStart) {
427 } else if (compare == KoSvgText::AlignCenter) {
429 } else if (compare == KoSvgText::AlignEnd) {
431 } else if (compare == KoSvgText::AlignLeft) {
433 } else if (compare == KoSvgText::AlignRight) {
435 } else if (align == KoSvgText::AlignJustify) {
437 }
439}
440
442 const QMap<int, int> &logicalToVisual,
444 QList<QPainterPath> shapes,
445 QPointF &startPos,
446 const KoSvgText::ResolutionHandler &resHandler)
447{
448 QVector<LineBox> lineBoxes;
451 bool ltr = direction == KoSvgText::DirectionLeftToRight;
452 bool isHorizontal = writingMode == KoSvgText::HorizontalTB;
455 KoSvgText::TextAnchor anchor = textAnchorForTextAlign(align, alignLast, ltr);
456
457 QPointF textIndent;
459
460 QVector<int> wordIndices;
462 QRectF wordBox;
463 QPointF wordAdvance;
464
465 LineBox currentLine;
466 bool firstLine = true;
467 bool indentLine = true;
468
469 QPointF currentPos = writingMode == KoSvgText::VerticalRL? shapes.at(0).boundingRect().topRight()
470 :shapes.at(0).boundingRect().topLeft();
471 QPointF lineOffset = currentPos;
472
473 QListIterator<int> it(logicalToVisual.keys());
474 QListIterator<QPainterPath> shapesIt(shapes);
475 if (shapes.isEmpty()) {
476 return lineBoxes;
477 }
478 {
479 // Calculate the default pos.
480 qreal fontSize = properties.fontSize().value;
481 QRectF wordBox = isHorizontal? QRectF(0, fontSize * -0.8, SHAPE_PRECISION, fontSize)
482 : QRectF(fontSize * -0.5, 0, fontSize, SHAPE_PRECISION);
483 getFirstPosition(startPos, shapes.first(), wordBox, currentPos, writingMode, ltr);
484 }
485 QPainterPath currentShape;
486 while (it.hasNext()) {
487 int index = it.next();
488 result[index].calculateAndApplyTabsize(wordAdvance + currentPos, isHorizontal, resHandler);
489 CharacterResult charResult = result.at(index);
490 if (!charResult.addressable) {
491 continue;
492 }
493
494 bool softBreak = false;
495 bool doNotCountAdvance =
496 ((charResult.lineEnd != LineEdgeBehaviour::NoChange)
497 && !(currentLine.isEmpty() && wordIndices.isEmpty()));
498 if (!doNotCountAdvance) {
499 if (wordIndices.isEmpty()) {
500 wordBox = charResult.lineHeightBox().translated(charResult.totalBaselineOffset());
501 wordAdvance = charResult.advance;
502 } else {
503 wordBox |= charResult.lineHeightBox().translated(wordAdvance+charResult.totalBaselineOffset());
504 wordAdvance += charResult.advance;
505 }
506 }
507 wordIndices.append(index);
508 currentLine.lastLine = !it.hasNext();
509 if (currentLine.lastLine) {
510 currentLine.justifyLine = alignLast == KoSvgText::AlignJustify;
511 }
512
513 if (charResult.breakType != BreakType::NoBreak || currentLine.lastLine) {
514 if (currentLine.chunks.isEmpty() || currentLine.lastLine) {
515 softBreak = true;
516 }
517
518 for (int i = currentLine.currentChunk; i < currentLine.chunks.size(); i++) {
519 if (i == -1) {
520 currentLine.currentChunk = 0;
521 i = 0;
522 }
523 QLineF line = currentLine.chunks.value(i).length;
524 qreal lineLength = isHorizontal ? (currentPos - line.p1() + wordAdvance).x()
525 : (currentPos - line.p1() + wordAdvance).y();
526 if (qRound((abs(lineLength) - line.length())) > 0) {
527 if (i == currentLine.chunks.size()-1) {
528 softBreak = true;
529 break;
530 } else {
531 QLineF nextLine = currentLine.chunks.value(i+1).length;
532 if (isHorizontal) {
533 currentPos.setX(ltr? qMax(nextLine.p1().x(), currentPos.x()):
534 qMin(nextLine.p1().x(), currentPos.x()));
535 } else {
536 currentPos.setY(nextLine.p1().y());
537 }
538 }
539 } else {
540 currentLine.currentChunk = i;
541 addWordToLine(result, currentPos, wordIndices, currentLine, ltr, isHorizontal);
542 break;
543 }
544 }
545 }
546
547 if (softBreak) {
548 if (!currentLine.isEmpty()) {
549 finalizeLine(result, currentPos, currentLine, lineOffset, anchor, writingMode, ltr, true, true, resHandler);
550 lineBoxes.append(currentLine);
551 firstLine = false;
552 indentLine = false;
553 }
554 // Not adding indent to the (first) word box means it'll overflow if there's no room,
555 // but being too strict might end with the whole text disappearing. Given Krita's text layout is
556 // in an interactive context, ugly result might be more communicative than all text disappearing.
557 bool ind = textIndentInfo.hanging? !indentLine: indentLine;
558 QPointF indent = ind? textIndent: QPointF();
559 bool foundFirst = false;
560 bool needNewLine = true;
561 // add text indent to wordbox.
562 if (!currentShape.isEmpty()) {
563 // we're going to try and get an offset line first before trying get first pos.
564 // This gives more stable results on curved shapes.
565 getEstimatedHeight(result, index, wordBox, currentShape.boundingRect(), writingMode);
566 currentPos -= writingMode == KoSvgText::VerticalRL? wordBox.topRight(): wordBox.topLeft();
567
568 currentLine = LineBox(findLineBoxesForFirstPos(currentShape, currentPos, wordBox, writingMode), ltr, indent, resHandler);
569 qreal length = isHorizontal? wordBox.width(): wordBox.height();
570 for (int i = 0; i < currentLine.chunks.size(); i++) {
571 if (currentLine.chunks.at(i).length.length() > length) {
572 currentLine.currentChunk = i;
573 foundFirst = true;
574 needNewLine = false;
575 break;
576 }
577 }
578 }
579 /*
580 * In theory we could have overflow wrap for shapes, but it'd require either generalizing
581 * the line-filling portion above and this new line seeking portion, or somehow reverting
582 * the iterator over the results to be on the last fitted glyph (which'd still require
583 * generalizing the line-filling portion about), and I am unsure how to do that.
584 * Either way, this place here is where you'd check for overflow wrap.
585 */
586 while(!foundFirst) {
587 foundFirst = getFirstPosition(currentPos, currentShape, wordBox, lineOffset, writingMode, ltr);
588 if (foundFirst || !shapesIt.hasNext()) {
589 break;
590 }
591 currentShape = shapesIt.next();
592 getEstimatedHeight(result, index, wordBox, currentShape.boundingRect(), writingMode);
593 bool indentPercent = textIndentInfo.length.unit == KoSvgText::CssLengthPercentage::Percentage;
594 qreal textIdentValue = textIndentInfo.length.value;
595 if (isHorizontal) {
596 if (indentPercent) {
597 textIdentValue *= currentShape.boundingRect().width();
598 }
599 textIndent = resHandler.adjust(QPointF(textIdentValue, 0));
600 } else {
601 if (indentPercent) {
602 textIdentValue *= currentShape.boundingRect().height();
603 }
604 textIndent = resHandler.adjust(QPointF(0, textIdentValue));
605 }
606 bool ind = textIndentInfo.hanging? !indentLine: indentLine;
607 indent = ind? textIndent: QPointF();
608 currentPos = writingMode == KoSvgText::VerticalRL? currentShape.boundingRect().topRight(): currentShape.boundingRect().topLeft();
609 lineOffset = currentPos;
610 }
611
612 bool lastDitch = false;
613 if (!foundFirst && firstLine && !wordIndices.isEmpty() && !currentShape.isEmpty()) {
614 // Last-ditch attempt to get any kind of positioning to happen.
615 // This typically happens when wrapping has been disabled.
616 wordBox = result[wordIndices.first()].lineHeightBox().translated(result[wordIndices.first()].totalBaselineOffset());
617 foundFirst = getFirstPosition(currentPos, currentShape, wordBox, lineOffset, writingMode, ltr);
618 lastDitch = true;
619 }
620
621 if (foundFirst) {
622
623 if (needNewLine) {
624 currentLine = LineBox(findLineBoxesForFirstPos(currentShape, currentPos, wordBox, writingMode), ltr, indent, resHandler);
625 // We could set this to find the first fitting width, but it's better to try and improve the precision of the firstpos algorithm,
626 // as this gives more stable results.
627 currentLine.setCurrentChunkForPos(currentPos, isHorizontal);
628 }
629 const qreal expectedLineT = isHorizontal? wordBox.top():
630 writingMode == KoSvgText::VerticalRL? wordBox.right(): wordBox.left();
631
632 currentLine.firstLine = firstLine;
633 currentLine.expectedLineTop = expectedLineT;
634 currentLine.justifyLine = align == KoSvgText::AlignJustify;
635 currentPos = currentLine.chunk().length.p1() + indent;
636 lineOffset = currentPos;
637
638 if (lastDitch) {
639 QVector<int> wordNew;
640 QPointF advance = currentPos;
641 Q_FOREACH(const int i, wordIndices) {
642 advance += result[i].advance;
643 if (currentShape.contains(advance)) {
644 wordNew.append(i);
645 } else {
646 result[i].hidden = true;
647 result[i].cssPosition = advance-result[i].advance;
648 }
649 wordIndices = wordNew;
650 }
651 }
652 addWordToLine(result, currentPos, wordIndices, currentLine, ltr, isHorizontal);
653 } else {
654 currentLine = LineBox();
655 QPointF advance = currentPos;
656 Q_FOREACH (const int j, wordIndices) {
657 result[j].cssPosition = advance;
658 advance += result[j].advance;
659 result[j].hidden = true;
660 }
661 }
662 }
663
664 if (charResult.breakType == BreakType::HardBreak) {
665 finalizeLine(result, currentPos, currentLine, lineOffset, anchor, writingMode, ltr, true, true, resHandler);
666 lineBoxes.append(currentLine);
667 currentLine = LineBox();
668 indentLine = textIndentInfo.hanging? false: textIndentInfo.eachLine;
669 }
670 }
671 finalizeLine(result, currentPos, currentLine, lineOffset, anchor, writingMode, ltr, true, true, resHandler);
672 lineBoxes.append(currentLine);
673 return lineBoxes;
674}
675
676} // namespace KoSvgTextShapeLayoutFunc
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
const Params2D p
QPointF p2
constexpr qreal SHAPE_PRECISION
Value that indicates the precision for testing coordinates for text-in-shape layout.
@ NoChange
Do nothing special.
The position of a path point within a path shape.
Definition KoPathShape.h:63
static QList< QPolygon > offsetPolygons(const QList< QPolygon > polygons, int offset, bool rounded=true, int circleSegments=16)
QTransform transformation() const
Returns the shapes local transformation matrix.
Definition KoShape.cpp:424
@ TextAlignAllId
KoSvgText::TextAlign.
@ WritingModeId
KoSvgText::WritingMode.
@ DirectionId
KoSvgText::Direction.
@ TextAlignLastId
KoSvgText::TextAlign.
@ TextIndentId
KoSvgText::TextIndentInfo Struct.
QVariant propertyOrDefault(PropertyId id) const
KoSvgText::CssLengthPercentage fontSize() const
qreal kisDistanceToLine(const QPointF &m, const QLineF &line)
Definition kis_global.h:234
static bool pointLessThan(const QPointF &a, const QPointF &b)
static KoSvgText::TextAnchor textAnchorForTextAlign(KoSvgText::TextAlign align, KoSvgText::TextAlign alignLast, bool ltr)
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 bool pointLessThanVertical(const QPointF &a, const QPointF &b)
static QVector< QLineF > findLineBoxesForFirstPos(QPainterPath shape, QPointF firstPos, const QRectF wordBox, KoSvgText::WritingMode writingMode)
void finalizeLine(QVector< CharacterResult > &result, QPointF &currentPos, LineBox &currentLine, 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 &currentPos, QVector< int > &wordIndices, LineBox &currentLine, bool ltr, bool isHorizontal)
addWordToLine Small function used in break lines to quickly add a 'word' to the current line....
@ AlignCenter
Center text in line.
Definition KoSvgText.h:173
TextAnchor
Where the text is anchored for SVG 1.1 text and 'inline-size'.
Definition KoSvgText.h:79
@ AnchorEnd
Anchor right for LTR, left for RTL.
Definition KoSvgText.h:82
@ AnchorStart
Anchor left for LTR, right for RTL.
Definition KoSvgText.h:80
@ AnchorMiddle
Anchor to the middle.
Definition KoSvgText.h:81
Direction
Base direction used by Bidi algorithm.
Definition KoSvgText.h:48
@ DirectionLeftToRight
Definition KoSvgText.h:49
@ HorizontalTB
Definition KoSvgText.h:38
QPointF totalBaselineOffset() const
QRectF lineHeightBox() const
lineHeightBox
LineEdgeBehaviour lineEnd
The ResolutionHandler class.
Definition KoSvgText.h:1084
QPointF adjust(const QPointF point) const
Adjusts the point to rounded pixel values, based on whether roundToPixelHorizontal or roundToPixelVer...
bool hanging
Flip the lines to which text-indent is applied.
Definition KoSvgText.h:659
bool eachLine
Apply the text-indent to each line following a hardbreak.
Definition KoSvgText.h:660
CssLengthPercentage length
Definition KoSvgText.h:658
The LineBox struct.
void setCurrentChunkForPos(QPointF pos, bool isHorizontal)
LineChunk chunk()
qreal expectedLineTop
Because fonts can affect lineheight mid-line, and this affects wrapping, this estimates the line-heig...
QVector< LineChunk > chunks
QLineF length
Used to measure how long the current line is allowed to be.