13#include <klocalizedstring.h>
41#include <QPainterPath>
108 d->textElement = rhs.
d->textElement;
120 return &
d->textElement->textPathInfo;
150 static const QString s_placeholderText = i18nc(
"Default text for the text shape",
"Placeholder Text");
151 return s_placeholderText;
173 int roundedX = qRound(
xRes);
174 int roundedY = qRound(
yRes);
175 if (roundedX !=
d->xRes || roundedY !=
d->yRes) {
249 if (pos < 0 || d->
cursorPos.isEmpty() ||
d->result.isEmpty()) {
253 if (
d->result.at(
p.cluster).anchored_chunk &&
p.offset == 0) {
257 for (
int i = 0; i < pos; i++) {
259 if (
d->result.at(
p2.cluster).anchored_chunk &&
p2.offset == 0) {
268 if (pos < 0 || d->
cursorPos.isEmpty() ||
d->result.isEmpty()) {
271 if (pos >
d->cursorPos.size() - 1) {
272 return d->cursorPos.size() - 1;
275 int posCluster =
d->cursorPos.at(pos).cluster;
276 for (
int i = pos; i <
d->cursorPos.size(); i++) {
278 if (
d->result.at(
p.cluster).anchored_chunk && i > pos && posCluster !=
p.cluster) {
290 if (pos < 0 || pos >
d->cursorPos.size()-1 ||
d->result.isEmpty() ||
d->cursorPos.isEmpty()) {
303 if (pos < 0 || pos >
d->cursorPos.size()-1 ||
d->result.isEmpty() ||
d->cursorPos.isEmpty()) {
315 if (
d->cursorPos.isEmpty()) {
318 int currentIndex =
d->cursorPos.at(pos).index;
320 for (
int i = pos; i <
d->cursorPos.size(); i++) {
321 if (
d->cursorPos.at(i).index > currentIndex) {
330 if (
d->cursorPos.isEmpty()) {
333 int currentIndex =
d->cursorPos.at(pos).index;
335 for (
int i = pos; i >= 0; i--) {
336 if (
d->cursorPos.at(i).index < currentIndex) {
345 if (
d->cursorPos.isEmpty()) {
350 int visualIndex =
d->logicalToVisualCursorPos.value(pos);
351 return d->logicalToVisualCursorPos.key(qMin(visualIndex + 1,
d->cursorPos.size() - 1),
d->cursorPos.size() - 1);
354 return qMin(pos + 1,
d->cursorPos.size() - 1);
359 if (
d->cursorPos.isEmpty()) {
364 int visualIndex =
d->logicalToVisualCursorPos.value(pos);
365 return d->logicalToVisualCursorPos.key(qMax(visualIndex - 1, 0), 0);
368 return qMax(pos - 1, 0);
373 if (pos < 0 || pos >
d->cursorPos.size()-1 ||
d->result.isEmpty() ||
d->cursorPos.isEmpty()) {
377 int nextLineStart =
lineEnd(pos)+1;
378 int nextLineEnd =
lineEnd(nextLineStart);
382 for (
int i = 0; i <
d->lineBoxes.size(); ++i) {
383 for (
int j = 0; j <
d->lineBoxes.at(i).chunks.size(); ++j) {
384 if (
d->lineBoxes.at(i).chunks.at(j).chunkIndices.contains(
cursorPos.cluster)) {
385 nextLineBox =
d->lineBoxes.at(qMin(i + 1,
d->lineBoxes.size()-1));
389 if (nextLineBox.
chunks.size()>0) {
394 if (
d->result.at(i).addressable) {
396 first =
d->result.at(i).cursorInfo.graphemeIndices.first();
398 last =
d->result.at(i).cursorInfo.graphemeIndices.last();
402 if (first > -1 && last > -1) {
410 if (nextLineStart >
d->cursorPos.size()-1) {
411 return d->cursorPos.size()-1;
417 int candidate =
posForPoint(currentPoint, nextLineStart, nextLineEnd+1);
427 if (pos < 0 || pos >
d->cursorPos.size()-1 ||
d->result.isEmpty() ||
d->cursorPos.isEmpty()) {
431 if (currentLineStart - 1 < 0) {
434 int previousLineStart =
lineStart(currentLineStart-1);
439 for (
int i = 0; i <
d->lineBoxes.size(); ++i) {
440 for (
int j = 0; j <
d->lineBoxes.at(i).chunks.size(); ++j) {
441 if (
d->lineBoxes.at(i).chunks.at(j).chunkIndices.contains(
cursorPos.cluster)) {
442 previousLineBox =
d->lineBoxes.at(qMax(i - 1, 0));
446 if (previousLineBox.
chunks.size()>0) {
451 if (
d->result.at(i).addressable) {
453 first =
d->result.at(i).cursorInfo.graphemeIndices.first();
455 last =
d->result.at(i).cursorInfo.graphemeIndices.last();
459 if (first > -1 && last > -1) {
469 int candidate =
posForPoint(currentPoint, previousLineStart, currentLineStart);
479 if (pos < 0 || pos >
d->cursorPos.size()-1 ||
d->result.isEmpty() ||
d->cursorPos.isEmpty()) {
482 int currentLineEnd =
lineEnd(pos);
483 if (pos ==
lineStart(pos) || pos == currentLineEnd) {
488 for (
int i = pos; i<= currentLineEnd; i++) {
502 if (pos < 0 || pos >
d->cursorPos.size()-1 ||
d->result.isEmpty() ||
d->cursorPos.isEmpty()) {
506 if (pos == currentLineStart || pos ==
lineEnd(pos)) {
511 bool breakNext =
false;
512 for (
int i = pos; i >= currentLineStart; i--) {
513 if (breakNext)
break;
530 p.moveTo(0, fontSize*0.2);
531 p.lineTo(0, -fontSize);
533 p.moveTo(-fontSize * 0.5, 0);
534 p.lineTo(fontSize, 0);
536 p.translate(
d->initialTextPosition);
543 if (
d->result.isEmpty() ||
d->cursorPos.isEmpty() || pos < 0 || pos >=
d->cursorPos.size()) {
557 p.moveTo(tf.map(caret.p1()));
558 p.lineTo(tf.map(caret.p2()));
559 if (
d->isBidi && bidiFlagSize > 0) {
561 double bidiFlagHalf = bidiFlagSize * 0.5;
565 qreal slope = bidiFlagHalf * (caret.dx()/ caret.dy());
566 point3 = QPointF(caret.p2().x() + slope + (
sign * bidiFlagHalf), caret.p2().y() + bidiFlagHalf);
567 point4 = QPointF(point3.x() + slope - (
sign * bidiFlagHalf),point3.y() + bidiFlagHalf);
569 qreal slope = bidiFlagHalf * (caret.dy()/ caret.dx());
570 point3 = QPointF(caret.p2().x() - bidiFlagHalf, caret.p2().y() - slope + (
sign * bidiFlagHalf));
571 point4 = QPointF(point3.x() - bidiFlagHalf, point3.y() - slope - (
sign * bidiFlagHalf));
573 p.lineTo(tf.map(point3));
574 p.lineTo(tf.map(point4));
576 caret = tf.map(caret);
583 int start = qMin(pos,
anchor);
584 int end = qMax(pos,
anchor);
585 end = qMin(
d->cursorPos.size()-1, end);
587 if (start == end || start < 0) {
588 return QPainterPath();
592 p.setFillRule(Qt::WindingFill);
593 for (
int i = start+1; i <= end; i++) {
607 poly << first.p1() << first.p2() << last.p2() << last.p1() << first.p1();
608 p.addPolygon(tf.map(poly));
616 int start = qMin(pos,
anchor);
617 int end = qMax(pos,
anchor);
619 if (start == end || start < 0 || end >=
d->cursorPos.size()) {
620 return QPainterPath();
622 QPainterPathStroker stroker;
625 stroker.setCapStyle(Qt::FlatCap);
627 stroker.setDashPattern(Qt::SolidLine);
629 stroker.setDashPattern(Qt::DashLine);
631 stroker.setDashPattern(Qt::DotLine);
633 stroker.setDashPattern(Qt::SolidLine);
636 QPainterPath underPath;
637 QPainterPath overPath;
638 QPainterPath middlePath;
639 qint32 strokeWidth = 0;
641 for (
int i = start+1; i <= end; i++) {
647 QPointF last = first;
669 underPath.moveTo(tf.map(first));
670 underPath.lineTo(tf.map(last));
674 overPath.moveTo(tf.map(first+diff));
675 overPath.lineTo(tf.map(last+diff));
678 middlePath.moveTo(tf.map(first+(diff*0.5)));
679 middlePath.lineTo(tf.map(last+(diff*0.5)));
683 const qreal freetypePixelsToPt = (1.0 / 64.0) * (72. / qMin(
d->xRes,
d->yRes));
684 const qreal width = strokeWidth > 0 ? qMax(qreal(strokeWidth/qMax(1, end-(start+1)))*freetypePixelsToPt, minimum): minimum;
686 stroker.setWidth(thick? width*2: width);
690 final.addPath(stroker.createStroke(underPath));
693 final.addPath(stroker.createStroke(overPath));
696 final.addPath(stroker.createStroke(middlePath));
705 int b =
d->cursorPos.size();
706 if (start >= 0 && end >= 0) {
710 double closest = std::numeric_limits<double>::max();
712 for (
int i = a; i < b; i++) {
731 bool overlaps =
false;
732 int initialPos =
posForPoint(point, -1, -1, &overlaps);
740 int candidateLineStart = 0;
741 double closest = std::numeric_limits<double>::max();
742 for (
int i = 0; i <
d->cursorPos.size(); i++) {
752 if (caret.p1().y() > point.y() && caret.p2().y() <= point.y() && closest >
distance) {
753 candidateLineStart = i;
757 if (caret.p2().x() > point.x() && caret.p1().x() <= point.x() && closest >
distance) {
758 candidateLineStart = i;
765 if (candidateLineStart > -1) {
766 int end =
lineEnd(candidateLineStart);
767 initialPos =
posForPoint(point, candidateLineStart, qMin(end + 1,
d->cursorPos.size()));
776 if (
d->cursorPos.isEmpty() || index < 0) {
779 for (
int i = 0; i<
d->cursorPos.size(); i++) {
780 if (skipSynthetic &&
d->cursorPos.at(i).synthetic) {
783 if (
d->cursorPos.at(i).index <= index) {
785 if (
d->cursorPos.at(i).index == index && firstIndex) {
788 }
else if (
d->cursorPos.at(i).index > index) {
798 if (
d->cursorPos.isEmpty() || pos < 0) {
802 return d->cursorPos.at(qMin(
d->cursorPos.size()-1, pos)).index;
807 return d->initialTextPosition;
812 bool success =
false;
813 int currentIndex = 0;
818 int elementIndex = 0;
819 int insertionIndex = 0;
820 if (pos > -1 && !
d->cursorPos.isEmpty()) {
825 elementIndex = qMin(elementIndex,
d->result.size()-1);
827 auto it =
d->findTextContentElementForIndex(
d->textData, currentIndex, elementIndex);
828 if (it !=
d->textData.depthFirstTailEnd()) {
829 const int offset = insertionIndex - currentIndex;
830 it->insertText(offset, text);
832 d->insertTransforms(
d->textData, insertionIndex, text.size(), (elementIndex == insertionIndex));
842 bool success =
false;
846 int currentLength =
length;
848 while (currentLength > 0) {
849 int currentIndex = 0;
851 auto it =
d->findTextContentElementForIndex(
d->textData, currentIndex, index,
true);
852 if (it !=
d->textData.depthFirstTailEnd()) {
853 int offset = index > currentIndex? index - currentIndex: 0;
854 int size = it->numChars(
false);
855 it->removeText(offset, currentLength);
856 int diff =
size - it->numChars(
false);
857 currentLength -= diff;
860 if (index >= currentIndex) {
861 index = currentIndex + offset;
864 d->removeTransforms(
d->textData, index, endLength);
900 if (
d->isLoading)
return props;
902 if (((startPos < 0 || startPos >=
d->cursorPos.size()) && startPos == endPos) ||
d->cursorPos.isEmpty()) {
906 const int finalPos =
d->cursorPos.size() - 1;
907 const int startIndex =
d->cursorPos.at(qBound(0, startPos, finalPos)).index;
908 const int endIndex =
d->cursorPos.at(qBound(0, endPos, finalPos)).index;
909 int sought = startIndex;
910 if (startIndex == endIndex) {
911 int currentIndex = 0;
912 auto it =
d->findTextContentElementForIndex(
d->textData, currentIndex, sought);
913 if (it !=
d->textData.depthFirstTailEnd()) {
917 props.append(it->properties);
921 it =
d->findTextContentElementForIndex(
d->textData, currentIndex, sought - 1);
922 if (it !=
d->textData.depthFirstTailEnd()) {
926 props.append(it->properties);
931 while(sought < endIndex) {
932 int currentIndex = 0;
933 auto it =
d->findTextContentElementForIndex(
d->textData, currentIndex, sought);
939 }
else if (it !=
d->textData.depthFirstTailEnd()) {
943 props.append(it->properties);
946 sought = currentIndex + it->numChars(
false);
957 d->textData.childBegin()->properties = properties;
965 int currentIndex = 0;
966 auto it =
d->findTextContentElementForIndex(
d->textData, currentIndex, res.
plaintTextIndex);
967 if (it !=
d->textData.depthFirstTailEnd()) {
968 it->properties = properties;
977 const QSet<KoSvgTextProperties::PropertyId> removeProperties)
979 if ((startPos < 0 && startPos == endPos) ||
d->cursorPos.isEmpty()) {
982 d->textData.childBegin()->properties.setProperty(
p, properties.
property(
p));
985 d->textData.childBegin()->properties.removeProperty(
p);
992 const int startIndex =
d->cursorPos.at(startPos).index;
993 const int endIndex =
d->cursorPos.at(endPos).index;
994 if (startIndex != endIndex) {
995 KoSvgTextShape::Private::splitContentElement(
d->textData, startIndex);
996 KoSvgTextShape::Private::splitContentElement(
d->textData, endIndex);
998 bool changed =
false;
999 int currentIndex = 0;
1001 bool isWrapping = !
d->shapesInside.isEmpty() || !inlineSize.
isAuto;
1002 for (
auto it =
d->textData.depthFirstTailBegin(); it !=
d->textData.depthFirstTailEnd(); it++) {
1003 if (KoSvgTextShape::Private::childCount(siblingCurrent(it)) > 0) {
1007 if (currentIndex >= startIndex && currentIndex < endIndex) {
1010 d->textData.childBegin()->properties.removeProperty(
p);
1012 it->properties.removeProperty(
p);
1017 d->textData.childBegin()->properties.setProperty(
p, properties.
property(
p));
1019 it->properties.setProperty(
p, properties.
property(
p));
1025 currentIndex += it->numChars(
false);
1029 KoSvgTextShape::Private::cleanUp(
d->textData);
1045 int endRange = index +
length;
1046 int size = KoSvgTextShape::Private::numChars(clone->
d->textData.childBegin(),
false) - endRange;
1049 KoSvgTextShape::Private::cleanUp(clone->
d->textData);
1050 return std::unique_ptr<KoSvgTextShape>(clone);
1055 bool success =
false;
1056 int currentIndex = 0;
1057 int elementIndex = 0;
1058 int insertionIndex = 0;
1060 if (isEnd(richText->
d->textData.childBegin())) {
1065 if (pos > -1 && !
d->cursorPos.isEmpty()) {
1070 elementIndex = qMin(elementIndex,
d->result.size()-1);
1073 KoSvgTextShape::Private::splitContentElement(this->
d->textData, insertionIndex);
1075 auto it =
d->findTextContentElementForIndex(
d->textData, currentIndex, insertionIndex);
1076 if (it !=
d->textData.depthFirstTailEnd()) {
1077 d->textData.move(richText->
d->textData.childBegin(), siblingCurrent(it));
1081 it =
d->findTextContentElementForIndex(
d->textData, currentIndex, elementIndex);
1082 if (it !=
d->textData.depthFirstTailEnd()) {
1083 d->textData.move(richText->
d->textData.childBegin(), siblingEnd(siblingCurrent(it)));
1097 KoSvgTextShape::Private::cleanUp(
d->textData);
1104 if (child->properties.hasProperty(propertyId)) {
1108 if (found != child) {
1118 for (
auto it =
d->textData.childBegin(); it !=
d->textData.childEnd(); it++) {
1119 if (it->properties.hasProperty(propertyId)) {
1120 return Private::createTextNodeIndex(it);
1124 return Private::createTextNodeIndex(found);
1128 return Private::createTextNodeIndex(
d->textData.childBegin());
1135 for (
auto child =
d->textData.childBegin(); child !=
d->textData.childEnd(); child++) {
1137 d->startIndexOfIterator(child, node.
d->textElement, startIndex);
1138 endIndex =
d->numChars(node.
d->textElement) + startIndex;
1203 if (first !=
Fill) {
1204 if (order.at(1) == first) {
1205 order[1] = order[0];
1207 }
else if (order.at(2) == first) {
1208 order[2] = order[0];
1212 if (second != first && second !=
Stroke) {
1213 if (order.at(2) == second) {
1214 order[2] = order[1];
1219 QVariant::fromValue(order));
1225 return d->plainText;
1240 if (cursorListener) {
1253 if (cursorListener) {
1262 d->applyWhiteSpace(
d->textData,
true);
1263 d->insertNewLinesAtAnchors(
d->textData, !
d->shapesInside.isEmpty());
1264 d->cleanUp(
d->textData);
1267 if (makeInlineSize) {
1285 if (
d->result.isEmpty())
return;
1286 d->setTransformsFromLayout(
d->textData,
d->result);
1287 d->cleanUp(
d->textData);
1300 bool success =
false;
1301 for (
auto it =
d->textData.compositionBegin(); it !=
d->textData.compositionEnd(); it++) {
1303 bool isTextPath =
false;
1304 QMap<QString, QString> shapeSpecificStyles;
1308 if (it ==
d->textData.compositionBegin()) {
1309 context.
shapeWriter().startElement(
"text",
false);
1317 context.
shapeWriter().addAttribute(
"krita:textVersion", 3);
1330 context.
shapeWriter().startElement(
"textPath",
false);
1332 context.
shapeWriter().startElement(
"tspan",
false);
1343 success = it->saveSvg(context,
1344 it ==
d->textData.compositionBegin(),
1345 d->childCount(siblingCurrent(it)) == 0,
1346 shapeSpecificStyles);
1348 if (it ==
d->textData.compositionBegin()) {
1359 bool success =
true;
1361 for (
auto it =
d->textData.compositionBegin(); it !=
d->textData.compositionEnd(); it++) {
1363 QMap<QString, QString> shapeSpecificStyles;
1365 if (it ==
d->textData.compositionBegin()) {
1371 it ==
d->textData.compositionBegin());
1372 parentProps.append(ownProperties);
1374 if (it ==
d->textData.compositionBegin())
1376 bool addedFill =
false;
1377 if (attributes.size() > 0) {
1378 QString styleString;
1379 for (
auto it = attributes.constBegin(); it != attributes.constEnd(); ++it) {
1380 if (QString(it.key().toLatin1().data()).contains(
"text-anchor")) {
1381 QString val = it.value();
1382 if (it.value()==
"middle") {
1384 }
else if (it.value()==
"end") {
1389 styleString.append(
"text-align")
1393 }
else if (QString(it.key().toLatin1().data()).contains(
"fill")) {
1394 styleString.append(
"color")
1399 }
else if (QString(it.key().toLatin1().data()).contains(
"font-size")) {
1400 QString val = it.value();
1401 styleString.append(it.key().toLatin1().data())
1406 styleString.append(it.key().toLatin1().data())
1415 styleString.append(
"color")
1417 .append(b->color().name())
1423 if (
d->childCount(siblingCurrent(it)) == 0) {
1424 debugFlake <<
"saveHTML" <<
this << it->text;
1430 parentProps.pop_back();
1453 d->logicalToVisualCursorPos,
1456 d->initialTextPosition));
1492 for (
auto it = compositionBegin(
d->textData); it != compositionEnd(
d->textData); it++) {
1495 qDebug() << QString(spaces +
"+") << it->text;
1496 qDebug() << QString(spaces +
"|") << it->properties.convertToSvgTextAttributes();
1503 qDebug() << QString(spaces +
"| TextPath set: ") << (!it->textPath.isNull());
1504 qDebug() << QString(spaces +
"| Transforms set: ") << it->localTransformations;
1516 d->isLoading = disable;
1521 return d->isLoading;
1526 d->disableFontMatching = disable;
1531 return d->disableFontMatching;
1540 painter.setRenderHint(QPainter::Antialiasing,
false);
1541 painter.setRenderHint(QPainter::SmoothPixmapTransform,
false);
1543 painter.setRenderHint(QPainter::Antialiasing,
true);
1544 painter.setRenderHint(QPainter::SmoothPixmapTransform,
true);
1548 int currentIndex = 0;
1549 if (!
d->result.isEmpty()) {
1550 QPainterPath rootBounds;
1554 d->paintPaths(painter, rootBounds,
this,
d->result, textRendering, chunk, currentIndex);
1558 Q_FOREACH (
KoShape *child, this->shapes()) {
1559 const KoSvgTextChunkShape *textPathChunk =
dynamic_cast<const KoSvgTextChunkShape *
>(child);
1560 if (textPathChunk) {
1562 painter.setPen(Qt::magenta);
1563 painter.setOpacity(0.5);
1564 if (textPathChunk->layoutInterface()->textPath()) {
1565 QPainterPath
p = textPathChunk->layoutInterface()->textPath()->outline();
1566 p = textPathChunk->layoutInterface()->textPath()->transformation().map(
p);
1567 painter.strokePath(
p, QPen(Qt::green));
1568 painter.drawPoint(
p.pointAtPercent(0));
1569 painter.drawPoint(
p.pointAtPercent(
p.percentAtLength(
p.length() * 0.5)));
1570 painter.drawPoint(
p.pointAtPercent(1.0));
1577 Q_FOREACH (
KoShape *shapeInside,
d->shapesInside) {
1578 QPainterPath
p = shapeInside->
outline();
1580 painter.strokePath(
p, QPen(Qt::green));
1582 Q_FOREACH (
KoShape *shapeInside,
d->shapesSubtract) {
1583 QPainterPath
p = shapeInside->
outline();
1585 painter.strokePath(
p, QPen(Qt::red));
1600 for (
auto it =
d->textData.depthFirstTailBegin(); it !=
d->textData.depthFirstTailEnd(); it++) {
1601 result.addPath(it->associatedOutline);
1602 for (
int i = 0; i < it->textDecorations.values().size(); ++i) {
1603 result.addPath(it->textDecorations.values().at(i));
1611 return outline().boundingRect();
1618 for (
auto it =
d->textData.compositionBegin(); it !=
d->textData.compositionEnd(); it++) {
1625 QRectF bb = it->associatedOutline.boundingRect();
1626 QMap<KoSvgText::TextDecoration, QPainterPath> decorations = it->textDecorations;
1627 for (
int i = 0; i < decorations.values().size(); ++i) {
1628 bb |= decorations.values().at(i).boundingRect();
1630 if (!bb.isEmpty()) {
1633 stroke->strokeInsets(
this, insets);
1641 parentStrokes.pop_back();
1651 int currentIndex = 0;
1652 if (!
d->result.isEmpty()) {
1653 QPainterPath rootBounds;
1655 d->paintDebug(painter,
d->result, currentIndex);
1660 Q_FOREACH (
LineBox lineBox,
d->lineBoxes) {
1663 pen.setCosmetic(
true);
1665 painter.setBrush(QBrush(Qt::transparent));
1666 pen.setColor(QColor(0, 128, 255, 128));
1667 painter.setPen(pen);
1668 painter.drawLine(chunk.
length);
1669 pen.setColor(QColor(255, 128, 0, 128));
1670 painter.setPen(pen);
1673 pen.setColor(QColor(255, 0, 0, 128));
1674 pen.setStyle(Qt::DashDotDotLine);
1675 painter.setPen(pen);
1677 pen.setColor(QColor(0, 128, 0, 128));
1678 pen.setStyle(Qt::DashDotLine);
1679 painter.setPen(pen);
1689 int currentIndex = 0;
1690 if (!
d->result.empty()) {
1691 shape =
d->collectPaths(
this,
d->result, currentIndex);
1700 if (!
d->shapesInside.isEmpty()) {
1702 }
else if (!inlineSize.
isAuto) {
1705 bool textSpaceCollapse =
false;
1706 for (
auto it =
d->textData.depthFirstTailBegin(); it !=
d->textData.depthFirstTailEnd(); it++) {
1709 textSpaceCollapse =
true;
1724 return d->shapesInside;
1734 return d->shapesSubtract;
1740 if (!
d->shapesInside.isEmpty()) {
1742 Q_FOREACH(
KoShape* shape,
d->shapesInside) {
1744 shapesInsideList.append(QString(
"url(#%1)").arg(
id));
1746 map.insert(
"shape-inside", shapesInsideList.join(
" "));
1748 if (!
d->shapesSubtract.isEmpty()) {
1750 Q_FOREACH(
KoShape* shape,
d->shapesSubtract) {
1752 shapesInsideList.append(QString(
"url(#%1)").arg(
id));
1754 map.insert(
"shape-subtract", shapesInsideList.join(
" "));
1774 t.
name = i18n(
"SVG Text");
1776 t.
toolTip = i18n(
"SVG Text Shape");
1782 Q_UNUSED(documentResources);
1783 debugFlake <<
"Create default svg text shape";
1787 shape->
insertText(0, i18nc(
"Default text for the text shape",
"Placeholder Text"));
1797 QString svgText = params->
stringProperty(
"svgText", i18nc(
"Default text for the text shape",
"<text>Placeholder Text</text>"));
1799 QRectF shapeRect = QRectF(0, 0, 200, 60);
1801 QVariant origin = params->
property(
"origin");
1803 if (
rect.type()==QVariant::RectF) {
1804 shapeRect =
rect.toRectF();
1812 if (origin.type() == QVariant::PointF) {
qreal length(const QPointF &vec)
float value(const T *src, size_t ch)
QList< QString > QStringList
qreal distance(const QPointF &p1, const QPointF &p2)
KisForest< KoSvgTextContentElement >::child_iterator findNodeIndexForPropertyIdImpl(KisForest< KoSvgTextContentElement >::child_iterator parent, KoSvgTextProperties::PropertyId propertyId)
KoSvgTextProperties inheritProperties(KisForest< KoSvgTextContentElement >::depth_first_tail_iterator it)
#define KoSvgTextShape_SHAPEID
QSharedPointer< KoSvgTextShapeMemento > KoSvgTextShapeMementoSP
The HtmlSavingContext class provides context for saving a flake-based document to html.
KoXmlWriter & shapeWriter()
Provides access to the shape writer.
A simple solid color shape background.
qreal documentResolution() const
QString stringProperty(const QString &name, const QString &defaultValue=QString()) const
bool property(const QString &name, QVariant &value) const
void addTemplate(const KoShapeTemplate ¶ms)
void setToolTip(const QString &tooltip)
void setLoadingPriority(int priority)
void setIconName(const char *iconName)
void setXmlElementNames(const QString &nameSpace, const QStringList &elementNames)
QList< KoShape::ShapeChangeListener * > listeners
QScopedPointer< Private > d
virtual QSizeF size() const
Get the size of the shape in pt.
virtual QPainterPath outline() const
KoShapeAnchor * anchor() const
virtual QVector< PaintOrder > paintOrder() const
paintOrder
static QVector< PaintOrder > defaultPaintOrder()
default paint order as per SVG specification
virtual void shapeChanged(ChangeType type, KoShape *shape=0)
void shapeChangedPriv(KoShape::ChangeType type)
QTransform absoluteTransformation() const
ChangeType
Used by shapeChanged() to select which change was made.
@ StrokeChanged
the shapes stroke has changed
@ ContentChanged
the content of the shape changed e.g. a new image inside a pixmap/text change inside a textshape
@ BackgroundChanged
the shapes background has changed
QTransform transformation() const
Returns the shapes local transformation matrix.
bool inheritPaintOrder() const
inheritPaintOrder
virtual void setPosition(const QPointF &position)
Set the position of the shape in pt.
void setShapeId(const QString &id)
void setInheritPaintOrder(bool value)
setInheritPaintOrder set inherit paint order.
The KoSvgTextNodeIndex class.
KoShape * textPath()
textPath
KoSvgTextProperties * properties()
properties The properties for this node as a pointer.
QScopedPointer< Private > d
KoSvgText::TextOnPathInfo * textPathInfo()
textPathInfo the text path info for this node as a pointer.
@ Visiblity
Bool, CSS visibility.
@ TextAnchorId
KoSvgText::TextAnchor.
@ InlineSizeId
KoSvgText::AutoValue.
@ PaintOrder
QVector<KoShape::PaintOrder>
@ Opacity
Double, SVG shape opacity.
@ KraTextStyleType
string, used to identify the style preset type (character or paragraph).
@ TextCollapseId
KoSvgText::TextSpaceCollapse.
@ StrokeId
KoSvgText::StrokeProperty.
@ FillId
KoSvgText::BackgroundProperty.
@ WritingModeId
KoSvgText::WritingMode.
@ DirectionId
KoSvgText::Direction.
@ TextWrapId
KoSvgText::TextWrap.
void removeProperty(PropertyId id)
QList< PropertyId > properties() const
static bool propertyIsBlockOnly(KoSvgTextProperties::PropertyId id)
QMap< QString, QString > convertToSvgTextAttributes() const
QVariant property(PropertyId id, const QVariant &defaultValue=QVariant()) const
QMap< QString, QString > convertParagraphProperties() const
convertParagraphProperties some properties only apply to the root shape, so we write those separately...
static const KoSvgTextProperties & defaultProperties()
bool hasProperty(PropertyId id) const
void setAllButNonInheritableProperties(const KoSvgTextProperties &properties)
Used to merge child properties into parent properties.
void setProperty(PropertyId id, const QVariant &value)
KoSvgText::CssLengthPercentage fontSize() const
KoSvgTextShapeFactory()
constructor
bool supports(const QDomElement &e, KoShapeLoadingContext &context) const override
Reimplemented.
KoShape * createDefaultShape(KoDocumentResourceManager *documentResources=0) const override
KoShape * createShape(const KoProperties *params, KoDocumentResourceManager *documentResources=0) const override
bool convertFromSvg(const QString &svgText, const QString &stylesText, const QRectF &boundsInPixels, qreal pixelsPerInch)
upload the svg representation of text into the shape
QVector< LineBox > lineBoxes
QPointF initialTextPosition
QMap< int, int > logicalToVisualCursorPos
KisForest< KoSvgTextContentElement > textData
KoSvgTextShapeMementoImpl(const KisForest< KoSvgTextContentElement > &textData, const QVector< CharacterResult > &result, const QVector< LineBox > &lineBoxes, const QVector< CursorPos > &cursorPos, const QMap< int, int > &logicalToVisualCursorPos, const QString &plainText, const bool &isBidi, const QPointF &initialTextPosition)
QVector< CharacterResult > result
QVector< CursorPos > cursorPos
~KoSvgTextShapeMementoImpl()
int posLeft(int pos, bool visual=false)
int posForPoint(QPointF point, int start=-1, int end=-1, bool *overlaps=nullptr)
posForPoint Finds the closest cursor position for the given point in shape coordinates.
KoShapeStrokeModelSP stroke() const override
QPair< int, int > findRangeForNodeIndex(const KoSvgTextNodeIndex &node) const
findRangeForNodeIndex Find the start and end cursor position for a given nodeIndex.
void setBackground(QSharedPointer< KoShapeBackground > background) override
int wordLeft(int pos, bool visual=false)
wordLeft return the cursorpos for the word left or the extreme of the line.
void notifyCursorPosChanged(int pos, int anchor)
Notify that the cursor position has changed.
void shapeChanged(ChangeType type, KoShape *shape) override
bool relayoutIsBlocked() const
relayoutIsBlocked
void setResolution(qreal xRes, qreal yRes) override
KoSvgTextShapeMementoSP getMemento()
Get a memento holding the current textdata and layout info.
bool fontMatchingDisabled() const
fontMatchingDisabled
QVector< CursorPos > cursorPos
QPainterPath selectionBoxes(int pos, int anchor)
selectionBoxes returns all selection boxes for a given range. Range will be normalized internally.
QVector< CharacterResult > result
void paint(QPainter &painter) const override
Paint the shape fill The class extending this one is responsible for painting itself....
QSharedPointer< KoShapeBackground > background() const override
bool saveHtml(HtmlSavingContext &context)
void setMemento(const KoSvgTextShapeMementoSP memento)
Set the text data and layout info, reset listening cursors to 0.
QRectF boundingRect() const override
Get the bounding box of the shape.
int wordRight(int pos, bool visual=false)
wordRight return the cursorpos for the word right or the extreme of the line.
void notifyMarkupChanged()
Notify that the markup has changed.
KoSvgTextNodeIndex findNodeIndexForPropertyId(KoSvgTextProperties::PropertyId propertyId)
findNodeIndexForPropertyId
int indexForPos(int pos) const
indexForPos get the string index for a given cursor position.
int posUp(int pos, bool visual=false)
return position above.
KoShape * textOutline() const
textOutline This turns the text object into non-text KoShape(s) to the best of its abilities.
@ PreformattedText
Text-on-Path falls under this or PrePositionedText depending on collapse of lines.
@ TextInShape
Uses shape-inside to wrap and preserves spaces.
@ InlineWrap
Uses inline size to wrap and preserves spaces.
std::unique_ptr< KoSvgTextShape > copyRange(int index, int length) const
copyRange Copy the rich text for the given range.
bool insertRichText(int pos, const KoSvgTextShape *richText)
insertRichText Insert rich text at the given cursor pos. This will first split contents at the given ...
QList< KoShape * > shapesInside
static const QString & defaultPlaceholderText()
int nextLine(int pos)
nextLine get a position on the next line for this position.
QPointF initialTextPosition
void setPaintOrder(PaintOrder first, PaintOrder second) override
setPaintOrder set the paint order. As there's only three entries in any given paintorder,...
int previousLine(int pos)
previousLine get a position on the previous line for this position.
int posForPointLineSensitive(QPointF point)
posForPointLineSensitive When clicking on an empty space in a wrapped text, it is preferable to have ...
void leaveNodeSubtree()
Set the current node to its parent, leaving the subtree.
void setFontMatchingDisabled(const bool disable)
setDisableFontMatching
void cleanUp()
Cleans up the internal text data. Used by undo commands.
int lineStart(int pos)
lineStart return the 'line start' for this pos. This uses anchored chunks, so each absolute x,...
QPainterPath defaultCursorShape()
defaultCursorShape This returns a default cursor shape for when there's no text inside the text shape...
QVector< PaintOrder > paintOrder() const override
paintOrder
KoSvgTextProperties propertiesForPos(const int pos, bool inherited=false) const
Return the properties at a given position.
int wordEnd(int pos)
wordEnd return the pos of the first wordbreak.
int previousIndex(int pos)
previousIndex Return the first pos which has a lower string index.
void setShapesSubtract(QList< KoShape * > shapesSubtract)
setShapesSubtract
QPainterPath cursorForPos(int pos, QLineF &caret, QColor &color, double bidiFlagSize=1.0)
cursorForPos returns the QPainterPath associated with this cursorPosition.
int lineEnd(int pos)
lineEnd return the 'line end' for this pos. This uses anchored chunks, so each absolute x,...
void enterNodeSubtree()
Set the current node to its first child, entering the subtree.
void setPropertiesAtPos(int pos, KoSvgTextProperties properties)
setPropertiesAtPos will set the properties at pos.
int posDown(int pos, bool visual=false)
return position below.
void debugParsing()
Outputs debug with the current textData tree.
QList< KoSvgTextProperties > propertiesForRange(const int startPos, const int endPos, bool inherited=false) const
propertiesForRange get the properties for a range.
TextType textType() const
textType This enum gives an indication of what kind of text this shape is. The different text types a...
int posForIndex(int index, bool firstIndex=false, bool skipSynthetic=false) const
posForIndex Get the cursor position for a given index in a string.
int nextPos(int pos, bool visual)
nextPos get the next position.
void mergePropertiesIntoRange(const int startPos, const int endPos, const KoSvgTextProperties properties, const QSet< KoSvgTextProperties::PropertyId > removeProperties=QSet< KoSvgTextProperties::PropertyId >())
mergePropertiesIntoRange Merge given properties into the given range. This will first split the nodes...
void setCharacterTransformsFromLayout()
setCharacterTransformsFromLayout Converts the text to a prepositioned SVG 1.1 text....
void setShapesInside(QList< KoShape * > shapesInside)
setShapesInside
QScopedPointer< Private > d
int wordStart(int pos)
wordStart return the first pos before a wordbreak in the start direction.
QPainterPath outline() const override
void setMementoImpl(const KoSvgTextShapeMementoSP memento)
void paintStroke(QPainter &painter) const override
paintStroke paints the shape's stroked outline
bool saveSvg(SvgSavingContext &context) override
Saves SVG data.
int previousPos(int pos, bool visual)
previousPos get the previous position.
QMap< QString, QString > shapeTypeSpecificStyles(SvgSavingContext &context) const
int posRight(int pos, bool visual=false)
QList< KoShape * > shapesSubtract
void setRelayoutBlocked(const bool disable)
void setStroke(KoShapeStrokeModelSP stroke) override
KoShape * cloneShape() const override
creates a deep copy of the shape or shape's subtree
QPainterPath underlines(int pos, int anchor, KoSvgText::TextDecorations decor, KoSvgText::TextDecorationStyle style, qreal minimum, bool thick)
void paintDebug(QPainter &painter, DebugElements elements) const
bool insertText(int pos, QString text)
insertText Insert a text somewhere in the KoTextShape.
bool removeText(int &index, int &length)
removeText Where insert text explicitly uses a cursorposition, remove text uses a string index....
void convertCharTransformsToPreformatted(bool makeInlineSize=false)
convertCharTransformsToPreformatted Converts the text to a preformatted SVG 2.0 text....
KoSvgText::WritingMode writingMode() const
writingMode There's a number of places we need to check the writing mode to provide proper controls.
~KoSvgTextShape() override
QRectF outlineRect() const override
int nextIndex(int pos)
nextIndex Return the first cursor position with a higher string index.
KoSvgTextProperties textProperties() const
void startElement(const char *tagName, bool indentInside=true)
void addTextNode(const QString &str)
void addAttribute(const char *attrName, const QString &value)
Context for saving svg files.
QScopedPointer< KoXmlWriter > shapeWriter
QString getID(const KoShape *obj)
Returns the unique id for the given shape.
static void saveSvgFill(QSharedPointer< KoShapeBackground > background, const bool fillRuleEvenOdd, const QRectF outlineRect, const QSizeF size, const QTransform absoluteTransform, SvgSavingContext &context)
Saves fill style of specified shape.
static QString embedShape(const KoShape *shape, SvgSavingContext &context)
static void saveSvgBasicStyle(const bool isVisible, const qreal transparency, const QVector< KoShape::PaintOrder > paintOrder, bool inheritPaintorder, SvgSavingContext &context, bool textShape=false)
Saves only stroke, fill and transparency of the shape.
static void saveSvgStyle(KoShape *shape, SvgSavingContext &context)
Saves the style of the specified shape.
static void saveMetadata(const KoShape *shape, SvgSavingContext &context)
static void saveSvgStroke(KoShapeStrokeModelSP, SvgSavingContext &context)
Saves stroke style of specified shape.
static void writeTransformAttributeLazy(const QString &name, const QTransform &transform, KoXmlWriter &shapeWriter)
Writes a transform as an attribute name iff the transform is not empty.
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
qreal kisDistance(const QPointF &pt1, const QPointF &pt2)
#define koIconNameCStr(name)
ResultIterator hierarchyBegin(Iterator it)
ChildIterator< value_type, is_const > childBegin(const ChildIterator< value_type, is_const > &it)
ResultIterator hierarchyEnd(Iterator it)
ChildIterator< value_type, is_const > siblingCurrent(ChildIterator< value_type, is_const > it)
int size(const Forest< T > &forest)
ChildIterator< value_type, is_const > childEnd(const ChildIterator< value_type, is_const > &it)
TextDecorationStyle
Style of the text-decoration.
@ Solid
Draw a solid line.Ex: --—.
@ Dashed
Draw a dashed line. Ex: - - - - -.
@ Dotted
Draw a dotted line. Ex: .....
Direction
Base direction used by Bidi algorithm.
@ Collapse
Collapse white space sequences into a single character.
@ PreserveSpaces
required for 'xml:space="preserve"' emulation.
QPointF finalPosition
the final position, taking into account both CSS and SVG positioning considerations.
KoSvgText::FontMetrics metrics
Fontmetrics for current font, in Freetype scanline coordinates.
bool anchored_chunk
whether this is the start of a new chunk.
QRectF layoutBox() const
layoutBox
QTransform finalTransform() const
QLineF caret
Caret for this characterResult.
bool rtl
Whether the current glyph is right-to-left, as opposed to the markup.
QColor color
Which color the current position has.
QVector< QPointF > offsets
The advance offsets for each grapheme index.
int cluster
Which character result this position belongs in.
int offset
Which offset this position belongs with.
qreal bottom
Bottom inset.
QString iconName
Icon name.
QString toolTip
The tooltip text for the template.
QString name
The name to be shown for this template.
The KoSvgTextContentElement struct.
KisForest< KoSvgTextContentElement >::child_iterator textElement
Private(KisForest< KoSvgTextContentElement >::child_iterator _textElement)
ShapeChangeListener so we can inform any text cursors that the cursor needs updating.
void notifyShapeChanged(ChangeType type, KoShape *shape) override
virtual void notifyMarkupChanged()=0
virtual void notifyCursorPosChanged(int pos, int anchor)=0
BackgroundProperty is a special wrapper around KoShapeBackground for managing it in KoSvgTextProperti...
qint32 underlineThickness
underline thickness from font.
StrokeProperty is a special wrapper around KoShapeStrokeModel for managing it in KoSvgTextProperties.
QPointF baselineTop
Used to identify the top of the line for baseline-alignment.
QVector< LineChunk > chunks
QPointF baselineBottom
Used to identify the bottom of the line for baseline-alignment.
QVector< int > chunkIndices
charResult indices that belong to this chunk.
QLineF length
Used to measure how long the current line is allowed to be.