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 ¤tIndex, 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 }