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)
 
bool pastTerminator (const QPointF point, const QPointF terminator, const KoSvgText::WritingMode writingMode)
 
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 444 of file KoSvgTextShapeLayoutFunc_lines.cpp.

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}
#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 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}
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 279 of file KoSvgTextShapeLayoutFunc_inShape.cpp.

280{
281 QVector<QLineF> lines;
282
283 QLineF baseLine;
284 QPointF lineTop;
285 QPointF lineBottom;
286 QRectF word = wordBox.normalized();
288
289 if (writingMode == KoSvgText::HorizontalTB) {
290 baseLine = QLineF(shape.boundingRect().left()-5, firstPos.y(), shape.boundingRect().right()+5, firstPos.y());
291 lineTop = QPointF(0, word.top());
292 lineBottom = QPointF(0, word.bottom());
293 } else {
294 baseLine = QLineF(firstPos.x(), shape.boundingRect().top()-5, firstPos.x(), shape.boundingRect().bottom()+5);
295 if (writingMode == KoSvgText::VerticalRL) {
296 lineTop = QPointF(word.left(), 0);
297 lineBottom = QPointF(word.right(), 0);
298 } else {
299 lineTop = QPointF(word.right(), 0);
300 lineBottom = QPointF(word.left(), 0);
301 }
302 }
303
304 QPolygonF polygon = shape.toFillPolygon();
305 QList<QPointF> intersects;
306 QLineF topLine = baseLine.translated(lineTop);
307 QLineF bottomLine = baseLine.translated(lineBottom);
308 for(int i = 0; i < polygon.size()-1; i++) {
309 QLineF line(polygon.at(i), polygon.at(i+1));
310 bool addedA = false;
311 QPointF intersectA;
312 QPointF intersectB;
313 QPointF intersect;
314 if (topLine.intersects(line, &intersect) == QLineF::BoundedIntersection) {
315 intersectA = intersect-lineTop;
316 intersects.append(intersectA);
317 addedA = true;
318 }
319 if (bottomLine.intersects(line, &intersect) == QLineF::BoundedIntersection) {
320 intersectB = intersect-lineBottom;
321 if (intersectA != intersectB || !addedA) {
322 intersects.append(intersectB);
323 }
324 }
325 }
326 if (!intersects.isEmpty()) {
327 intersects.append(baseLine.p1());
328 intersects.append(baseLine.p2());
329 }
330 if (writingMode == KoSvgText::HorizontalTB) {
331 std::sort(intersects.begin(), intersects.end(), pointLessThan);
332 } else {
333 std::sort(intersects.begin(), intersects.end(), pointLessThanVertical);
334 }
335
336
337 for (int i = 0; i< intersects.size()-1; i++) {
338 QLineF line(intersects.at(i), intersects.at(i+1));
339
340 if (!(shape.contains(line.translated(lineTop).center())
341 && shape.contains(line.translated(lineBottom).center()))
342 || line.length() == 0) {
343 continue;
344 }
345
346 QRectF lineBox = QRectF(line.p1() + lineTop, line.p2() + lineBottom).normalized();
347
348 QVector<QPointF> relevant;
349 for(int i = 0; i < polygon.size()-1; i++) {
350
351 if (lineBox.contains(polygon.at(i))) {
352 relevant.append(polygon.at(i));
353 }
354 }
355 qreal start = writingMode == KoSvgText::HorizontalTB? lineBox.left(): lineBox.top();
356 qreal end = writingMode == KoSvgText::HorizontalTB? lineBox.right(): lineBox.bottom();
357 for(int j = 0; j < relevant.size(); j++) {
358 QPointF current = relevant.at(j);
359
360 if (writingMode == KoSvgText::HorizontalTB) {
361 if (current.x() < line.center().x()) {
362 start = qMax(current.x(), start);
363 } else if (current.x() > line.center().x()) {
364 end = qMin(current.x(), end);
365 }
366 } else {
367 if (current.y() < line.center().y()) {
368 start = qMax(current.y(), start);
369 } else if (current.y() > line.center().y()) {
370 end = qMin(current.y(), end);
371 }
372 }
373 }
374 if (writingMode == KoSvgText::HorizontalTB) {
375
376 QLineF newLine(start, line.p1().y(), end, line.p2().y());
377 if (!lines.isEmpty()) {
378 if (lines.last().p2() == intersects.at(i)) {
379 newLine.setP1(lines.last().p1());
380 lines.removeLast();
381 }
382 }
383 lines.append(newLine);
384 } else {
385 QLineF newLine(line.p1().x(), start, line.p2().x(), end);
386 if (!lines.isEmpty()) {
387 if (lines.last().p2() == intersects.at(i)) {
388 newLine.setP1(lines.last().p1());
389 lines.removeLast();
390 }
391 }
392 lines.append(newLine);
393 }
394 }
395
396 return lines;
397}
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;

< 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;

< Current position with advances of each character.

< Current line offset.

< Whether to break a line.

Definition at line 459 of file KoSvgTextShapeLayoutFunc_inShape.cpp.

465{
466 QVector<LineBox> lineBoxes;
469 bool ltr = direction == KoSvgText::DirectionLeftToRight;
470 bool isHorizontal = writingMode == KoSvgText::HorizontalTB;
473 KoSvgText::TextAnchor anchor = textAnchorForTextAlign(align, alignLast, ltr);
474
475 QPointF textIndent;
477
478 QVector<int> wordIndices;
480 QRectF wordBox;
481 QPointF wordAdvance;
482
483 LineBox currentLine;
484 bool indentLine = true;
485
486 QPointF currentPos = writingMode == KoSvgText::VerticalRL? shapes.first().boundingRect().topRight()
487 : shapes.first().boundingRect().topLeft();
488 QPointF lineOffset = currentPos;
489
490 QListIterator<int> it(logicalToVisual.keys());
491 QListIterator<QPainterPath> shapesIt(shapes);
492 if (shapes.isEmpty()) {
493 return lineBoxes;
494 }
495 {
496 // Calculate the default pos.
497 qreal fontSize = properties.fontSize().value;
498 QRectF wordBox = isHorizontal? QRectF(0, fontSize * -0.8, SHAPE_PRECISION, fontSize)
499 : QRectF(fontSize * -0.5, 0, fontSize, SHAPE_PRECISION);
500 getFirstPosition(startPos, shapes.first(), wordBox, currentPos, writingMode, ltr);
501 }
502 QPainterPath currentShape;
503 while (it.hasNext()) {
504 int index = it.next();
505 result[index].calculateAndApplyTabsize(wordAdvance + currentPos, isHorizontal, resHandler);
506 CharacterResult charResult = result.at(index);
507 if (!charResult.addressable) {
508 continue;
509 }
510
511 bool softBreak = false;
512 bool doNotCountAdvance =
513 ((charResult.lineEnd != LineEdgeBehaviour::NoChange)
514 && !(currentLine.isEmpty() && wordIndices.isEmpty()));
515 if (!doNotCountAdvance) {
516 if (wordIndices.isEmpty()) {
517 wordBox = charResult.lineHeightBox().translated(charResult.totalBaselineOffset());
518 wordAdvance = charResult.advance;
519 } else {
520 wordBox |= charResult.lineHeightBox().translated(wordAdvance+charResult.totalBaselineOffset());
521 wordAdvance += charResult.advance;
522 }
523 }
524 wordIndices.append(index);
525 currentLine.lastLine = !it.hasNext();
526 if (currentLine.lastLine) {
527 currentLine.justifyLine = alignLast == KoSvgText::AlignJustify;
528 }
529
530 if (charResult.breakType != BreakType::NoBreak || currentLine.lastLine) {
531 if (currentLine.chunks.isEmpty() || currentLine.lastLine) {
532 softBreak = true;
533 }
534
535 for (int i = currentLine.currentChunk; i < currentLine.chunks.size(); i++) {
536 if (i == -1) {
537 currentLine.currentChunk = 0;
538 i = 0;
539 }
540 QLineF line = currentLine.chunks.value(i).length;
541 qreal lineLength = isHorizontal ? (currentPos - line.p1() + wordAdvance).x()
542 : (currentPos - line.p1() + wordAdvance).y();
543 if (qRound((abs(lineLength) - line.length())) > 0) {
544 if (i == currentLine.chunks.size()-1) {
545 softBreak = true;
546 break;
547 } else {
548 QLineF nextLine = currentLine.chunks.value(i+1).length;
549 if (isHorizontal) {
550 currentPos.setX(ltr? qMax(nextLine.p1().x(), currentPos.x()):
551 qMin(nextLine.p1().x(), currentPos.x()));
552 } else {
553 currentPos.setY(nextLine.p1().y());
554 }
555 }
556 } else {
557 currentLine.currentChunk = i;
558 addWordToLine(result, currentPos, wordIndices, currentLine, ltr, isHorizontal);
559 break;
560 }
561 }
562 }
563
564 if (softBreak) {
565 if (!currentLine.isEmpty()) {
566 finalizeLine(result, currentPos, currentLine, lineOffset, anchor, writingMode, ltr, true, true, resHandler);
567 lineBoxes.append(currentLine);
568 indentLine = false;
569 }
570 // Not adding indent to the (first) word box means it'll overflow if there's no room,
571 // but being too strict might end with the whole text disappearing. Given Krita's text layout is
572 // in an interactive context, ugly result might be more communicative than all text disappearing.
573 bool ind = textIndentInfo.hanging? !indentLine: indentLine;
574 QPointF indent = ind? textIndent: QPointF();
575 bool foundFirst = false;
576 bool needNewLine = true;
577 // add text indent to wordbox.
578 if (!currentShape.isEmpty()) {
579 // we're going to try and get an offset line first before trying get first pos.
580 // This gives more stable results on curved shapes.
581 getEstimatedHeight(result, index, wordBox, currentShape.boundingRect(), writingMode);
582 currentPos -= writingMode == KoSvgText::VerticalRL? wordBox.topRight(): wordBox.topLeft();
583
584 currentLine = LineBox(findLineBoxesForFirstPos(currentShape, currentPos, wordBox, writingMode), ltr, indent, resHandler);
585 qreal length = isHorizontal? wordBox.width(): wordBox.height();
586 for (int i = 0; i < currentLine.chunks.size(); i++) {
587 const LineChunk chunk = currentLine.chunks.at(i);
588 // We have a new line, so we find the first chunk that fits the word.
589 if ( chunk.length.length() > length ) {
590 currentLine.currentChunk = i;
591 foundFirst = true;
592 needNewLine = false;
593 break;
594 }
595 }
596 }
597 /*
598 * In theory we could have overflow wrap for shapes, but it'd require either generalizing
599 * the line-filling portion above and this new line seeking portion, or somehow reverting
600 * the iterator over the results to be on the last fitted glyph (which'd still require
601 * generalizing the line-filling portion about), and I am unsure how to do that.
602 * Either way, this place here is where you'd check for overflow wrap.
603 */
604 while(!foundFirst) {
605 foundFirst = getFirstPosition(currentPos, currentShape, wordBox, lineOffset, writingMode, ltr);
606 if (foundFirst || !shapesIt.hasNext()) {
607 break;
608 }
609 currentShape = shapesIt.next();
610 getEstimatedHeight(result, index, wordBox, currentShape.boundingRect(), writingMode);
611
612 bool indentPercent = textIndentInfo.length.unit == KoSvgText::CssLengthPercentage::Percentage;
613 qreal textIdentValue = textIndentInfo.length.value;
614 if (isHorizontal) {
615 if (indentPercent) {
616 textIdentValue *= currentShape.boundingRect().width();
617 }
618 textIndent = resHandler.adjust(QPointF(textIdentValue, 0));
619 } else {
620 if (indentPercent) {
621 textIdentValue *= currentShape.boundingRect().height();
622 }
623 textIndent = resHandler.adjust(QPointF(0, textIdentValue));
624 }
625 bool ind = textIndentInfo.hanging? !indentLine: indentLine;
626 indent = ind? textIndent: QPointF();
627 currentPos = writingMode == KoSvgText::VerticalRL? currentShape.boundingRect().topRight(): currentShape.boundingRect().topLeft();
628 lineOffset = currentPos;
629 }
630
631 bool lastDitch = false;
632 if (!foundFirst && lineBoxes.isEmpty() && !wordIndices.isEmpty() && !currentShape.isEmpty()) {
633 // Last-ditch attempt to get any kind of positioning to happen.
634 // This typically happens when wrapping has been disabled.
635 wordBox = result[wordIndices.first()].lineHeightBox().translated(result[wordIndices.first()].totalBaselineOffset());
636 foundFirst = getFirstPosition(currentPos, currentShape, wordBox, lineOffset, writingMode, ltr);
637 lastDitch = true;
638 }
639
640 if (foundFirst) {
641
642 if (needNewLine) {
643 currentLine = LineBox(findLineBoxesForFirstPos(currentShape, currentPos, wordBox, writingMode), ltr, indent, resHandler);
644 // We could set this to find the first fitting width, but it's better to try and improve the precision of the firstpos algorithm,
645 // as this gives more stable results.
646 currentLine.setCurrentChunkForPos(currentPos, isHorizontal);
647 }
648 const qreal expectedLineT = isHorizontal? wordBox.top():
649 writingMode == KoSvgText::VerticalRL? wordBox.right(): wordBox.left();
650
651 currentLine.firstLine = lineBoxes.isEmpty();
652 currentLine.expectedLineTop = expectedLineT;
653 currentLine.justifyLine = align == KoSvgText::AlignJustify;
654 currentPos = currentLine.chunk().length.p1();
655 lineOffset = currentPos;
656 currentPos += needNewLine? currentLine.textIndent: QPointF();
657
658 if (lastDitch) {
659 QVector<int> wordNew;
660 QPointF advance = currentPos;
661 Q_FOREACH(const int i, wordIndices) {
662 advance += result[i].advance;
663 if (currentShape.contains(advance)) {
664 wordNew.append(i);
665 } else {
666 result[i].hidden = true;
667 result[i].cssPosition = advance-result[i].advance;
668 }
669 wordIndices = wordNew;
670 }
671 }
672 addWordToLine(result, currentPos, wordIndices, currentLine, ltr, isHorizontal);
673 } else {
674 currentLine = LineBox();
675 QPointF advance = currentPos;
676 Q_FOREACH (const int j, wordIndices) {
677 result[j].cssPosition = advance;
678 advance += result[j].advance;
679 result[j].hidden = true;
680 }
681 }
682 }
683
684 if (charResult.breakType == BreakType::HardBreak) {
685 finalizeLine(result, currentPos, currentLine, lineOffset, anchor, writingMode, ltr, true, true, resHandler);
686 lineBoxes.append(currentLine);
687 currentLine = LineBox();
688 indentLine = textIndentInfo.hanging? false: textIndentInfo.eachLine;
689 }
690 }
691 finalizeLine(result, currentPos, currentLine, lineOffset, anchor, writingMode, ltr, true, true, resHandler);
692 lineBoxes.append(currentLine);
693 return lineBoxes;
694}
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(), LineBox::textIndent, 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 404 of file KoSvgTextShapeLayoutFunc_inShape.cpp.

409{
410 bool isHorizontal = writingMode == KoSvgText::HorizontalTB;
411 QPointF totalAdvance = wordBox.bottomRight() - wordBox.topLeft();
412 qreal maxAscent = isHorizontal? wordBox.top(): wordBox.right();
413 qreal maxDescent = isHorizontal? wordBox.bottom(): wordBox.left();
414
415 for (int i=index; i<result.size(); i++) {
416 if (!result.at(i).addressable || result.at(i).hidden) {
417 continue;
418 }
419 totalAdvance += result.at(i).advance;
420 if ((totalAdvance.x() > boundingBox.width() && isHorizontal) ||
421 (totalAdvance.y() > boundingBox.height() && !isHorizontal)) {
422 break;
423 }
424 calculateLineHeight(result.at(i), maxAscent, maxDescent, isHorizontal, true);
425 }
426 if (writingMode == KoSvgText::HorizontalTB) {
427 wordBox.setTop(maxAscent);
428 wordBox.setBottom(maxDescent);
429 } else {
430 // vertical lr has top at the right even though block flow is also to the right.
431 wordBox.setRight(maxAscent);
432 wordBox.setLeft(maxDescent);
433 }
434}

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 108 of file KoSvgTextShapeLayoutFunc_inShape.cpp.

114{
115 if (wordBox.isEmpty()) {
116 // line-height: 0 will give us empty rects, make them slightly tall/wide
117 // to avoid issues with complex QRectF operations.
118 if (writingMode == KoSvgText::HorizontalTB) {
119 wordBox.setHeight(1e-3);
120 } else {
121 wordBox.setWidth(1e-3);
122 }
123 }
124
127
128 QVector<QPointF> candidatePositions;
129 QRectF word = wordBox.normalized();
130 word.translate(-wordBox.topLeft());
131 // Slightly shrink the box to account for precision error.
133
134 QPointF terminatorAdjusted = terminator;
135 Q_FOREACH(const QPolygonF polygon, p.toFillPolygons()) {
136 QVector<QLineF> offsetPoly;
137 for(int i = 0; i < polygon.size()-1; i++) {
138 QLineF line;
139 line.setP1(polygon.at(i));
140 line.setP2(polygon.at(i+1));
141 if (!(pastTerminator(line.p1(), terminatorAdjusted, writingMode)
142 || pastTerminator(line.p2(), terminatorAdjusted, writingMode))) continue;
143
144 if (line.angle() == 0.0 || line.angle() == 180.0) {
145 qreal offset = word.center().y();
146 offsetPoly.append(line.translated(0, offset));
147 offsetPoly.append(line.translated(0, -offset));
148 } else if (line.angle() == 90.0 || line.angle() == 270.0) {
149 qreal offset = word.center().x();
150 offsetPoly.append(line.translated(offset, 0));
151 offsetPoly.append(line.translated(-offset, 0));
152 } else {
153 qreal tAngle = fmod(line.angle(), 180.0);
154 QPointF cPos = tAngle > 90? line.center() + QPointF(-word.center().x(), word.center().y()): line.center() + word.center();
155 qreal offset = kisDistanceToLine(cPos, line);
156 const QPointF vectorT(qCos(qDegreesToRadians(tAngle)), -qSin(qDegreesToRadians(tAngle)));
157 QPointF vectorN(-vectorT.y(), vectorT.x());
158 QPointF offsetP = QPointF() - (0.0 * vectorT) + (offset * vectorN);
159 offsetPoly.append(line.translated(offsetP));
160 offsetPoly.append(line.translated(-offsetP));
161 }
162 }
163 if (writingMode == KoSvgText::HorizontalTB) {
164 terminatorAdjusted = terminator + word.center();
165 QLineF top(polygon.boundingRect().topLeft(), polygon.boundingRect().topRight());
166 offsetPoly.append(top.translated(0, terminatorAdjusted.y()));
167 } else if (writingMode == KoSvgText::VerticalRL) {
168 terminatorAdjusted = terminator - word.center();
169 QLineF top(terminatorAdjusted.x(), polygon.boundingRect().top(),
170 terminatorAdjusted.x(), polygon.boundingRect().bottom());
171 offsetPoly.append(top);
172 } else{
173 terminatorAdjusted = terminator + word.center();
174 QLineF top(terminatorAdjusted.x(), polygon.boundingRect().top(),
175 terminatorAdjusted.x(), polygon.boundingRect().bottom());
176 offsetPoly.append(top);
177 }
178 for (int i=0; i < offsetPoly.size(); i++) {
179 QLineF line = offsetPoly.at(i);
180 for (int j=i; j< offsetPoly.size(); j++){
181 QLineF line2 = offsetPoly.at(j);
182 QPointF intersectPoint;
183 QLineF::IntersectType intersect = line.intersects(line2, &intersectPoint);
184 if (intersect != QLineF::NoIntersection) {
185 // should proly handle 'reflex' vertices better.
186 if (!p.contains(intersectPoint)) {
187 continue;
188 }
189 if(!p.contains(word.translated(intersectPoint-word.center()))) {
190 continue;
191 }
192 if (!candidatePositions.contains(intersectPoint)) {
193 candidatePositions.append(intersectPoint);
194 }
195 }
196 }
197 }
198 }
199 if (candidatePositions.isEmpty()) {
200
201 return false;
202 }
203 QPointF firstPointC = writingMode == KoSvgText::VerticalRL? p.boundingRect().bottomLeft(): p.boundingRect().bottomRight();
204 Q_FOREACH(const QPointF candidate, candidatePositions) {
205 if (writingMode == KoSvgText::HorizontalTB) {
206 if (terminatorAdjusted.y() - candidate.y() < SHAPE_PRECISION) {
207
208 if (firstPointC.y() - candidate.y() > SHAPE_PRECISION) {
209 firstPointC = candidate;
210 } else if (firstPointC.y() - candidate.y() > -SHAPE_PRECISION) {
211 if (ltr) {
212 if (candidate.x() < firstPointC.x()) {
213 firstPointC = candidate;
214 }
215 } else {
216 if (candidate.x() > firstPointC.x()) {
217 firstPointC = candidate;
218 }
219 }
220 }
221 }
222 } else if (writingMode == KoSvgText::VerticalRL) {
223 if (terminatorAdjusted.x() - candidate.x() >= -SHAPE_PRECISION) {
224
225 if (firstPointC.x() - candidate.x() < -SHAPE_PRECISION) {
226 firstPointC = candidate;
227 } else if (firstPointC.x() - candidate.x() < SHAPE_PRECISION) {
228 if (ltr) {
229 if (candidate.y() < firstPointC.y()) {
230 firstPointC = candidate;
231 }
232 } else {
233 if (candidate.y() > firstPointC.y()) {
234 firstPointC = candidate;
235 }
236 }
237 }
238 }
239 } else {
240 if (terminatorAdjusted.x() - candidate.x() < SHAPE_PRECISION) {
241
242 if (firstPointC.x() - candidate.x() > SHAPE_PRECISION) {
243 firstPointC = candidate;
244 } else if (firstPointC.x() - candidate.x() > -SHAPE_PRECISION) {
245 if (ltr) {
246 if (candidate.y() < firstPointC.y()) {
247 firstPointC = candidate;
248 }
249 } else {
250 if (candidate.y() > firstPointC.y()) {
251 firstPointC = candidate;
252 }
253 }
254 }
255 }
256 }
257 }
258 if (!p.contains(firstPointC)) {
259 return false;
260 }
261 firstPointC -= word.center();
262 firstPointC -= wordBox.topLeft();
263 firstPoint = firstPointC;
264
265 return true;
266}
qreal kisDistanceToLine(const QPointF &m, const QLineF &line)
Definition kis_global.h:234
bool pastTerminator(const QPointF point, const QPointF terminator, const KoSvgText::WritingMode writingMode)

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

◆ getShapes()

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

Definition at line 27 of file KoSvgTextShapeLayoutFunc_inShape.cpp.

28{
29 // the boost polygon method requires (and gives best result) on a inter-based polygon,
30 // so we need to scale up. The scale selected here is the size freetype coordinates give to a single pixel.
31 qreal scale = 64.0;
32 QTransform precisionTF = QTransform::fromScale(scale, scale);
33
34 KoSvgTextProperties resolved = properties;
36
39
40 QPainterPath subtract;
41 Q_FOREACH(const KoShape *shape, shapesSubtract) {
42 const KoPathShape *path = dynamic_cast<const KoPathShape*>(shape);
43 if (path) {
44 QPainterPath p = path->transformation().map(path->outline());
45 p.setFillRule(path->fillRule());
46 // grow each polygon here with the shape margin size.
47 if (shapeMargin > 0) {
48 QList<QPolygon> subpathPolygons;
49 Q_FOREACH(QPolygonF subPath, p.toSubpathPolygons()) {
50 subpathPolygons.append(precisionTF.map(subPath).toPolygon());
51 }
52 subpathPolygons = KoPolygonUtils::offsetPolygons(subpathPolygons, shapeMargin);
53 p.clear();
54 Q_FOREACH (const QPolygon poly, subpathPolygons) {
55 p.addPolygon(poly);
56 }
57 } else {
58 p = precisionTF.map(p);
59 }
60
61 subtract.addPath(p);
62 }
63 }
64
66 Q_FOREACH(const KoShape *shape, shapesInside) {
67 const KoPathShape *path = dynamic_cast<const KoPathShape*>(shape);
68 if (path) {
69 QPainterPath p = path->transformation().map(path->outline());
70 p.setFillRule(path->fillRule());
71 QPainterPath p2;
72 p2.setFillRule(path->fillRule());
73
74 QList<QPolygon> subpathPolygons;
75 Q_FOREACH(QPolygonF subPath, p.toSubpathPolygons()) {
76 subpathPolygons.append(precisionTF.map(subPath).toPolygon());
77 }
78 subpathPolygons = KoPolygonUtils::offsetPolygons(subpathPolygons, -shapePadding);
79
80 for (int i=0; i < subpathPolygons.size(); i++) {
81 QPolygonF subpathPoly = subpathPolygons.at(i);
82 Q_FOREACH(QPolygonF subtractPoly, subtract.toSubpathPolygons()) {
83 if (subpathPoly.intersects(subtractPoly)) {
84 subpathPoly = subpathPoly.subtracted(subtractPoly);
85 }
86 }
87 p2.addPolygon(subpathPoly);
88 }
89 p2 = KisAlgebra2D::trySimplifyPath(precisionTF.inverted().map(p2), 1.0);
90 p2.closeSubpath();
91 shapes.append(p2);
92 }
93 }
94 return shapes;
95}
float value(const T *src, size_t ch)
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:383
static const KoSvgTextProperties & defaultProperties()
void inheritFrom(const KoSvgTextProperties &parentProperties, bool resolve=false)
QPainterPath trySimplifyPath(const QPainterPath &path, qreal lengthThreshold)
trySimplifyPath Tries to simplify a QPainterPath

References KoSvgTextProperties::defaultProperties(), KoSvgTextProperties::inheritFrom(), KoPolygonUtils::offsetPolygons(), p, p2, KoSvgTextProperties::propertyOrDefault(), KoSvgTextProperties::ShapeMarginId, KoSvgTextProperties::ShapePaddingId, KoShape::transformation(), KisAlgebra2D::trySimplifyPath(), and value().

◆ 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.

◆ pastTerminator()

bool KoSvgTextShapeLayoutFunc::pastTerminator ( const QPointF point,
const QPointF terminator,
const KoSvgText::WritingMode writingMode )

Definition at line 97 of file KoSvgTextShapeLayoutFunc_inShape.cpp.

97 {
98 if (writingMode == KoSvgText::HorizontalTB) {
99 return (terminator.y() - point.y() < SHAPE_PRECISION);
100 } else if (writingMode == KoSvgText::VerticalRL) {
101 return (point.x() - terminator.x() < SHAPE_PRECISION);
102 } else {
103 return (terminator.x() - point.x() < SHAPE_PRECISION);
104 }
105 return false;
106}

References KoSvgText::HorizontalTB, SHAPE_PRECISION, and KoSvgText::VerticalRL.

◆ pointLessThan()

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

Definition at line 268 of file KoSvgTextShapeLayoutFunc_inShape.cpp.

269{
270 return a.x() < b.x();
271}

◆ pointLessThanVertical()

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

Definition at line 273 of file KoSvgTextShapeLayoutFunc_inShape.cpp.

274{
275 return a.y() < b.y();
276}

◆ textAnchorForTextAlign()

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

Definition at line 437 of file KoSvgTextShapeLayoutFunc_inShape.cpp.

438{
439 KoSvgText::TextAlign compare = align;
440 if (align == KoSvgText::AlignJustify) {
441 compare = alignLast;
442 }
443 if (compare == KoSvgText::AlignStart) {
445 } else if (compare == KoSvgText::AlignCenter) {
447 } else if (compare == KoSvgText::AlignEnd) {
449 } else if (compare == KoSvgText::AlignLeft) {
451 } else if (compare == KoSvgText::AlignRight) {
453 } else if (align == KoSvgText::AlignJustify) {
455 }
457}
@ 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.