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