File indexing completed on 2024-06-09 04:21:02

0001 /*
0002  *  SPDX-FileCopyrightText: 2017 Dmitry Kazakov <dimula73@gmail.com>
0003  *  SPDX-FileCopyrightText: 2022 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "KoSvgTextShape.h"
0009 #include "KoSvgTextShape_p.h"
0010 
0011 #include <QTextLayout>
0012 
0013 #include <klocalizedstring.h>
0014 
0015 #include "KoSvgTextProperties.h"
0016 #include <KoDocumentResourceManager.h>
0017 #include <KoShapeContainer_p.h>
0018 #include <KoShapeController.h>
0019 #include <text/KoCssTextUtils.h>
0020 #include <text/KoFontRegistry.h>
0021 #include <text/KoSvgTextShapeMarkupConverter.h>
0022 #include <text/KoPolygonUtils.h>
0023 
0024 #include <kis_global.h>
0025 
0026 #include <KoClipMaskPainter.h>
0027 #include <KoColorBackground.h>
0028 #include <KoIcon.h>
0029 #include <KoPathShape.h>
0030 #include <KoProperties.h>
0031 #include <KoShapeLoadingContext.h>
0032 #include <KoXmlNS.h>
0033 #include <KoInsets.h>
0034 
0035 #include <SvgLoadingContext.h>
0036 #include <SvgGraphicContext.h>
0037 #include <SvgUtil.h>
0038 #include <SvgStyleWriter.h>
0039 
0040 #include <QPainter>
0041 #include <QPainterPath>
0042 #include <QtMath>
0043 
0044 #include <FlakeDebug.h>
0045 
0046 
0047 KoSvgTextShape::KoSvgTextShape()
0048     : KoShape()
0049     , d(new Private)
0050 {
0051     setShapeId(KoSvgTextShape_SHAPEID);
0052     d->textData.insert(d->textData.childBegin(), KoSvgTextContentElement());
0053 }
0054 
0055 KoSvgTextShape::KoSvgTextShape(const KoSvgTextShape &rhs)
0056     : KoShape(rhs)
0057     , d(new Private(*rhs.d))
0058 {
0059     setShapeId(KoSvgTextShape_SHAPEID);
0060 }
0061 
0062 KoSvgTextShape::~KoSvgTextShape()
0063 {
0064 }
0065 
0066 const QString &KoSvgTextShape::defaultPlaceholderText()
0067 {
0068     static const QString s_placeholderText = i18nc("Default text for the text shape", "Placeholder Text");
0069     return s_placeholderText;
0070 }
0071 
0072 KoShape *KoSvgTextShape::cloneShape() const
0073 {
0074     return new KoSvgTextShape(*this);
0075 }
0076 
0077 void KoSvgTextShape::shapeChanged(ChangeType type, KoShape *shape)
0078 {
0079     KoShape::shapeChanged(type, shape);
0080 
0081     if (type == StrokeChanged || type == BackgroundChanged || type == ContentChanged) {
0082         relayout();
0083     }
0084 }
0085 
0086 void KoSvgTextShape::setResolution(qreal xRes, qreal yRes)
0087 {
0088     int roundedX = qRound(xRes);
0089     int roundedY = qRound(yRes);
0090     if (roundedX != d->xRes || roundedY != d->yRes) {
0091         d->xRes = roundedX;
0092         d->yRes = roundedY;
0093         relayout();
0094     }
0095 }
0096 
0097 int KoSvgTextShape::posLeft(int pos, bool visual)
0098 {
0099     KoSvgText::WritingMode mode = writingMode();
0100     KoSvgText::Direction direction = KoSvgText::Direction(this->textProperties().propertyOrDefault(KoSvgTextProperties::DirectionId).toInt());
0101     if (mode == KoSvgText::VerticalRL) {
0102         return nextLine(pos);
0103     } else if (mode == KoSvgText::VerticalLR) {
0104         return previousLine(pos);
0105     } else {
0106         if (direction == KoSvgText::DirectionRightToLeft) {
0107             return nextPos(pos, visual);
0108         } else {
0109             return previousPos(pos, visual);
0110         }
0111     }
0112 }
0113 
0114 int KoSvgTextShape::posRight(int pos, bool visual)
0115 {
0116     KoSvgText::WritingMode mode = writingMode();
0117     KoSvgText::Direction direction = KoSvgText::Direction(this->textProperties().propertyOrDefault(KoSvgTextProperties::DirectionId).toInt());
0118 
0119     if (mode == KoSvgText::VerticalRL) {
0120         return previousLine(pos);
0121     } else if (mode == KoSvgText::VerticalLR) {
0122         return nextLine(pos);
0123     } else {
0124         if (direction == KoSvgText::DirectionRightToLeft) {
0125             return previousPos(pos, visual);
0126         } else {
0127             return nextPos(pos, visual);
0128         }
0129     }
0130 }
0131 
0132 int KoSvgTextShape::posUp(int pos, bool visual)
0133 {
0134     KoSvgText::WritingMode mode = writingMode();
0135     KoSvgText::Direction direction = KoSvgText::Direction(this->textProperties().propertyOrDefault(KoSvgTextProperties::DirectionId).toInt());
0136     if (mode == KoSvgText::VerticalRL || mode == KoSvgText::VerticalLR) {
0137         if (direction == KoSvgText::DirectionRightToLeft) {
0138             return nextPos(pos, visual);
0139         } else {
0140             return previousPos(pos, visual);
0141         }
0142     } else {
0143         return previousLine(pos);
0144     }
0145 }
0146 
0147 int KoSvgTextShape::posDown(int pos, bool visual)
0148 {
0149     KoSvgText::WritingMode mode = writingMode();
0150     KoSvgText::Direction direction = KoSvgText::Direction(this->textProperties().propertyOrDefault(KoSvgTextProperties::DirectionId).toInt());
0151     if (mode == KoSvgText::VerticalRL || mode == KoSvgText::VerticalLR) {
0152         if (direction == KoSvgText::DirectionRightToLeft) {
0153             return previousPos(pos, visual);
0154         } else {
0155             return nextPos(pos, visual);
0156         }
0157     } else {
0158         return nextLine(pos);
0159     }
0160 }
0161 
0162 int KoSvgTextShape::lineStart(int pos)
0163 {
0164     if (pos < 0 || d->cursorPos.isEmpty() || d->result.isEmpty()) {
0165         return pos;
0166     }
0167     CursorPos p = d->cursorPos.at(pos);
0168     if (d->result.at(p.cluster).anchored_chunk && p.offset == 0) {
0169         return pos;
0170     }
0171     int candidate = 0;
0172     for (int i = 0; i < pos; i++) {
0173         CursorPos p2 = d->cursorPos.at(i);
0174         if (d->result.at(p2.cluster).anchored_chunk && p2.offset == 0) {
0175             candidate = i;
0176         }
0177     }
0178     return candidate;
0179 }
0180 
0181 int KoSvgTextShape::lineEnd(int pos)
0182 {
0183     if (pos < 0 || d->cursorPos.isEmpty() || d->result.isEmpty()) {
0184         return pos;
0185     }
0186     if (pos > d->cursorPos.size() - 1) {
0187         return d->cursorPos.size() - 1;
0188     }
0189     int candidate = 0;
0190     int posCluster = d->cursorPos.at(pos).cluster;
0191     for (int i = pos; i < d->cursorPos.size(); i++) {
0192         CursorPos p = d->cursorPos.at(i);
0193         if (d->result.at(p.cluster).anchored_chunk && i > pos && posCluster != p.cluster) {
0194             break;
0195         }
0196         candidate = i;
0197     }
0198     return candidate;
0199 }
0200 
0201 int KoSvgTextShape::wordLeft(int pos, bool visual)
0202 {
0203     //TODO: figure out preferred behaviour for wordLeft in RTL && visual.
0204     Q_UNUSED(visual)
0205     if (pos < 0 || pos > d->cursorPos.size()-1 || d->result.isEmpty() || d->cursorPos.isEmpty()) {
0206         return pos;
0207     }
0208     KoSvgText::Direction direction = KoSvgText::Direction(this->textProperties().propertyOrDefault(KoSvgTextProperties::DirectionId).toInt());
0209     if (direction == KoSvgText::DirectionRightToLeft) {
0210         return wordEnd(pos);
0211     }
0212     return wordStart(pos);
0213 }
0214 
0215 int KoSvgTextShape::wordRight(int pos, bool visual)
0216 {
0217     Q_UNUSED(visual)
0218     if (pos < 0 || pos > d->cursorPos.size()-1 || d->result.isEmpty() || d->cursorPos.isEmpty()) {
0219         return pos;
0220     }
0221     KoSvgText::Direction direction = KoSvgText::Direction(this->textProperties().propertyOrDefault(KoSvgTextProperties::DirectionId).toInt());
0222     if (direction == KoSvgText::DirectionRightToLeft) {
0223         return wordStart(pos);
0224     }
0225     return wordEnd(pos);
0226 }
0227 
0228 int KoSvgTextShape::nextIndex(int pos)
0229 {
0230     if (d->cursorPos.isEmpty()) {
0231         return pos;
0232     }
0233     int currentIndex = d->cursorPos.at(pos).index;
0234 
0235     for (int i = pos; i < d->cursorPos.size(); i++) {
0236         if (d->cursorPos.at(i).index > currentIndex) {
0237             return i;
0238         }
0239     }
0240     return pos;
0241 }
0242 
0243 int KoSvgTextShape::previousIndex(int pos)
0244 {
0245     if (d->cursorPos.isEmpty()) {
0246         return pos;
0247     }
0248     int currentIndex = d->cursorPos.at(pos).index;
0249 
0250     for (int i = pos; i >= 0; i--) {
0251         if (d->cursorPos.at(i).index < currentIndex) {
0252             return i;
0253         }
0254     }
0255     return pos;
0256 }
0257 
0258 int KoSvgTextShape::nextPos(int pos, bool visual)
0259 {
0260     if (d->cursorPos.isEmpty()) {
0261         return -1;
0262     }
0263 
0264     if(visual) {
0265         int visualIndex = d->logicalToVisualCursorPos.value(pos);
0266         return d->logicalToVisualCursorPos.key(qMin(visualIndex + 1, d->cursorPos.size() - 1), d->cursorPos.size() - 1);
0267     }
0268 
0269     return qMin(pos + 1, d->cursorPos.size() - 1);
0270 }
0271 
0272 int KoSvgTextShape::previousPos(int pos, bool visual)
0273 {
0274     if (d->cursorPos.isEmpty()) {
0275         return -1;
0276     }
0277 
0278     if(visual) {
0279         int visualIndex = d->logicalToVisualCursorPos.value(pos);
0280         return d->logicalToVisualCursorPos.key(qMax(visualIndex - 1, 0), 0);
0281     }
0282 
0283     return qMax(pos - 1, 0);
0284 }
0285 
0286 int KoSvgTextShape::nextLine(int pos)
0287 {
0288     if (pos < 0 || pos > d->cursorPos.size()-1 || d->result.isEmpty() || d->cursorPos.isEmpty()) {
0289         return pos;
0290     }
0291 
0292     int nextLineStart = lineEnd(pos)+1;
0293     int nextLineEnd = lineEnd(nextLineStart);
0294     CursorPos cursorPos = d->cursorPos.at(pos);
0295     if (!this->shapesInside().isEmpty()) {
0296         LineBox nextLineBox;
0297         for (int i = 0; i < d->lineBoxes.size(); ++i) {
0298             for (int j = 0; j < d->lineBoxes.at(i).chunks.size(); ++j) {
0299                 if (d->lineBoxes.at(i).chunks.at(j).chunkIndices.contains(cursorPos.cluster)) {
0300                     nextLineBox = d->lineBoxes.at(qMin(i + 1, d->lineBoxes.size()-1));
0301                 }
0302             }
0303         }
0304         if (nextLineBox.chunks.size()>0) {
0305             int first = -1;
0306             int last = -1;
0307             Q_FOREACH(LineChunk chunk, nextLineBox.chunks) {
0308                 Q_FOREACH (int i, chunk.chunkIndices) {
0309                     if (d->result.at(i).addressable) {
0310                         if (first < 0) {
0311                             first = d->result.at(i).cursorInfo.graphemeIndices.first();
0312                         }
0313                         last = d->result.at(i).cursorInfo.graphemeIndices.last();
0314                     }
0315                 }
0316             }
0317             if (first > -1 && last > -1) {
0318                 nextLineStart = posForIndex(first);
0319                 nextLineEnd = posForIndex(last);
0320             }
0321         }
0322     }
0323 
0324 
0325     if (nextLineStart > d->cursorPos.size()-1) {
0326         return d->cursorPos.size()-1;
0327     }
0328 
0329     CharacterResult res = d->result.at(cursorPos.cluster);
0330     QPointF currentPoint = res.finalPosition;
0331     currentPoint += res.cursorInfo.offsets.value(cursorPos.offset, res.advance);
0332     int candidate = posForPoint(currentPoint, nextLineStart, nextLineEnd+1);
0333     if (candidate < 0) {
0334         return pos;
0335     }
0336 
0337     return candidate;
0338 }
0339 
0340 int KoSvgTextShape::previousLine(int pos)
0341 {
0342     if (pos < 0 || pos > d->cursorPos.size()-1 || d->result.isEmpty() || d->cursorPos.isEmpty()) {
0343         return pos;
0344     }
0345     int currentLineStart = lineStart(pos);
0346     if (currentLineStart - 1 < 0) {
0347         return 0;
0348     }
0349     int previousLineStart = lineStart(currentLineStart-1);
0350 
0351     CursorPos cursorPos = d->cursorPos.at(pos);
0352     if (!this->shapesInside().isEmpty()) {
0353         LineBox previousLineBox;
0354         for (int i = 0; i < d->lineBoxes.size(); ++i) {
0355             for (int j = 0; j < d->lineBoxes.at(i).chunks.size(); ++j) {
0356                 if (d->lineBoxes.at(i).chunks.at(j).chunkIndices.contains(cursorPos.cluster)) {
0357                     previousLineBox = d->lineBoxes.at(qMax(i - 1, 0));
0358                 }
0359             }
0360         }
0361         if (previousLineBox.chunks.size()>0) {
0362             int first = -1;
0363             int last = -1;
0364             Q_FOREACH(LineChunk chunk, previousLineBox.chunks) {
0365                 Q_FOREACH (int i, chunk.chunkIndices) {
0366                     if (d->result.at(i).addressable) {
0367                         if (first < 0) {
0368                             first = d->result.at(i).cursorInfo.graphemeIndices.first();
0369                         }
0370                         last = d->result.at(i).cursorInfo.graphemeIndices.last();
0371                     }
0372                 }
0373             }
0374             if (first > -1 && last > -1) {
0375                 previousLineStart = posForIndex(first);
0376                 currentLineStart = posForIndex(last);
0377             }
0378         }
0379     }
0380 
0381     CharacterResult res = d->result.at(cursorPos.cluster);
0382     QPointF currentPoint = res.finalPosition;
0383     currentPoint += res.cursorInfo.offsets.value(cursorPos.offset, res.advance);
0384     int candidate = posForPoint(currentPoint, previousLineStart, currentLineStart);
0385     if (candidate < 0) {
0386         return pos;
0387     }
0388 
0389     return candidate;
0390 }
0391 
0392 int KoSvgTextShape::wordEnd(int pos)
0393 {
0394     if (pos < 0 || pos > d->cursorPos.size()-1 || d->result.isEmpty() || d->cursorPos.isEmpty()) {
0395         return pos;
0396     }
0397     int currentLineEnd = lineEnd(pos);
0398     if (pos == lineStart(pos) || pos == currentLineEnd) {
0399         return pos;
0400     }
0401 
0402     int wordEnd = pos;
0403     for (int i = pos; i<= currentLineEnd; i++) {
0404         wordEnd = i;
0405         CursorPos cursorPos = d->cursorPos.at(i);
0406         if (d->result.at(cursorPos.cluster).cursorInfo.isWordBoundary && cursorPos.offset == 0) {
0407             break;
0408         }
0409 
0410     }
0411 
0412     return wordEnd;
0413 }
0414 
0415 int KoSvgTextShape::wordStart(int pos)
0416 {
0417     if (pos < 0 || pos > d->cursorPos.size()-1 || d->result.isEmpty() || d->cursorPos.isEmpty()) {
0418         return pos;
0419     }
0420     int currentLineStart = lineStart(pos);
0421     if (pos == currentLineStart || pos == lineEnd(pos)) {
0422         return pos;
0423     }
0424 
0425     int wordStart = pos;
0426     bool breakNext = false;
0427     for (int i = pos; i >= currentLineStart; i--) {
0428         if (breakNext) break;
0429         CursorPos cursorPos = d->cursorPos.at(i);
0430         if (d->result.at(cursorPos.cluster).cursorInfo.isWordBoundary && cursorPos.offset == 0) {
0431             breakNext = true;
0432         }
0433         wordStart = i;
0434     }
0435 
0436     return wordStart;
0437 }
0438 
0439 QPainterPath KoSvgTextShape::defaultCursorShape()
0440 {
0441     KoSvgText::WritingMode mode = writingMode();
0442     double fontSize = this->textProperties().propertyOrDefault(KoSvgTextProperties::FontSizeId).toReal();
0443     QPainterPath p;
0444     if (mode == KoSvgText::HorizontalTB) {
0445         p.moveTo(0, fontSize*0.2);
0446         p.lineTo(0, -fontSize);
0447     } else {
0448         p.moveTo(-fontSize * 0.5, 0);
0449         p.lineTo(fontSize, 0);
0450     }
0451     p.translate(d->initialTextPosition);
0452 
0453     return p;
0454 }
0455 
0456 QPainterPath KoSvgTextShape::cursorForPos(int pos, QLineF &caret, QColor &color, double bidiFlagSize)
0457 {
0458     if (d->result.isEmpty() || d->cursorPos.isEmpty() || pos < 0 || pos >= d->cursorPos.size()) {
0459         return defaultCursorShape();
0460     }
0461     QPainterPath p;
0462 
0463     CursorPos cursorPos = d->cursorPos.at(pos);
0464 
0465     CharacterResult res = d->result.at(cursorPos.cluster);
0466 
0467     const QTransform tf = res.finalTransform();
0468     color = res.cursorInfo.color;
0469     caret = res.cursorInfo.caret;
0470     caret.translate(res.cursorInfo.offsets.value(cursorPos.offset, QPointF()));
0471 
0472     p.moveTo(tf.map(caret.p1()));
0473     p.lineTo(tf.map(caret.p2()));
0474     if (d->isBidi && bidiFlagSize > 0) {
0475         int sign = res.cursorInfo.rtl ? -1 : 1;
0476         double bidiFlagHalf = bidiFlagSize * 0.5;
0477         QPointF point3;
0478         QPointF point4;
0479         if (writingMode() == KoSvgText::HorizontalTB) {
0480             qreal slope = bidiFlagHalf * (caret.dx()/ caret.dy());
0481             point3 = QPointF(caret.p2().x() + slope + (sign * bidiFlagHalf), caret.p2().y() + bidiFlagHalf);
0482             point4 = QPointF(point3.x() + slope - (sign * bidiFlagHalf),point3.y() + bidiFlagHalf);
0483         } else {
0484             qreal slope = bidiFlagHalf * (caret.dy()/ caret.dx());
0485             point3 = QPointF(caret.p2().x() - bidiFlagHalf, caret.p2().y() - slope + (sign * bidiFlagHalf));
0486             point4 = QPointF(point3.x() - bidiFlagHalf, point3.y() - slope - (sign * bidiFlagHalf));
0487         }
0488         p.lineTo(tf.map(point3));
0489         p.lineTo(tf.map(point4));
0490     }
0491     caret = tf.map(caret);
0492 
0493     return p;
0494 }
0495 
0496 QPainterPath KoSvgTextShape::selectionBoxes(int pos, int anchor)
0497 {
0498     int start = qMin(pos, anchor);
0499     int end = qMax(pos, anchor);
0500 
0501     if (start == end || start < 0 || end >= d->cursorPos.size()) {
0502         return QPainterPath();
0503     }
0504 
0505     QPainterPath p;
0506     p.setFillRule(Qt::WindingFill);
0507     for (int i = start+1; i <= end; i++) {
0508         CursorPos cursorPos = d->cursorPos.at(i);
0509         CharacterResult res = d->result.at(cursorPos.cluster);
0510         const QTransform tf = res.finalTransform();
0511         QLineF first = res.cursorInfo.caret;
0512         QLineF last = first;
0513         if (res.cursorInfo.rtl) {
0514             last.translate(res.cursorInfo.offsets.value(cursorPos.offset-1,  res.advance));
0515             first.translate(res.cursorInfo.offsets.value(cursorPos.offset, QPointF()));
0516         } else {
0517             first.translate(res.cursorInfo.offsets.value(cursorPos.offset-1,  QPointF()));
0518             last.translate(res.cursorInfo.offsets.value(cursorPos.offset, res.advance));
0519         }
0520         QPolygonF poly;
0521         poly << first.p1() << first.p2() << last.p2() << last.p1() << first.p1();
0522         p.addPolygon(tf.map(poly));
0523     }
0524 
0525     return p;
0526 }
0527 
0528 QPainterPath KoSvgTextShape::underlines(int pos, int anchor, KoSvgText::TextDecorations decor, KoSvgText::TextDecorationStyle style, qreal minimum, bool thick)
0529 {
0530     int start = qMin(pos, anchor);
0531     int end = qMax(pos, anchor);
0532 
0533     if (start == end || start < 0 || end >= d->cursorPos.size()) {
0534         return QPainterPath();
0535     }
0536 
0537     QPainterPathStroker stroker;
0538     qreal width = qMax(minimum, d->textData.childBegin()->textDecorationWidths.value(KoSvgText::DecorationUnderline));
0539     if (thick) {
0540         width *= 2;
0541     }
0542     stroker.setWidth(width);
0543     KoSvgText::WritingMode mode = KoSvgText::WritingMode(this->textProperties().propertyOrDefault(KoSvgTextProperties::WritingModeId).toInt());
0544     stroker.setCapStyle(Qt::FlatCap);
0545     if (style == KoSvgText::Solid) {
0546         stroker.setDashPattern(Qt::SolidLine);
0547     } else if (style == KoSvgText::Dashed) {
0548         stroker.setDashPattern(Qt::DashLine);
0549     } else if (style == KoSvgText::Dotted) {
0550         stroker.setDashPattern(Qt::DotLine);
0551     } else {
0552         stroker.setDashPattern(Qt::SolidLine);
0553     }
0554 
0555     QPainterPath underPath;
0556     QPainterPath overPath;
0557     QPainterPath middlePath;
0558     QPointF inset = mode == KoSvgText::HorizontalTB? QPointF(width*0.5, 0): QPointF(0, width*0.5);
0559     for (int i = start+1; i <= end; i++) {
0560         CursorPos pos = d->cursorPos.at(i);
0561         CharacterResult res = d->result.at(pos.cluster);
0562         const QTransform tf = res.finalTransform();
0563         QPointF first = res.cursorInfo.caret.p1();
0564         QPointF last = first;
0565         if (res.cursorInfo.rtl) {
0566             last  += res.cursorInfo.offsets.value(pos.offset-1,  res.advance);
0567             first += res.cursorInfo.offsets.value(pos.offset, QPointF());
0568             if (i == start+1) {
0569                 first -= inset;
0570             }
0571             if (i == end) {
0572                 last += inset;
0573             }
0574         } else {
0575             first += res.cursorInfo.offsets.value(pos.offset-1,  QPointF());
0576             last  += res.cursorInfo.offsets.value(pos.offset, res.advance);
0577             if (i == start+1) {
0578                 first += inset;
0579             }
0580             if (i == end) {
0581                 last -= inset;
0582             }
0583         }
0584 
0585         if (decor.testFlag(KoSvgText::DecorationUnderline)){
0586             underPath.moveTo(tf.map(first));
0587             underPath.lineTo(tf.map(last));
0588         }
0589         QPointF diff = res.cursorInfo.caret.p2() - res.cursorInfo.caret.p1();
0590         if (decor.testFlag(KoSvgText::DecorationOverline)){
0591             overPath.moveTo(tf.map(first+diff));
0592             overPath.lineTo(tf.map(last+diff));
0593         }
0594         if (decor.testFlag(KoSvgText::DecorationLineThrough)){
0595             middlePath.moveTo(tf.map(first+(diff*0.5)));
0596             middlePath.lineTo(tf.map(last+(diff*0.5)));
0597         }
0598     }
0599 
0600     QPainterPath final;
0601     if (decor.testFlag(KoSvgText::DecorationUnderline)){
0602         final.addPath(stroker.createStroke(underPath));
0603     }
0604     if (decor.testFlag(KoSvgText::DecorationOverline)){
0605         final.addPath(stroker.createStroke(overPath));
0606     }
0607     if (decor.testFlag(KoSvgText::DecorationLineThrough)){
0608         final.addPath(stroker.createStroke(middlePath));
0609     }
0610 
0611     return final;
0612 }
0613 
0614 int KoSvgTextShape::posForPoint(QPointF point, int start, int end, bool *overlaps)
0615 {
0616     int a = 0;
0617     int b = d->cursorPos.size();
0618     if (start >= 0 && end >= 0) {
0619         a = qMax(start, a);
0620         b = qMin(end, b);
0621     }
0622     double closest = std::numeric_limits<double>::max();
0623     int candidate = -1;
0624     for (int i = a; i < b; i++) {
0625         CursorPos pos = d->cursorPos.at(i);
0626         CharacterResult res = d->result.at(pos.cluster);
0627         QPointF cursorStart = res.finalPosition;
0628         cursorStart += res.cursorInfo.offsets.value(pos.offset, res.advance);
0629         double distance = kisDistance(cursorStart, point);
0630         if (distance < closest) {
0631             candidate = i;
0632             closest = distance;
0633         }
0634         if (res.finalTransform().map(res.boundingBox).containsPoint(point, Qt::WindingFill) && overlaps) {
0635             *overlaps = true;
0636         }
0637     }
0638     return candidate;
0639 }
0640 
0641 int KoSvgTextShape::posForPointLineSensitive(QPointF point)
0642 {
0643     bool overlaps = false;
0644     int initialPos = posForPoint(point, -1, -1, &overlaps);
0645 
0646     if (overlaps) {
0647         return initialPos;
0648     }
0649 
0650     KoSvgText::WritingMode mode = writingMode();
0651 
0652     int candidateLineStart = -1;
0653     double closest = std::numeric_limits<double>::max();
0654     for (int i = 0; i < d->cursorPos.size(); i++) {
0655         CursorPos pos = d->cursorPos.at(i);
0656         CharacterResult res = d->result.at(pos.cluster);
0657         if (res.anchored_chunk) {
0658             QLineF caret = res.cursorInfo.caret;
0659             caret.translate(res.finalPosition);
0660             QPointF cursorStart = res.finalPosition;
0661             cursorStart += res.cursorInfo.offsets.value(pos.offset, res.advance);
0662             double distance = kisDistance(cursorStart, point);
0663             if (mode == KoSvgText::HorizontalTB) {
0664                 if (caret.p1().y() > point.y() && caret.p2().y() <= point.y() && closest > distance) {
0665                     candidateLineStart = i;
0666                     closest = distance;
0667                 }
0668             } else {
0669                 if (caret.p2().x() > point.x() && caret.p1().x() <= point.x() && closest > distance) {
0670                     candidateLineStart = i;
0671                     closest = distance;
0672                 }
0673             }
0674         }
0675     }
0676 
0677     if (candidateLineStart > -1) {
0678         int end = lineEnd(candidateLineStart);
0679         initialPos = posForPoint(point, candidateLineStart, qMin(end + 1, d->cursorPos.size()));
0680     }
0681 
0682     return initialPos;
0683 }
0684 
0685 int KoSvgTextShape::posForIndex(int index, bool firstIndex, bool skipSynthetic)
0686 {
0687     int pos = -1;
0688     if (d->cursorPos.isEmpty() || index < 0) {
0689         return pos;
0690     }
0691     for (int i = 0; i< d->cursorPos.size(); i++) {
0692         if (skipSynthetic && d->cursorPos.at(i).synthetic) {
0693             continue;
0694         }
0695         if (d->cursorPos.at(i).index <= index) {
0696             pos = i;
0697             if (d->cursorPos.at(i).index == index && firstIndex) {
0698                 break;
0699             }
0700         } else if (d->cursorPos.at(i).index > index) {
0701             break;
0702         }
0703     }
0704 
0705     return pos;
0706 }
0707 
0708 int KoSvgTextShape::indexForPos(int pos)
0709 {
0710     if (d->cursorPos.isEmpty() || pos < 0) {
0711         return -1;
0712     }
0713 
0714     return d->cursorPos.at(qMin(d->cursorPos.size()-1, pos)).index;
0715 }
0716 
0717 QPointF KoSvgTextShape::initialTextPosition() const
0718 {
0719     return d->initialTextPosition;
0720 }
0721 
0722 KisForest<KoSvgTextContentElement>::depth_first_tail_iterator findTextChunkForIndex(KisForest<KoSvgTextContentElement> &tree, int &currentIndex, int sought, bool skipZeroWidth = false)
0723 {
0724     auto it = tree.depthFirstTailBegin();
0725     for (; it != tree.depthFirstTailEnd(); it++) {
0726         if (std::distance(KisForestDetail::childBegin(it), KisForestDetail::childEnd(it)) > 0) {
0727             continue;
0728         }
0729         int length = it->numChars(false);
0730         if (length == 0 && skipZeroWidth) {
0731             continue;
0732         }
0733 
0734         if (sought == currentIndex || (sought > currentIndex && sought < currentIndex + length)) {
0735             break;
0736         } else {
0737             currentIndex += length;
0738         }
0739     }
0740     return it;
0741 }
0742 
0743 bool KoSvgTextShape::insertText(int pos, QString text)
0744 {
0745     bool succes = false;
0746     int currentIndex = 0;
0747     int index = 0;
0748     int oldIndex = 0;
0749     if (pos > -1 && !d->cursorPos.isEmpty()) {
0750         CursorPos cursorPos = d->cursorPos.at(pos);
0751         CharacterResult res = d->result.at(cursorPos.cluster);
0752         index = res.plaintTextIndex;
0753         oldIndex = cursorPos.index;
0754         index = qMin(index, d->result.size()-1);
0755     }
0756     auto it = findTextChunkForIndex(d->textData, currentIndex, index);
0757     if (!isEnd(siblingCurrent(it))) {
0758         int offset = oldIndex - currentIndex;
0759         it->insertText(offset, text);
0760         notifyChanged();
0761         shapeChangedPriv(ContentChanged);
0762         succes = true;
0763     }
0764     return succes;
0765 }
0766 
0767 bool KoSvgTextShape::removeText(int &index, int &length)
0768 {
0769     bool succes = false;
0770     if (index < -1 || d->cursorPos.isEmpty()) {
0771         return succes;
0772     }
0773     int currentLength = length;
0774     int endLength = 0;
0775     while (currentLength > 0) {
0776         int currentIndex = 0;
0777         auto it = findTextChunkForIndex(d->textData, currentIndex, index, true);
0778         if (!isEnd(siblingCurrent(it))) {
0779             int offset = index > currentIndex? index - currentIndex: 0;
0780             int size = it->numChars(false);
0781             it->removeText(offset, currentLength);
0782             int diff = size - it->numChars(false);
0783             currentLength -= diff;
0784             endLength += diff;
0785 
0786             if (index >= currentIndex) {
0787                 index = currentIndex + offset;
0788             }
0789             succes = true;
0790         } else {
0791             currentLength = -1;
0792         }
0793     }
0794     if (succes) {
0795         length = endLength;
0796         notifyChanged();
0797         shapeChangedPriv(ContentChanged);
0798     }
0799     return succes;
0800 }
0801 
0802 KoSvgTextProperties KoSvgTextShape::propertiesForPos(int pos)
0803 {
0804     KoSvgTextProperties props = KisForestDetail::size(d->textData)? d->textData.childBegin()->properties: KoSvgTextProperties();
0805     if (pos < 0 || d->cursorPos.isEmpty()) {
0806         return props;
0807     }
0808     CursorPos cursorPos = d->cursorPos.at(pos);
0809     CharacterResult res = d->result.at(cursorPos.cluster);
0810     int currentIndex = 0;
0811     auto it = findTextChunkForIndex(d->textData, currentIndex, res.plaintTextIndex);
0812     if (!isEnd(siblingCurrent(it))) {
0813         props = it->properties;
0814     }
0815 
0816     return props;
0817 }
0818 
0819 void KoSvgTextShape::setPropertiesAtPos(int pos, KoSvgTextProperties properties)
0820 {
0821     if (pos < 0 || d->cursorPos.isEmpty()) {
0822         if (KisForestDetail::size(d->textData)) {
0823             d->textData.childBegin()->properties = properties;
0824         }
0825         notifyChanged();
0826         shapeChangedPriv(ContentChanged);
0827         return;
0828     }
0829     CursorPos cursorPos = d->cursorPos.at(pos);
0830     CharacterResult res = d->result.at(cursorPos.cluster);
0831     int currentIndex = 0;
0832     auto it = findTextChunkForIndex(d->textData, currentIndex, res.plaintTextIndex);
0833     if (!isEnd(siblingCurrent(it))) {
0834         it->properties = properties;
0835         notifyChanged();
0836         shapeChangedPriv(ContentChanged);
0837     }
0838 }
0839 
0840 KoSvgTextProperties KoSvgTextShape::textProperties() const
0841 {
0842     return KisForestDetail::size(d->textData)? d->textData.childBegin()->properties: KoSvgTextProperties();
0843 }
0844 
0845 QSharedPointer<KoShapeBackground> KoSvgTextShape::background() const
0846 {
0847     QSharedPointer<KoShapeBackground> bg(new KoColorBackground(Qt::black));
0848     KoSvgTextProperties props = KisForestDetail::size(d->textData)? d->textData.childBegin()->properties: KoSvgTextProperties();
0849     if (props.hasProperty(KoSvgTextProperties::FillId)) {
0850         return props.property(KoSvgTextProperties::FillId).value<KoSvgText::BackgroundProperty>().property;
0851     }
0852     return QSharedPointer<KoShapeBackground>(new KoColorBackground(Qt::black));
0853 }
0854 
0855 void KoSvgTextShape::setBackground(QSharedPointer<KoShapeBackground> background)
0856 {
0857     if (KisForestDetail::size(d->textData) == 0) {
0858         d->textData.insert(d->textData.childBegin(), KoSvgTextContentElement());
0859     }
0860     d->textData.childBegin()->properties.setProperty(KoSvgTextProperties::FillId,
0861                                                      QVariant::fromValue(KoSvgText::BackgroundProperty(background)));
0862 
0863     shapeChangedPriv(BackgroundChanged);
0864     notifyChanged();
0865 }
0866 
0867 KoShapeStrokeModelSP KoSvgTextShape::stroke() const
0868 {
0869     KoSvgTextProperties props = KisForestDetail::size(d->textData)? d->textData.childBegin()->properties: KoSvgTextProperties();
0870     return props.property(KoSvgTextProperties::StrokeId).value<KoSvgText::StrokeProperty>().property;
0871 }
0872 
0873 void KoSvgTextShape::setStroke(KoShapeStrokeModelSP stroke)
0874 {
0875     if (KisForestDetail::size(d->textData) == 0) {
0876         d->textData.insert(d->textData.childBegin(), KoSvgTextContentElement());
0877     }
0878     d->textData.childBegin()->properties.setProperty(KoSvgTextProperties::StrokeId,
0879                                                      QVariant::fromValue(KoSvgText::StrokeProperty(stroke)));
0880     shapeChangedPriv(StrokeChanged);
0881     notifyChanged();
0882 }
0883 
0884 QVector<KoShape::PaintOrder> KoSvgTextShape::paintOrder() const
0885 {
0886     KoSvgTextProperties props = KisForestDetail::size(d->textData)? d->textData.childBegin()->properties: KoSvgTextProperties();
0887     if (props.hasProperty(KoSvgTextProperties::PaintOrder)) {
0888         return props.property(KoSvgTextProperties::PaintOrder).value<QVector<KoShape::PaintOrder>>();
0889     }
0890     return KoShape::paintOrder();
0891 }
0892 
0893 void KoSvgTextShape::setPaintOrder(KoShape::PaintOrder first, KoShape::PaintOrder second)
0894 {
0895     if (KisForestDetail::size(d->textData) == 0) {
0896         d->textData.insert(d->textData.childBegin(), KoSvgTextContentElement());
0897     }
0898     KIS_SAFE_ASSERT_RECOVER_RETURN(first != second);
0899     QVector<PaintOrder> order = {Fill, Stroke, Markers};
0900 
0901     if (first != Fill) {
0902         if (order.at(1) == first) {
0903             order[1] = order[0];
0904             order[0] = first;
0905         } else if (order.at(2) == first) {
0906             order[2] = order[0];
0907             order[0] = first;
0908         }
0909     }
0910     if (second != first && second != Stroke) {
0911         if (order.at(2) == second) {
0912             order[2] = order[1];
0913             order[1] = second;
0914         }
0915     }
0916     d->textData.childBegin()->properties.setProperty(KoSvgTextProperties::PaintOrder,
0917                                                     QVariant::fromValue(order));
0918     setInheritPaintOrder(false);
0919 }
0920 
0921 QString KoSvgTextShape::plainText()
0922 {
0923     return d->plainText;
0924 }
0925 
0926 KoSvgText::WritingMode KoSvgTextShape::writingMode() const
0927 {
0928     return KoSvgText::WritingMode(this->textProperties().propertyOrDefault(KoSvgTextProperties::WritingModeId).toInt());
0929 }
0930 
0931 void KoSvgTextShape::notifyCursorPosChanged(int pos, int anchor)
0932 {
0933     Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners()) {
0934         TextCursorChangeListener *cursorListener = dynamic_cast<TextCursorChangeListener*>(listener);
0935         if (cursorListener) {
0936             cursorListener->notifyCursorPosChanged(pos, anchor);
0937         }
0938     }
0939 }
0940 
0941 #include "KoXmlWriter.h"
0942 bool KoSvgTextShape::saveSvg(SvgSavingContext &context)
0943 {
0944     bool success = false;
0945     QList<KoSvgTextProperties> parentProps = {KoSvgTextProperties::defaultProperties()};
0946     for (auto it = d->textData.compositionBegin(); it != d->textData.compositionEnd(); it++) {
0947         if (it.state() == KisForestDetail::Enter) {
0948             bool isTextPath = false;
0949             QMap<QString, QString> shapeSpecificStyles;
0950             if (it->textPath) {
0951                 isTextPath = true;
0952             }
0953             if (it == d->textData.compositionBegin()) {
0954                 context.shapeWriter().startElement("text", false);
0955 
0956                 if (!context.strippedTextMode()) {
0957                     context.shapeWriter().addAttribute("id", context.getID(this));
0958 
0959                     context.shapeWriter().addAttribute("text-rendering", textRenderingString());
0960 
0961                     // save the version to distinguish from the buggy Krita version
0962                     // 2: Wrong font-size.
0963                     // 3: Wrong font-size-adjust.
0964                     context.shapeWriter().addAttribute("krita:textVersion", 3);
0965 
0966                     SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter());
0967                     SvgStyleWriter::saveSvgStyle(this, context);
0968                 } else {
0969                     context.shapeWriter().addAttribute("text-rendering", textRenderingString());
0970                     SvgStyleWriter::saveSvgFill(this->background(), false, this->outlineRect(), this->size(), this->absoluteTransformation(), context);
0971                     SvgStyleWriter::saveSvgStroke(this->stroke(), context);
0972                     SvgStyleWriter::saveSvgBasicStyle(true, 0, paintOrder(),
0973                                                       inheritPaintOrder(), context, true);
0974                 }
0975                 shapeSpecificStyles = this->shapeTypeSpecificStyles(context);
0976             } else {
0977                 if (isTextPath) {
0978                     context.shapeWriter().startElement("textPath", false);
0979                 } else {
0980                     context.shapeWriter().startElement("tspan", false);
0981                 }
0982                 SvgStyleWriter::saveSvgBasicStyle(it->properties.property(KoSvgTextProperties::Visiblity).toBool(),
0983                                                   it->properties.property(KoSvgTextProperties::Opacity).toReal(),
0984                                                   it->properties.property(KoSvgTextProperties::PaintOrder).value<QVector<KoShape::PaintOrder>>(),
0985                                                   !it->properties.hasProperty(KoSvgTextProperties::PaintOrder), context, true);
0986 
0987             }
0988 
0989             success = it->saveSvg(context,
0990                                   parentProps.last(),
0991                                   it == d->textData.compositionBegin(),
0992                                   d->childCount(siblingCurrent(it)) == 0,
0993                                   shapeSpecificStyles);
0994             parentProps.append(it->properties);
0995         } else {
0996             parentProps.pop_back();
0997             context.shapeWriter().endElement();
0998         }
0999     }
1000     return success;
1001 }
1002 
1003 bool KoSvgTextShape::saveHtml(HtmlSavingContext &context)
1004 {
1005     bool success = true;
1006     QList<KoSvgTextProperties> parentProps = {KoSvgTextProperties::defaultProperties()};
1007     for (auto it = d->textData.compositionBegin(); it != d->textData.compositionEnd(); it++) {
1008         if (it.state() == KisForestDetail::Enter) {
1009             QMap<QString, QString> shapeSpecificStyles;
1010 
1011             if (it == d->textData.compositionBegin()) {
1012                 context.shapeWriter().startElement("p", false);
1013             } else {
1014                 context.shapeWriter().startElement("span", false);
1015             }
1016             KoSvgTextProperties ownProperties = it->properties.ownProperties(parentProps.last(),
1017                                                                              it == d->textData.compositionBegin());
1018             parentProps.append(ownProperties);
1019             QMap<QString, QString> attributes = ownProperties.convertToSvgTextAttributes();
1020             if (attributes.size() > 0) {
1021                 QString styleString;
1022                 for (auto it = attributes.constBegin(); it != attributes.constEnd(); ++it) {
1023                     if (QString(it.key().toLatin1().data()).contains("text-anchor")) {
1024                         QString val = it.value();
1025                         if (it.value()=="middle") {
1026                             val = "center";
1027                         } else if (it.value()=="end") {
1028                             val = "right";
1029                         } else {
1030                             val = "left";
1031                         }
1032                         styleString.append("text-align")
1033                                 .append(": ")
1034                                 .append(val)
1035                                 .append(";" );
1036                     } else if (QString(it.key().toLatin1().data()).contains("fill")){
1037                         styleString.append("color")
1038                                 .append(": ")
1039                                 .append(it.value())
1040                                 .append(";" );
1041                     } else if (QString(it.key().toLatin1().data()).contains("font-size")){
1042                         QString val = it.value();
1043                         styleString.append(it.key().toLatin1().data())
1044                                 .append(": ")
1045                                 .append(val)
1046                                 .append(";" );
1047                     } else {
1048                         styleString.append(it.key().toLatin1().data())
1049                                 .append(": ")
1050                                 .append(it.value())
1051                                 .append(";" );
1052                     }
1053                 }
1054                 context.shapeWriter().addAttribute("style", styleString);
1055 
1056                 if (d->childCount(siblingCurrent(it)) == 0) {
1057                     debugFlake << "saveHTML" << this << it->text;
1058                     // After adding all the styling to the <p> element, add the text
1059                     context.shapeWriter().addTextNode(it->text);
1060                 }
1061             }
1062         } else {
1063             parentProps.pop_back();
1064             context.shapeWriter().endElement();
1065         }
1066     }
1067     return success;
1068 }
1069 
1070 void KoSvgTextShape::enterNodeSubtree()
1071 {
1072 
1073 }
1074 
1075 void KoSvgTextShape::leaveNodeSubtree()
1076 {
1077 
1078 }
1079 
1080 void KoSvgTextShape::debugParsing()
1081 {
1082     qDebug() << "Tree size:" << KisForestDetail::size(d->textData);
1083     QString spaces;
1084     QList<KoSvgTextProperties> parentProps = {KoSvgTextProperties::defaultProperties()};
1085     for (auto it = compositionBegin(d->textData); it != compositionEnd(d->textData); it++) {
1086         if (it.state() == KisForestDetail::Enter) {
1087 
1088             qDebug() << QString(spaces + "+") << it->text;
1089             qDebug() << QString(spaces + "|") << it->properties.ownProperties(parentProps.last()).convertToSvgTextAttributes();
1090             qDebug() << QString(spaces + "| Fill set: ") << it->properties.hasProperty(KoSvgTextProperties::FillId);
1091             qDebug() << QString(spaces + "| Stroke set: ") << it->properties.hasProperty(KoSvgTextProperties::StrokeId);
1092             qDebug() << QString(spaces + "| Opacity: ") << it->properties.property(KoSvgTextProperties::Opacity);
1093             qDebug() << QString(spaces + "| PaintOrder: ") << it->properties.hasProperty(KoSvgTextProperties::PaintOrder);
1094             qDebug() << QString(spaces + "| Visibility set: ") << it->properties.hasProperty(KoSvgTextProperties::Visiblity);
1095             qDebug() << QString(spaces + "| TextPath set: ") << (it->textPath);
1096             qDebug() << QString(spaces + "| Transforms set: ") << it->localTransformations;
1097             spaces.append(" ");
1098             parentProps.append(it->properties);
1099         }
1100 
1101         if (it.state() == KisForestDetail::Leave) {
1102             spaces.chop(1);
1103             parentProps.pop_back();
1104         }
1105     }
1106 }
1107 
1108 void KoSvgTextShape::paint(QPainter &painter) const
1109 {
1110     painter.save();
1111     if (d->textRendering == OptimizeSpeed) {
1112         painter.setRenderHint(QPainter::Antialiasing, false);
1113         painter.setRenderHint(QPainter::SmoothPixmapTransform, false);
1114     } else {
1115         painter.setRenderHint(QPainter::Antialiasing, true);
1116         painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
1117     }
1118 
1119     QPainterPath chunk;
1120     int currentIndex = 0;
1121     if (!d->result.isEmpty()) {
1122         QPainterPath rootBounds;
1123         rootBounds.addRect(this->outline().boundingRect());
1124         d->paintPaths(painter, rootBounds, this, d->result, chunk, currentIndex);
1125     }
1126 #if 0 // Debug
1127     Q_FOREACH (KoShape *child, this->shapes()) {
1128         const KoSvgTextChunkShape *textPathChunk = dynamic_cast<const KoSvgTextChunkShape *>(child);
1129         if (textPathChunk) {
1130             painter.save();
1131             painter.setPen(Qt::magenta);
1132             painter.setOpacity(0.5);
1133             if (textPathChunk->layoutInterface()->textPath()) {
1134                 QPainterPath p = textPathChunk->layoutInterface()->textPath()->outline();
1135                 p = textPathChunk->layoutInterface()->textPath()->transformation().map(p);
1136                 painter.strokePath(p, QPen(Qt::green));
1137                 painter.drawPoint(p.pointAtPercent(0));
1138                 painter.drawPoint(p.pointAtPercent(p.percentAtLength(p.length() * 0.5)));
1139                 painter.drawPoint(p.pointAtPercent(1.0));
1140             }
1141             painter.restore();
1142         }
1143     }
1144 #endif
1145 #if 0 // Debug
1146     Q_FOREACH (KoShape *shapeInside, d->shapesInside) {
1147         QPainterPath p = shapeInside->outline();
1148         p = shapeInside->transformation().map(p);
1149         painter.strokePath(p, QPen(Qt::green));
1150     }
1151     Q_FOREACH (KoShape *shapeInside, d->shapesSubtract) {
1152         QPainterPath p = shapeInside->outline();
1153         p = shapeInside->transformation().map(p);
1154         painter.strokePath(p, QPen(Qt::red));
1155     }
1156 #endif
1157 
1158     painter.restore();
1159 }
1160 
1161 void KoSvgTextShape::paintStroke(QPainter &painter) const
1162 {
1163     Q_UNUSED(painter);
1164     // do nothing! everything is painted in paint()
1165 }
1166 
1167 QPainterPath KoSvgTextShape::outline() const {
1168     QPainterPath result;
1169     for (auto it = d->textData.depthFirstTailBegin(); it != d->textData.depthFirstTailEnd(); it++) {
1170         result.addPath(it->associatedOutline);
1171         for (int i = 0; i < it->textDecorations.values().size(); ++i) {
1172             result.addPath(it->textDecorations.values().at(i));
1173         }
1174 
1175     }
1176     return result;
1177 }
1178 QRectF KoSvgTextShape::outlineRect() const
1179 {
1180     return outline().boundingRect();
1181 }
1182 
1183 QRectF KoSvgTextShape::boundingRect() const
1184 {
1185     QRectF result;
1186     KoShapeStrokeModelSP stroke = nullptr;
1187     for (auto it = d->textData.depthFirstTailBegin(); it != d->textData.depthFirstTailEnd(); it++) {
1188         if (it->properties.hasProperty(KoSvgTextProperties::StrokeId)) {
1189             stroke = it->properties.property(KoSvgTextProperties::StrokeId).value<KoSvgText::StrokeProperty>().property;
1190         }
1191         if (stroke) {
1192             QRectF bb = outlineRect();
1193             KoInsets insets;
1194             stroke->strokeInsets(this, insets);
1195             result |= bb.adjusted(-insets.left, -insets.top, insets.right, insets.bottom);
1196         } else {
1197             result |= outlineRect();
1198         }
1199     }
1200     return this->absoluteTransformation().mapRect(result);
1201 }
1202 
1203 void KoSvgTextShape::paintDebug(QPainter &painter, const DebugElements elements) const
1204 {
1205     if (elements & DebugElement::CharBbox) {
1206         int currentIndex = 0;
1207         if (!d->result.isEmpty()) {
1208             QPainterPath rootBounds;
1209             rootBounds.addRect(this->outline().boundingRect());
1210             d->paintDebug(painter, d->result, currentIndex);
1211         }
1212     }
1213 
1214     if (elements & DebugElement::LineBox) {
1215         Q_FOREACH (LineBox lineBox, d->lineBoxes) {
1216             Q_FOREACH (const LineChunk &chunk, lineBox.chunks) {
1217                 QPen pen;
1218                 pen.setCosmetic(true);
1219                 pen.setWidth(2);
1220                 painter.setBrush(QBrush(Qt::transparent));
1221                 pen.setColor(QColor(0, 128, 255, 128));
1222                 painter.setPen(pen);
1223                 painter.drawLine(chunk.length);
1224                 pen.setColor(QColor(255, 128, 0, 128));
1225                 painter.setPen(pen);
1226                 painter.drawRect(chunk.boundingBox);
1227             }
1228         }
1229     }
1230 }
1231 
1232 QList<KoShape *> KoSvgTextShape::textOutline() const
1233 {
1234     QList<KoShape *> shapes;
1235     int currentIndex = 0;
1236     if (!d->result.empty()) {
1237         shapes = d->collectPaths(this, d->result, currentIndex);
1238     }
1239 
1240     return shapes;
1241 }
1242 
1243 void KoSvgTextShape::setTextRenderingFromString(const QString &textRendering)
1244 {
1245     if (textRendering == "optimizeSpeed") {
1246         d->textRendering = OptimizeSpeed;
1247     } else if (textRendering == "optimizeLegibility") {
1248         d->textRendering = OptimizeLegibility;
1249     } else if (textRendering == "geometricPrecision") {
1250         d->textRendering = GeometricPrecision;
1251     } else {
1252         d->textRendering = Auto;
1253     }
1254 }
1255 
1256 QString KoSvgTextShape::textRenderingString() const
1257 {
1258     if (d->textRendering == OptimizeSpeed) {
1259         return "optimizeSpeed";
1260     } else if (d->textRendering == OptimizeLegibility) {
1261         return "optimizeLegibility";
1262     } else if (d->textRendering == GeometricPrecision) {
1263         return "geometricPrecision";
1264     } else {
1265         return "auto";
1266     }
1267 }
1268 
1269 void KoSvgTextShape::setShapesInside(QList<KoShape *> shapesInside)
1270 {
1271     d->shapesInside = shapesInside;
1272 }
1273 
1274 QList<KoShape *> KoSvgTextShape::shapesInside() const
1275 {
1276     return d->shapesInside;
1277 }
1278 
1279 void KoSvgTextShape::setShapesSubtract(QList<KoShape *> shapesSubtract)
1280 {
1281     d->shapesSubtract = shapesSubtract;
1282 }
1283 
1284 QList<KoShape *> KoSvgTextShape::shapesSubtract() const
1285 {
1286     return d->shapesSubtract;
1287 }
1288 
1289 QMap<QString, QString> KoSvgTextShape::shapeTypeSpecificStyles(SvgSavingContext &context) const
1290 {
1291     QMap<QString, QString> map = this->textProperties().convertParagraphProperties();
1292     if (!d->shapesInside.isEmpty()) {
1293         QStringList shapesInsideList;
1294         Q_FOREACH(KoShape* shape, d->shapesInside) {
1295             QString id = SvgStyleWriter::embedShape(shape, context);
1296             shapesInsideList.append(QString("url(#%1)").arg(id));
1297         }
1298         map.insert("shape-inside", shapesInsideList.join(" "));
1299     }
1300     if (!d->shapesSubtract.isEmpty()) {
1301         QStringList shapesInsideList;
1302         Q_FOREACH(KoShape* shape, d->shapesSubtract) {
1303             QString id = SvgStyleWriter::embedShape(shape, context);
1304             shapesInsideList.append(QString("url(#%1)").arg(id));
1305         }
1306         map.insert("shape-subtract", shapesInsideList.join(" "));
1307     }
1308 
1309     return map;
1310 }
1311 
1312 void KoSvgTextShape::relayout() const
1313 {
1314     d->relayout();
1315 }
1316 
1317 KoSvgTextShapeFactory::KoSvgTextShapeFactory()
1318     : KoShapeFactoryBase(KoSvgTextShape_SHAPEID, i18nc("Text label in SVG Text Tool", "Text"))
1319 {
1320     setToolTip(i18n("SVG Text Shape"));
1321     setIconName(koIconNameCStr("x-shape-text"));
1322     setLoadingPriority(5);
1323     setXmlElementNames(KoXmlNS::svg, QStringList("text"));
1324 
1325     KoShapeTemplate t;
1326     t.name = i18n("SVG Text");
1327     t.iconName = koIconName("x-shape-text");
1328     t.toolTip = i18n("SVG Text Shape");
1329     addTemplate(t);
1330 }
1331 
1332 KoShape *KoSvgTextShapeFactory::createDefaultShape(KoDocumentResourceManager *documentResources) const
1333 {
1334     Q_UNUSED(documentResources);
1335     debugFlake << "Create default svg text shape";
1336 
1337     KoSvgTextShape *shape = new KoSvgTextShape();
1338     shape->setShapeId(KoSvgTextShape_SHAPEID);
1339     shape->insertText(0, i18nc("Default text for the text shape", "Placeholder Text"));
1340 
1341     return shape;
1342 }
1343 
1344 KoShape *KoSvgTextShapeFactory::createShape(const KoProperties *params, KoDocumentResourceManager *documentResources) const
1345 {
1346     KoSvgTextShape *shape = new KoSvgTextShape();
1347     shape->setShapeId(KoSvgTextShape_SHAPEID);
1348 
1349     QString svgText = params->stringProperty("svgText", i18nc("Default text for the text shape", "<text>Placeholder Text</text>"));
1350     QString defs = params->stringProperty("defs" , "<defs/>");
1351     QRectF shapeRect = QRectF(0, 0, 200, 60);
1352     QVariant rect = params->property("shapeRect");
1353     QVariant origin = params->property("origin");
1354 
1355     if (rect.type()==QVariant::RectF) {
1356         shapeRect = rect.toRectF();
1357     }
1358 
1359     KoSvgTextShapeMarkupConverter converter(shape);
1360     converter.convertFromSvg(svgText,
1361                              defs,
1362                              shapeRect,
1363                              documentResources->documentResolution());
1364     if (origin.type() == QVariant::PointF) {
1365         shape->setPosition(origin.toPointF());
1366     } else {
1367         shape->setPosition(shapeRect.topLeft());
1368     }
1369     shape->setPaintOrder(KoShape::Stroke, KoShape::Fill);
1370 
1371     return shape;
1372 }
1373 
1374 bool KoSvgTextShapeFactory::supports(const QDomElement &/*e*/, KoShapeLoadingContext &/*context*/) const
1375 {
1376     return false;
1377 }
1378 
1379 void KoSvgTextShape::TextCursorChangeListener::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape)
1380 {
1381     Q_UNUSED(type);
1382     Q_UNUSED(shape);
1383 }
1384 
1385 struct KoSvgTextLoader::Private {
1386 
1387     Private(KoSvgTextShape *shape)
1388         : shape(shape)
1389         , currentNode(shape->d->textData.childEnd())
1390     {
1391         shape->d->textData.erase(shape->d->textData.childBegin(), shape->d->textData.childEnd());
1392         currentNode = shape->d->textData.childEnd();
1393     }
1394 
1395     KoSvgTextShape *shape;
1396     KisForest<KoSvgTextContentElement>::child_iterator currentNode;
1397 };
1398 
1399 KoSvgTextLoader::KoSvgTextLoader(KoSvgTextShape *shape)
1400     : d(new Private(shape))
1401 {
1402 
1403 }
1404 
1405 KoSvgTextLoader::~KoSvgTextLoader()
1406 {
1407 }
1408 
1409 void KoSvgTextLoader::enterNodeSubtree()
1410 {
1411     if (KisForestDetail::isEnd(d->currentNode)) {
1412         nextNode();
1413     }
1414     d->currentNode = childEnd(d->currentNode);
1415 }
1416 
1417 void KoSvgTextLoader::leaveNodeSubtree()
1418 {
1419     d->currentNode = KisForestDetail::parent(d->currentNode);
1420 }
1421 
1422 void KoSvgTextLoader::nextNode()
1423 {
1424     d->currentNode = d->shape->d->textData.insert(KisForestDetail::siblingEnd(d->currentNode), KoSvgTextContentElement());
1425 }
1426 
1427 bool KoSvgTextLoader::loadSvg(const QDomElement &element, SvgLoadingContext &context)
1428 {
1429     if (KisForestDetail::isEnd(d->currentNode)) {
1430         nextNode();
1431     }
1432     return d->currentNode->loadSvg(element, context);
1433 }
1434 
1435 bool KoSvgTextLoader::loadSvgText(const QDomText &text, SvgLoadingContext &context)
1436 {
1437     if (KisForestDetail::isEnd(d->currentNode)) {
1438         nextNode();
1439     }
1440     return d->currentNode->loadSvgTextNode(text, context);
1441 }
1442 
1443 void KoSvgTextLoader::setStyleInfo(KoShape *s)
1444 {
1445     if (!KisForestDetail::isEnd(d->currentNode)) {
1446         // find closest parent stroke and fill so we can check for inheritance.
1447         QSharedPointer<KoShapeBackground> parentBg;
1448         KoShapeStrokeModelSP parentStroke;
1449         for (auto it = KisForestDetail::hierarchyBegin(d->currentNode); it != KisForestDetail::hierarchyEnd(d->currentNode); it++) {
1450             if (it->properties.hasProperty(KoSvgTextProperties::FillId)) {
1451                 parentBg = it->properties.background();
1452                 break;
1453             }
1454         }
1455         for (auto it = KisForestDetail::hierarchyBegin(d->currentNode); it != KisForestDetail::hierarchyEnd(d->currentNode); it++) {
1456             if (it->properties.hasProperty(KoSvgTextProperties::StrokeId)) {
1457                 parentStroke = it->properties.stroke();
1458                 break;
1459             }
1460         }
1461 
1462         if ((parentBg && !parentBg->compareTo(s->background().data()))
1463                 || (!parentBg && s->background())) {
1464             d->currentNode->properties.setProperty(KoSvgTextProperties::FillId,
1465                                                    QVariant::fromValue(KoSvgText::BackgroundProperty(s->background())));
1466         }
1467         if ((parentStroke && !parentStroke->compareFillTo(s->stroke().data()) && !parentStroke->compareStyleTo(s->stroke().data()))
1468                 || (!parentStroke && s->stroke())) {
1469             d->currentNode->properties.setProperty(KoSvgTextProperties::StrokeId,
1470                                                    QVariant::fromValue(KoSvgText::StrokeProperty(s->stroke())));
1471         }
1472         d->currentNode->properties.setProperty(KoSvgTextProperties::Opacity,
1473                                                s->transparency());
1474         d->currentNode->properties.setProperty(KoSvgTextProperties::Visiblity,
1475                                                s->isVisible());
1476         if (!s->inheritPaintOrder()) {
1477             d->currentNode->properties.setProperty(KoSvgTextProperties::PaintOrder,
1478                                                    QVariant::fromValue(s->paintOrder()));
1479         }
1480     }
1481 }
1482 
1483 void KoSvgTextLoader::setTextPathOnCurrentNode(KoShape *s)
1484 {
1485     if (!KisForestDetail::isEnd(d->currentNode)) {
1486         d->currentNode->textPath.reset(s);
1487     }
1488 }