31 QTransform precisionTF = QTransform::fromScale(scale, scale);
36 QPainterPath subtract;
37 Q_FOREACH(
const KoShape *shape, shapesSubtract) {
41 p.setFillRule(path->fillRule());
43 if (shapeMargin > 0) {
45 Q_FOREACH(QPolygonF subPath,
p.toSubpathPolygons()) {
46 subpathPolygons.append(precisionTF.map(subPath).toPolygon());
50 Q_FOREACH (
const QPolygon poly, subpathPolygons) {
54 p = precisionTF.map(
p);
61 Q_FOREACH(
const KoShape *shape, shapesInside) {
65 p.setFillRule(path->fillRule());
67 p2.setFillRule(path->fillRule());
70 Q_FOREACH(QPolygonF subPath,
p.toSubpathPolygons()) {
71 subpathPolygons.append(precisionTF.map(subPath).toPolygon());
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);
82 p2.addPolygon(subpathPoly);
84 shapes.append(precisionTF.inverted().map(
p2));
97 if (wordBox.isEmpty()) {
101 wordBox.setHeight(1e-3);
103 wordBox.setWidth(1e-3);
111 QRectF word = wordBox.normalized();
112 word.translate(-wordBox.topLeft());
116 QPointF terminatorAdjusted = terminator;
117 Q_FOREACH(
const QPolygonF polygon,
p.toFillPolygons()) {
119 for(
int i = 0; i < polygon.size()-1; i++) {
121 line.setP1(polygon.at(i));
122 line.setP2(polygon.at(i+1));
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));
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();
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));
144 terminatorAdjusted = terminator + word.center();
145 QLineF top(polygon.boundingRect().topLeft(), polygon.boundingRect().topRight());
146 offsetPoly.append(top.translated(0, terminatorAdjusted.y()));
148 terminatorAdjusted = terminator - word.center();
149 QLineF top(terminatorAdjusted.x(), polygon.boundingRect().top(),
150 terminatorAdjusted.x(), polygon.boundingRect().bottom());
151 offsetPoly.append(top);
153 terminatorAdjusted = terminator + word.center();
154 QLineF top(terminatorAdjusted.x(), polygon.boundingRect().top(),
155 terminatorAdjusted.x(), polygon.boundingRect().bottom());
156 offsetPoly.append(top);
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) {
166 if (!
p.contains(intersectPoint)) {
169 if(!
p.contains(word.translated(intersectPoint-word.center()))) {
172 if (!candidatePositions.contains(intersectPoint)) {
173 candidatePositions.append(intersectPoint);
179 if (candidatePositions.isEmpty()) {
183 QPointF firstPointC = writingMode ==
KoSvgText::VerticalRL?
p.boundingRect().bottomLeft():
p.boundingRect().bottomRight();
184 Q_FOREACH(
const QPointF candidate, candidatePositions) {
189 firstPointC = candidate;
192 if (candidate.x() < firstPointC.x()) {
193 firstPointC = candidate;
196 if (candidate.x() > firstPointC.x()) {
197 firstPointC = candidate;
206 firstPointC = candidate;
209 if (candidate.y() < firstPointC.y()) {
210 firstPointC = candidate;
213 if (candidate.y() > firstPointC.y()) {
214 firstPointC = candidate;
223 firstPointC = candidate;
226 if (candidate.y() < firstPointC.y()) {
227 firstPointC = candidate;
230 if (candidate.y() > firstPointC.y()) {
231 firstPointC = candidate;
238 if (!
p.contains(firstPointC)) {
241 firstPointC -= word.center();
242 firstPointC -= wordBox.topLeft();
243 firstPoint = firstPointC;
266 QRectF word = wordBox.normalized();
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());
274 baseLine = QLineF(firstPos.x(), shape.boundingRect().top()-5, firstPos.x(), shape.boundingRect().bottom()+5);
276 lineTop = QPointF(word.left(), 0);
277 lineBottom = QPointF(word.right(), 0);
279 lineTop = QPointF(word.right(), 0);
280 lineBottom = QPointF(word.left(), 0);
284 QPolygonF polygon = shape.toFillPolygon();
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));
294 if (topLine.intersects(line, &intersect) == QLineF::BoundedIntersection) {
295 intersectA = intersect-lineTop;
296 intersects.append(intersectA);
299 if (bottomLine.intersects(line, &intersect) == QLineF::BoundedIntersection) {
300 intersectB = intersect-lineBottom;
301 if (intersectA != intersectB || !addedA) {
302 intersects.append(intersectB);
306 if (!intersects.isEmpty()) {
307 intersects.append(baseLine.p1());
308 intersects.append(baseLine.p2());
311 std::sort(intersects.begin(), intersects.end(),
pointLessThan);
317 for (
int i = 0; i< intersects.size()-1; i++) {
318 QLineF line(intersects.at(i), intersects.at(i+1));
320 if (!(shape.contains(line.translated(lineTop).center())
321 && shape.contains(line.translated(lineBottom).center()))
322 || line.length() == 0) {
326 QRectF lineBox = QRectF(line.p1() + lineTop, line.p2() + lineBottom).normalized();
329 for(
int i = 0; i < polygon.size()-1; i++) {
331 QLineF edgeLine(polygon.at(i), polygon.at(i+1));
333 if (lineBox.contains(polygon.at(i))) {
334 relevant.append(polygon.at(i));
339 for(
int j = 0; j < relevant.size(); j++) {
340 QPointF current = relevant.at(j);
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);
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);
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());
365 lines.append(newLine);
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());
374 lines.append(newLine);
442 const QMap<int, int> &logicalToVisual,
466 bool firstLine =
true;
467 bool indentLine =
true;
470 :shapes.at(0).boundingRect().topLeft();
471 QPointF lineOffset = currentPos;
473 QListIterator<int> it(logicalToVisual.keys());
474 QListIterator<QPainterPath> shapesIt(shapes);
475 if (shapes.isEmpty()) {
481 QRectF wordBox = isHorizontal? QRectF(0, fontSize * -0.8,
SHAPE_PRECISION, fontSize)
483 getFirstPosition(startPos, shapes.first(), wordBox, currentPos, writingMode, ltr);
485 QPainterPath currentShape;
486 while (it.hasNext()) {
487 int index = it.next();
488 result[index].calculateAndApplyTabsize(wordAdvance + currentPos, isHorizontal, resHandler);
494 bool softBreak =
false;
495 bool doNotCountAdvance =
497 && !(currentLine.
isEmpty() && wordIndices.isEmpty()));
498 if (!doNotCountAdvance) {
499 if (wordIndices.isEmpty()) {
501 wordAdvance = charResult.
advance;
504 wordAdvance += charResult.
advance;
507 wordIndices.append(index);
508 currentLine.
lastLine = !it.hasNext();
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) {
531 QLineF nextLine = currentLine.
chunks.value(i+1).length;
533 currentPos.setX(ltr? qMax(nextLine.p1().x(), currentPos.x()):
534 qMin(nextLine.p1().x(), currentPos.x()));
536 currentPos.setY(nextLine.p1().y());
541 addWordToLine(result, currentPos, wordIndices, currentLine, ltr, isHorizontal);
549 finalizeLine(result, currentPos, currentLine, lineOffset, anchor, writingMode, ltr,
true,
true, resHandler);
550 lineBoxes.append(currentLine);
557 bool ind = textIndentInfo.
hanging? !indentLine: indentLine;
558 QPointF indent = ind? textIndent: QPointF();
559 bool foundFirst =
false;
560 bool needNewLine =
true;
562 if (!currentShape.isEmpty()) {
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) {
587 foundFirst =
getFirstPosition(currentPos, currentShape, wordBox, lineOffset, writingMode, ltr);
588 if (foundFirst || !shapesIt.hasNext()) {
591 currentShape = shapesIt.next();
594 qreal textIdentValue = textIndentInfo.
length.
value;
597 textIdentValue *= currentShape.boundingRect().width();
599 textIndent = resHandler.
adjust(QPointF(textIdentValue, 0));
602 textIdentValue *= currentShape.boundingRect().height();
604 textIndent = resHandler.
adjust(QPointF(0, textIdentValue));
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;
612 bool lastDitch =
false;
613 if (!foundFirst && firstLine && !wordIndices.isEmpty() && !currentShape.isEmpty()) {
616 wordBox = result[wordIndices.first()].lineHeightBox().translated(result[wordIndices.first()].totalBaselineOffset());
617 foundFirst =
getFirstPosition(currentPos, currentShape, wordBox, lineOffset, writingMode, ltr);
629 const qreal expectedLineT = isHorizontal? wordBox.top():
635 currentPos = currentLine.
chunk().
length.p1() + indent;
636 lineOffset = currentPos;
640 QPointF advance = currentPos;
641 Q_FOREACH(
const int i, wordIndices) {
642 advance += result[i].advance;
643 if (currentShape.contains(advance)) {
646 result[i].hidden =
true;
647 result[i].cssPosition = advance-result[i].advance;
649 wordIndices = wordNew;
652 addWordToLine(result, currentPos, wordIndices, currentLine, ltr, isHorizontal);
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;
665 finalizeLine(result, currentPos, currentLine, lineOffset, anchor, writingMode, ltr,
true,
true, resHandler);
666 lineBoxes.append(currentLine);
671 finalizeLine(result, currentPos, currentLine, lineOffset, anchor, writingMode, ltr,
true,
true, resHandler);
672 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...