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#include <KoShapePainter.h>
16#include <KoShapeManager.h>
17
18#include "KoCssTextUtils.h"
19#include <KoShapeGroup.h>
20
21#include <kis_assert.h>
22#include <KisForest.h>
23
24#include <QFont>
25#include <QImage>
26#include <QLineF>
27#include <QPainterPath>
28#include <QPointF>
29#include <QRectF>
30#include <QVector>
31#include <QtMath>
32
33#include <variant>
34
35#include <ft2build.h>
36#include FT_FREETYPE_H
37
38constexpr qreal SHAPE_PRECISION = 1e-6;
39
40class KoPathShape;
41struct raqm_glyph_t;
42
43enum class BreakType {
44 NoBreak,
47};
48
55
64
65struct CursorPos {
66 int cluster;
67 int index;
68 int offset = 0;
69 bool synthetic = false;
70};
71
72namespace Glyph {
73
74struct Outline {
75 QPainterPath path;
76};
77
82
88
89using Variant = std::variant<std::monostate, Outline, Bitmap, ColorLayers>;
90
91} // namespace Glyph
92
94 QPointF finalPosition;
95 qreal rotate = 0.0;
96 bool hidden = false; // whether the character will be drawn.
97 // The original svg specs' notion of addressable character relies on utf16,
98 // but there's an issue to switch to unicode codepoints proper (that is, utf 32), though it was never applied.
99 // https://github.com/w3c/svgwg/issues/537
100 bool addressable = true;
103 bool middle = false;
105 bool anchored_chunk = false;
106
108
110 bool isHorizontal = true;
111 int visualIndex = -1;
113 QPointF cssPosition = QPointF();
114 QPointF textLengthOffset = QPointF();
115 QPointF textPathAndAnchoringOffset = QPointF();
116 QPointF dominantBaselineOffset = QPointF(); // Shift caused by aligning glyphs to dominant baseline.
117 QPointF baselineOffset = QPointF();
119 QPointF advance;
123 bool justifyBefore = false;
124 bool justifyAfter = false;
125 bool isHanging = false;
126 bool textLengthApplied = false;
127 bool overflowWrap = false;
128
129 qreal extraFontScaling = 1.0;
133 qreal scaledAscent{};
135
136 std::optional<qreal> tabSize;
137
138 void calculateAndApplyTabsize(QPointF currentPos, bool isHorizontal, const KoSvgText::ResolutionHandler &resHandler) {
139 if (!tabSize) return;
140 if (*tabSize == qInf() || qIsNaN(*tabSize)) return;
141
142 if (*tabSize > 0) {
143 qreal remainder = *tabSize - (isHorizontal? fmod(currentPos.x(), *tabSize): fmod(currentPos.y(), *tabSize));
144 advance = resHandler.adjust(isHorizontal? QPointF(remainder, advance.y()): QPointF(advance.x(), remainder));
145 }
146 }
147
152 QRectF layoutBox() const {
155 }
160 QRectF lineHeightBox () const {
161 QRectF lBox = layoutBox();
162 return isHorizontal? lBox.adjusted(0, -scaledHalfLeading, 0, scaledHalfLeading)
163 : lBox.adjusted(-scaledHalfLeading, 0, scaledHalfLeading, 0);
164 }
165
171 void translateOrigin(QPointF newOrigin) {
172 if (newOrigin == QPointF()) return;
173 if (Glyph::Outline *outlineGlyph = std::get_if<Glyph::Outline>(&glyph)) {
174 outlineGlyph->path.translate(-newOrigin);
175 } else if (Glyph::Bitmap *bitmapGlyph = std::get_if<Glyph::Bitmap>(&glyph)) {
176 for (int i = 0; i< bitmapGlyph->drawRects.size(); i++) {
177 bitmapGlyph->drawRects[i].translate(-newOrigin);
178 }
179 } else if (Glyph::ColorLayers *colorGlyph = std::get_if<Glyph::ColorLayers>(&glyph)) {
180 for (int i = 0; i< colorGlyph->paths.size(); i++) {
181 colorGlyph->paths[i].translate(-newOrigin);
182 }
183 }
184 cursorInfo.caret.translate(-newOrigin);
185 inkBoundingBox.translate(-newOrigin);
186
187 if (isHorizontal) {
188 scaledDescent -= newOrigin.y();
189 scaledAscent -= newOrigin.y();
190 } else {
191 scaledDescent -= newOrigin.x();
192 scaledAscent -= newOrigin.x();
193 }
194 }
195
202 void scaleCharacterResult(qreal xScale, qreal yScale) {
203 QTransform scale = QTransform::fromScale(xScale, yScale);
204 if (scale.isIdentity()) return;
205 const bool scaleToZero = !(xScale > 0 && yScale > 0);
206
207 if (Glyph::Outline *outlineGlyph = std::get_if<Glyph::Outline>(&glyph)) {
208 if (!outlineGlyph->path.isEmpty()) {
209 if (scaleToZero) {
210 outlineGlyph->path = QPainterPath();
211 } else {
212 outlineGlyph->path = scale.map(outlineGlyph->path);
213 }
214 }
215 } else if (Glyph::Bitmap *bitmapGlyph = std::get_if<Glyph::Bitmap>(&glyph)) {
216 if (scaleToZero) {
217 bitmapGlyph->drawRects.clear();
218 bitmapGlyph->images.clear();
219 } else {
220 for (int i = 0; i< bitmapGlyph->drawRects.size(); i++) {
221 bitmapGlyph->drawRects[i] = scale.mapRect(bitmapGlyph->drawRects[i]);
222 }
223 }
224 } else if (Glyph::ColorLayers *colorGlyph = std::get_if<Glyph::ColorLayers>(&glyph)) {
225 for (int i = 0; i< colorGlyph->paths.size(); i++) {
226 if (scaleToZero) {
227 colorGlyph->paths[i] = QPainterPath();
228 } else {
229 colorGlyph->paths[i] = scale.map(colorGlyph->paths[i]);
230 }
231 }
232 }
233 advance = scale.map(advance);
234 cursorInfo.caret = scale.map(cursorInfo.caret);
235 for (int i = 0; i < cursorInfo.offsets.size(); i++) {
236 cursorInfo.offsets[i] = scale.map(cursorInfo.offsets.at(i));
237 }
238 inkBoundingBox = scale.mapRect(inkBoundingBox);
239
240 if (isHorizontal) {
241 scaledDescent *= yScale;
242 scaledAscent *= yScale;
243 scaledHalfLeading *= yScale;
244 metrics.scaleBaselines(yScale);
245 if (tabSize) {
246 tabSize = scale.map(QPointF(*tabSize, *tabSize)).x();
247 }
248 } else {
249 scaledDescent *= xScale;
250 scaledAscent *= xScale;
251 scaledHalfLeading *= xScale;
252 metrics.scaleBaselines(xScale);
253 if (tabSize) {
254 tabSize = scale.map(QPointF(*tabSize, *tabSize)).y();
255 }
256 }
257 }
258
259 QPointF totalBaselineOffset() const {
261 }
262
263 QFont::Style fontStyle = QFont::StyleNormal;
264 int fontWeight = 400;
265
267
270
271 QTransform finalTransform() const {
272 QTransform tf =
273 QTransform::fromTranslate(finalPosition.x(), finalPosition.y());
274 tf.rotateRadians(rotate);
275 return tf;
276 }
277};
278
279struct LineChunk {
280 QLineF length;
283 QPointF conditionalHangEnd = QPointF();
284};
285
303struct LineBox {
304
306 }
307
308 LineBox(QPointF start, QPointF end, const KoSvgText::ResolutionHandler &resHandler) {
310 chunk.length = QLineF(resHandler.adjustCeil(start), resHandler.adjustFloor(end));
311 chunks.append(chunk);
312 currentChunk = 0;
313 }
314
315 LineBox(QVector<QLineF> lineWidths, bool ltr, QPointF indent, const KoSvgText::ResolutionHandler &resHandler) {
316 textIndent = indent;
317 if (ltr) {
318 Q_FOREACH(QLineF line, lineWidths) {
320 chunk.length = QLineF(resHandler.adjustCeil(line.p1()), resHandler.adjustFloor(line.p2()));
321 chunks.append(chunk);
322 currentChunk = 0;
323 }
324 } else {
325 Q_FOREACH(QLineF line, lineWidths) {
327 chunk.length = QLineF(resHandler.adjustFloor(line.p2()), resHandler.adjustCeil(line.p1()));
328 chunks.insert(0, chunk);
329 currentChunk = 0;
330 }
331 }
332 }
333
335 int currentChunk = -1;
336
337 qreal expectedLineTop = 0;
338 qreal actualLineTop = 0;
340
341 QPointF baselineTop = QPointF();
342 QPointF baselineBottom = QPointF();
343
344 QPointF textIndent = QPointF();
345 bool firstLine = false;
346 bool lastLine = false;
347 bool lineFinalized = false;
348 bool justifyLine = false;
349
351 return chunks.value(currentChunk);
352 }
353
355 currentChunk = qMax(currentChunk, 0);
356 if (currentChunk < chunks.size()) {
358 } else {
359 chunks.append(chunk);
360 }
361 }
362
363 void clearAndAdjust(bool isHorizontal, QPointF current, QPointF indent) {
365 actualLineTop = 0;
367 textIndent = indent;
368 QLineF length = chunks.at(currentChunk).length;
369 if (isHorizontal) {
370 length.setP1(QPointF(length.p1().x(), current.y()));
371 length.setP2(QPointF(length.p2().x(), current.y()));
372 } else {
373 length.setP1(QPointF(current.x(), length.p1().y()));
374 length.setP2(QPointF(current.x(), length.p2().y()));
375 }
376 chunks.clear();
377 currentChunk = 0;
379 chunks.append(chunk);
380 firstLine = false;
381 }
382
383 void setCurrentChunkForPos(QPointF pos, bool isHorizontal) {
384 for (int i=0; i<chunks.size(); i++) {
385 LineChunk chunk = chunks.at(i);
386 if (isHorizontal) {
387 qreal min = qMin(chunk.length.p1().x(), chunk.length.p2().x()) - SHAPE_PRECISION;
388 qreal max = qMax(chunk.length.p1().x(), chunk.length.p2().x()) + SHAPE_PRECISION;
389 if ((pos.x() < max) &&
390 (pos.x() >= min)) {
391 currentChunk = i;
392 break;
393 }
394 } else {
395 qreal min = qMin(chunk.length.p1().y(), chunk.length.p2().y()) - SHAPE_PRECISION;
396 qreal max = qMax(chunk.length.p1().y(), chunk.length.p2().y()) + SHAPE_PRECISION;
397 if ((pos.y() < max) &&
398 (pos.y() >= min)) {
399 currentChunk = i;
400 break;
401 }
402 }
403 }
404 }
405
406 bool isEmpty() {
407 if (chunks.isEmpty()) return true;
408 for (int i =0; i < chunks.size(); i++) {
409 if (!chunks.at(i).chunkIndices.isEmpty()) return false;
410 }
411 return true;
412 }
413
414};
415
436
437class KRITAFLAKE_EXPORT KoSvgTextShape::Private
438{
439public:
440 // NOTE: the cache data is shared between all the instances of
441 // the shape, though it will be reset locally if the
442 // accessing thread changes
443
445 : internalShapesPainter(new KoShapePainter)
446 , shapeGroup(new KoShapeGroup)
447 {
448 shapeGroup->setSelectable(false);
449 }
450
457
458 Private(const Private &rhs)
459 : internalShapesPainter(new KoShapePainter)
460 , textData(rhs.textData)
461 {
462
463 KoShapeGroup *g = dynamic_cast<KoShapeGroup*>(rhs.shapeGroup.data()->cloneShape());
464 shapeGroup.reset(g);
465 shapeGroup->setSelectable(false);
466 handleShapes(shapeGroup->shapes(), rhs.shapeGroup->shapes(), rhs.shapesInside, shapesInside);
467 handleShapes(shapeGroup->shapes(), rhs.shapeGroup->shapes(), rhs.shapesSubtract, shapesSubtract);
468 handleShapes(shapeGroup->shapes(), rhs.shapeGroup->shapes(), rhs.textPaths, textPaths);
469 updateInternalShapesList();
470
471 yRes = rhs.yRes;
472 xRes = rhs.xRes;
473 result = rhs.result;
474 lineBoxes = rhs.lineBoxes;
475
476 cursorPos = rhs.cursorPos;
477 logicalToVisualCursorPos = rhs.logicalToVisualCursorPos;
478 plainText = rhs.plainText;
479 isBidi = rhs.isBidi;
480 initialTextPosition = rhs.initialTextPosition;
481
482 isLoading = rhs.isLoading;
483 disableFontMatching = rhs.disableFontMatching;
484
485 currentTextWrappingAreas = rhs.currentTextWrappingAreas;
486 }
487
488 void handleShapes(const QList<KoShape*> &sourceShapeList, const QList<KoShape*> referenceList2, const QList<KoShape*> referenceShapeList, QList<KoShape*> &destinationShapeList) {
489 for (int i = 0; i<sourceShapeList.size(); i++) {
490 if (referenceShapeList.contains(referenceList2.at(i))) {
491 destinationShapeList.append(sourceShapeList.at(i));
492 }
493 }
494 }
495
497
498 internalShapesPainter.reset();
499 Q_FOREACH(KoShape *shape, shapeGroup->shapes()) {
500 shapeGroup->removeShape(shape);
501 }
502 shapeGroup.reset();
503 qDeleteAll(shapesInside);
504 shapesInside.clear();
505 qDeleteAll(shapesSubtract);
506 shapesSubtract.clear();
507 qDeleteAll(textPaths);
508 textPaths.clear();
509 }
510
511 int xRes = 72;
512 int yRes = 72;
513
514 QScopedPointer<KoShapePainter> internalShapesPainter;
515 QScopedPointer<KoShapeGroup> shapeGroup;
516
518 return internalShapesPainter->internalShapeManager()->shapes();
519 }
520
522 Q_FOREACH(KoShape *shape, shapeGroup->shapes()) {
523 shapeGroup->removeShape(shape);
524 }
525 Q_FOREACH(KoShape *shape, shapesInside) {
526 shapeGroup->addShape(shape);
527 }
528 Q_FOREACH(KoShape *shape, shapesSubtract) {
529 shapeGroup->addShape(shape);
530 }
531 Q_FOREACH(KoShape *shape, textPaths) {
532 shapeGroup->addShape(shape);
533 }
534 updateTextWrappingAreas();
535 updateInternalShapesList();
536 }
538 if (shapeGroup) {
539 internalShapesPainter->setShapes(shapeGroup->shapes());
540 }
541 }
542
544 BulkActionState(QRectF originalBoundingRectArg) : originalBoundingRect(originalBoundingRectArg) {}
545
547 bool contourHasChanged = false;
548 bool layoutHasChanged = false;
549
550 bool changed() const {
551 return contourHasChanged || layoutHasChanged;
552 }
553 };
554
555 std::optional<BulkActionState> bulkActionState;
556
560
562
569 static QList<QPainterPath> generateShapes(const QList<KoShape*> shapesInside, const QList<KoShape*> shapesSubtract, const KoSvgTextProperties &properties);
570
571 static KoShape *textPathByName(QString name, QList<KoShape*> textPaths) {
572 auto it = std::find_if(textPaths.begin(), textPaths.end(), [&name](const KoShape *s) -> bool {return s->name() == name;});
573 return it != textPaths.end()? *it: nullptr;
574 }
575
577 bool isLoading = false;
578
579 bool disableFontMatching = false;
580
583
586
587 QString plainText;
588 bool isBidi = false;
589 QPointF initialTextPosition = QPointF();
590
591 void relayout();
592
593 static bool loadGlyph(const KoSvgText::ResolutionHandler &resHandler,
594 const FT_Int32 faceLoadFlags,
595 const bool isHorizontal,
596 const char32_t firstCodepoint,
597 const KoSvgText::TextRendering rendering,
598 raqm_glyph_t &currentGlyph,
599 CharacterResult &charResult,
600 QPointF &totalAdvanceFTFontCoordinates);
601
603 const bool isHorizontal,
604 raqm_glyph_t &currentGlyph);
605
606 static std::pair<QTransform, qreal> loadGlyphOnly(const QTransform &ftTF,
607 FT_Int32 faceLoadFlags,
608 bool isHorizontal,
609 raqm_glyph_t &currentGlyph,
610 CharacterResult &charResult, const KoSvgText::TextRendering rendering);
611
614 QString text, QVector<CharacterResult> &result, int &currentIndex,
615 bool isHorizontal, bool wrapped, bool textInPath, QVector<KoSvgText::CharTransformation> &resolved,
616 QVector<bool> collapsedChars, const KoSvgTextProperties resolvedProps, bool withControls = true);
617
618 void applyTextLength(KisForest<KoSvgTextContentElement>::child_iterator currentTextElement, QVector<CharacterResult> &result, int &currentIndex, int &resolvedDescendentNodes, bool isHorizontal,
619 const KoSvgTextProperties resolvedProps, const KoSvgText::ResolutionHandler &resHandler);
620 static void applyAnchoring(QVector<CharacterResult> &result, bool isHorizontal, const KoSvgText::ResolutionHandler resHandler);
621 static qreal anchoredChunkShift(const QVector<CharacterResult> &result, const bool isHorizontal, const int start, int &end);
622 static qreal
623 characterResultOnPath(CharacterResult &cr, qreal length, qreal offset, bool isHorizontal, bool isClosed);
624 static QPainterPath stretchGlyphOnPath(const QPainterPath &glyph,
625 const QPainterPath &path,
626 bool isHorizontal,
627 qreal offset,
628 bool isClosed);
629 static void applyTextPath(KisForest<KoSvgTextContentElement>::child_iterator parent, QVector<CharacterResult> &result, bool isHorizontal, QPointF &startPos, const KoSvgTextProperties resolvedProps, QList<KoShape*> textPaths);
631 const KoSvgText::FontMetrics &parentBaselineTable, const KoSvgText::Baseline parentBaseline,
633 int &currentIndex,
634 const KoSvgText::ResolutionHandler resHandler,
635 const bool isHorizontal,
636 const bool disableFontMatching);
638 QVector<CharacterResult> &result, const QVector<LineBox> lineBoxes,
639 int &currentIndex,
640 const bool isHorizontal, const KoSvgTextProperties resolvedProps);
642 const QVector<CharacterResult>& result,
643 const QMap<int, int>& logicalToVisual,
644 const KoSvgText::ResolutionHandler resHandler,
645 KoPathShape *textPath,
646 qreal textPathoffset,
647 bool side,
648 int &currentIndex,
649 bool isHorizontal,
650 bool ltr,
651 bool wrapping,
652 const KoSvgTextProperties resolvedProps, QList<KoShape*> textPaths);
653 QMap<KoSvgText::TextDecoration, QPainterPath> generateDecorationPaths(const int &start, const int &end,
654 const KoSvgText::ResolutionHandler resHandler,
655 const QVector<CharacterResult> &result,
656 const bool isHorizontal,
657 const KoSvgText::TextDecorations &decor,
659 const bool textDecorationSkipInset = false,
660 const KoPathShape *currentTextPath = nullptr,
661 const qreal currentTextPathOffset = 0.0,
662 const bool textPathSide = false,
665 static void finalizeDecoration (
666 QPainterPath decorationPath,
667 const QPointF offset,
668 const QPainterPathStroker &stroker,
669 const KoSvgText::TextDecoration type,
670 QMap<KoSvgText::TextDecoration, QPainterPath> &decorationPaths,
671 const KoPathShape *currentTextPath,
672 const bool isHorizontal,
673 const qreal currentTextPathOffset,
674 const bool textPathSide
675 );
676
677 void paintTextDecoration(QPainter &painter,
678 const QPainterPath &outlineRect,
679 const KoShape *rootShape,
680 const KoSvgText::TextDecoration type,
681 const KoSvgText::TextRendering rendering);
682 void paintPaths(QPainter &painter,
683 const QPainterPath &outlineRect,
684 const KoShape *rootShape,
685 const QVector<CharacterResult> &result,
686 const KoSvgText::TextRendering rendering,
687 QPainterPath &chunk,
688 int &currentIndex);
689 KoShape* collectPaths(const KoShape *rootShape, QVector<CharacterResult> &result, int &currentIndex);
690 void paintDebug(QPainter &painter,
691 const QVector<CharacterResult> &result,
692 int &currentIndex);
693
696 KoSvgTextProperties props = parent->properties;
697 props.inheritFrom(resolvedProps, true);
698 int count = parent->numChars(withControls, props);
699 for (auto it = KisForestDetail::childBegin(parent); it != KisForestDetail::childEnd(parent); it++) {
700 count += numChars(it, withControls, props);
701 }
702 return count;
703 }
704
713
724 int &currentIndex,
725 int sought,
726 bool skipZeroWidth = false)
727 {
728 auto it = tree.depthFirstTailBegin();
729 for (; it != tree.depthFirstTailEnd(); it++) {
730 if (childCount(siblingCurrent(it)) > 0) {
731 continue;
732 }
733 int length = it->numChars(false);
734 if (length == 0 && skipZeroWidth) {
735 continue;
736 }
737
738 if (sought == currentIndex || (sought > currentIndex && sought < currentIndex + length)) {
739 break;
740 } else {
741 currentIndex += length;
742 }
743 }
744 return it;
745 }
755 static bool splitContentElement(KisForest<KoSvgTextContentElement> &tree, int index, bool allowEmptyText = true) {
756 int currentIndex = 0;
757
758 // If there's only a single root element, don't bother searching.
759 auto contentElement = depth(tree) == 1? tree.depthFirstTailBegin(): findTextContentElementForIndex(tree, currentIndex, index, true);
760 if (contentElement == tree.depthFirstTailEnd()) return false;
761
762 bool suitableStartIndex = siblingCurrent(contentElement) == tree.childBegin()? index >= currentIndex: index > currentIndex;
763 bool suitableEndIndex = siblingCurrent(contentElement) == tree.childBegin()? true: index < currentIndex + contentElement->numChars(false);
764
765 if (suitableStartIndex && suitableEndIndex) {
767 duplicate.text = contentElement->text;
768 int start = index - currentIndex;
769 int length = contentElement->numChars(false) - start;
770 int zero = 0;
771 duplicate.removeText(start, length);
772
773 if (!allowEmptyText && (duplicate.text.isEmpty() || length == 0)) {
774 return true;
775 }
776
777 // TODO: handle localtransforms better; annoyingly, this requires whitespace handling
778
779 if (siblingCurrent(contentElement) != tree.childBegin()
780 && contentElement->textPathId.isEmpty()
781 && contentElement->textLength.isAuto
782 && contentElement->localTransformations.isEmpty()) {
783 contentElement->removeText(zero, start);
784 duplicate.properties = contentElement->properties;
785 tree.insert(siblingCurrent(contentElement), duplicate);
786 } else {
788 duplicate2.text = contentElement->text;
789 duplicate2.removeText(zero, start);
790 contentElement->text.clear();
791 tree.insert(childBegin(contentElement), duplicate);
792 tree.insert(childEnd(contentElement), duplicate2);
793 }
794 return true;
795 }
796 return false;
797 }
798
806 static void splitTree(KisForest<KoSvgTextContentElement> &tree, int index, bool textPathAfterSplit) {
807 splitContentElement(tree, index, false);
808 int currentIndex = 0;
809 auto contentElement = depth(tree) == 1? tree.depthFirstTailBegin(): findTextContentElementForIndex(tree, currentIndex, index, true);
810
811 // We're either at the start or end.
812 if (contentElement == tree.depthFirstTailEnd()) return;
813 if (siblingCurrent(contentElement) == tree.childBegin()) return;
814
815 auto lastNode = siblingCurrent(contentElement);
816 for (auto parentIt = KisForestDetail::hierarchyBegin(siblingCurrent(contentElement));
817 parentIt != KisForestDetail::hierarchyEnd(siblingCurrent(contentElement)); parentIt++) {
818 if (lastNode == siblingCurrent(parentIt)) continue;
819 if (siblingCurrent(parentIt) == tree.childBegin()) {
820 break;
821 }
822
823 if (lastNode != childBegin(siblingCurrent(parentIt))) {
825 duplicate.properties = parentIt->properties;
826 if (textPathAfterSplit) {
827 duplicate.textPathId = parentIt->textPathId;
828 duplicate.textPathInfo = parentIt->textPathInfo;
829 parentIt->textPathId = QString();
830 parentIt->textPathInfo = KoSvgText::TextOnPathInfo();
831 }
832 auto insert = siblingCurrent(parentIt);
833 insert ++;
834 auto it = tree.insert(insert, duplicate);
835
837 for (auto child = lastNode; child != childEnd(siblingCurrent(parentIt)); child++) {
838 movableChildren.append(child);
839 }
840 while(!movableChildren.isEmpty()) {
841 auto child = movableChildren.takeLast();
842 tree.move(child, childBegin(it));
843 }
844 lastNode = it;
845 } else {
846 lastNode = siblingCurrent(parentIt);
847 }
848 }
849 }
850
861 // An earlier version of the code used Hierarchy iterator,
862 // but that had too many exceptions when the child was not in the root.
863 if (!child.node()) return childEnd(root);
864 if (KisForestDetail::parent(child) == root) return child;
865 for (auto rootChild = childBegin(root); rootChild != childEnd(root); rootChild++) {
866 for (auto leaf = KisForestDetail::tailSubtreeBegin(rootChild);
867 leaf != KisForestDetail::tailSubtreeEnd(rootChild); leaf++) {
868 if (siblingCurrent(leaf) == child) {
869 return rootChild;
870 }
871 }
872 }
873 return childEnd(root);
874 }
875
884 static QVector<bool> collapsedWhiteSpacesForText(KisForest<KoSvgTextContentElement> &tree, QString &allText, const bool alsoCollapseLowSurrogate = false, bool includeBidiControls = false) {
885 QMap<int, KoSvgText::TextSpaceCollapse> collapseModes;
886
888 for (auto it = tree.compositionBegin(); it != tree.compositionEnd(); it++) {
889 if (it.state() == KisForestDetail::Enter) {
890 KoSvgTextProperties ownProperties = it->properties;
891 ownProperties.inheritFrom(parentProps.last());
892 parentProps.append(ownProperties);
893
894 const int children = childCount(siblingCurrent(it));
895 if (children == 0) {
896 QString text = it->text;
897 if (includeBidiControls) {
898 KoSvgText::UnicodeBidi bidi = KoSvgText::UnicodeBidi(parentProps.last().propertyOrDefault(KoSvgTextProperties::UnicodeBidiId).toInt());
899 KoSvgText::Direction direction = KoSvgText::Direction(parentProps.last().propertyOrDefault(KoSvgTextProperties::DirectionId).toInt());
900 QVector<QPair<int, int>> positions;
901 QString text = KoCssTextUtils::getBidiOpening(direction, bidi);
902 text += it->getTransformedString(positions, parentProps.last());
903 text += KoCssTextUtils::getBidiClosing(bidi);
904 }
905 KoSvgText::TextSpaceCollapse collapse = KoSvgText::TextSpaceCollapse(parentProps.last().propertyOrDefault(KoSvgTextProperties::TextCollapseId).toInt());
906 collapseModes.insert(allText.size(), collapse);
907 allText += text;
908 }
909 } else {
910 parentProps.pop_back();
911 }
912 }
913 QVector<bool> collapsed = KoCssTextUtils::collapseSpaces(&allText, collapseModes);
914
915 if (alsoCollapseLowSurrogate) {
916 for (int i = 0; i < allText.size(); i++) {
917 if (i > 0 && allText.at(i).isLowSurrogate() && allText.at(i-1).isHighSurrogate()) {
918 collapsed[i] = true;
919 }
920 }
921 }
922 return collapsed;
923 }
924
936 static void removeTransforms(KisForest<KoSvgTextContentElement> &tree, const int start, const int length) {
937 QString all;
938 QVector<bool> collapsedCharacters = collapsedWhiteSpacesForText(tree, all, true);
939
940 auto root = tree.childBegin();
941 removeTransformsImpl(root, 0, start, length, collapsedCharacters);
942 }
943
949 static int removeTransformsImpl(KisForest<KoSvgTextContentElement>::child_iterator currentTextElement, const int globalIndex, const int start, const int length, const QVector<bool> collapsedCharacters) {
950 int currentLength = 0;
951 auto it = childBegin(currentTextElement);
952 if (it != childEnd(currentTextElement)) {
953 for (; it != childEnd(currentTextElement); it++) {
954 currentLength += removeTransformsImpl(it, globalIndex + currentLength, start, length, collapsedCharacters);
955 }
956 } else {
957 currentLength = currentTextElement->text.size();
958 }
959
960 if (!currentTextElement->localTransformations.isEmpty()) {
961 int transformOffset = 0;
962 int transformOffsetEnd = 0;
963
964 for (int i = globalIndex; i < globalIndex + currentLength; i++) {
965 if (i >= collapsedCharacters.size()) break;
966 if (i < start) {
967 transformOffset += collapsedCharacters.at(i)? 0: 1;
968 }
969 if (i < start + length) {
970 transformOffsetEnd += collapsedCharacters.at(i)? 0: 1;
971 } else {
972 break;
973 }
974 }
975 if (transformOffset < currentTextElement->localTransformations.size()) {
976 int length = qBound(0, transformOffsetEnd-transformOffset, qMax(0, currentTextElement->localTransformations.size()-transformOffset));
977 currentTextElement->localTransformations.remove(transformOffset,
978 length);
979 }
980
981 }
982 return currentLength;
983 }
984
994 static void insertTransforms(KisForest<KoSvgTextContentElement> &tree, const int start, const int length, const bool allowSkipFirst) {
995 QString all;
996 QVector<bool> collapsedCharacters = collapsedWhiteSpacesForText(tree, all, true);
997
998 auto root = tree.childBegin();
999 insertTransformsImpl(root, 0, start, length, collapsedCharacters, allowSkipFirst);
1000 }
1006 static int insertTransformsImpl(KisForest<KoSvgTextContentElement>::child_iterator currentTextElement, const int globalIndex, const int start, const int length, const QVector<bool> collapsedCharacters, const bool allowSkipFirst) {
1007 int currentLength = 0;
1008 auto it = childBegin(currentTextElement);
1009 if (it != childEnd(currentTextElement)) {
1010 for (; it != childEnd(currentTextElement); it++) {
1011 currentLength += insertTransformsImpl(it, globalIndex + currentLength, start, length, collapsedCharacters, allowSkipFirst);
1012 }
1013 } else {
1014 currentLength = currentTextElement->text.size();
1015 }
1016
1017 if (!currentTextElement->localTransformations.isEmpty()) {
1018 int transformOffset = 0;
1019 int transformOffsetEnd = 0;
1020
1021 for (int i = globalIndex; i < globalIndex + currentLength; i++) {
1022 if (i < start) {
1023 transformOffset += collapsedCharacters.at(i)? 0: 1;
1024 }
1025 if (i < start + length) {
1026 transformOffsetEnd += collapsedCharacters.at(i)? 0: 1;
1027 } else {
1028 break;
1029 }
1030 }
1031
1032 // When at the start, skip the first transform.
1033 if (transformOffset == 0 && allowSkipFirst && currentTextElement->localTransformations.at(0).startsNewChunk()) {
1034 transformOffset += 1;
1035 }
1036
1037 if (transformOffset < currentTextElement->localTransformations.size()) {
1038 for (int i = transformOffset; i < transformOffsetEnd; i++) {
1039 currentTextElement->localTransformations.insert(i, KoSvgText::CharTransformation());
1040 }
1041 }
1042
1043 }
1044 return currentLength;
1045 }
1046
1057 void applyWhiteSpace(KisForest<KoSvgTextContentElement> &tree, const bool convertToPreWrapped = false) {
1058 QString allText;
1059 QVector<bool> collapsed = collapsedWhiteSpacesForText(tree, allText, false);
1060
1061 auto end = std::make_reverse_iterator(tree.childBegin());
1062 auto begin = std::make_reverse_iterator(tree.childEnd());
1063
1064 for (; begin != end; begin++) {
1065 applyWhiteSpaceImpl(begin, collapsed, allText, convertToPreWrapped);
1066 }
1067 if (convertToPreWrapped) {
1068 tree.childBegin()->properties.setProperty(KoSvgTextProperties::TextCollapseId, QVariant::fromValue(KoSvgText::Preserve));
1069 tree.childBegin()->properties.setProperty(KoSvgTextProperties::TextWrapId, QVariant::fromValue(KoSvgText::Wrap));
1070 }
1071 }
1072
1073 void applyWhiteSpaceImpl(std::reverse_iterator<KisForest<KoSvgTextContentElement>::child_iterator> current, QVector<bool> &collapsed, QString &allText, const bool convertToPreWrapped) {
1074 auto base = current.base();
1075 // It seems that .base() refers to the next entry instead of the pointer,
1076 // which is coherent with the template implementation of "operator*" here:
1077 // https://en.cppreference.com/w/cpp/iterator/reverse_iterator.html
1078 base--;
1079 if(base != siblingEnd(base)) {
1080 auto end = std::make_reverse_iterator(childBegin(base));
1081 auto begin = std::make_reverse_iterator(childEnd(base));
1082 for (; begin != end; begin++) {
1083 applyWhiteSpaceImpl(begin, collapsed, allText, convertToPreWrapped);
1084 }
1085 }
1086
1087 if (!current->text.isEmpty()) {
1088 const int total = current->text.size();
1089 QString currentText = allText.right(total);
1090
1091 for (int i = 0; i < total; i++) {
1092 const int j = total - (i+1);
1093 const bool col = collapsed.takeLast();
1094 if (col) {
1095 currentText.remove(j, 1);
1096 }
1097
1098 }
1099 current->text = currentText;
1100 allText.chop(total);
1101 }
1102 if (convertToPreWrapped) {
1103 current->properties.removeProperty(KoSvgTextProperties::TextCollapseId);
1104 current->properties.removeProperty(KoSvgTextProperties::TextWrapId);
1105 }
1106 }
1107
1108 static QVector<KoSvgText::CharTransformation> resolvedTransformsForTree(KisForest<KoSvgTextContentElement> &tree, bool shapesInside = false, bool includeBidiControls = false) {
1109 QString all;
1110 QVector<bool> collapsed = collapsedWhiteSpacesForText(tree, all, false, includeBidiControls);
1111 QVector<CharacterResult> result(all.size());
1112 int globalIndex = 0;
1113 KoSvgTextProperties props = tree.childBegin()->properties;
1117 bool isHorizontal = mode == KoSvgText::HorizontalTB;
1118 bool isWrapped = (props.hasProperty(KoSvgTextProperties::InlineSizeId)
1119 || shapesInside);
1120 QVector<KoSvgText::CharTransformation> resolvedTransforms(all.size());
1121 resolveTransforms(tree.childBegin(), all, result, globalIndex,
1122 isHorizontal, isWrapped, false, resolvedTransforms,
1124 false);
1125 return resolvedTransforms;
1126 }
1127
1135 static void insertNewLinesAtAnchors(KisForest<KoSvgTextContentElement> &tree, bool shapesInside = false) {
1136 QVector<KoSvgText::CharTransformation> resolvedTransforms = resolvedTransformsForTree(tree, shapesInside);
1137
1138 auto end = std::make_reverse_iterator(tree.childBegin());
1139 auto begin = std::make_reverse_iterator(tree.childEnd());
1140
1141 bool inTextPath = false;
1142 for (; begin != end; begin++) {
1143 insertNewLinesAtAnchorsImpl(begin, resolvedTransforms, inTextPath);
1144 }
1145 }
1146
1148 QVector<KoSvgText::CharTransformation> &resolvedTransforms,
1149 bool &inTextPath) {
1150
1151 inTextPath = (!current->textPathId.isEmpty());
1152 auto base = current.base();
1153 base--;
1154 if(base != siblingEnd(base)) {
1155 auto end = std::make_reverse_iterator(childBegin(base));
1156 auto begin = std::make_reverse_iterator(childEnd(base));
1157 for (; begin != end; begin++) {
1158 insertNewLinesAtAnchorsImpl(begin,
1159 resolvedTransforms,
1160 inTextPath);
1161 }
1162 }
1163
1164 if (!current->text.isEmpty()) {
1165 const int total = current->text.size();
1166
1167 for (int i = 0; i < total; i++) {
1168 const int j = total - (i+1);
1169 KoSvgText::CharTransformation transform = resolvedTransforms.takeLast();
1176 bool startsNewChunk = transform.startsNewChunk() && j == 0;
1177
1178 if (inTextPath) {
1179 // First transform in path is always absolute, so we don't insert a newline.
1180 startsNewChunk = false;
1181 inTextPath = false;
1182 }
1183 // 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.
1184 if (startsNewChunk && !resolvedTransforms.isEmpty() && current->text.at(j) != QChar::LineFeed) {
1185 current->text.insert(j, "\n");
1186 }
1187 }
1188 }
1189 current->localTransformations.clear();
1190 }
1191
1193 for (int i = 0; i< layout.size(); i++) {
1194 //split all anchored chunks, so we can set transforms on them.
1195 if (layout.at(i).anchored_chunk) {
1196 int plainTextIndex = layout.at(i).plaintTextIndex;
1197 splitContentElement(tree, plainTextIndex);
1198 }
1199 }
1200
1201 int globalIndex = 0;
1202 for (auto it = tree.childBegin(); it != tree.childEnd(); it++) {
1204 it->properties.propertyOrDefault(KoSvgTextProperties::WritingModeId).toInt());
1205 bool isHorizontal = mode == KoSvgText::HorizontalTB;
1206 setTransformsFromLayoutImpl(it, KoSvgTextProperties::defaultProperties(), layout, globalIndex, isHorizontal);
1207 }
1208 }
1209
1211 const KoSvgTextProperties parentProps, const QVector<CharacterResult> layout,
1212 int &globalIndex, bool isHorizontal) {
1213 KoSvgTextProperties props = current->properties;
1214 props.inheritFrom(parentProps);
1215 if (!current->textPathId.isEmpty()) return; // When we're doing text-on-path, we're already in preformatted mode.
1216 for (auto it = childBegin(current); it!= childEnd(current); it++) {
1217 setTransformsFromLayoutImpl(it, props, layout, globalIndex, isHorizontal);
1218 }
1219
1220 if (current->text.isEmpty()) {
1221 current->localTransformations.clear();
1222 } else {
1224 const int length = current->numChars(true, props);
1225
1226 for (int i = globalIndex; i< globalIndex+length; i++) {
1227 CharacterResult result = layout.value(i);
1228
1229 if (!result.addressable) {
1230 continue;
1231 }
1233
1234 //TODO: Also split up content element if multiple anchored chunks.
1235 if (result.anchored_chunk) {
1236 int endIndex = 0;
1237 qreal shift = anchoredChunkShift(layout, isHorizontal, i, endIndex);
1238 QPointF offset = isHorizontal? QPointF(shift, 0): QPointF(0, shift);
1239 transform.xPos = result.finalPosition.x() - offset.x();
1240 transform.yPos = result.finalPosition.y() - offset.y();
1241 } else if (i > 0) {
1242 CharacterResult resultPrev = layout.value(i-1);
1243 QPointF offset = (result.finalPosition - result.cssPosition) - (resultPrev.finalPosition - resultPrev.cssPosition);
1244
1245 transform.dxPos = offset.x();
1246 transform.dyPos = offset.y();
1247 }
1248 transform.rotate = result.rotate;
1249
1250 transforms.append(transform);
1251 }
1252 current->localTransformations = transforms;
1253 current->text = current->text.split("\n").join(" ");
1254 globalIndex += length;
1255 }
1256 }
1257
1267 for (auto it = tree.depthFirstTailBegin(); it != tree.depthFirstTailEnd(); it++) {
1268 const int children = childCount(siblingCurrent(it));
1269 if (children == 0) {
1270
1271 // Remove empty leafs that are not the root or text paths.
1272 const int length = it->numChars(false);
1273 if (length == 0 && siblingCurrent(it) != tree.childBegin() && it->textPathId.isEmpty()) {
1274 tree.erase(siblingCurrent(it));
1275 } else {
1276 // check if siblings are similar.
1277 auto siblingPrev = siblingCurrent(it);
1279 QVariant(KoSvgText::BidiNormal)).toInt());
1280 siblingPrev--;
1281 if (!isEnd(siblingPrev)
1282 && siblingPrev != siblingCurrent(it)
1283 && (siblingPrev->localTransformations.isEmpty() && it->localTransformations.isEmpty())
1284 && (siblingPrev->textPathId.isEmpty() && it->textPathId.isEmpty())
1285 && (siblingPrev->textLength.isAuto && it->textLength.isAuto)
1286 && (siblingPrev->properties == it->properties)
1288 && childCount(siblingPrev) == 0) {
1289 // TODO: handle localtransforms better; annoyingly, this requires whitespace handling
1290 siblingPrev->text += it->text;
1291 tree.erase(siblingCurrent(it));
1292 }
1293 }
1294 } else if (children == 1) {
1295 // merge single children into parents if possible.
1296 auto child = childBegin(siblingCurrent(it));
1297 if ((child->localTransformations.isEmpty() && it->localTransformations.isEmpty())
1298 && (child->textPathId.isEmpty() && it->textPathId.isEmpty())
1299 && (child->textLength.isAuto && it->textLength.isAuto)
1300 && (!child->properties.hasNonInheritableProperties() || !it->properties.hasNonInheritableProperties())) {
1301 if (it->properties.hasNonInheritableProperties()) {
1302 KoSvgTextProperties props = it->properties;
1303 props.setAllButNonInheritableProperties(child->properties);
1304 child->properties = props;
1305 } else {
1306 child->properties.inheritFrom(it->properties);
1307 }
1308 tree.move(child, siblingCurrent(it));
1309 tree.erase(siblingCurrent(it));
1310 }
1311 }
1312 }
1313 }
1314
1316 if (treeIndex.isEmpty()) return parent;
1317 QVector<int> idx = treeIndex;
1318 int count = idx.takeFirst();
1319 for (auto child = KisForestDetail::childBegin(parent); child != KisForestDetail::childEnd(parent); child++) {
1320 if (count == 0) {
1322 return iteratorForTreeIndex(idx, child);
1323 } else {
1324 return child;
1325 }
1326 }
1327 count -= 1;
1328 }
1329 return KisForestDetail::childEnd(parent);
1330 }
1331
1333 for (auto child = KisForestDetail::childBegin(parent); child != KisForestDetail::childEnd(parent); child++) {
1334 if (child == target) {
1335 return true;
1336 } else if ((KisForestDetail::childBegin(child) != KisForestDetail::childEnd(child))) {
1337 if (startIndexOfIterator(child, target, currentIndex)) {
1338 return true;
1339 }
1340 } else {
1341 currentIndex += numChars(child);
1342 }
1343 }
1344 return false;
1345 }
1346
1348
1354 for (auto it = childBegin(parent); it != childEnd(parent); it++) {
1355 if (it->textPathId == name) {
1356 it->textPathId = QString();
1357 break;
1358 }
1359 }
1360 }
1361
1362 static void makeTextPathNameUnique(QList<KoShape*> textPaths, KoShape *textPath) {
1363 bool textPathNameUnique = false;
1364 int textPathNumber = textPaths.size();
1365 QString newTextPathName = textPath->name();
1366 while(!textPathNameUnique) {
1367 textPathNameUnique = true;
1368 Q_FOREACH(KoShape *shape, textPaths) {
1369 if (shape->name() == newTextPathName) {
1370 textPathNameUnique = false;
1371 textPathNumber += 1;
1372 break;
1373 }
1374 }
1375 if (textPathNameUnique && !newTextPathName.isEmpty()) {
1376 textPath->setName(newTextPathName);
1377 } else {
1378 textPathNameUnique = false;
1379 }
1380 newTextPathName = QString("textPath"+QString::number(textPathNumber));
1381 }
1382 }
1383};
1384
1385#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.
NodeType * node() const
Definition KisForest.h:78
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...
static QString getBidiClosing(KoSvgText::UnicodeBidi bidi)
getBidiClosing Returns the bidi closing string associated with the given Css unicode-bidi value.
static QString getBidiOpening(bool ltr, KoSvgText::UnicodeBidi bidi)
getBidiOpening Get the bidi opening string associated with the given Css unicode-bidi value and direc...
The position of a path point within a path shape.
Definition KoPathShape.h:63
void setName(const QString &name)
Definition KoShape.cpp:960
void setSelectable(bool selectable)
Definition KoShape.cpp:832
QString name() const
Definition KoShape.cpp:955
The KoSvgTextNodeIndex class.
@ InlineSizeId
KoSvgText::AutoValue.
@ UnicodeBidiId
KoSvgText::UnicodeBidi.
@ TextCollapseId
KoSvgText::TextSpaceCollapse.
@ WritingModeId
KoSvgText::WritingMode.
@ DirectionId
KoSvgText::Direction.
@ 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
QVector< CursorPos > cursorPos
void updateTextWrappingAreas()
updateShapeContours The current shape contours can be slow to compute, so this function calls computi...
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)
static QVector< KoSvgText::CharTransformation > resolvedTransformsForTree(KisForest< KoSvgTextContentElement > &tree, bool shapesInside=false, bool includeBidiControls=false)
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)
QScopedPointer< KoShapeGroup > shapeGroup
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.
static void computeFontMetrics(KisForest< KoSvgTextContentElement >::child_iterator parent, const KoSvgTextProperties &parentProps, const KoSvgText::FontMetrics &parentBaselineTable, const KoSvgText::Baseline parentBaseline, QVector< CharacterResult > &result, int &currentIndex, const KoSvgText::ResolutionHandler resHandler, const bool isHorizontal, const bool disableFontMatching)
void paintPaths(QPainter &painter, const QPainterPath &outlineRect, const KoShape *rootShape, const QVector< CharacterResult > &result, const KoSvgText::TextRendering rendering, QPainterPath &chunk, int &currentIndex)
QList< QPainterPath > currentTextWrappingAreas
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....
QList< KoShape * > internalShapes() const
void updateInternalShapesList()
QList< KoShape * > shapesInside
static KisForest< KoSvgTextContentElement >::child_iterator iteratorForTreeIndex(const QVector< int > treeIndex, KisForest< KoSvgTextContentElement >::child_iterator parent)
static KoShape * textPathByName(QString name, QList< KoShape * > textPaths)
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 splitTree(KisForest< KoSvgTextContentElement > &tree, int index, bool textPathAfterSplit)
splitTree Split the whole hierarchy of nodes at the given index.
static void applyAnchoring(QVector< CharacterResult > &result, bool isHorizontal, const KoSvgText::ResolutionHandler resHandler)
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, QList< KoShape * > textPaths)
static void insertNewLinesAtAnchorsImpl(std::reverse_iterator< KisForest< KoSvgTextContentElement >::child_iterator > current, QVector< KoSvgText::CharTransformation > &resolvedTransforms, bool &inTextPath)
KoSvgTextNodeIndex createTextNodeIndex(KisForest< KoSvgTextContentElement >::child_iterator textElement) const
static void applyTextPath(KisForest< KoSvgTextContentElement >::child_iterator parent, QVector< CharacterResult > &result, bool isHorizontal, QPointF &startPos, const KoSvgTextProperties resolvedProps, QList< KoShape * > textPaths)
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 makeTextPathNameUnique(QList< KoShape * > textPaths, KoShape *textPath)
static QVector< QPointF > getLigatureCarets(const KoSvgText::ResolutionHandler &resHandler, const bool isHorizontal, raqm_glyph_t &currentGlyph)
static void insertNewLinesAtAnchors(KisForest< KoSvgTextContentElement > &tree, bool shapesInside=false)
insertNewLinesAtAnchors Resolves character transforms and then inserts new lines at each transform th...
static KisForest< KoSvgTextContentElement >::child_iterator findTopLevelParent(KisForest< KoSvgTextContentElement >::child_iterator root, KisForest< KoSvgTextContentElement >::child_iterator child)
findTopLevelParent Returns the toplevel parent of child that is not root.
static bool splitContentElement(KisForest< KoSvgTextContentElement > &tree, int index, bool allowEmptyText=true)
splitContentElement split the contentElement in tree at index into two nodes.
QScopedPointer< KoShapePainter > internalShapesPainter
std::optional< BulkActionState > bulkActionState
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.
void handleShapes(const QList< KoShape * > &sourceShapeList, const QList< KoShape * > referenceList2, const QList< KoShape * > referenceShapeList, QList< KoShape * > &destinationShapeList)
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)
static QVector< bool > collapsedWhiteSpacesForText(KisForest< KoSvgTextContentElement > &tree, QString &allText, const bool alsoCollapseLowSurrogate=false, bool includeBidiControls=false)
collapsedWhiteSpacesForText This returns the collapsed spaces for a given piece of text,...
void clearAssociatedOutlines()
static QVector< SubChunk > collectSubChunks(KisForest< KoSvgTextContentElement >::child_iterator it, KoSvgTextProperties parent, bool textInPath, bool &firstTextInPath)
static QList< QPainterPath > generateShapes(const QList< KoShape * > shapesInside, const QList< KoShape * > shapesSubtract, const KoSvgTextProperties &properties)
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)
QList< KoShape * > textPaths
static void handleLineBoxAlignment(KisForest< KoSvgTextContentElement >::child_iterator parent, QVector< CharacterResult > &result, const QVector< LineBox > lineBoxes, int &currentIndex, const bool isHorizontal, const KoSvgTextProperties resolvedProps)
static void removeTextPathId(KisForest< KoSvgTextContentElement >::child_iterator parent, const QString &name)
removeTextPathId Remove the text path id with the given name from the toplevel elements.
void applyWhiteSpace(KisForest< KoSvgTextContentElement > &tree, const bool convertToPreWrapped=false)
applyWhiteSpace CSS Whitespace processes whitespaces so that duplicate white spaces and unnecessary h...
std::variant< std::monostate, Outline, Bitmap, ColorLayers > Variant
ResultIterator hierarchyBegin(Iterator it)
Definition KisForest.h:419
ChildIterator< value_type, is_const > childBegin(const ChildIterator< value_type, is_const > &it)
Definition KisForest.h:290
ResultIterator hierarchyEnd(Iterator it)
Definition KisForest.h:427
ResultIterator tailSubtreeBegin(Iterator it)
Definition KisForest.h:706
ChildIterator< value_type, is_const > parent(const ChildIterator< value_type, is_const > &it)
Definition KisForest.h:327
ResultIterator tailSubtreeEnd(Iterator it)
Definition KisForest.h:714
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 textPathAndAnchoringOffset
Offset caused by textPath and anchoring.
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,...
QPointF textLengthOffset
offset caused by textLength
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 textPathId
The textpath's name, if any.
QString text
Plain text of the current node. Use insertText and removeText to manipulate it.
KoSvgText::TextOnPathInfo textPathInfo
Text path info for the text-on-path algorithm.
BulkActionState(QRectF originalBoundingRectArg)
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