Krita Source Code Documentation
Loading...
Searching...
No Matches
KoSvgTextShape_p.h
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2017 Dmitry Kazakov <dimula73@gmail.com>
3 * SPDX-FileCopyrightText: 2022 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#ifndef KO_SVG_TEXT_SHAPE_P_H
9#define KO_SVG_TEXT_SHAPE_P_H
10
11#include "KoSvgTextShape.h"
12
13#include "KoSvgText.h"
15
16#include "KoCssTextUtils.h"
17
18#include <kis_assert.h>
19#include <KisForest.h>
20
21#include <QFont>
22#include <QImage>
23#include <QLineF>
24#include <QPainterPath>
25#include <QPointF>
26#include <QRectF>
27#include <QVector>
28#include <QtMath>
29
30#include <variant>
31
32#include <ft2build.h>
33#include FT_FREETYPE_H
34
35constexpr qreal SHAPE_PRECISION = 1e-6;
36
37class KoPathShape;
38struct raqm_glyph_t;
39
40enum class BreakType {
41 NoBreak,
44};
45
52
61
62struct CursorPos {
63 int cluster;
64 int index;
65 int offset;
66 bool synthetic = false;
67};
68
69namespace Glyph {
70
71struct Outline {
72 QPainterPath path;
73};
74
79
85
86using Variant = std::variant<std::monostate, Outline, Bitmap, ColorLayers>;
87
88} // namespace Glyph
89
91 QPointF finalPosition;
92 qreal rotate = 0.0;
93 bool hidden = false; // whether the character will be drawn.
94 // The original svg specs' notion of addressable character relies on utf16,
95 // but there's an issue to switch to unicode codepoints proper (that is, utf 32), though it was never applied.
96 // https://github.com/w3c/svgwg/issues/537
97 bool addressable = true;
100 bool middle = false;
102 bool anchored_chunk = false;
103
105
107 bool isHorizontal = true;
108 int visualIndex = -1;
110 QPointF cssPosition = QPointF();
111 QPointF dominantBaselineOffset = QPointF(); // Shift caused by aligning glyphs to dominant baseline.
112 QPointF baselineOffset = QPointF();
114 QPointF advance;
118 bool justifyBefore = false;
119 bool justifyAfter = false;
120 bool isHanging = false;
121 bool textLengthApplied = false;
122 bool overflowWrap = false;
123
124 qreal extraFontScaling = 1.0;
128 qreal scaledAscent{};
130
131 std::optional<qreal> tabSize;
132
133 void calculateAndApplyTabsize(QPointF currentPos, bool isHorizontal, const KoSvgText::ResolutionHandler &resHandler) {
134 if (!tabSize) return;
135 if (*tabSize == qInf() || qIsNaN(*tabSize)) return;
136
137 if (*tabSize > 0) {
138 qreal remainder = *tabSize - (isHorizontal? fmod(currentPos.x(), *tabSize): fmod(currentPos.y(), *tabSize));
139 advance = resHandler.adjust(isHorizontal? QPointF(remainder, advance.y()): QPointF(advance.x(), remainder));
140 }
141 }
142
147 QRectF layoutBox() const {
150 }
155 QRectF lineHeightBox () const {
156 QRectF lBox = layoutBox();
157 return isHorizontal? lBox.adjusted(0, -scaledHalfLeading, 0, scaledHalfLeading)
158 : lBox.adjusted(-scaledHalfLeading, 0, scaledHalfLeading, 0);
159 }
160
166 void translateOrigin(QPointF newOrigin) {
167 if (newOrigin == QPointF()) return;
168 if (Glyph::Outline *outlineGlyph = std::get_if<Glyph::Outline>(&glyph)) {
169 outlineGlyph->path.translate(-newOrigin);
170 } else if (Glyph::Bitmap *bitmapGlyph = std::get_if<Glyph::Bitmap>(&glyph)) {
171 for (int i = 0; i< bitmapGlyph->drawRects.size(); i++) {
172 bitmapGlyph->drawRects[i].translate(-newOrigin);
173 }
174 } else if (Glyph::ColorLayers *colorGlyph = std::get_if<Glyph::ColorLayers>(&glyph)) {
175 for (int i = 0; i< colorGlyph->paths.size(); i++) {
176 colorGlyph->paths[i].translate(-newOrigin);
177 }
178 }
179 cursorInfo.caret.translate(-newOrigin);
180 inkBoundingBox.translate(-newOrigin);
181
182 if (isHorizontal) {
183 scaledDescent -= newOrigin.y();
184 scaledAscent -= newOrigin.y();
185 } else {
186 scaledDescent -= newOrigin.x();
187 scaledAscent -= newOrigin.x();
188 }
189 }
190
197 void scaleCharacterResult(qreal xScale, qreal yScale) {
198 QTransform scale = QTransform::fromScale(xScale, yScale);
199 if (scale.isIdentity()) return;
200 const bool scaleToZero = !(xScale > 0 && yScale > 0);
201
202 if (Glyph::Outline *outlineGlyph = std::get_if<Glyph::Outline>(&glyph)) {
203 if (!outlineGlyph->path.isEmpty()) {
204 if (scaleToZero) {
205 outlineGlyph->path = QPainterPath();
206 } else {
207 outlineGlyph->path = scale.map(outlineGlyph->path);
208 }
209 }
210 } else if (Glyph::Bitmap *bitmapGlyph = std::get_if<Glyph::Bitmap>(&glyph)) {
211 if (scaleToZero) {
212 bitmapGlyph->drawRects.clear();
213 bitmapGlyph->images.clear();
214 } else {
215 for (int i = 0; i< bitmapGlyph->drawRects.size(); i++) {
216 bitmapGlyph->drawRects[i] = scale.mapRect(bitmapGlyph->drawRects[i]);
217 }
218 }
219 } else if (Glyph::ColorLayers *colorGlyph = std::get_if<Glyph::ColorLayers>(&glyph)) {
220 for (int i = 0; i< colorGlyph->paths.size(); i++) {
221 if (scaleToZero) {
222 colorGlyph->paths[i] = QPainterPath();
223 } else {
224 colorGlyph->paths[i] = scale.map(colorGlyph->paths[i]);
225 }
226 }
227 }
228 advance = scale.map(advance);
229 cursorInfo.caret = scale.map(cursorInfo.caret);
230 for (int i = 0; i < cursorInfo.offsets.size(); i++) {
231 cursorInfo.offsets[i] = scale.map(cursorInfo.offsets.at(i));
232 }
233 inkBoundingBox = scale.mapRect(inkBoundingBox);
234
235 if (isHorizontal) {
236 scaledDescent *= yScale;
237 scaledAscent *= yScale;
238 scaledHalfLeading *= yScale;
239 metrics.scaleBaselines(yScale);
240 if (tabSize) {
241 tabSize = scale.map(QPointF(*tabSize, *tabSize)).x();
242 }
243 } else {
244 scaledDescent *= xScale;
245 scaledAscent *= xScale;
246 scaledHalfLeading *= xScale;
247 metrics.scaleBaselines(xScale);
248 if (tabSize) {
249 tabSize = scale.map(QPointF(*tabSize, *tabSize)).y();
250 }
251 }
252 }
253
254 QPointF totalBaselineOffset() const {
256 }
257
258 QFont::Style fontStyle = QFont::StyleNormal;
259 int fontWeight = 400;
260
262
265
266 QTransform finalTransform() const {
267 QTransform tf =
268 QTransform::fromTranslate(finalPosition.x(), finalPosition.y());
269 tf.rotateRadians(rotate);
270 return tf;
271 }
272};
273
274struct LineChunk {
275 QLineF length;
278 QPointF conditionalHangEnd = QPointF();
279};
280
298struct LineBox {
299
301 }
302
303 LineBox(QPointF start, QPointF end, const KoSvgText::ResolutionHandler &resHandler) {
305 chunk.length = QLineF(resHandler.adjustCeil(start), resHandler.adjustFloor(end));
306 chunks.append(chunk);
307 currentChunk = 0;
308 }
309
310 LineBox(QVector<QLineF> lineWidths, bool ltr, QPointF indent, const KoSvgText::ResolutionHandler &resHandler) {
311 textIndent = indent;
312 if (ltr) {
313 Q_FOREACH(QLineF line, lineWidths) {
315 chunk.length = QLineF(resHandler.adjustCeil(line.p1()), resHandler.adjustFloor(line.p2()));
316 chunks.append(chunk);
317 currentChunk = 0;
318 }
319 } else {
320 Q_FOREACH(QLineF line, lineWidths) {
322 chunk.length = QLineF(resHandler.adjustFloor(line.p2()), resHandler.adjustCeil(line.p1()));
323 chunks.insert(0, chunk);
324 currentChunk = 0;
325 }
326 }
327 }
328
330 int currentChunk = -1;
331
332 qreal expectedLineTop = 0;
333 qreal actualLineTop = 0;
335
336 QPointF baselineTop = QPointF();
337 QPointF baselineBottom = QPointF();
338
339 QPointF textIndent = QPointF();
340 bool firstLine = false;
341 bool lastLine = false;
342 bool lineFinalized = false;
343 bool justifyLine = false;
344
346 return chunks.value(currentChunk);
347 }
348
350 currentChunk = qMax(currentChunk, 0);
351 if (currentChunk < chunks.size()) {
353 } else {
354 chunks.append(chunk);
355 }
356 }
357
358 void clearAndAdjust(bool isHorizontal, QPointF current, QPointF indent) {
360 actualLineTop = 0;
362 textIndent = indent;
363 QLineF length = chunks.at(currentChunk).length;
364 if (isHorizontal) {
365 length.setP1(QPointF(length.p1().x(), current.y()));
366 length.setP2(QPointF(length.p2().x(), current.y()));
367 } else {
368 length.setP1(QPointF(current.x(), length.p1().y()));
369 length.setP2(QPointF(current.x(), length.p2().y()));
370 }
371 chunks.clear();
372 currentChunk = 0;
374 chunks.append(chunk);
375 firstLine = false;
376 }
377
378 void setCurrentChunkForPos(QPointF pos, bool isHorizontal) {
379 for (int i=0; i<chunks.size(); i++) {
380 LineChunk chunk = chunks.at(i);
381 if (isHorizontal) {
382 qreal min = qMin(chunk.length.p1().x(), chunk.length.p2().x()) - SHAPE_PRECISION;
383 qreal max = qMax(chunk.length.p1().x(), chunk.length.p2().x()) + SHAPE_PRECISION;
384 if ((pos.x() < max) &&
385 (pos.x() >= min)) {
386 currentChunk = i;
387 break;
388 }
389 } else {
390 qreal min = qMin(chunk.length.p1().y(), chunk.length.p2().y()) - SHAPE_PRECISION;
391 qreal max = qMax(chunk.length.p1().y(), chunk.length.p2().y()) + SHAPE_PRECISION;
392 if ((pos.y() < max) &&
393 (pos.y() >= min)) {
394 currentChunk = i;
395 break;
396 }
397 }
398 }
399 }
400
401 bool isEmpty() {
402 if (chunks.isEmpty()) return true;
403 for (int i =0; i < chunks.size(); i++) {
404 if (!chunks.at(i).chunkIndices.isEmpty()) return false;
405 }
406 return true;
407 }
408
409};
410
431
432class KRITAFLAKE_EXPORT KoSvgTextShape::Private
433{
434public:
435 // NOTE: the cache data is shared between all the instances of
436 // the shape, though it will be reset locally if the
437 // accessing thread changes
438
439 Private() = default;
440
441 Private(const Private &rhs)
442 : textData(rhs.textData) {
443 Q_FOREACH (KoShape *shape, rhs.shapesInside) {
444 KoShape *clonedShape = shape->cloneShape();
445 KIS_ASSERT_RECOVER(clonedShape) { continue; }
446
447 shapesInside.append(clonedShape);
448 }
449 Q_FOREACH (KoShape *shape, rhs.shapesSubtract) {
450 KoShape *clonedShape = shape->cloneShape();
451 KIS_ASSERT_RECOVER(clonedShape) { continue; }
452
453 shapesSubtract.append(clonedShape);
454 }
455 yRes = rhs.yRes;
456 xRes = rhs.xRes;
457 result = rhs.result;
458 lineBoxes = rhs.lineBoxes;
459
460 cursorPos = rhs.cursorPos;
461 logicalToVisualCursorPos = rhs.logicalToVisualCursorPos;
462 plainText = rhs.plainText;
463 isBidi = rhs.isBidi;
464 initialTextPosition = rhs.initialTextPosition;
465
466 isLoading = rhs.isLoading;
467 disableFontMatching = rhs.disableFontMatching;
468 }
469
471 qDeleteAll(shapesInside);
472 qDeleteAll(shapesSubtract);
473 }
474
475 int xRes = 72;
476 int yRes = 72;
479
481 bool isLoading = false;
482
483 bool disableFontMatching = false;
484
487
490
491 QString plainText;
492 bool isBidi = false;
493 QPointF initialTextPosition = QPointF();
494
495 void relayout();
496
497 static bool loadGlyph(const KoSvgText::ResolutionHandler &resHandler,
498 const FT_Int32 faceLoadFlags,
499 const bool isHorizontal,
500 const char32_t firstCodepoint,
501 const KoSvgText::TextRendering rendering,
502 raqm_glyph_t &currentGlyph,
503 CharacterResult &charResult,
504 QPointF &totalAdvanceFTFontCoordinates);
505
506 static std::pair<QTransform, qreal> loadGlyphOnly(const QTransform &ftTF,
507 FT_Int32 faceLoadFlags,
508 bool isHorizontal,
509 raqm_glyph_t &currentGlyph,
510 CharacterResult &charResult, const KoSvgText::TextRendering rendering);
511
514 QString text, QVector<CharacterResult> &result, int &currentIndex,
515 bool isHorizontal, bool wrapped, bool textInPath, QVector<KoSvgText::CharTransformation> &resolved,
516 QVector<bool> collapsedChars, const KoSvgTextProperties resolvedProps, bool withControls = true);
517
518 void applyTextLength(KisForest<KoSvgTextContentElement>::child_iterator currentTextElement, QVector<CharacterResult> &result, int &currentIndex, int &resolvedDescendentNodes, bool isHorizontal,
519 const KoSvgTextProperties resolvedProps, const KoSvgText::ResolutionHandler &resHandler);
520 static void applyAnchoring(QVector<CharacterResult> &result, bool isHorizontal, const KoSvgText::ResolutionHandler resHandler);
521 static qreal anchoredChunkShift(const QVector<CharacterResult> &result, const bool isHorizontal, const int start, int &end);
522 static qreal
523 characterResultOnPath(CharacterResult &cr, qreal length, qreal offset, bool isHorizontal, bool isClosed);
524 static QPainterPath stretchGlyphOnPath(const QPainterPath &glyph,
525 const QPainterPath &path,
526 bool isHorizontal,
527 qreal offset,
528 bool isClosed);
529 static void applyTextPath(KisForest<KoSvgTextContentElement>::child_iterator parent, QVector<CharacterResult> &result, bool isHorizontal, QPointF &startPos, const KoSvgTextProperties resolvedProps);
531 const KoSvgText::FontMetrics &parentBaselineTable, const KoSvgText::Baseline parentBaseline,
532 const QPointF superScript,
533 const QPointF subScript,
535 int &currentIndex,
536 const KoSvgText::ResolutionHandler resHandler,
537 const bool isHorizontal,
538 const bool disableFontMatching);
540 QVector<CharacterResult> &result, const QVector<LineBox> lineBoxes,
541 int &currentIndex,
542 const bool isHorizontal, const KoSvgTextProperties resolvedProps);
544 const QVector<CharacterResult>& result,
545 const QMap<int, int>& logicalToVisual,
546 const KoSvgText::ResolutionHandler resHandler,
547 KoPathShape *textPath,
548 qreal textPathoffset,
549 bool side,
550 int &currentIndex,
551 bool isHorizontal,
552 bool ltr,
553 bool wrapping,
554 const KoSvgTextProperties resolvedProps);
555 QMap<KoSvgText::TextDecoration, QPainterPath> generateDecorationPaths(const int &start, const int &end,
556 const KoSvgText::ResolutionHandler resHandler,
557 const QVector<CharacterResult> &result,
558 const bool isHorizontal,
559 const KoSvgText::TextDecorations &decor,
561 const bool textDecorationSkipInset = false,
562 const KoPathShape *currentTextPath = nullptr,
563 const qreal currentTextPathOffset = 0.0,
564 const bool textPathSide = false,
567 static void finalizeDecoration (
568 QPainterPath decorationPath,
569 const QPointF offset,
570 const QPainterPathStroker &stroker,
571 const KoSvgText::TextDecoration type,
572 QMap<KoSvgText::TextDecoration, QPainterPath> &decorationPaths,
573 const KoPathShape *currentTextPath,
574 const bool isHorizontal,
575 const qreal currentTextPathOffset,
576 const bool textPathSide
577 );
578
579 void paintTextDecoration(QPainter &painter,
580 const QPainterPath &outlineRect,
581 const KoShape *rootShape,
582 const KoSvgText::TextDecoration type,
583 const KoSvgText::TextRendering rendering);
584 void paintPaths(QPainter &painter,
585 const QPainterPath &outlineRect,
586 const KoShape *rootShape,
587 const QVector<CharacterResult> &result,
588 const KoSvgText::TextRendering rendering,
589 QPainterPath &chunk,
590 int &currentIndex);
591 KoShape* collectPaths(const KoShape *rootShape, QVector<CharacterResult> &result, int &currentIndex);
592 void paintDebug(QPainter &painter,
593 const QVector<CharacterResult> &result,
594 int &currentIndex);
595
598 KoSvgTextProperties props = parent->properties;
599 props.inheritFrom(resolvedProps, true);
600 int count = parent->numChars(withControls, props);
601 for (auto it = KisForestDetail::childBegin(parent); it != KisForestDetail::childEnd(parent); it++) {
602 count += numChars(it, withControls, props);
603 }
604 return count;
605 }
606
615
626 int &currentIndex,
627 int sought,
628 bool skipZeroWidth = false)
629 {
630 auto it = tree.depthFirstTailBegin();
631 for (; it != tree.depthFirstTailEnd(); it++) {
632 if (childCount(siblingCurrent(it)) > 0) {
633 continue;
634 }
635 int length = it->numChars(false);
636 if (length == 0 && skipZeroWidth) {
637 continue;
638 }
639
640 if (sought == currentIndex || (sought > currentIndex && sought < currentIndex + length)) {
641 break;
642 } else {
643 currentIndex += length;
644 }
645 }
646 return it;
647 }
656 int currentIndex = 0;
657
658 // If there's only a single root element, don't bother searching.
659 auto contentElement = depth(tree) == 1? tree.depthFirstTailBegin(): findTextContentElementForIndex(tree, currentIndex, index, true);
660 if (contentElement == tree.depthFirstTailEnd()) return false;
661
662 bool suitableStartIndex = siblingCurrent(contentElement) == tree.childBegin()? index >= currentIndex: index > currentIndex;
663 bool suitableEndIndex = siblingCurrent(contentElement) == tree.childBegin()? true: index < currentIndex + contentElement->numChars(false);
664
665 if (suitableStartIndex && suitableEndIndex) {
667 duplicate.text = contentElement->text;
668 int start = index - currentIndex;
669 int length = contentElement->numChars(false) - start;
670 int zero = 0;
671 duplicate.removeText(start, length);
672
673 // TODO: handle localtransforms better; annoyingly, this requires whitespace handling
674
675 if (siblingCurrent(contentElement) != tree.childBegin()
676 && !contentElement->textPath
677 && contentElement->textLength.isAuto
678 && contentElement->localTransformations.isEmpty()) {
679 contentElement->removeText(zero, start);
680 duplicate.properties = contentElement->properties;
681 tree.insert(siblingCurrent(contentElement), duplicate);
682 } else {
684 duplicate2.text = contentElement->text;
685 duplicate2.removeText(zero, start);
686 contentElement->text.clear();
687 tree.insert(childBegin(contentElement), duplicate);
688 tree.insert(childEnd(contentElement), duplicate2);
689 }
690 return true;
691 }
692 return false;
693 }
694
703 static QVector<bool> collapsedWhiteSpacesForText(KisForest<KoSvgTextContentElement> &tree, QString &allText, const bool alsoCollapseLowSurrogate = false) {
704 QMap<int, KoSvgText::TextSpaceCollapse> collapseModes;
705
707 for (auto it = tree.compositionBegin(); it != tree.compositionEnd(); it++) {
708 if (it.state() == KisForestDetail::Enter) {
709 KoSvgTextProperties ownProperties = it->properties;
710 ownProperties.inheritFrom(parentProps.last());
711 parentProps.append(ownProperties);
712
713 const int children = childCount(siblingCurrent(it));
714 if (children == 0) {
715 QString text = it->text;
716 KoSvgText::TextSpaceCollapse collapse = KoSvgText::TextSpaceCollapse(parentProps.last().propertyOrDefault(KoSvgTextProperties::TextCollapseId).toInt());
717 collapseModes.insert(allText.size(), collapse);
718 allText += text;
719 }
720 } else {
721 parentProps.pop_back();
722 }
723 }
724 QVector<bool> collapsed = KoCssTextUtils::collapseSpaces(&allText, collapseModes);
725
726 if (alsoCollapseLowSurrogate) {
727 for (int i = 0; i < allText.size(); i++) {
728 if (i > 0 && allText.at(i).isLowSurrogate() && allText.at(i-1).isHighSurrogate()) {
729 collapsed[i] = true;
730 }
731 }
732 }
733 return collapsed;
734 }
735
747 static void removeTransforms(KisForest<KoSvgTextContentElement> &tree, const int start, const int length) {
748 QString all;
749 QVector<bool> collapsedCharacters = collapsedWhiteSpacesForText(tree, all, true);
750
751 auto root = tree.childBegin();
752 removeTransformsImpl(root, 0, start, length, collapsedCharacters);
753 }
754
760 static int removeTransformsImpl(KisForest<KoSvgTextContentElement>::child_iterator currentTextElement, const int globalIndex, const int start, const int length, const QVector<bool> collapsedCharacters) {
761 int currentLength = 0;
762 auto it = childBegin(currentTextElement);
763 if (it != childEnd(currentTextElement)) {
764 for (; it != childEnd(currentTextElement); it++) {
765 currentLength += removeTransformsImpl(it, globalIndex + currentLength, start, length, collapsedCharacters);
766 }
767 } else {
768 currentLength = currentTextElement->text.size();
769 }
770
771 if (!currentTextElement->localTransformations.isEmpty()) {
772 int transformOffset = 0;
773 int transformOffsetEnd = 0;
774
775 for (int i = globalIndex; i < globalIndex + currentLength; i++) {
776 if (i < start) {
777 transformOffset += collapsedCharacters.at(i)? 0: 1;
778 }
779 if (i < start + length) {
780 transformOffsetEnd += collapsedCharacters.at(i)? 0: 1;
781 } else {
782 break;
783 }
784 }
785 if (transformOffset < currentTextElement->localTransformations.size()) {
786 currentTextElement->localTransformations.remove(transformOffset, transformOffsetEnd-transformOffset);
787 }
788
789 }
790 return currentLength;
791 }
792
802 static void insertTransforms(KisForest<KoSvgTextContentElement> &tree, const int start, const int length, const bool allowSkipFirst) {
803 QString all;
804 QVector<bool> collapsedCharacters = collapsedWhiteSpacesForText(tree, all, true);
805
806 auto root = tree.childBegin();
807 insertTransformsImpl(root, 0, start, length, collapsedCharacters, allowSkipFirst);
808 }
814 static int insertTransformsImpl(KisForest<KoSvgTextContentElement>::child_iterator currentTextElement, const int globalIndex, const int start, const int length, const QVector<bool> collapsedCharacters, const bool allowSkipFirst) {
815 int currentLength = 0;
816 auto it = childBegin(currentTextElement);
817 if (it != childEnd(currentTextElement)) {
818 for (; it != childEnd(currentTextElement); it++) {
819 currentLength += insertTransformsImpl(it, globalIndex + currentLength, start, length, collapsedCharacters, allowSkipFirst);
820 }
821 } else {
822 currentLength = currentTextElement->text.size();
823 }
824
825 if (!currentTextElement->localTransformations.isEmpty()) {
826 int transformOffset = 0;
827 int transformOffsetEnd = 0;
828
829 for (int i = globalIndex; i < globalIndex + currentLength; i++) {
830 if (i < start) {
831 transformOffset += collapsedCharacters.at(i)? 0: 1;
832 }
833 if (i < start + length) {
834 transformOffsetEnd += collapsedCharacters.at(i)? 0: 1;
835 } else {
836 break;
837 }
838 }
839
840 // When at the start, skip the first transform.
841 if (transformOffset == 0 && allowSkipFirst && currentTextElement->localTransformations.at(0).startsNewChunk()) {
842 transformOffset += 1;
843 }
844
845 if (transformOffset < currentTextElement->localTransformations.size()) {
846 for (int i = transformOffset; i < transformOffsetEnd; i++) {
847 currentTextElement->localTransformations.insert(i, KoSvgText::CharTransformation());
848 }
849 }
850
851 }
852 return currentLength;
853 }
854
865 void applyWhiteSpace(KisForest<KoSvgTextContentElement> &tree, const bool convertToPreWrapped = false) {
866 QString allText;
867 QVector<bool> collapsed = collapsedWhiteSpacesForText(tree, allText, false);
868
869 auto end = std::make_reverse_iterator(tree.childBegin());
870 auto begin = std::make_reverse_iterator(tree.childEnd());
871
872 for (; begin != end; begin++) {
873 applyWhiteSpaceImpl(begin, collapsed, allText, convertToPreWrapped);
874 }
875 if (convertToPreWrapped) {
876 tree.childBegin()->properties.setProperty(KoSvgTextProperties::TextCollapseId, QVariant::fromValue(KoSvgText::Preserve));
877 tree.childBegin()->properties.setProperty(KoSvgTextProperties::TextWrapId, QVariant::fromValue(KoSvgText::Wrap));
878 }
879 }
880
881 void applyWhiteSpaceImpl(std::reverse_iterator<KisForest<KoSvgTextContentElement>::child_iterator> current, QVector<bool> &collapsed, QString &allText, const bool convertToPreWrapped) {
882 auto base = current.base();
883 // It seems that .base() refers to the next entry instead of the pointer,
884 // which is coherent with the template implementation of "operator*" here:
885 // https://en.cppreference.com/w/cpp/iterator/reverse_iterator.html
886 base--;
887 if(base != siblingEnd(base)) {
888 auto end = std::make_reverse_iterator(childBegin(base));
889 auto begin = std::make_reverse_iterator(childEnd(base));
890 for (; begin != end; begin++) {
891 applyWhiteSpaceImpl(begin, collapsed, allText, convertToPreWrapped);
892 }
893 }
894
895 if (!current->text.isEmpty()) {
896 const int total = current->text.size();
897 QString currentText = allText.right(total);
898
899 for (int i = 0; i < total; i++) {
900 const int j = total - (i+1);
901 const bool col = collapsed.takeLast();
902 if (col) {
903 currentText.remove(j, 1);
904 }
905
906 }
907 current->text = currentText;
908 allText.chop(total);
909 }
910 if (convertToPreWrapped) {
911 current->properties.removeProperty(KoSvgTextProperties::TextCollapseId);
912 current->properties.removeProperty(KoSvgTextProperties::TextWrapId);
913 }
914 }
915
923 static void insertNewLinesAtAnchors(KisForest<KoSvgTextContentElement> &tree, bool shapesInside = false) {
924 QString all;
925 QVector<bool> collapsed = collapsedWhiteSpacesForText(tree, all, false);
926 QVector<CharacterResult> result(all.size());
927 int globalIndex = 0;
928 KoSvgTextProperties props = tree.childBegin()->properties;
932 bool isHorizontal = mode == KoSvgText::HorizontalTB;
933 bool isWrapped = (props.hasProperty(KoSvgTextProperties::InlineSizeId)
934 || shapesInside);
935 QVector<KoSvgText::CharTransformation> resolvedTransforms(all.size());
936 resolveTransforms(tree.childBegin(), all, result, globalIndex,
937 isHorizontal, isWrapped, false, resolvedTransforms,
939 false);
940
941 auto end = std::make_reverse_iterator(tree.childBegin());
942 auto begin = std::make_reverse_iterator(tree.childEnd());
943
944 bool inTextPath = false;
945 for (; begin != end; begin++) {
946 insertNewLinesAtAnchorsImpl(begin, resolvedTransforms, inTextPath);
947 }
948 }
949
951 QVector<KoSvgText::CharTransformation> &resolvedTransforms,
952 bool &inTextPath) {
953
954 inTextPath = (!current->textPath.isNull());
955 auto base = current.base();
956 base--;
957 if(base != siblingEnd(base)) {
958 auto end = std::make_reverse_iterator(childBegin(base));
959 auto begin = std::make_reverse_iterator(childEnd(base));
960 for (; begin != end; begin++) {
961 insertNewLinesAtAnchorsImpl(begin,
962 resolvedTransforms,
963 inTextPath);
964 }
965 }
966
967 if (!current->text.isEmpty()) {
968 const int total = current->text.size();
969
970 for (int i = 0; i < total; i++) {
971 const int j = total - (i+1);
972 KoSvgText::CharTransformation transform = resolvedTransforms.takeLast();
979 bool startsNewChunk = transform.startsNewChunk() && j == 0;
980
981 if (inTextPath) {
982 // First transform in path is always absolute, so we don't insert a newline.
983 startsNewChunk = false;
984 inTextPath = false;
985 }
986 // When there's no new chunk, we're not at the start of the text and there isn't already a line feed, insert a line feed.
987 if (startsNewChunk && !resolvedTransforms.isEmpty() && current->text.at(j) != QChar::LineFeed) {
988 current->text.insert(j, "\n");
989 }
990 }
991 }
992 current->localTransformations.clear();
993 }
994
996 for (int i = 0; i< layout.size(); i++) {
997 //split all anchored chunks, so we can set transforms on them.
998 if (layout.at(i).anchored_chunk) {
999 int plainTextIndex = layout.at(i).plaintTextIndex;
1000 splitContentElement(tree, plainTextIndex);
1001 }
1002 }
1003
1004 int globalIndex = 0;
1005 for (auto it = tree.childBegin(); it != tree.childEnd(); it++) {
1007 it->properties.propertyOrDefault(KoSvgTextProperties::WritingModeId).toInt());
1008 bool isHorizontal = mode == KoSvgText::HorizontalTB;
1009 setTransformsFromLayoutImpl(it, KoSvgTextProperties::defaultProperties(), layout, globalIndex, isHorizontal);
1010 }
1011 }
1012
1014 const KoSvgTextProperties parentProps, const QVector<CharacterResult> layout,
1015 int &globalIndex, bool isHorizontal) {
1016 KoSvgTextProperties props = current->properties;
1017 props.inheritFrom(parentProps);
1018 if (current->textPath) return; // When we're doing text-on-path, we're already in preformatted mode.
1019 for (auto it = childBegin(current); it!= childEnd(current); it++) {
1020 setTransformsFromLayoutImpl(it, props, layout, globalIndex, isHorizontal);
1021 }
1022
1023 if (current->text.isEmpty()) {
1024 current->localTransformations.clear();
1025 } else {
1027 const int length = current->numChars(true, props);
1028
1029 for (int i = globalIndex; i< globalIndex+length; i++) {
1030 CharacterResult result = layout.value(i);
1031
1032 if (!result.addressable) {
1033 continue;
1034 }
1036
1037 //TODO: Also split up content element if multiple anchored chunks.
1038 if (result.anchored_chunk) {
1039 int endIndex = 0;
1040 qreal shift = anchoredChunkShift(layout, isHorizontal, i, endIndex);
1041 QPointF offset = isHorizontal? QPointF(shift, 0): QPointF(0, shift);
1042 transform.xPos = result.finalPosition.x() - offset.x();
1043 transform.yPos = result.finalPosition.y() - offset.y();
1044 } else if (i > 0) {
1045 CharacterResult resultPrev = layout.value(i-1);
1046 QPointF offset = (result.finalPosition - result.cssPosition) - (resultPrev.finalPosition - resultPrev.cssPosition);
1047
1048 transform.dxPos = offset.x();
1049 transform.dyPos = offset.y();
1050 }
1051 transform.rotate = result.rotate;
1052
1053 transforms.append(transform);
1054 }
1055 current->localTransformations = transforms;
1056 current->text = current->text.split("\n").join(" ");
1057 globalIndex += length;
1058 }
1059 }
1060
1070 for (auto it = tree.depthFirstTailBegin(); it != tree.depthFirstTailEnd(); it++) {
1071 const int children = childCount(siblingCurrent(it));
1072 if (children == 0) {
1073
1074 // Remove empty leafs that are not the root or text paths.
1075 const int length = it->numChars(false);
1076 if (length == 0 && siblingCurrent(it) != tree.childBegin() && !it->textPath) {
1077 tree.erase(siblingCurrent(it));
1078 } else {
1079 // check if siblings are similar.
1080 auto siblingPrev = siblingCurrent(it);
1082 QVariant(KoSvgText::BidiNormal)).toInt());
1083 siblingPrev--;
1084 if (!isEnd(siblingPrev)
1085 && siblingPrev != siblingCurrent(it)
1086 && (siblingPrev->localTransformations.isEmpty() && it->localTransformations.isEmpty())
1087 && (!siblingPrev->textPath && !it->textPath)
1088 && (siblingPrev->textLength.isAuto && it->textLength.isAuto)
1089 && (siblingPrev->properties == it->properties)
1091 // TODO: handle localtransforms better; annoyingly, this requires whitespace handling
1092 siblingPrev->text += it->text;
1093 tree.erase(siblingCurrent(it));
1094 }
1095 }
1096 } else if (children == 1) {
1097 // merge single children into parents if possible.
1098 auto child = childBegin(siblingCurrent(it));
1099 if ((child->localTransformations.isEmpty() && it->localTransformations.isEmpty())
1100 && (!child->textPath && !it->textPath)
1101 && (child->textLength.isAuto && it->textLength.isAuto)
1102 && (!child->properties.hasNonInheritableProperties() || !it->properties.hasNonInheritableProperties())) {
1103 if (it->properties.hasNonInheritableProperties()) {
1104 KoSvgTextProperties props = it->properties;
1105 props.setAllButNonInheritableProperties(child->properties);
1106 child->properties = props;
1107 } else {
1108 child->properties.inheritFrom(it->properties);
1109 }
1110 tree.move(child, siblingCurrent(it));
1111 tree.erase(siblingCurrent(it));
1112 }
1113 }
1114 }
1115 }
1116
1118 if (treeIndex.isEmpty()) return parent;
1119 QVector<int> idx = treeIndex;
1120 int count = idx.takeFirst();
1121 for (auto child = KisForestDetail::childBegin(parent); child != KisForestDetail::childEnd(parent); child++) {
1122 if (count == 0) {
1124 return iteratorForTreeIndex(idx, child);
1125 } else {
1126 return child;
1127 }
1128 }
1129 count -= 1;
1130 }
1131 return KisForestDetail::childEnd(parent);
1132 }
1133
1135 for (auto child = KisForestDetail::childBegin(parent); child != KisForestDetail::childEnd(parent); child++) {
1136 if (child == target) {
1137 return true;
1138 } else if ((KisForestDetail::childBegin(child) != KisForestDetail::childEnd(child))) {
1139 if (startIndexOfIterator(child, target, currentIndex)) {
1140 return true;
1141 }
1142 } else {
1143 currentIndex += numChars(child);
1144 }
1145 }
1146 return false;
1147 }
1148
1150};
1151
1152#endif // KO_SVG_TEXT_SHAPE_P_H
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
KisMagneticGraph::vertex_descriptor target(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g)
constexpr qreal SHAPE_PRECISION
Value that indicates the precision for testing coordinates for text-in-shape layout.
BreakType
LineEdgeBehaviour
@ ConditionallyHang
Only hang if no space otherwise, only measured for justification if not hanging.
@ Collapse
Collapse if first or last in line.
@ ForceHang
Force hanging at the start or end of a line, never measured for justification.
@ NoChange
Do nothing special.
child_iterator childEnd()
Definition KisForest.h:880
child_iterator insert(child_iterator pos, X &&value)
Inserts element value into position pos. value becomes the child of the same parent as pos and is pla...
Definition KisForest.h:943
depth_first_tail_iterator depthFirstTailEnd()
Definition KisForest.h:856
child_iterator childBegin()
Definition KisForest.h:876
child_iterator move(child_iterator subtree, child_iterator newPos)
move a subtree to new position Moves subtree into a new position pointer by newPos....
Definition KisForest.h:994
composition_iterator compositionBegin()
Definition KisForest.h:912
composition_iterator compositionEnd()
Definition KisForest.h:916
depth_first_tail_iterator depthFirstTailBegin()
Definition KisForest.h:852
child_iterator erase(child_iterator pos)
Removes element at position pos. If pos is 'end', then result is undefined.
Definition KisForest.h:956
static QVector< bool > collapseSpaces(QString *text, QMap< int, KoSvgText::TextSpaceCollapse > collapseMethods)
collapseSpaces Some versions of CSS-Text 'white-space' or 'text-space-collapse' will collapse or tran...
The position of a path point within a path shape.
Definition KoPathShape.h:63
virtual KoShape * cloneShape() const
creates a deep copy of the shape or shape's subtree
Definition KoShape.cpp:200
The KoSvgTextNodeIndex class.
@ InlineSizeId
KoSvgText::AutoValue.
@ UnicodeBidiId
KoSvgText::UnicodeBidi.
@ TextCollapseId
KoSvgText::TextSpaceCollapse.
@ WritingModeId
KoSvgText::WritingMode.
@ TextWrapId
KoSvgText::TextWrap.
QList< PropertyId > properties() const
static const KoSvgTextProperties & defaultProperties()
bool hasProperty(PropertyId id) const
void setAllButNonInheritableProperties(const KoSvgTextProperties &properties)
Used to merge child properties into parent properties.
QVariant propertyOrDefault(PropertyId id) const
void inheritFrom(const KoSvgTextProperties &parentProperties, bool resolve=false)
void applyTextLength(KisForest< KoSvgTextContentElement >::child_iterator currentTextElement, QVector< CharacterResult > &result, int &currentIndex, int &resolvedDescendentNodes, bool isHorizontal, const KoSvgTextProperties resolvedProps, const KoSvgText::ResolutionHandler &resHandler)
static qreal anchoredChunkShift(const QVector< CharacterResult > &result, const bool isHorizontal, const int start, int &end)
static int removeTransformsImpl(KisForest< KoSvgTextContentElement >::child_iterator currentTextElement, const int globalIndex, const int start, const int length, const QVector< bool > collapsedCharacters)
removeTransformsImpl recursive function that handles removing local transforms in a certain range....
QMap< KoSvgText::TextDecoration, QPainterPath > generateDecorationPaths(const int &start, const int &end, const KoSvgText::ResolutionHandler resHandler, const QVector< CharacterResult > &result, const bool isHorizontal, const KoSvgText::TextDecorations &decor, const KoSvgText::TextDecorationStyle style=KoSvgText::TextDecorationStyle::Solid, const bool textDecorationSkipInset=false, const KoPathShape *currentTextPath=nullptr, const qreal currentTextPathOffset=0.0, const bool textPathSide=false, const KoSvgText::TextDecorationUnderlinePosition underlinePosH=KoSvgText::TextDecorationUnderlinePosition::UnderlineAuto, const KoSvgText::TextDecorationUnderlinePosition underlinePosV=KoSvgText::TextDecorationUnderlinePosition::UnderlineAuto)
void paintDebug(QPainter &painter, const QVector< CharacterResult > &result, int &currentIndex)
static void removeTransforms(KisForest< KoSvgTextContentElement > &tree, const int start, const int length)
removeTransforms Remove all local SVG character transforms in a certain range. Local transforms are i...
static void resolveTransforms(KisForest< KoSvgTextContentElement >::child_iterator currentTextElement, QString text, QVector< CharacterResult > &result, int &currentIndex, bool isHorizontal, bool wrapped, bool textInPath, QVector< KoSvgText::CharTransformation > &resolved, QVector< bool > collapsedChars, const KoSvgTextProperties resolvedProps, bool withControls=true)
KisForest< KoSvgTextContentElement > textData
static bool splitContentElement(KisForest< KoSvgTextContentElement > &tree, int index)
splitContentElement split the contentElement in tree at index into two nodes.
QVector< CursorPos > cursorPos
static qreal characterResultOnPath(CharacterResult &cr, qreal length, qreal offset, bool isHorizontal, bool isClosed)
void setTransformsFromLayoutImpl(KisForest< KoSvgTextContentElement >::child_iterator current, const KoSvgTextProperties parentProps, const QVector< CharacterResult > layout, int &globalIndex, bool isHorizontal)
QVector< CharacterResult > result
static void finalizeDecoration(QPainterPath decorationPath, const QPointF offset, const QPainterPathStroker &stroker, const KoSvgText::TextDecoration type, QMap< KoSvgText::TextDecoration, QPainterPath > &decorationPaths, const KoPathShape *currentTextPath, const bool isHorizontal, const qreal currentTextPathOffset, const bool textPathSide)
void applyWhiteSpaceImpl(std::reverse_iterator< KisForest< KoSvgTextContentElement >::child_iterator > current, QVector< bool > &collapsed, QString &allText, const bool convertToPreWrapped)
static KisForest< KoSvgTextContentElement >::depth_first_tail_iterator findTextContentElementForIndex(KisForest< KoSvgTextContentElement > &tree, int &currentIndex, int sought, bool skipZeroWidth=false)
findTextContentElementForIndex Finds the given leaf of the current tree-wide string index.
Private()=default
void paintPaths(QPainter &painter, const QPainterPath &outlineRect, const KoShape *rootShape, const QVector< CharacterResult > &result, const KoSvgText::TextRendering rendering, QPainterPath &chunk, int &currentIndex)
static int insertTransformsImpl(KisForest< KoSvgTextContentElement >::child_iterator currentTextElement, const int globalIndex, const int start, const int length, const QVector< bool > collapsedCharacters, const bool allowSkipFirst)
insertTransformsImpl Recursive function that handles inserting empty transforms into a given range....
static KoSvgTextNodeIndex createTextNodeIndex(KisForest< KoSvgTextContentElement >::child_iterator textElement)
QList< KoShape * > shapesInside
static KisForest< KoSvgTextContentElement >::child_iterator iteratorForTreeIndex(const QVector< int > treeIndex, KisForest< KoSvgTextContentElement >::child_iterator parent)
static int numChars(KisForest< KoSvgTextContentElement >::child_iterator parent, bool withControls=false, KoSvgTextProperties resolvedProps=KoSvgTextProperties::defaultProperties())
Get the number of characters for the whole subtree of this node.
QMap< int, int > logicalToVisualCursorPos
static void cleanUp(KisForest< KoSvgTextContentElement > &tree)
cleanUp This cleans up the tree by...
static void applyAnchoring(QVector< CharacterResult > &result, bool isHorizontal, const KoSvgText::ResolutionHandler resHandler)
static void insertNewLinesAtAnchorsImpl(std::reverse_iterator< KisForest< KoSvgTextContentElement >::child_iterator > current, QVector< KoSvgText::CharTransformation > &resolvedTransforms, bool &inTextPath)
void computeTextDecorations(KisForest< KoSvgTextContentElement >::child_iterator currentTextElement, const QVector< CharacterResult > &result, const QMap< int, int > &logicalToVisual, const KoSvgText::ResolutionHandler resHandler, KoPathShape *textPath, qreal textPathoffset, bool side, int &currentIndex, bool isHorizontal, bool ltr, bool wrapping, const KoSvgTextProperties resolvedProps)
void setTransformsFromLayout(KisForest< KoSvgTextContentElement > &tree, const QVector< CharacterResult > layout)
Private(const Private &rhs)
KoShape * collectPaths(const KoShape *rootShape, QVector< CharacterResult > &result, int &currentIndex)
static int childCount(KisForest< KoSvgTextContentElement >::child_iterator it)
Get the child count of the current node. A node without children is a text node.
void paintTextDecoration(QPainter &painter, const QPainterPath &outlineRect, const KoShape *rootShape, const KoSvgText::TextDecoration type, const KoSvgText::TextRendering rendering)
static void applyTextPath(KisForest< KoSvgTextContentElement >::child_iterator parent, QVector< CharacterResult > &result, bool isHorizontal, QPointF &startPos, const KoSvgTextProperties resolvedProps)
static void insertNewLinesAtAnchors(KisForest< KoSvgTextContentElement > &tree, bool shapesInside=false)
insertNewLinesAtAnchors Resolves character transforms and then inserts new lines at each transform th...
static std::pair< QTransform, qreal > loadGlyphOnly(const QTransform &ftTF, FT_Int32 faceLoadFlags, bool isHorizontal, raqm_glyph_t &currentGlyph, CharacterResult &charResult, const KoSvgText::TextRendering rendering)
static void insertTransforms(KisForest< KoSvgTextContentElement > &tree, const int start, const int length, const bool allowSkipFirst)
insertTransforms Inserts empty transforms into tree recursively.
static bool loadGlyph(const KoSvgText::ResolutionHandler &resHandler, const FT_Int32 faceLoadFlags, const bool isHorizontal, const char32_t firstCodepoint, const KoSvgText::TextRendering rendering, raqm_glyph_t &currentGlyph, CharacterResult &charResult, QPointF &totalAdvanceFTFontCoordinates)
void clearAssociatedOutlines()
static QVector< SubChunk > collectSubChunks(KisForest< KoSvgTextContentElement >::child_iterator it, KoSvgTextProperties parent, bool textInPath, bool &firstTextInPath)
static QVector< bool > collapsedWhiteSpacesForText(KisForest< KoSvgTextContentElement > &tree, QString &allText, const bool alsoCollapseLowSurrogate=false)
collapsedWhiteSpacesForText This returns the collapsed spaces for a given piece of text,...
QList< KoShape * > shapesSubtract
QVector< LineBox > lineBoxes
static QPainterPath stretchGlyphOnPath(const QPainterPath &glyph, const QPainterPath &path, bool isHorizontal, qreal offset, bool isClosed)
static bool startIndexOfIterator(KisForest< KoSvgTextContentElement >::child_iterator parent, KisForest< KoSvgTextContentElement >::child_iterator target, int &currentIndex)
static void computeFontMetrics(KisForest< KoSvgTextContentElement >::child_iterator parent, const KoSvgTextProperties &parentProps, const KoSvgText::FontMetrics &parentBaselineTable, const KoSvgText::Baseline parentBaseline, const QPointF superScript, const QPointF subScript, QVector< CharacterResult > &result, int &currentIndex, const KoSvgText::ResolutionHandler resHandler, const bool isHorizontal, const bool disableFontMatching)
static void handleLineBoxAlignment(KisForest< KoSvgTextContentElement >::child_iterator parent, QVector< CharacterResult > &result, const QVector< LineBox > lineBoxes, int &currentIndex, const bool isHorizontal, const KoSvgTextProperties resolvedProps)
void applyWhiteSpace(KisForest< KoSvgTextContentElement > &tree, const bool convertToPreWrapped=false)
applyWhiteSpace CSS Whitespace processes whitespaces so that duplicate white spaces and unnecessary h...
#define KIS_ASSERT_RECOVER(cond)
Definition kis_assert.h:55
std::variant< std::monostate, Outline, Bitmap, ColorLayers > Variant
ChildIterator< value_type, is_const > childBegin(const ChildIterator< value_type, is_const > &it)
Definition KisForest.h:290
ChildIterator< value_type, is_const > childEnd(const ChildIterator< value_type, is_const > &it)
Definition KisForest.h:300
TextAnchor
Where the text is anchored for SVG 1.1 text and 'inline-size'.
Definition KoSvgText.h:79
@ AnchorStart
Anchor left for LTR, right for RTL.
Definition KoSvgText.h:80
TextDecorationStyle
Style of the text-decoration.
Definition KoSvgText.h:265
@ Solid
Draw a solid line.Ex: --—.
Definition KoSvgText.h:266
TextDecorationUnderlinePosition
Which location to choose for the underline.
Definition KoSvgText.h:275
@ UnderlineAuto
Use Font metrics.
Definition KoSvgText.h:276
TextDecoration
Flags for text-decoration, for underline, overline and strikethrough.
Definition KoSvgText.h:257
Direction
Base direction used by Bidi algorithm.
Definition KoSvgText.h:48
@ DirectionLeftToRight
Definition KoSvgText.h:49
Baseline
Baseline values used by dominant-baseline and baseline-align.
Definition KoSvgText.h:213
@ HorizontalTB
Definition KoSvgText.h:38
@ BidiNormal
No new bidi-level is started.
Definition KoSvgText.h:57
@ BidiIsolate
Content is ordered as if in a separate paragraph.
Definition KoSvgText.h:61
@ BidiIsolateOverride
Definition KoSvgText.h:62
TextSpaceCollapse
Definition KoSvgText.h:96
@ Preserve
Do not collapse any space.
Definition KoSvgText.h:99
LineEdgeBehaviour lineStart
QPointF totalBaselineOffset() const
QRectF inkBoundingBox
The bounds of the drawn glyph. Different from the bounds the charresult takes up in the layout,...
KoSvgText::TextAnchor anchor
qreal scaledDescent
Descender, in pt.
QPointF finalPosition
the final position, taking into account both CSS and SVG positioning considerations.
Glyph::Variant glyph
KoSvgText::FontMetrics metrics
Fontmetrics for current font, in Freetype scanline coordinates.
bool justifyAfter
Justification Opportunity follows this character.
KoSvgText::Direction direction
bool justifyBefore
Justification Opportunity precedes this character.
std::optional< qreal > tabSize
If present, this is a tab and it should align to multiples of this tabSize value.
QRectF lineHeightBox() const
lineHeightBox
qreal extraFontScaling
Freetype doesn't allow us to scale below 1pt, so we need to do an extra transformation in these cases...
bool anchored_chunk
whether this is the start of a new chunk.
QPointF dominantBaselineOffset
LineEdgeBehaviour lineEnd
void scaleCharacterResult(qreal xScale, qreal yScale)
scaleCharacterResult convenience function to scale the whole character result.
qreal scaledAscent
Ascender, in pt.
void calculateAndApplyTabsize(QPointF currentPos, bool isHorizontal, const KoSvgText::ResolutionHandler &resHandler)
bool isHorizontal
Whether the current glyph lays out horizontal or vertical. Currently same as paragraph,...
QPointF cssPosition
the position in accordance with the CSS specs, as opossed to the SVG spec.
QFont::Style fontStyle
qreal scaledHalfLeading
Leading for both sides, can be either negative or positive, in pt.
qreal fontHalfLeading
Leading for both sides, can be either negative or positive.
QRectF layoutBox() const
layoutBox
void translateOrigin(QPointF newOrigin)
translateOrigin For dominant baseline, we want to move the glyph origin. This encompassed the glyph,...
QTransform finalTransform() const
QLineF caret
Caret for this characterResult.
bool rtl
Whether the current glyph is right-to-left, as opposed to the markup.
QVector< int > graphemeIndices
The text-string indices of graphemes starting here, starting grapheme is not present.
QColor color
Which color the current position has.
QVector< QPointF > offsets
The advance offsets for each grapheme index.
int cluster
Which character result this position belongs in.
bool synthetic
Whether this position was inserted to have a visual indicator.
int index
Which grapheme this position belongs with.
int offset
Which offset this position belongs with.
QVector< QImage > images
QVector< QRectF > drawRects
QVector< bool > replaceWithForeGroundColor
QVector< QPainterPath > paths
QVector< QBrush > colors
QPainterPath path
The KoSvgTextContentElement struct.
void removeText(int &start, int length)
removeText removes text,
KoSvgTextProperties properties
The textProperties. This includes.
QString text
Plain text of the current node. Use insertText and removeText to manipulate it.
boost::optional< qreal > yPos
Definition KoSvgText.h:606
boost::optional< qreal > dxPos
Definition KoSvgText.h:607
boost::optional< qreal > dyPos
Definition KoSvgText.h:608
boost::optional< qreal > rotate
Definition KoSvgText.h:609
boost::optional< qreal > xPos
Definition KoSvgText.h:605
The FontMetrics class A class to keep track of a variety of font metrics. Note that values are in Fre...
Definition KoSvgText.h:327
void scaleBaselines(const qreal multiplier)
The ResolutionHandler class.
Definition KoSvgText.h:1084
QPointF adjustCeil(const QPointF point) const
Adjusts the point to ceiled pixel values.
QPointF adjustFloor(const QPointF point) const
Adjusts the point to floored pixel values.
QPointF adjust(const QPointF point) const
Adjusts the point to rounded pixel values, based on whether roundToPixelHorizontal or roundToPixelVer...
The LineBox struct.
LineBox(QVector< QLineF > lineWidths, bool ltr, QPointF indent, const KoSvgText::ResolutionHandler &resHandler)
void setCurrentChunk(LineChunk chunk)
qreal actualLineBottom
QPointF baselineTop
Used to identify the top of the line for baseline-alignment.
void setCurrentChunkForPos(QPointF pos, bool isHorizontal)
qreal actualLineTop
LineChunk chunk()
void clearAndAdjust(bool isHorizontal, QPointF current, QPointF indent)
QPointF textIndent
qreal expectedLineTop
Because fonts can affect lineheight mid-line, and this affects wrapping, this estimates the line-heig...
QVector< LineChunk > chunks
QPointF baselineBottom
Used to identify the bottom of the line for baseline-alignment.
LineBox(QPointF start, QPointF end, const KoSvgText::ResolutionHandler &resHandler)
QPointF conditionalHangEnd
QVector< int > chunkIndices
charResult indices that belong to this chunk.
QLineF length
Used to measure how long the current line is allowed to be.
KisForest< KoSvgTextContentElement >::child_iterator associatedLeaf
SubChunk(KisForest< KoSvgTextContentElement >::child_iterator leaf)
QSharedPointer< KoShapeBackground > bg
KoSvgTextProperties inheritedProps
QVector< QPair< int, int > > newToOldPositions
For transformed strings, we need to know which.
QString originalText