Krita Source Code Documentation
Loading...
Searching...
No Matches
KoSvgTextShapeLayoutFunc Namespace Reference

Functions

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. Returns the last added index.
 
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)
 
QVector< LineBoxbreakLines (const KoSvgTextProperties &properties, const QMap< int, int > &logicalToVisual, QVector< CharacterResult > &result, QPointF startPos, const KoSvgText::ResolutionHandler &resHandler)
 
void calculateLineHeight (CharacterResult cr, double &ascent, double &descent, bool isHorizontal, bool compare)
 calculateLineHeight calculate the total ascent and descent (including baseline-offset) of a charResult and optionally only return it if it is larger than the provided ascent and descent variable. This is necessary for proper line-height calculation.
 
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 QVector< QLineF > findLineBoxesForFirstPos (QPainterPath shape, QPointF firstPos, const QRectF wordBox, KoSvgText::WritingMode writingMode)
 
QVector< LineBoxflowTextInShapes (const KoSvgTextProperties &properties, const QMap< int, int > &logicalToVisual, QVector< CharacterResult > &result, QList< QPainterPath > shapes, QPointF &startPos, const KoSvgText::ResolutionHandler &resHandler)
 
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.
 
static bool getFirstPosition (QPointF &firstPoint, QPainterPath p, QRectF wordBox, QPointF terminator, KoSvgText::WritingMode writingMode, bool ltr)
 
QList< QPainterPath > getShapes (QList< KoShape * > shapesInside, QList< KoShape * > shapesSubtract, const KoSvgTextProperties &properties)
 
static void handleCollapseAndHang (QVector< CharacterResult > &result, LineChunk &chunk, bool ltr, bool isHorizontal)
 
static QPointF lineHeightOffset (KoSvgText::WritingMode writingMode, QVector< CharacterResult > &result, LineBox &currentLine, const bool firstLine, const KoSvgText::ResolutionHandler resHandler)
 
static bool pointLessThan (const QPointF &a, const QPointF &b)
 
static bool pointLessThanVertical (const QPointF &a, const QPointF &b)
 
static KoSvgText::TextAnchor textAnchorForTextAlign (KoSvgText::TextAlign align, KoSvgText::TextAlign alignLast, bool ltr)
 

Function Documentation

◆ addWordToLine()

void KoSvgTextShapeLayoutFunc::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. Returns the last added index.

Definition at line 54 of file KoSvgTextShapeLayoutFunc_lines.cpp.

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}
@ Collapse
Collapse if first or last in line.
@ ForceHang
Force hanging at the start or end of a line, never measured for justification.
QPointF totalBaselineOffset() const
bool anchored_chunk
whether this is the start of a new chunk.
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
void setCurrentChunk(LineChunk chunk)
qreal actualLineBottom
qreal actualLineTop
LineChunk chunk()
QVector< int > chunkIndices
charResult indices that belong to this chunk.

References LineBox::actualLineBottom, LineBox::actualLineTop, CharacterResult::advance, CharacterResult::anchored_chunk, LineChunk::boundingBox, CharacterResult::calculateAndApplyTabsize(), calculateLineHeight(), LineBox::chunk(), LineChunk::chunkIndices, Collapse, CharacterResult::cssPosition, LineBox::firstLine, ForceHang, LineBox::isEmpty(), CharacterResult::isHanging, CharacterResult::layoutBox(), LineBox::setCurrentChunk(), and CharacterResult::totalBaselineOffset().

◆ applyInlineSizeAnchoring()

static void KoSvgTextShapeLayoutFunc::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 )
static

Definition at line 248 of file KoSvgTextShapeLayoutFunc_lines.cpp.

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}
const Params2D p
@ AnchorEnd
Anchor right for LTR, left for RTL.
Definition KoSvgText.h:82
@ AnchorStart
Anchor left for LTR, right for RTL.
Definition KoSvgText.h:80
QPointF adjust(const QPointF point) const
Adjusts the point to rounded pixel values, based on whether roundToPixelHorizontal or roundToPixelVer...
QPointF conditionalHangEnd

References KoSvgText::ResolutionHandler::adjust(), KoSvgText::AnchorEnd, KoSvgText::AnchorStart, LineChunk::boundingBox, LineChunk::chunkIndices, LineChunk::conditionalHangEnd, and p.

◆ breakLines()

QVector< LineBox > KoSvgTextShapeLayoutFunc::breakLines ( const KoSvgTextProperties & properties,
const QMap< int, int > & logicalToVisual,
QVector< CharacterResult > & result,
QPointF startPos,
const KoSvgText::ResolutionHandler & resHandler )

< Used for hanging glyphs at the end of a line.

< 'word' in this case meaning characters inbetween softbreaks.

< Approximated advance of the current wordindices.

< Current position with advances of each character.

< Current line offset.

< Indices of characters in line.

< Whether to do a softbreak;

< Used for hanging glyphs at the end of a line.

< 'word' in this case meaning characters inbetween softbreaks.

< Approximated advance of the current wordindices.

< Current position with advances of each character.

< Current line offset.

< Indices of characters in line.

< Whether to do a softbreak;

Definition at line 442 of file KoSvgTextShapeLayoutFunc_lines.cpp.

447{
452
453 bool ltr = direction == KoSvgText::DirectionLeftToRight;
454 bool isHorizontal = writingMode == KoSvgText::HorizontalTB;
455
456 QVector<LineBox> lineBoxes;
457
458 QPointF endPos;
459
461 QPointF textIndent;
462 if (!inlineSize.isAuto) {
463
464 qreal textIdentValue = textIndentInfo.length.unit == KoSvgText::CssLengthPercentage::Percentage?
465 textIndentInfo.length.value * inlineSize.customValue: textIndentInfo.length.value;
466 if (isHorizontal) {
467 textIndent = resHandler.adjust(QPointF(textIdentValue, 0));
468 endPos = ltr ? QPointF(startPos.x() + inlineSize.customValue, 0) : QPointF(startPos.x() - inlineSize.customValue, 0);
469 } else {
470 textIndent = resHandler.adjust(QPointF(0, textIdentValue));
471 endPos = ltr ? QPointF(0, startPos.y() + inlineSize.customValue) : QPointF(0, startPos.y() - inlineSize.customValue);
472 }
473 }
474 LineBox currentLine(startPos, endPos, resHandler);
475 currentLine.firstLine = true;
476
477 QVector<int> wordIndices;
479 QPointF wordAdvance;
480
481 QPointF currentPos = startPos;
482 if (!textIndentInfo.hanging && !inlineSize.isAuto) {
483 currentLine.textIndent = textIndent;
484 currentPos += currentLine.textIndent;
485 }
486 QPointF lineOffset = startPos;
487
488 QVector<int> lineIndices;
489
490 QListIterator<int> it(logicalToVisual.keys());
491 while (it.hasNext()) {
492 int index = it.next();
493 result[index].calculateAndApplyTabsize(wordAdvance + currentPos, isHorizontal, resHandler);
494 CharacterResult charResult = result.at(index);
495 if (!charResult.addressable) {
496 continue;
497 }
498 bool softBreak = false;
499 bool doNotCountAdvance =
500 ((charResult.lineEnd != LineEdgeBehaviour::NoChange)
501 && !(currentLine.isEmpty() && wordIndices.isEmpty()));
502 if (!doNotCountAdvance) {
503 if (wordIndices.isEmpty()) {
504 wordAdvance = charResult.advance;
505 } else {
506 wordAdvance += charResult.advance;
507 }
508 }
509 wordIndices.append(index);
510 currentLine.lastLine = !it.hasNext();
511
512 if (charResult.breakType != BreakType::NoBreak || currentLine.lastLine) {
513 qreal lineLength = isHorizontal ? (currentPos - startPos + wordAdvance).x()
514 : (currentPos - startPos + wordAdvance).y();
515 if (!inlineSize.isAuto) {
516 // Sometimes glyphs are a fraction larger than you'd expect, but
517 // not enough to really break the line, so the following is a
518 // bit more stable than a simple compare.
519 if (abs(lineLength) - inlineSize.customValue > 0.01) {
520 softBreak = true;
521 } else {
522 addWordToLine(result, currentPos, wordIndices, currentLine, ltr, isHorizontal);
523 }
524 } else {
525 addWordToLine(result, currentPos, wordIndices, currentLine, ltr, isHorizontal);
526 }
527 }
528
529 if (softBreak) {
530 bool firstLine = currentLine.firstLine;
531 if (!currentLine.isEmpty()) {
532 finalizeLine(result,
533 currentPos,
534 currentLine,
535 lineOffset,
536 anchor,
537 writingMode,
538 ltr,
539 !inlineSize.isAuto,
540 false,
541 resHandler);
542 lineBoxes.append(currentLine);
543 currentLine.clearAndAdjust(isHorizontal, lineOffset, textIndentInfo.hanging? textIndent: QPointF());
544 if (!inlineSize.isAuto) {
545 currentPos += currentLine.textIndent;
546 }
547 }
548
549 if (charResult.overflowWrap) {
550 qreal wordLength = isHorizontal ? wordAdvance.x() : wordAdvance.y();
551 if (!inlineSize.isAuto && wordLength > inlineSize.customValue) {
552 // Word is too large, so we try to add it in
553 // max-width-friendly-chunks.
554 wordAdvance = QPointF();
555 wordLength = 0;
556 QVector<int> partialWord;
557 currentLine.firstLine = firstLine;
558 Q_FOREACH (const int i, wordIndices) {
559 result[i].calculateAndApplyTabsize(wordAdvance + currentPos, isHorizontal, resHandler);
560 wordAdvance += result.at(i).advance;
561 wordLength = isHorizontal ? wordAdvance.x() : wordAdvance.y();
562 if (wordLength <= inlineSize.customValue) {
563 partialWord.append(i);
564 } else {
565 addWordToLine(result, currentPos, partialWord, currentLine, ltr, isHorizontal);
566
567 finalizeLine(result,
568 currentPos,
569 currentLine,
570 lineOffset,
571 anchor,
572 writingMode,
573 ltr,
574 !inlineSize.isAuto,
575 false,
576 resHandler);
577 lineBoxes.append(currentLine);
578 currentLine.clearAndAdjust(isHorizontal, lineOffset, textIndentInfo.hanging? textIndent: QPointF());
579 if (!inlineSize.isAuto) {
580 currentPos += currentLine.textIndent;
581 }
582 result[i].calculateAndApplyTabsize(wordAdvance + currentPos, isHorizontal, resHandler);
583 wordAdvance = result.at(i).advance;
584 partialWord.append(i);
585 }
586 }
587 wordIndices = partialWord;
588 }
589 }
590 addWordToLine(result, currentPos, wordIndices, currentLine, ltr, isHorizontal);
591 }
592
593 if (charResult.breakType == BreakType::HardBreak) {
594 finalizeLine(result,
595 currentPos,
596 currentLine,
597 lineOffset,
598 anchor,
599 writingMode,
600 ltr,
601 !inlineSize.isAuto,
602 false,
603 resHandler);
604 lineBoxes.append(currentLine);
605 bool indentLine = textIndentInfo.hanging? false: textIndentInfo.eachLine;
606 currentLine.clearAndAdjust(isHorizontal, lineOffset, indentLine? textIndent: QPointF());
607 if (!inlineSize.isAuto) {
608 currentPos += currentLine.textIndent;
609 }
610 }
611
612 if (currentLine.lastLine) {
613 if (!wordIndices.isEmpty()) {
614 addWordToLine(result, currentPos, wordIndices, currentLine, ltr, isHorizontal);
615 }
616 finalizeLine(result,
617 currentPos,
618 currentLine,
619 lineOffset,
620 anchor,
621 writingMode,
622 ltr,
623 !inlineSize.isAuto,
624 false,
625 resHandler);
626 lineBoxes.append(currentLine);
627 }
628 }
629 debugFlake << "Linebreaking finished";
630 return lineBoxes;
631}
#define debugFlake
Definition FlakeDebug.h:15
@ 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
Point abs(const Point &pt)
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)
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
Direction
Base direction used by Bidi algorithm.
Definition KoSvgText.h:48
@ DirectionLeftToRight
Definition KoSvgText.h:49
@ HorizontalTB
Definition KoSvgText.h:38
LineEdgeBehaviour lineEnd
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.

References CharacterResult::addressable, addWordToLine(), KoSvgText::ResolutionHandler::adjust(), CharacterResult::advance, CharacterResult::breakType, LineBox::clearAndAdjust(), KoSvgText::AutoValue::customValue, debugFlake, KoSvgTextProperties::DirectionId, KoSvgText::DirectionLeftToRight, KoSvgText::TextIndentInfo::eachLine, finalizeLine(), LineBox::firstLine, KoSvgText::TextIndentInfo::hanging, HardBreak, KoSvgText::HorizontalTB, KoSvgTextProperties::InlineSizeId, KoSvgText::AutoValue::isAuto, LineBox::isEmpty(), LineBox::lastLine, KoSvgText::TextIndentInfo::length, CharacterResult::lineEnd, NoBreak, NoChange, CharacterResult::overflowWrap, KoSvgText::CssLengthPercentage::Percentage, KoSvgTextProperties::propertyOrDefault(), KoSvgTextProperties::TextAnchorId, LineBox::textIndent, KoSvgTextProperties::TextIndentId, KoSvgText::CssLengthPercentage::unit, KoSvgText::CssLengthPercentage::value, and KoSvgTextProperties::WritingModeId.

◆ calculateLineHeight()

void KoSvgTextShapeLayoutFunc::calculateLineHeight ( CharacterResult cr,
double & ascent,
double & descent,
bool isHorizontal,
bool compare )

calculateLineHeight calculate the total ascent and descent (including baseline-offset) of a charResult and optionally only return it if it is larger than the provided ascent and descent variable. This is necessary for proper line-height calculation.

Parameters
cr- char result with the data.
ascent- output ascent variable.
descent- output descent variable.
isHorizontal- whether it is horizontal.
compare- whether to only return the value if it is larger than the relative ascent or descent.

Definition at line 29 of file KoSvgTextShapeLayoutFunc_lines.cpp.

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}
QRectF lineHeightBox() const
lineHeightBox

References CharacterResult::lineHeightBox(), and CharacterResult::totalBaselineOffset().

◆ finalizeLine()

void KoSvgTextShapeLayoutFunc::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 )

Finalizing the line consists of several steps, like hang/collapse, anchoring into place and offsetting correctly. This can happen several times during a linebreak, hence this convenience function to handle this.

< Because we may have collapsed the last glyph, we'll need to recalculate the total advance;

< Because we may have collapsed the last glyph, we'll need to recalculate the total advance;

Definition at line 321 of file KoSvgTextShapeLayoutFunc_lines.cpp.

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 if (isHorizontal) {
381 double val = currentChunk->length.length() + hangingGlyphLength - advanceLength.x();
382 val = val / justificationCount;
383 justifyOffset = QPointF(val, 0);
384 } else {
385 double val = currentChunk->length.length() + hangingGlyphLength - advanceLength.y();
386 val = val / justificationCount;
387 justifyOffset = QPointF(0, val);
388 }
389 }
390 }
391
392 Q_FOREACH (const int j, visualToLogical.values()) {
393 if (!result.at(j).addressable) {
394 continue;
395 }
396 if ((result.at(j).isHanging && result.at(j).anchored_chunk)) {
397 if (ltr) {
398 result[j].cssPosition = currentPos - result.at(j).advance;
399 result[j].finalPosition = result[j].cssPosition;
400 } else {
401 result[j].cssPosition = currentPos;
402 result[j].finalPosition = result[j].cssPosition;
403 }
404 } else {
405 if (before.contains(j)) {
406 currentPos += justifyOffset;
407 }
408 result[j].cssPosition = currentPos;
409 result[j].finalPosition = currentPos;
410 currentPos = currentPos + result.at(j).advance;
411 if (after.contains(j)) {
412 currentPos += justifyOffset;
413 }
414 }
415 }
416
417 if (inlineSize) {
418 QPointF anchorPoint = currentChunk->length.p1();
419 if (textInShape) {
420 if (anchor == KoSvgText::AnchorMiddle) {
421 anchorPoint = currentChunk->length.center();
422 } else if (anchor == KoSvgText::AnchorEnd) {
423 anchorPoint = currentChunk->length.p2();
424 }
425 }
426 applyInlineSizeAnchoring(result, *currentChunk, anchor, anchorPoint, ltr, isHorizontal, currentLine.textIndent, resHandler);
427 } else {
428 if (!ltr) {
429 // RTL relies on the start being 0.0, so we need to align it to the startAnchor.
430 applyInlineSizeAnchoring(result, *currentChunk, KoSvgText::AnchorStart, currentChunk->length.p1(), ltr, isHorizontal, currentLine.textIndent, resHandler);
431 } else {
432 // this adds a length for preformated text, only useful for debug.
433 currentChunk->length.setLength(isHorizontal? currentChunk->boundingBox.width(): currentChunk->boundingBox.height());
434 }
435 }
436 }
437 lineOffset += lineHeightOffset(writingMode, result, currentLine, firstLine, resHandler);
438 currentPos = lineOffset;
439}
static QPointF lineHeightOffset(KoSvgText::WritingMode writingMode, QVector< CharacterResult > &result, LineBox &currentLine, const bool firstLine, const KoSvgText::ResolutionHandler resHandler)
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)
@ AnchorMiddle
Anchor to the middle.
Definition KoSvgText.h:81
QPointF textIndent
QVector< LineChunk > chunks

References KoSvgText::AnchorEnd, KoSvgText::AnchorMiddle, KoSvgText::AnchorStart, applyInlineSizeAnchoring(), LineBox::chunks, LineBox::firstLine, handleCollapseAndHang(), KoSvgText::HorizontalTB, LineBox::justifyLine, lineHeightOffset(), and LineBox::textIndent.

◆ findLineBoxesForFirstPos()

static QVector< QLineF > KoSvgTextShapeLayoutFunc::findLineBoxesForFirstPos ( QPainterPath shape,
QPointF firstPos,
const QRectF wordBox,
KoSvgText::WritingMode writingMode )
static

Definition at line 259 of file KoSvgTextShapeLayoutFunc_inShape.cpp.

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}
constexpr qreal SHAPE_PRECISION
Value that indicates the precision for testing coordinates for text-in-shape layout.
static bool pointLessThan(const KisCubicCurvePoint &a, const KisCubicCurvePoint &b)

References KoSvgText::HorizontalTB, pointLessThan(), pointLessThanVertical(), SHAPE_PRECISION, and KoSvgText::VerticalRL.

◆ flowTextInShapes()

QVector< LineBox > KoSvgTextShapeLayoutFunc::flowTextInShapes ( const KoSvgTextProperties & properties,
const QMap< int, int > & logicalToVisual,
QVector< CharacterResult > & result,
QList< QPainterPath > shapes,
QPointF & startPos,
const KoSvgText::ResolutionHandler & resHandler )

< The textIndent.

< 'word' in this case meaning characters inbetween softbreaks.

< Approximated box of the current word;

< First line will be created proper after we get our first wordbox, this tracks if it's the first.

< Current position with advances of each character.

< Current line offset.

< Whether to break a line.

< The textIndent.

< 'word' in this case meaning characters inbetween softbreaks.

< Approximated box of the current word;

< First line will be created proper after we get our first wordbox, this tracks if it's the first.

< Current position with advances of each character.

< Current line offset.

< Whether to break a line.

Definition at line 441 of file KoSvgTextShapeLayoutFunc_inShape.cpp.

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}
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
QPointF p1
@ TextAlignAllId
KoSvgText::TextAlign.
@ TextAlignLastId
KoSvgText::TextAlign.
KoSvgText::CssLengthPercentage fontSize() const
static KoSvgText::TextAnchor textAnchorForTextAlign(KoSvgText::TextAlign align, KoSvgText::TextAlign alignLast, bool ltr)
static bool getFirstPosition(QPointF &firstPoint, QPainterPath p, QRectF wordBox, QPointF terminator, KoSvgText::WritingMode writingMode, bool ltr)
static QVector< QLineF > findLineBoxesForFirstPos(QPainterPath shape, QPointF firstPos, const QRectF wordBox, KoSvgText::WritingMode writingMode)
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 setCurrentChunkForPos(QPointF pos, bool isHorizontal)
qreal expectedLineTop
Because fonts can affect lineheight mid-line, and this affects wrapping, this estimates the line-heig...
QLineF length
Used to measure how long the current line is allowed to be.

References CharacterResult::addressable, addWordToLine(), KoSvgText::ResolutionHandler::adjust(), CharacterResult::advance, KoSvgText::AlignJustify, CharacterResult::breakType, LineBox::chunk(), LineBox::chunks, LineBox::currentChunk, KoSvgTextProperties::DirectionId, KoSvgText::DirectionLeftToRight, KoSvgText::TextIndentInfo::eachLine, LineBox::expectedLineTop, finalizeLine(), findLineBoxesForFirstPos(), LineBox::firstLine, KoSvgTextProperties::fontSize(), getEstimatedHeight(), getFirstPosition(), KoSvgText::TextIndentInfo::hanging, HardBreak, KoSvgText::HorizontalTB, LineBox::isEmpty(), LineBox::justifyLine, LineBox::lastLine, KoSvgText::TextIndentInfo::length, LineChunk::length, length(), CharacterResult::lineEnd, CharacterResult::lineHeightBox(), NoBreak, NoChange, KoSvgText::CssLengthPercentage::Percentage, KoSvgTextProperties::propertyOrDefault(), LineBox::setCurrentChunkForPos(), SHAPE_PRECISION, KoSvgTextProperties::TextAlignAllId, KoSvgTextProperties::TextAlignLastId, textAnchorForTextAlign(), KoSvgTextProperties::TextIndentId, CharacterResult::totalBaselineOffset(), KoSvgText::CssLengthPercentage::unit, KoSvgText::CssLengthPercentage::value, KoSvgText::VerticalRL, and KoSvgTextProperties::WritingModeId.

◆ getEstimatedHeight()

static void KoSvgTextShapeLayoutFunc::getEstimatedHeight ( QVector< CharacterResult > & result,
const int index,
QRectF & wordBox,
const QRectF boundingBox,
KoSvgText::WritingMode writingMode )
static

getEstimatedHeight Adjust the wordbox with the estimated height.

Definition at line 386 of file KoSvgTextShapeLayoutFunc_inShape.cpp.

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}

References calculateLineHeight(), and KoSvgText::HorizontalTB.

◆ getFirstPosition()

static bool KoSvgTextShapeLayoutFunc::getFirstPosition ( QPointF & firstPoint,
QPainterPath p,
QRectF wordBox,
QPointF terminator,
KoSvgText::WritingMode writingMode,
bool ltr )
static

Precision of fitting the word box into the polygon to account for floating-point precision error.

Definition at line 90 of file KoSvgTextShapeLayoutFunc_inShape.cpp.

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}
qreal kisDistanceToLine(const QPointF &m, const QLineF &line)
Definition kis_global.h:234

References KoSvgText::HorizontalTB, kisDistanceToLine(), p, SHAPE_PRECISION, and KoSvgText::VerticalRL.

◆ getShapes()

QList< QPainterPath > KoSvgTextShapeLayoutFunc::getShapes ( QList< KoShape * > shapesInside,
QList< KoShape * > shapesSubtract,
const KoSvgTextProperties & properties )

Definition at line 26 of file KoSvgTextShapeLayoutFunc_inShape.cpp.

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}
QPointF p2
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

References KoPolygonUtils::offsetPolygons(), p, p2, KoSvgTextProperties::propertyOrDefault(), KoSvgTextProperties::ShapeMarginId, KoSvgTextProperties::ShapePaddingId, and KoShape::transformation().

◆ handleCollapseAndHang()

static void KoSvgTextShapeLayoutFunc::handleCollapseAndHang ( QVector< CharacterResult > & result,
LineChunk & chunk,
bool ltr,
bool isHorizontal )
static

Definition at line 194 of file KoSvgTextShapeLayoutFunc_lines.cpp.

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}
@ ConditionallyHang
Only hang if no space otherwise, only measured for justification if not hanging.

References LineChunk::chunkIndices, Collapse, LineChunk::conditionalHangEnd, ConditionallyHang, ForceHang, and LineChunk::length.

◆ lineHeightOffset()

static QPointF KoSvgTextShapeLayoutFunc::lineHeightOffset ( KoSvgText::WritingMode writingMode,
QVector< CharacterResult > & result,
LineBox & currentLine,
const bool firstLine,
const KoSvgText::ResolutionHandler resHandler )
static

This offsets the last line by it's ascent, and then returns the last line's descent.

< This is for determining the difference between a predicted line-height (for text-in-shape) and the actual line-height. Note: this is required to be positive.

When the line is empty, but caused by a hardbreak, we will need to use that hardbreak to space the line. This can only be done at this point as it would otherwise need to use visible characters.

Definition at line 103 of file KoSvgTextShapeLayoutFunc_lines.cpp.

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()];
127 calculateLineHeight(cr,
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}
QPointF baselineTop
Used to identify the top of the line for baseline-alignment.
QPointF baselineBottom
Used to identify the bottom of the line for baseline-alignment.

References LineBox::actualLineBottom, LineBox::actualLineTop, KoSvgText::ResolutionHandler::adjust(), LineBox::baselineBottom, LineBox::baselineTop, calculateLineHeight(), LineBox::chunks, LineBox::expectedLineTop, KoSvgText::HorizontalTB, KoSvgText::ResolutionHandler::roundToPixelHorizontal, KoSvgText::ResolutionHandler::roundToPixelVertical, and KoSvgText::VerticalLR.

◆ pointLessThan()

static bool KoSvgTextShapeLayoutFunc::pointLessThan ( const QPointF & a,
const QPointF & b )
static

Definition at line 248 of file KoSvgTextShapeLayoutFunc_inShape.cpp.

249{
250 return a.x() < b.x();
251}

◆ pointLessThanVertical()

static bool KoSvgTextShapeLayoutFunc::pointLessThanVertical ( const QPointF & a,
const QPointF & b )
static

Definition at line 253 of file KoSvgTextShapeLayoutFunc_inShape.cpp.

254{
255 return a.y() < b.y();
256}

◆ textAnchorForTextAlign()

static KoSvgText::TextAnchor KoSvgTextShapeLayoutFunc::textAnchorForTextAlign ( KoSvgText::TextAlign align,
KoSvgText::TextAlign alignLast,
bool ltr )
static

Definition at line 419 of file KoSvgTextShapeLayoutFunc_inShape.cpp.

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}
@ AlignCenter
Center text in line.
Definition KoSvgText.h:173

References KoSvgText::AlignCenter, KoSvgText::AlignEnd, KoSvgText::AlignJustify, KoSvgText::AlignLeft, KoSvgText::AlignRight, KoSvgText::AlignStart, KoSvgText::AnchorEnd, KoSvgText::AnchorMiddle, and KoSvgText::AnchorStart.