Krita Source Code Documentation
Loading...
Searching...
No Matches
KoSvgTextShapeLayoutFunc_lines.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 "KoSvgTextProperties.h"
11
12#include <FlakeDebug.h>
13
15{
16
29void calculateLineHeight(CharacterResult cr, double &ascent, double &descent, bool isHorizontal, bool compare)
30{
31 QRectF lineHeightBox = cr.lineHeightBox().translated(cr.totalBaselineOffset());
32 double offsetAsc = isHorizontal? lineHeightBox.top(): lineHeightBox.right();
33 double offsetDsc = isHorizontal? lineHeightBox.bottom(): lineHeightBox.left();
34
35 if (!compare) {
36 ascent = offsetAsc;
37 descent = offsetDsc;
38 } else {
39 if (isHorizontal) {
40 ascent = qMin(offsetAsc, ascent);
41 descent = qMax(offsetDsc, descent);
42 } else {
43 ascent = qMax(offsetAsc, ascent);
44 descent = qMin(offsetDsc, descent);
45 }
46 }
47}
48
55 QPointF &currentPos,
56 QVector<int> &wordIndices,
57 LineBox &currentLine,
58 bool ltr,
59 bool isHorizontal)
60{
61 QPointF lineAdvance = currentPos;
62
63 LineChunk currentChunk = currentLine.chunk();
64
65 Q_FOREACH (const int j, wordIndices) {
66 CharacterResult cr = result.at(j);
67 if (currentLine.isEmpty() && j == wordIndices.first()) {
68 if (result.at(j).lineStart == LineEdgeBehaviour::Collapse) {
69 if (isHorizontal) {
70 result[j].scaleCharacterResult(0.0, 1.0);
71 } else {
72 result[j].scaleCharacterResult(1.0, 0.0);
73 }
74 result[j].hidden = true;
75 continue;
76 }
77 cr.anchored_chunk = true;
78 if (result.at(j).lineStart == LineEdgeBehaviour::ForceHang && currentLine.firstLine) {
79 currentPos -= cr.advance;
80 cr.isHanging = true;
81 }
82 }
83 calculateLineHeight(cr, currentLine.actualLineTop, currentLine.actualLineBottom, isHorizontal, !cr.anchored_chunk);
84
85 cr.cssPosition = currentPos;
86 cr.calculateAndApplyTabsize(currentPos, isHorizontal, KoSvgText::ResolutionHandler());
87 currentPos += cr.advance;
88 lineAdvance = currentPos;
89
90 result[j] = cr;
91 currentChunk.boundingBox |= cr.layoutBox().translated(cr.cssPosition + cr.totalBaselineOffset());
92 }
93 currentPos = lineAdvance;
94 currentChunk.chunkIndices += wordIndices;
95 currentLine.setCurrentChunk(currentChunk);
96 wordIndices.clear();
97}
98
103static QPointF lineHeightOffset(KoSvgText::WritingMode writingMode,
105 LineBox &currentLine,
106 const bool firstLine,
107 const KoSvgText::ResolutionHandler resHandler)
108{
109 QPointF lineTop;
110 QPointF lineBottom;
111 QPointF correctionOffset;
114
115 if (currentLine.chunks.isEmpty()) {
116 return QPointF();
117 } else if (currentLine.chunks.size() == 1 && currentLine.actualLineTop == 0 &&
118 currentLine.actualLineBottom == 0){
124 QVector<int> chunkIndices = currentLine.chunks[0].chunkIndices;
125 if (chunkIndices.size() > 0) {
126 CharacterResult cr = result[chunkIndices.first()];
128 currentLine.actualLineTop,
129 currentLine.actualLineBottom,
130 writingMode == KoSvgText::HorizontalTB,
131 false);
132 result[chunkIndices.first()].anchored_chunk = true;
133 }
134 }
135
136 // We do qmin/qmax here so that later, the correction offset will not go below either value.
137 const qreal expectedLineTop = writingMode == KoSvgText::HorizontalTB? qMin(currentLine.expectedLineTop, currentLine.actualLineTop):
138 qMax(currentLine.expectedLineTop, currentLine.actualLineTop);
139 if (writingMode == KoSvgText::HorizontalTB) {
140 currentLine.baselineTop = QPointF(0, currentLine.actualLineTop);
141 currentLine.baselineBottom = QPointF(0, currentLine.actualLineBottom);
142 correctionOffset = QPointF(0, -expectedLineTop) + currentLine.baselineTop;
143 lineTop = -currentLine.baselineTop;
144 lineBottom = currentLine.baselineBottom;
145 } else if (writingMode == KoSvgText::VerticalLR) {
146 currentLine.baselineTop = QPointF(currentLine.actualLineTop, 0);
147 currentLine.baselineBottom = QPointF(currentLine.actualLineBottom, 0);
148 correctionOffset = QPointF(-expectedLineTop, 0) + currentLine.baselineTop;
149 // Note: while Vertical LR goes left-to-right in its lines, its lines themselves are
150 // oriented with the top pointed in the positive x direction.
151 lineBottom = currentLine.baselineTop;
152 lineTop = -currentLine.baselineBottom;
153 } else {
154 currentLine.baselineTop = QPointF(currentLine.actualLineTop, 0);
155 currentLine.baselineBottom = QPointF(currentLine.actualLineBottom, 0);
156 correctionOffset = QPointF(expectedLineTop, 0) - currentLine.baselineTop;
157 lineTop = -currentLine.baselineTop;
158 lineBottom = currentLine.baselineBottom;
159 }
160 bool returnDescent = firstLine;
161 QPointF offset = resHandler.adjust(lineTop + lineBottom);
162 if (resHandler.roundToPixelHorizontal || resHandler.roundToPixelVertical) {
163 lineTop = offset - resHandler.adjust(lineBottom);
164 correctionOffset = resHandler.adjust(correctionOffset);
165 }
166
167 if (!returnDescent) {
168 for (auto chunk = currentLine.chunks.begin(); chunk != currentLine.chunks.end(); chunk++) {
169 Q_FOREACH (int j, chunk->chunkIndices) {
170 result[j].cssPosition += lineTop;
171 result[j].cssPosition += result[j].totalBaselineOffset();
172 result[j].finalPosition = result.at(j).cssPosition;
173 }
174 chunk->length.translate(lineTop);
175 chunk->boundingBox.translate(lineTop);
176 }
177 } else {
178 offset = lineBottom - correctionOffset;
179 for (auto chunk = currentLine.chunks.begin(); chunk != currentLine.chunks.end(); chunk++) {
180 Q_FOREACH (int j, chunk->chunkIndices) {
181 result[j].cssPosition -= correctionOffset;
182 result[j].cssPosition = result[j].cssPosition + result[j].totalBaselineOffset();
183 result[j].finalPosition = result.at(j).cssPosition;
184 }
185 chunk->length.translate(-correctionOffset);
186 chunk->boundingBox.translate(-correctionOffset);
187 }
188 }
189 return resHandler.adjust(offset);
190}
191
192// NOLINTNEXTLINE(readability-function-cognitive-complexity)
193static void
194handleCollapseAndHang(QVector<CharacterResult> &result, LineChunk &chunk, bool ltr, bool isHorizontal)
195{
196 QVector<int> lineIndices = chunk.chunkIndices;
197 QPointF endPos = chunk.length.p2();
198
199 if (!lineIndices.isEmpty()) {
200 QVectorIterator<int> it(lineIndices);
201 it.toBack();
202 while (it.hasPrevious()) {
203 int lastIndex = it.previous();
204 if (result.at(lastIndex).lineEnd == LineEdgeBehaviour::Collapse) {
205 result[lastIndex].hidden = true;
206 // We literally collapse the advance of the last collapsed white-space to ensure it may
207 // still be possible to track it by cursor movement.
208 result[lastIndex].advance = QPointF();
209 if (isHorizontal) {
210 result[lastIndex].inkBoundingBox.setWidth(0);
211 } else {
212 result[lastIndex].inkBoundingBox.setHeight(0);
213 }
214 } else if (result.at(lastIndex).lineEnd == LineEdgeBehaviour::ConditionallyHang) {
215 if (ltr) {
216 QPointF hangPos = result[lastIndex].cssPosition + result[lastIndex].advance;
217 if (isHorizontal) {
218 if (hangPos.x() > endPos.x()) {
219 result[lastIndex].isHanging = true;
220 chunk.conditionalHangEnd = hangPos - endPos;
221 }
222 } else {
223 if (hangPos.y() > endPos.y()) {
224 result[lastIndex].isHanging = true;
225 chunk.conditionalHangEnd = hangPos - endPos;
226 }
227 }
228 } else {
229 QPointF hangPos = result[lastIndex].cssPosition;
230 if (hangPos.x() < endPos.x()) {
231 result[lastIndex].isHanging = true;
232 chunk.conditionalHangEnd = hangPos - endPos;
233 }
234 }
235
236 } else if (result.at(lastIndex).lineEnd == LineEdgeBehaviour::ForceHang) {
237 result[lastIndex].isHanging = true;
238 chunk.conditionalHangEnd = result[lastIndex].advance;
239 }
240 if (result.at(lastIndex).lineEnd != LineEdgeBehaviour::Collapse) {
241 break;
242 }
243 }
244 }
245}
246
247// NOLINTNEXTLINE(readability-function-cognitive-complexity)
249 LineChunk &chunk,
250 const KoSvgText::TextAnchor anchor,
251 const QPointF anchorPoint,
252 const bool ltr,
253 const bool isHorizontal,
254 const QPointF textIndent,
255 const KoSvgText::ResolutionHandler &resHandler)
256{
257 const QVector<int> lineIndices = chunk.chunkIndices;
258 qreal shift = isHorizontal ? anchorPoint.x() : anchorPoint.y();
259
260 qreal a = 0;
261 qreal b = 0;
262
263 bool first = true;
264 Q_FOREACH (int i, lineIndices) {
265 if (!result.at(i).addressable || result.at(i).hidden || (result.at(i).isHanging && result.at(i).anchored_chunk)) {
266 continue;
267 }
268
269 QPointF p = result.at(i).finalPosition;
270 QPointF d = result.at(i).advance;
271 if (result.at(i).isHanging) {
272 d -= chunk.conditionalHangEnd;
273 if (!ltr) {
274 p += chunk.conditionalHangEnd;
275 }
276 }
277 const qreal pos = isHorizontal ? p.x() : p.y();
278 const qreal advance = isHorizontal ? d.x() : d.y();
279
280 if (first) {
281 a = qMin(pos, pos + advance);
282 b = qMax(pos, pos + advance);
283 first = false;
284 } else {
285 a = qMin(a, qMin(pos, pos + advance));
286 b = qMax(b, qMax(pos, pos + advance));
287 }
288 }
289
290
291 if (anchor == KoSvgText::AnchorStart) {
292 const qreal indent = isHorizontal ? textIndent.x() : textIndent.y();
293 if (ltr) {
294 a -= indent;
295 } else {
296 b += indent;
297 }
298 }
299
300 if ((anchor == KoSvgText::AnchorStart && ltr) || (anchor == KoSvgText::AnchorEnd && !ltr)) {
301 shift -= a;
302
303 } else if ((anchor == KoSvgText::AnchorEnd && ltr) || (anchor == KoSvgText::AnchorStart && !ltr)) {
304 shift -= b;
305
306 } else {
307 shift -= ((a + b) * 0.5);
308 }
309
310 QPointF shiftP = resHandler.adjust(isHorizontal ? QPointF(shift, 0) : QPointF(0, shift));
311 Q_FOREACH (int j, lineIndices) {
312 result[j].cssPosition = result[j].cssPosition+shiftP;
313 result[j].finalPosition = result.at(j).cssPosition;
314 }
315 chunk.boundingBox.translate(shiftP);
316}
317
322 QPointF &currentPos,
323 LineBox &currentLine,
324 QPointF &lineOffset,
325 const KoSvgText::TextAnchor anchor,
326 const KoSvgText::WritingMode writingMode,
327 const bool ltr,
328 const bool inlineSize,
329 const bool textInShape,
330 const KoSvgText::ResolutionHandler &resHandler)
331{
332 bool isHorizontal = writingMode == KoSvgText::HorizontalTB;
333
334 bool firstLine = textInShape? true: currentLine.firstLine;
335
336 for (auto currentChunk = currentLine.chunks.begin(); currentChunk != currentLine.chunks.end(); currentChunk++) {
337 QMap<int, int> visualToLogical;
338 Q_FOREACH (int j, currentChunk->chunkIndices) {
339 visualToLogical.insert(result.at(j).visualIndex, j);
340 }
341 currentPos = lineOffset;
342
343 handleCollapseAndHang(result, *currentChunk, ltr, isHorizontal);
344
345 QPointF justifyOffset;
346 QVector<int> before;
347 QVector<int> after;
348
349 if (currentLine.justifyLine) {
350 double hangingGlyphLength = isHorizontal? currentChunk->conditionalHangEnd.x(): currentChunk->conditionalHangEnd.y();
351 QPointF advanceLength;
352 bool first = true;
353 Q_FOREACH (int j, visualToLogical.values()) {
354 if (!result.at(j).addressable || result.at(j).hidden) {
355 continue;
356 }
357 advanceLength += result.at(j).advance;
358 if (result.at(j).isHanging) {
359 if (result.at(j).anchored_chunk) {
360 hangingGlyphLength += isHorizontal? result.at(j).advance.x(): result.at(j).advance.y();
361 }
362 continue;
363 }
364 bool last = visualToLogical.values().last() == j;
365 if (!last) {
366 last = result.at(j+1).isHanging;
367 }
368
369 if (result.at(j).justifyBefore && !first) {
370 before.append(j);
371 }
372 if (result.at(j).justifyAfter && !last) {
373 after.append(j);
374 }
375 first = false;
376 }
377
378 int justificationCount = before.size()+after.size();
379 if (justificationCount > 0) {
380 const QPointF indent = currentChunk == currentLine.chunks.begin()? currentLine.textIndent: QPointF();
381 const QLineF modified = QLineF(currentChunk->length.p1()+indent, currentChunk->length.p2());
382 if (isHorizontal) {
383 double val = modified.length() + hangingGlyphLength - advanceLength.x();
384 val = val / justificationCount;
385 justifyOffset = QPointF(val, 0);
386 } else {
387 double val = modified.length() + hangingGlyphLength - advanceLength.y();
388 val = val / justificationCount;
389 justifyOffset = QPointF(0, val);
390 }
391 }
392 }
393
394 Q_FOREACH (const int j, visualToLogical.values()) {
395 if (!result.at(j).addressable) {
396 continue;
397 }
398 if ((result.at(j).isHanging && result.at(j).anchored_chunk)) {
399 if (ltr) {
400 result[j].cssPosition = currentPos - result.at(j).advance;
401 result[j].finalPosition = result[j].cssPosition;
402 } else {
403 result[j].cssPosition = currentPos;
404 result[j].finalPosition = result[j].cssPosition;
405 }
406 } else {
407 if (before.contains(j)) {
408 currentPos += justifyOffset;
409 }
410 result[j].cssPosition = currentPos;
411 result[j].finalPosition = currentPos;
412 currentPos = currentPos + result.at(j).advance;
413 if (after.contains(j)) {
414 currentPos += justifyOffset;
415 }
416 }
417 }
418
419 if (inlineSize) {
420 QPointF anchorPoint = currentChunk->length.p1();
421 if (textInShape) {
422 if (anchor == KoSvgText::AnchorMiddle) {
423 anchorPoint = currentChunk->length.center();
424 } else if (anchor == KoSvgText::AnchorEnd) {
425 anchorPoint = currentChunk->length.p2();
426 }
427 }
428 applyInlineSizeAnchoring(result, *currentChunk, anchor, anchorPoint, ltr, isHorizontal, currentLine.textIndent, resHandler);
429 } else {
430 if (!ltr) {
431 // RTL relies on the start being 0.0, so we need to align it to the startAnchor.
432 applyInlineSizeAnchoring(result, *currentChunk, KoSvgText::AnchorStart, currentChunk->length.p1(), ltr, isHorizontal, currentLine.textIndent, resHandler);
433 } else {
434 // this adds a length for preformated text, only useful for debug.
435 currentChunk->length.setLength(isHorizontal? currentChunk->boundingBox.width(): currentChunk->boundingBox.height());
436 }
437 }
438 }
439 lineOffset += lineHeightOffset(writingMode, result, currentLine, firstLine, resHandler);
440 currentPos = lineOffset;
441}
442
443// NOLINTNEXTLINE(readability-function-cognitive-complexity)
445 const QMap<int, int> &logicalToVisual,
447 QPointF startPos,
448 const KoSvgText::ResolutionHandler &resHandler)
449{
454
455 bool ltr = direction == KoSvgText::DirectionLeftToRight;
456 bool isHorizontal = writingMode == KoSvgText::HorizontalTB;
457
458 QVector<LineBox> lineBoxes;
459
460 QPointF endPos;
461
463 QPointF textIndent;
464 if (!inlineSize.isAuto) {
465
466 qreal textIdentValue = textIndentInfo.length.unit == KoSvgText::CssLengthPercentage::Percentage?
467 textIndentInfo.length.value * inlineSize.customValue: textIndentInfo.length.value;
468 if (isHorizontal) {
469 textIndent = resHandler.adjust(QPointF(textIdentValue, 0));
470 endPos = ltr ? QPointF(startPos.x() + inlineSize.customValue, 0) : QPointF(startPos.x() - inlineSize.customValue, 0);
471 } else {
472 textIndent = resHandler.adjust(QPointF(0, textIdentValue));
473 endPos = ltr ? QPointF(0, startPos.y() + inlineSize.customValue) : QPointF(0, startPos.y() - inlineSize.customValue);
474 }
475 }
476 LineBox currentLine(startPos, endPos, resHandler);
477 currentLine.firstLine = true;
478
479 QVector<int> wordIndices;
481 QPointF wordAdvance;
482
483 QPointF currentPos = startPos;
484 if (!textIndentInfo.hanging && !inlineSize.isAuto) {
485 currentLine.textIndent = textIndent;
486 currentPos += currentLine.textIndent;
487 }
488 QPointF lineOffset = startPos;
489
490 QVector<int> lineIndices;
491
492 QListIterator<int> it(logicalToVisual.keys());
493 while (it.hasNext()) {
494 int index = it.next();
495 result[index].calculateAndApplyTabsize(wordAdvance + currentPos, isHorizontal, resHandler);
496 CharacterResult charResult = result.at(index);
497 if (!charResult.addressable) {
498 continue;
499 }
500 bool softBreak = false;
501 bool doNotCountAdvance =
502 ((charResult.lineEnd != LineEdgeBehaviour::NoChange)
503 && !(currentLine.isEmpty() && wordIndices.isEmpty()));
504 if (!doNotCountAdvance) {
505 if (wordIndices.isEmpty()) {
506 wordAdvance = charResult.advance;
507 } else {
508 wordAdvance += charResult.advance;
509 }
510 }
511 wordIndices.append(index);
512 currentLine.lastLine = !it.hasNext();
513
514 if (charResult.breakType != BreakType::NoBreak || currentLine.lastLine) {
515 qreal lineLength = isHorizontal ? (currentPos - startPos + wordAdvance).x()
516 : (currentPos - startPos + wordAdvance).y();
517 if (!inlineSize.isAuto) {
518 // Sometimes glyphs are a fraction larger than you'd expect, but
519 // not enough to really break the line, so the following is a
520 // bit more stable than a simple compare.
521 if (abs(lineLength) - inlineSize.customValue > 0.01) {
522 softBreak = true;
523 } else {
524 addWordToLine(result, currentPos, wordIndices, currentLine, ltr, isHorizontal);
525 }
526 } else {
527 addWordToLine(result, currentPos, wordIndices, currentLine, ltr, isHorizontal);
528 }
529 }
530
531 if (softBreak) {
532 bool firstLine = currentLine.firstLine;
533 if (!currentLine.isEmpty()) {
534 finalizeLine(result,
535 currentPos,
536 currentLine,
537 lineOffset,
538 anchor,
539 writingMode,
540 ltr,
541 !inlineSize.isAuto,
542 false,
543 resHandler);
544 lineBoxes.append(currentLine);
545 currentLine.clearAndAdjust(isHorizontal, lineOffset, textIndentInfo.hanging? textIndent: QPointF());
546 if (!inlineSize.isAuto) {
547 currentPos += currentLine.textIndent;
548 }
549 }
550
551 if (charResult.overflowWrap) {
552 qreal wordLength = isHorizontal ? wordAdvance.x() : wordAdvance.y();
553 if (!inlineSize.isAuto && wordLength > inlineSize.customValue) {
554 // Word is too large, so we try to add it in
555 // max-width-friendly-chunks.
556 wordAdvance = QPointF();
557 wordLength = 0;
558 QVector<int> partialWord;
559 currentLine.firstLine = firstLine;
560 Q_FOREACH (const int i, wordIndices) {
561 result[i].calculateAndApplyTabsize(wordAdvance + currentPos, isHorizontal, resHandler);
562 wordAdvance += result.at(i).advance;
563 wordLength = isHorizontal ? wordAdvance.x() : wordAdvance.y();
564 if (wordLength <= inlineSize.customValue) {
565 partialWord.append(i);
566 } else {
567 addWordToLine(result, currentPos, partialWord, currentLine, ltr, isHorizontal);
568
569 finalizeLine(result,
570 currentPos,
571 currentLine,
572 lineOffset,
573 anchor,
574 writingMode,
575 ltr,
576 !inlineSize.isAuto,
577 false,
578 resHandler);
579 lineBoxes.append(currentLine);
580 currentLine.clearAndAdjust(isHorizontal, lineOffset, textIndentInfo.hanging? textIndent: QPointF());
581 if (!inlineSize.isAuto) {
582 currentPos += currentLine.textIndent;
583 }
584 result[i].calculateAndApplyTabsize(wordAdvance + currentPos, isHorizontal, resHandler);
585 wordAdvance = result.at(i).advance;
586 partialWord.append(i);
587 }
588 }
589 wordIndices = partialWord;
590 }
591 }
592 addWordToLine(result, currentPos, wordIndices, currentLine, ltr, isHorizontal);
593 }
594
595 if (charResult.breakType == BreakType::HardBreak) {
596 finalizeLine(result,
597 currentPos,
598 currentLine,
599 lineOffset,
600 anchor,
601 writingMode,
602 ltr,
603 !inlineSize.isAuto,
604 false,
605 resHandler);
606 lineBoxes.append(currentLine);
607 bool indentLine = textIndentInfo.hanging? false: textIndentInfo.eachLine;
608 currentLine.clearAndAdjust(isHorizontal, lineOffset, indentLine? textIndent: QPointF());
609 if (!inlineSize.isAuto) {
610 currentPos += currentLine.textIndent;
611 }
612 }
613
614 if (currentLine.lastLine) {
615 if (!wordIndices.isEmpty()) {
616 addWordToLine(result, currentPos, wordIndices, currentLine, ltr, isHorizontal);
617 }
618 finalizeLine(result,
619 currentPos,
620 currentLine,
621 lineOffset,
622 anchor,
623 writingMode,
624 ltr,
625 !inlineSize.isAuto,
626 false,
627 resHandler);
628 lineBoxes.append(currentLine);
629 }
630 }
631 debugFlake << "Linebreaking finished";
632 return lineBoxes;
633}
634
635} // namespace KoSvgTextShapeLayoutFunc
#define debugFlake
Definition FlakeDebug.h:15
const Params2D p
@ ConditionallyHang
Only hang if no space otherwise, only measured for justification if not hanging.
@ Collapse
Collapse if first or last in line.
@ ForceHang
Force hanging at the start or end of a line, never measured for justification.
@ NoChange
Do nothing special.
@ TextAnchorId
KoSvgText::TextAnchor.
@ InlineSizeId
KoSvgText::AutoValue.
@ WritingModeId
KoSvgText::WritingMode.
@ DirectionId
KoSvgText::Direction.
@ TextIndentId
KoSvgText::TextIndentInfo Struct.
QVariant propertyOrDefault(PropertyId id) const
static QPointF lineHeightOffset(KoSvgText::WritingMode writingMode, QVector< CharacterResult > &result, LineBox &currentLine, const bool firstLine, const KoSvgText::ResolutionHandler resHandler)
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 void applyInlineSizeAnchoring(QVector< CharacterResult > &result, LineChunk &chunk, const KoSvgText::TextAnchor anchor, const QPointF anchorPoint, const bool ltr, const bool isHorizontal, const QPointF textIndent, const KoSvgText::ResolutionHandler &resHandler)
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)
static void handleCollapseAndHang(QVector< CharacterResult > &result, LineChunk &chunk, bool ltr, bool isHorizontal)
QVector< LineBox > breakLines(const KoSvgTextProperties &properties, const QMap< int, int > &logicalToVisual, QVector< CharacterResult > &result, QPointF startPos, const KoSvgText::ResolutionHandler &resHandler)
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....
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
bool anchored_chunk
whether this is the start of a new chunk.
LineEdgeBehaviour lineEnd
void calculateAndApplyTabsize(QPointF currentPos, bool isHorizontal, const KoSvgText::ResolutionHandler &resHandler)
QPointF cssPosition
the position in accordance with the CSS specs, as opossed to the SVG spec.
QRectF layoutBox() const
layoutBox
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 setCurrentChunk(LineChunk chunk)
qreal actualLineBottom
QPointF baselineTop
Used to identify the top of the line for baseline-alignment.
qreal actualLineTop
LineChunk chunk()
void clearAndAdjust(bool isHorizontal, QPointF current, QPointF indent)
QPointF textIndent
qreal expectedLineTop
Because fonts can affect lineheight mid-line, and this affects wrapping, this estimates the line-heig...
QVector< LineChunk > chunks
QPointF baselineBottom
Used to identify the bottom of the line for baseline-alignment.
QPointF conditionalHangEnd
QVector< int > chunkIndices
charResult indices that belong to this chunk.
QLineF length
Used to measure how long the current line is allowed to be.