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

59{
60 QPointF lineAdvance = currentPos;
61
62 LineChunk currentChunk = currentLine.chunk();
63
64 Q_FOREACH (const int j, wordIndices) {
65 CharacterResult cr = result.at(j);
66 if (currentLine.isEmpty() && j == wordIndices.first()) {
67 if (result.at(j).lineStart == LineEdgeBehaviour::Collapse) {
68 if (isHorizontal) {
69 result[j].scaleCharacterResult(0.0, 1.0);
70 } else {
71 result[j].scaleCharacterResult(1.0, 0.0);
72 }
73 result[j].hidden = true;
74 continue;
75 }
76 cr.anchored_chunk = true;
77 if (result.at(j).lineStart == LineEdgeBehaviour::ForceHang && currentLine.firstLine) {
78 currentPos -= cr.advance;
79 cr.isHanging = true;
80 }
81 }
82 calculateLineHeight(cr, currentLine.actualLineTop, currentLine.actualLineBottom, isHorizontal, !cr.anchored_chunk);
83
84 cr.cssPosition = currentPos;
85 cr.calculateAndApplyTabsize(currentPos, isHorizontal, KoSvgText::ResolutionHandler());
86 currentPos += cr.advance;
87 lineAdvance = currentPos;
88
89 result[j] = cr;
90 currentChunk.boundingBox |= cr.layoutBox().translated(cr.cssPosition + cr.totalBaselineOffset());
91 }
92 currentPos = lineAdvance;
93 currentChunk.chunkIndices += wordIndices;
94 currentLine.setCurrentChunk(currentChunk);
95 wordIndices.clear();
96}
@ 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 247 of file KoSvgTextShapeLayoutFunc_lines.cpp.

255{
256 const QVector<int> lineIndices = chunk.chunkIndices;
257 qreal shift = isHorizontal ? anchorPoint.x() : anchorPoint.y();
258
259 qreal a = 0;
260 qreal b = 0;
261
262 bool first = true;
263 Q_FOREACH (int i, lineIndices) {
264 if (!result.at(i).addressable || result.at(i).hidden || (result.at(i).isHanging && result.at(i).anchored_chunk)) {
265 continue;
266 }
267
268 QPointF p = result.at(i).finalPosition;
269 QPointF d = result.at(i).advance;
270 if (result.at(i).isHanging) {
271 d -= chunk.conditionalHangEnd;
272 if (!ltr) {
273 p += chunk.conditionalHangEnd;
274 }
275 }
276 const qreal pos = isHorizontal ? p.x() : p.y();
277 const qreal advance = isHorizontal ? d.x() : d.y();
278
279 if (first) {
280 a = qMin(pos, pos + advance);
281 b = qMax(pos, pos + advance);
282 first = false;
283 } else {
284 a = qMin(a, qMin(pos, pos + advance));
285 b = qMax(b, qMax(pos, pos + advance));
286 }
287 }
288
289
290 if (anchor == KoSvgText::AnchorStart) {
291 const qreal indent = isHorizontal ? textIndent.x() : textIndent.y();
292 if (ltr) {
293 a -= indent;
294 } else {
295 b += indent;
296 }
297 }
298
299 if ((anchor == KoSvgText::AnchorStart && ltr) || (anchor == KoSvgText::AnchorEnd && !ltr)) {
300 shift -= a;
301
302 } else if ((anchor == KoSvgText::AnchorEnd && ltr) || (anchor == KoSvgText::AnchorStart && !ltr)) {
303 shift -= b;
304
305 } else {
306 shift -= ((a + b) * 0.5);
307 }
308
309 QPointF shiftP = resHandler.adjust(isHorizontal ? QPointF(shift, 0) : QPointF(0, shift));
310 Q_FOREACH (int j, lineIndices) {
311 result[j].cssPosition = result[j].cssPosition+shiftP;
312 result[j].finalPosition = result.at(j).cssPosition;
313 }
314 chunk.boundingBox.translate(shiftP);
315}
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 443 of file KoSvgTextShapeLayoutFunc_lines.cpp.

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

330{
331 bool isHorizontal = writingMode == KoSvgText::HorizontalTB;
332
333 bool firstLine = textInShape? true: currentLine.firstLine;
334
335 for (auto currentChunk = currentLine.chunks.begin(); currentChunk != currentLine.chunks.end(); currentChunk++) {
336 QMap<int, int> visualToLogical;
337 Q_FOREACH (int j, currentChunk->chunkIndices) {
338 visualToLogical.insert(result.at(j).visualIndex, j);
339 }
340 currentPos = lineOffset;
341
342 handleCollapseAndHang(result, *currentChunk, ltr, isHorizontal);
343
344 QPointF justifyOffset;
345 QVector<int> before;
346 QVector<int> after;
347
348 if (currentLine.justifyLine) {
349 double hangingGlyphLength = isHorizontal? currentChunk->conditionalHangEnd.x(): currentChunk->conditionalHangEnd.y();
350 QPointF advanceLength;
351 bool first = true;
352 Q_FOREACH (int j, visualToLogical.values()) {
353 if (!result.at(j).addressable || result.at(j).hidden) {
354 continue;
355 }
356 advanceLength += result.at(j).advance;
357 if (result.at(j).isHanging) {
358 if (result.at(j).anchored_chunk) {
359 hangingGlyphLength += isHorizontal? result.at(j).advance.x(): result.at(j).advance.y();
360 }
361 continue;
362 }
363 bool last = visualToLogical.values().last() == j;
364 if (!last) {
365 last = result.at(j+1).isHanging;
366 }
367
368 if (result.at(j).justifyBefore && !first) {
369 before.append(j);
370 }
371 if (result.at(j).justifyAfter && !last) {
372 after.append(j);
373 }
374 first = false;
375 }
376
377 int justificationCount = before.size()+after.size();
378 if (justificationCount > 0) {
379 const QPointF indent = currentChunk == currentLine.chunks.begin()? currentLine.textIndent: QPointF();
380 const QLineF modified = QLineF(currentChunk->length.p1()+indent, currentChunk->length.p2());
381 if (isHorizontal) {
382 double val = modified.length() + hangingGlyphLength - advanceLength.x();
383 val = val / justificationCount;
384 justifyOffset = QPointF(val, 0);
385 } else {
386 double val = modified.length() + hangingGlyphLength - advanceLength.y();
387 val = val / justificationCount;
388 justifyOffset = QPointF(0, val);
389 }
390 }
391 }
392
393 Q_FOREACH (const int j, visualToLogical.values()) {
394 if (!result.at(j).addressable) {
395 continue;
396 }
397 if ((result.at(j).isHanging && result.at(j).anchored_chunk)) {
398 if (ltr) {
399 result[j].cssPosition = currentPos - result.at(j).advance;
400 result[j].finalPosition = result[j].cssPosition;
401 } else {
402 result[j].cssPosition = currentPos;
403 result[j].finalPosition = result[j].cssPosition;
404 }
405 } else {
406 if (before.contains(j)) {
407 currentPos += justifyOffset;
408 }
409 result[j].cssPosition = currentPos;
410 result[j].finalPosition = currentPos;
411 currentPos = currentPos + result.at(j).advance;
412 if (after.contains(j)) {
413 currentPos += justifyOffset;
414 }
415 }
416 }
417
418 if (inlineSize) {
419 QPointF anchorPoint = currentChunk->length.p1();
420 if (textInShape) {
421 if (anchor == KoSvgText::AnchorMiddle) {
422 anchorPoint = currentChunk->length.center();
423 } else if (anchor == KoSvgText::AnchorEnd) {
424 anchorPoint = currentChunk->length.p2();
425 }
426 }
427 applyInlineSizeAnchoring(result, *currentChunk, anchor, anchorPoint, ltr, isHorizontal, currentLine.textIndent, resHandler);
428 } else {
429 if (!ltr) {
430 // RTL relies on the start being 0.0, so we need to align it to the startAnchor.
431 applyInlineSizeAnchoring(result, *currentChunk, KoSvgText::AnchorStart, currentChunk->length.p1(), ltr, isHorizontal, currentLine.textIndent, resHandler);
432 } else {
433 // this adds a length for preformated text, only useful for debug.
434 currentChunk->length.setLength(isHorizontal? currentChunk->boundingBox.width(): currentChunk->boundingBox.height());
435 }
436 }
437 }
438 lineOffset += lineHeightOffset(writingMode, result, currentLine, firstLine, resHandler);
439 currentPos = lineOffset;
440}
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, 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, 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:378
static const KoSvgTextProperties & defaultProperties()
void inheritFrom(const KoSvgTextProperties &parentProperties, bool resolve=false, bool onlyFontAndLineHeight=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 193 of file KoSvgTextShapeLayoutFunc_lines.cpp.

194{
195 QVector<int> lineIndices = chunk.chunkIndices;
196 QPointF endPos = chunk.length.p2();
197
198 if (!lineIndices.isEmpty()) {
199 QVectorIterator<int> it(lineIndices);
200 it.toBack();
201 while (it.hasPrevious()) {
202 int lastIndex = it.previous();
203 if (result.at(lastIndex).lineEnd == LineEdgeBehaviour::Collapse) {
204 result[lastIndex].hidden = true;
205 // We literally collapse the advance of the last collapsed white-space to ensure it may
206 // still be possible to track it by cursor movement.
207 result[lastIndex].advance = QPointF();
208 if (isHorizontal) {
209 result[lastIndex].inkBoundingBox.setWidth(0);
210 } else {
211 result[lastIndex].inkBoundingBox.setHeight(0);
212 }
213 } else if (result.at(lastIndex).lineEnd == LineEdgeBehaviour::ConditionallyHang) {
214 if (ltr) {
215 QPointF hangPos = result[lastIndex].cssPosition + result[lastIndex].advance;
216 if (isHorizontal) {
217 if (hangPos.x() > endPos.x()) {
218 result[lastIndex].isHanging = true;
219 chunk.conditionalHangEnd = hangPos - endPos;
220 }
221 } else {
222 if (hangPos.y() > endPos.y()) {
223 result[lastIndex].isHanging = true;
224 chunk.conditionalHangEnd = hangPos - endPos;
225 }
226 }
227 } else {
228 QPointF hangPos = result[lastIndex].cssPosition;
229 if (hangPos.x() < endPos.x()) {
230 result[lastIndex].isHanging = true;
231 chunk.conditionalHangEnd = hangPos - endPos;
232 }
233 }
234
235 } else if (result.at(lastIndex).lineEnd == LineEdgeBehaviour::ForceHang) {
236 result[lastIndex].isHanging = true;
237 chunk.conditionalHangEnd = result[lastIndex].advance;
238 }
239 if (result.at(lastIndex).lineEnd != LineEdgeBehaviour::Collapse) {
240 break;
241 }
242 }
243 }
244}
@ 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 102 of file KoSvgTextShapeLayoutFunc_lines.cpp.

107{
108 QPointF lineTop;
109 QPointF lineBottom;
110 QPointF correctionOffset;
113
114 if (currentLine.chunks.isEmpty()) {
115 return QPointF();
116 } else if (currentLine.chunks.size() == 1 && currentLine.actualLineTop == 0 &&
117 currentLine.actualLineBottom == 0){
123 QVector<int> chunkIndices = currentLine.chunks[0].chunkIndices;
124 if (chunkIndices.size() > 0) {
125 CharacterResult cr = result[chunkIndices.first()];
126 calculateLineHeight(cr,
127 currentLine.actualLineTop,
128 currentLine.actualLineBottom,
129 writingMode == KoSvgText::HorizontalTB,
130 false);
131 result[chunkIndices.first()].anchored_chunk = true;
132 }
133 }
134
135 // We do qmin/qmax here so that later, the correction offset will not go below either value.
136 const qreal expectedLineTop = writingMode == KoSvgText::HorizontalTB? qMin(currentLine.expectedLineTop, currentLine.actualLineTop):
137 qMax(currentLine.expectedLineTop, currentLine.actualLineTop);
138 if (writingMode == KoSvgText::HorizontalTB) {
139 currentLine.baselineTop = QPointF(0, currentLine.actualLineTop);
140 currentLine.baselineBottom = QPointF(0, currentLine.actualLineBottom);
141 correctionOffset = QPointF(0, -expectedLineTop) + currentLine.baselineTop;
142 lineTop = -currentLine.baselineTop;
143 lineBottom = currentLine.baselineBottom;
144 } else if (writingMode == KoSvgText::VerticalLR) {
145 currentLine.baselineTop = QPointF(currentLine.actualLineTop, 0);
146 currentLine.baselineBottom = QPointF(currentLine.actualLineBottom, 0);
147 correctionOffset = QPointF(-expectedLineTop, 0) + currentLine.baselineTop;
148 // Note: while Vertical LR goes left-to-right in its lines, its lines themselves are
149 // oriented with the top pointed in the positive x direction.
150 lineBottom = currentLine.baselineTop;
151 lineTop = -currentLine.baselineBottom;
152 } else {
153 currentLine.baselineTop = QPointF(currentLine.actualLineTop, 0);
154 currentLine.baselineBottom = QPointF(currentLine.actualLineBottom, 0);
155 correctionOffset = QPointF(expectedLineTop, 0) - currentLine.baselineTop;
156 lineTop = -currentLine.baselineTop;
157 lineBottom = currentLine.baselineBottom;
158 }
159 bool returnDescent = firstLine;
160 QPointF offset = resHandler.adjust(lineTop + lineBottom);
161 if (resHandler.roundToPixelHorizontal || resHandler.roundToPixelVertical) {
162 lineTop = offset - resHandler.adjust(lineBottom);
163 correctionOffset = resHandler.adjust(correctionOffset);
164 }
165
166 if (!returnDescent) {
167 for (auto chunk = currentLine.chunks.begin(); chunk != currentLine.chunks.end(); chunk++) {
168 Q_FOREACH (int j, chunk->chunkIndices) {
169 result[j].cssPosition += lineTop;
170 result[j].cssPosition += result[j].totalBaselineOffset();
171 result[j].finalPosition = result.at(j).cssPosition;
172 }
173 chunk->length.translate(lineTop);
174 chunk->boundingBox.translate(lineTop);
175 }
176 } else {
177 offset = lineBottom - correctionOffset;
178 for (auto chunk = currentLine.chunks.begin(); chunk != currentLine.chunks.end(); chunk++) {
179 Q_FOREACH (int j, chunk->chunkIndices) {
180 result[j].cssPosition -= correctionOffset;
181 result[j].cssPosition = result[j].cssPosition + result[j].totalBaselineOffset();
182 result[j].finalPosition = result.at(j).cssPosition;
183 }
184 chunk->length.translate(-correctionOffset);
185 chunk->boundingBox.translate(-correctionOffset);
186 }
187 }
188 return resHandler.adjust(offset);
189}
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.