Krita Source Code Documentation
Loading...
Searching...
No Matches
KoSvgTextShape.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2017 Dmitry Kazakov <dimula73@gmail.com>
3 * SPDX-FileCopyrightText: 2022 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include "KoSvgTextShape.h"
9#include "KoSvgTextShape_p.h"
10
11#include <QTextLayout>
12
13#include <klocalizedstring.h>
14
15#include "KoSvgTextProperties.h"
17#include <KoShapeContainer_p.h>
18#include <KoShapeController.h>
19#include <text/KoCssTextUtils.h>
20#include <text/KoFontRegistry.h>
22#include <text/KoPolygonUtils.h>
23
24#include <kis_global.h>
25
26#include <KoClipMaskPainter.h>
27#include <KoColorBackground.h>
28#include <KoIcon.h>
29#include <KoPathShape.h>
30#include <KoProperties.h>
32#include <KoXmlNS.h>
33#include <KoInsets.h>
34
35#include <SvgLoadingContext.h>
36#include <SvgGraphicContext.h>
37#include <SvgUtil.h>
38#include <SvgStyleWriter.h>
39
40#include <QPainter>
41#include <QPainterPath>
42#include <QtMath>
43
44#include <FlakeDebug.h>
45
46// Memento pointer to hold data for Undo commands.
47
86
92
93// for the use in KoSvgTextShape::Private::createTextNodeIndex() only
95 : d() // Private is **not** initialized, to be initialized by the factory method
96{
97}
98
99KoSvgTextNodeIndex KoSvgTextShape::Private::createTextNodeIndex(KisForest<KoSvgTextContentElement>::child_iterator textElement)
100{
101 KoSvgTextNodeIndex index;
102 index.d.reset(new KoSvgTextNodeIndex::Private(textElement));
103 return index;
104}
105
107{
108 d->textElement = rhs.d->textElement;
109}
110
114
118
120 return &d->textElement->textPathInfo;
121}
122
124 // TODO: implement this properly in the text-in-shape branch.
125 return nullptr;
126}
127
128
130 : KoShape()
131 , d(new Private)
132{
134 d->textData.insert(d->textData.childBegin(), KoSvgTextContentElement());
135}
136
138 : KoShape(rhs)
139 , d(new Private(*rhs.d))
140{
142}
143
147
149{
150 static const QString s_placeholderText = i18nc("Default text for the text shape", "Placeholder Text");
151 return s_placeholderText;
152}
153
155{
156 return new KoSvgTextShape(*this);
157}
158
160{
161 if (d->isLoading) {
162 return;
163 }
164 KoShape::shapeChanged(type, shape);
165
166 if (type == ContentChanged) {
167 relayout();
168 }
169}
170
171void KoSvgTextShape::setResolution(qreal xRes, qreal yRes)
172{
173 int roundedX = qRound(xRes);
174 int roundedY = qRound(yRes);
175 if (roundedX != d->xRes || roundedY != d->yRes) {
176 d->xRes = roundedX;
177 d->yRes = roundedY;
178 relayout();
179 }
180}
181
182int KoSvgTextShape::posLeft(int pos, bool visual)
183{
186 if (mode == KoSvgText::VerticalRL) {
187 return nextLine(pos);
188 } else if (mode == KoSvgText::VerticalLR) {
189 return previousLine(pos);
190 } else {
191 if (direction == KoSvgText::DirectionRightToLeft) {
192 return nextPos(pos, visual);
193 } else {
194 return previousPos(pos, visual);
195 }
196 }
197}
198
199int KoSvgTextShape::posRight(int pos, bool visual)
200{
203
204 if (mode == KoSvgText::VerticalRL) {
205 return previousLine(pos);
206 } else if (mode == KoSvgText::VerticalLR) {
207 return nextLine(pos);
208 } else {
209 if (direction == KoSvgText::DirectionRightToLeft) {
210 return previousPos(pos, visual);
211 } else {
212 return nextPos(pos, visual);
213 }
214 }
215}
216
217int KoSvgTextShape::posUp(int pos, bool visual)
218{
221 if (mode == KoSvgText::VerticalRL || mode == KoSvgText::VerticalLR) {
222 if (direction == KoSvgText::DirectionRightToLeft) {
223 return nextPos(pos, visual);
224 } else {
225 return previousPos(pos, visual);
226 }
227 } else {
228 return previousLine(pos);
229 }
230}
231
232int KoSvgTextShape::posDown(int pos, bool visual)
233{
236 if (mode == KoSvgText::VerticalRL || mode == KoSvgText::VerticalLR) {
237 if (direction == KoSvgText::DirectionRightToLeft) {
238 return previousPos(pos, visual);
239 } else {
240 return nextPos(pos, visual);
241 }
242 } else {
243 return nextLine(pos);
244 }
245}
246
248{
249 if (pos < 0 || d->cursorPos.isEmpty() || d->result.isEmpty()) {
250 return pos;
251 }
252 CursorPos p = d->cursorPos.at(pos);
253 if (d->result.at(p.cluster).anchored_chunk && p.offset == 0) {
254 return pos;
255 }
256 int candidate = 0;
257 for (int i = 0; i < pos; i++) {
258 CursorPos p2 = d->cursorPos.at(i);
259 if (d->result.at(p2.cluster).anchored_chunk && p2.offset == 0) {
260 candidate = i;
261 }
262 }
263 return candidate;
264}
265
267{
268 if (pos < 0 || d->cursorPos.isEmpty() || d->result.isEmpty()) {
269 return pos;
270 }
271 if (pos > d->cursorPos.size() - 1) {
272 return d->cursorPos.size() - 1;
273 }
274 int candidate = 0;
275 int posCluster = d->cursorPos.at(pos).cluster;
276 for (int i = pos; i < d->cursorPos.size(); i++) {
277 CursorPos p = d->cursorPos.at(i);
278 if (d->result.at(p.cluster).anchored_chunk && i > pos && posCluster != p.cluster) {
279 break;
280 }
281 candidate = i;
282 }
283 return candidate;
284}
285
286int KoSvgTextShape::wordLeft(int pos, bool visual)
287{
288 //TODO: figure out preferred behaviour for wordLeft in RTL && visual.
289 Q_UNUSED(visual)
290 if (pos < 0 || pos > d->cursorPos.size()-1 || d->result.isEmpty() || d->cursorPos.isEmpty()) {
291 return pos;
292 }
294 if (direction == KoSvgText::DirectionRightToLeft) {
295 return wordEnd(pos);
296 }
297 return wordStart(pos);
298}
299
300int KoSvgTextShape::wordRight(int pos, bool visual)
301{
302 Q_UNUSED(visual)
303 if (pos < 0 || pos > d->cursorPos.size()-1 || d->result.isEmpty() || d->cursorPos.isEmpty()) {
304 return pos;
305 }
307 if (direction == KoSvgText::DirectionRightToLeft) {
308 return wordStart(pos);
309 }
310 return wordEnd(pos);
311}
312
314{
315 if (d->cursorPos.isEmpty()) {
316 return pos;
317 }
318 int currentIndex = d->cursorPos.at(pos).index;
319
320 for (int i = pos; i < d->cursorPos.size(); i++) {
321 if (d->cursorPos.at(i).index > currentIndex) {
322 return i;
323 }
324 }
325 return pos;
326}
327
329{
330 if (d->cursorPos.isEmpty()) {
331 return pos;
332 }
333 int currentIndex = d->cursorPos.at(pos).index;
334
335 for (int i = pos; i >= 0; i--) {
336 if (d->cursorPos.at(i).index < currentIndex) {
337 return i;
338 }
339 }
340 return pos;
341}
342
343int KoSvgTextShape::nextPos(int pos, bool visual)
344{
345 if (d->cursorPos.isEmpty()) {
346 return -1;
347 }
348
349 if(visual) {
350 int visualIndex = d->logicalToVisualCursorPos.value(pos);
351 return d->logicalToVisualCursorPos.key(qMin(visualIndex + 1, d->cursorPos.size() - 1), d->cursorPos.size() - 1);
352 }
353
354 return qMin(pos + 1, d->cursorPos.size() - 1);
355}
356
357int KoSvgTextShape::previousPos(int pos, bool visual)
358{
359 if (d->cursorPos.isEmpty()) {
360 return -1;
361 }
362
363 if(visual) {
364 int visualIndex = d->logicalToVisualCursorPos.value(pos);
365 return d->logicalToVisualCursorPos.key(qMax(visualIndex - 1, 0), 0);
366 }
367
368 return qMax(pos - 1, 0);
369}
370
372{
373 if (pos < 0 || pos > d->cursorPos.size()-1 || d->result.isEmpty() || d->cursorPos.isEmpty()) {
374 return pos;
375 }
376
377 int nextLineStart = lineEnd(pos)+1;
378 int nextLineEnd = lineEnd(nextLineStart);
379 CursorPos cursorPos = d->cursorPos.at(pos);
380 if (!this->shapesInside().isEmpty()) {
381 LineBox nextLineBox;
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));
386 }
387 }
388 }
389 if (nextLineBox.chunks.size()>0) {
390 int first = -1;
391 int last = -1;
392 Q_FOREACH(LineChunk chunk, nextLineBox.chunks) {
393 Q_FOREACH (int i, chunk.chunkIndices) {
394 if (d->result.at(i).addressable) {
395 if (first < 0) {
396 first = d->result.at(i).cursorInfo.graphemeIndices.first();
397 }
398 last = d->result.at(i).cursorInfo.graphemeIndices.last();
399 }
400 }
401 }
402 if (first > -1 && last > -1) {
403 nextLineStart = posForIndex(first);
404 nextLineEnd = posForIndex(last);
405 }
406 }
407 }
408
409
410 if (nextLineStart > d->cursorPos.size()-1) {
411 return d->cursorPos.size()-1;
412 }
413
414 CharacterResult res = d->result.at(cursorPos.cluster);
415 QPointF currentPoint = res.finalPosition;
416 currentPoint += res.cursorInfo.offsets.value(cursorPos.offset, res.advance);
417 int candidate = posForPoint(currentPoint, nextLineStart, nextLineEnd+1);
418 if (candidate < 0) {
419 return pos;
420 }
421
422 return candidate;
423}
424
426{
427 if (pos < 0 || pos > d->cursorPos.size()-1 || d->result.isEmpty() || d->cursorPos.isEmpty()) {
428 return pos;
429 }
430 int currentLineStart = lineStart(pos);
431 if (currentLineStart - 1 < 0) {
432 return 0;
433 }
434 int previousLineStart = lineStart(currentLineStart-1);
435
436 CursorPos cursorPos = d->cursorPos.at(pos);
437 if (!this->shapesInside().isEmpty()) {
438 LineBox previousLineBox;
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));
443 }
444 }
445 }
446 if (previousLineBox.chunks.size()>0) {
447 int first = -1;
448 int last = -1;
449 Q_FOREACH(LineChunk chunk, previousLineBox.chunks) {
450 Q_FOREACH (int i, chunk.chunkIndices) {
451 if (d->result.at(i).addressable) {
452 if (first < 0) {
453 first = d->result.at(i).cursorInfo.graphemeIndices.first();
454 }
455 last = d->result.at(i).cursorInfo.graphemeIndices.last();
456 }
457 }
458 }
459 if (first > -1 && last > -1) {
460 previousLineStart = posForIndex(first);
461 currentLineStart = posForIndex(last);
462 }
463 }
464 }
465
466 CharacterResult res = d->result.at(cursorPos.cluster);
467 QPointF currentPoint = res.finalPosition;
468 currentPoint += res.cursorInfo.offsets.value(cursorPos.offset, res.advance);
469 int candidate = posForPoint(currentPoint, previousLineStart, currentLineStart);
470 if (candidate < 0) {
471 return pos;
472 }
473
474 return candidate;
475}
476
478{
479 if (pos < 0 || pos > d->cursorPos.size()-1 || d->result.isEmpty() || d->cursorPos.isEmpty()) {
480 return pos;
481 }
482 int currentLineEnd = lineEnd(pos);
483 if (pos == lineStart(pos) || pos == currentLineEnd) {
484 return pos;
485 }
486
487 int wordEnd = pos;
488 for (int i = pos; i<= currentLineEnd; i++) {
489 wordEnd = i;
490 CursorPos cursorPos = d->cursorPos.at(i);
491 if (d->result.at(cursorPos.cluster).cursorInfo.isWordBoundary && cursorPos.offset == 0) {
492 break;
493 }
494
495 }
496
497 return wordEnd;
498}
499
501{
502 if (pos < 0 || pos > d->cursorPos.size()-1 || d->result.isEmpty() || d->cursorPos.isEmpty()) {
503 return pos;
504 }
505 int currentLineStart = lineStart(pos);
506 if (pos == currentLineStart || pos == lineEnd(pos)) {
507 return pos;
508 }
509
510 int wordStart = pos;
511 bool breakNext = false;
512 for (int i = pos; i >= currentLineStart; i--) {
513 if (breakNext) break;
514 CursorPos cursorPos = d->cursorPos.at(i);
515 if (d->result.at(cursorPos.cluster).cursorInfo.isWordBoundary && cursorPos.offset == 0) {
516 breakNext = true;
517 }
518 wordStart = i;
519 }
520
521 return wordStart;
522}
523
525{
527 double fontSize = this->textProperties().fontSize().value;
528 QPainterPath p;
529 if (mode == KoSvgText::HorizontalTB) {
530 p.moveTo(0, fontSize*0.2);
531 p.lineTo(0, -fontSize);
532 } else {
533 p.moveTo(-fontSize * 0.5, 0);
534 p.lineTo(fontSize, 0);
535 }
536 p.translate(d->initialTextPosition);
537
538 return p;
539}
540
541QPainterPath KoSvgTextShape::cursorForPos(int pos, QLineF &caret, QColor &color, double bidiFlagSize)
542{
543 if (d->result.isEmpty() || d->cursorPos.isEmpty() || pos < 0 || pos >= d->cursorPos.size()) {
544 return defaultCursorShape();
545 }
546 QPainterPath p;
547
548 CursorPos cursorPos = d->cursorPos.at(pos);
549
550 CharacterResult res = d->result.at(cursorPos.cluster);
551
552 const QTransform tf = res.finalTransform();
553 color = res.cursorInfo.color;
554 caret = res.cursorInfo.caret;
555 caret.translate(res.cursorInfo.offsets.value(cursorPos.offset, QPointF()));
556
557 p.moveTo(tf.map(caret.p1()));
558 p.lineTo(tf.map(caret.p2()));
559 if (d->isBidi && bidiFlagSize > 0) {
560 int sign = res.cursorInfo.rtl ? -1 : 1;
561 double bidiFlagHalf = bidiFlagSize * 0.5;
562 QPointF point3;
563 QPointF point4;
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);
568 } else {
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));
572 }
573 p.lineTo(tf.map(point3));
574 p.lineTo(tf.map(point4));
575 }
576 caret = tf.map(caret);
577
578 return p;
579}
580
581QPainterPath KoSvgTextShape::selectionBoxes(int pos, int anchor)
582{
583 int start = qMin(pos, anchor);
584 int end = qMax(pos, anchor);
585 end = qMin(d->cursorPos.size()-1, end);
586
587 if (start == end || start < 0) {
588 return QPainterPath();
589 }
590
591 QPainterPath p;
592 p.setFillRule(Qt::WindingFill);
593 for (int i = start+1; i <= end; i++) {
594 CursorPos cursorPos = d->cursorPos.at(i);
595 CharacterResult res = d->result.at(cursorPos.cluster);
596 const QTransform tf = res.finalTransform();
597 QLineF first = res.cursorInfo.caret;
598 QLineF last = first;
599 if (res.cursorInfo.rtl) {
600 last.translate(res.cursorInfo.offsets.value(cursorPos.offset-1, res.advance));
601 first.translate(res.cursorInfo.offsets.value(cursorPos.offset, QPointF()));
602 } else {
603 first.translate(res.cursorInfo.offsets.value(cursorPos.offset-1, QPointF()));
604 last.translate(res.cursorInfo.offsets.value(cursorPos.offset, res.advance));
605 }
606 QPolygonF poly;
607 poly << first.p1() << first.p2() << last.p2() << last.p1() << first.p1();
608 p.addPolygon(tf.map(poly));
609 }
610
611 return p;
612}
613
614QPainterPath KoSvgTextShape::underlines(int pos, int anchor, KoSvgText::TextDecorations decor, KoSvgText::TextDecorationStyle style, qreal minimum, bool thick)
615{
616 int start = qMin(pos, anchor);
617 int end = qMax(pos, anchor);
618
619 if (start == end || start < 0 || end >= d->cursorPos.size()) {
620 return QPainterPath();
621 }
622 QPainterPathStroker stroker;
623
625 stroker.setCapStyle(Qt::FlatCap);
626 if (style == KoSvgText::Solid) {
627 stroker.setDashPattern(Qt::SolidLine);
628 } else if (style == KoSvgText::Dashed) {
629 stroker.setDashPattern(Qt::DashLine);
630 } else if (style == KoSvgText::Dotted) {
631 stroker.setDashPattern(Qt::DotLine);
632 } else {
633 stroker.setDashPattern(Qt::SolidLine);
634 }
635
636 QPainterPath underPath;
637 QPainterPath overPath;
638 QPainterPath middlePath;
639 qint32 strokeWidth = 0;
640 QPointF inset = mode == KoSvgText::HorizontalTB? QPointF(minimum*0.5, 0): QPointF(0, minimum*0.5);
641 for (int i = start+1; i <= end; i++) {
642 CursorPos pos = d->cursorPos.at(i);
643 CharacterResult res = d->result.at(pos.cluster);
644 strokeWidth += res.metrics.underlineThickness;
645 const QTransform tf = res.finalTransform();
646 QPointF first = res.cursorInfo.caret.p1();
647 QPointF last = first;
648 if (res.cursorInfo.rtl) {
649 last += res.cursorInfo.offsets.value(pos.offset-1, res.advance);
650 first += res.cursorInfo.offsets.value(pos.offset, QPointF());
651 if (i == start+1) {
652 first -= inset;
653 }
654 if (i == end) {
655 last += inset;
656 }
657 } else {
658 first += res.cursorInfo.offsets.value(pos.offset-1, QPointF());
659 last += res.cursorInfo.offsets.value(pos.offset, res.advance);
660 if (i == start+1) {
661 first += inset;
662 }
663 if (i == end) {
664 last -= inset;
665 }
666 }
667
668 if (decor.testFlag(KoSvgText::DecorationUnderline)){
669 underPath.moveTo(tf.map(first));
670 underPath.lineTo(tf.map(last));
671 }
672 QPointF diff = res.cursorInfo.caret.p2() - res.cursorInfo.caret.p1();
673 if (decor.testFlag(KoSvgText::DecorationOverline)){
674 overPath.moveTo(tf.map(first+diff));
675 overPath.lineTo(tf.map(last+diff));
676 }
677 if (decor.testFlag(KoSvgText::DecorationLineThrough)){
678 middlePath.moveTo(tf.map(first+(diff*0.5)));
679 middlePath.lineTo(tf.map(last+(diff*0.5)));
680 }
681 }
682
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;
685
686 stroker.setWidth(thick? width*2: width);
687
688 QPainterPath final;
689 if (decor.testFlag(KoSvgText::DecorationUnderline)){
690 final.addPath(stroker.createStroke(underPath));
691 }
692 if (decor.testFlag(KoSvgText::DecorationOverline)){
693 final.addPath(stroker.createStroke(overPath));
694 }
695 if (decor.testFlag(KoSvgText::DecorationLineThrough)){
696 final.addPath(stroker.createStroke(middlePath));
697 }
698
699 return final;
700}
701
702int KoSvgTextShape::posForPoint(QPointF point, int start, int end, bool *overlaps)
703{
704 int a = 0;
705 int b = d->cursorPos.size();
706 if (start >= 0 && end >= 0) {
707 a = qMax(start, a);
708 b = qMin(end, b);
709 }
710 double closest = std::numeric_limits<double>::max();
711 int candidate = 0;
712 for (int i = a; i < b; i++) {
713 CursorPos pos = d->cursorPos.at(i);
714 CharacterResult res = d->result.at(pos.cluster);
715 QPointF cursorStart = res.finalPosition;
716 cursorStart += res.cursorInfo.offsets.value(pos.offset, res.advance);
717 double distance = kisDistance(cursorStart, point);
718 if (distance < closest) {
719 candidate = i;
720 closest = distance;
721 if (overlaps) {
722 *overlaps = res.finalTransform().map(res.layoutBox()).containsPoint(point, Qt::WindingFill);
723 }
724 }
725 }
726 return candidate;
727}
728
730{
731 bool overlaps = false;
732 int initialPos = posForPoint(point, -1, -1, &overlaps);
733
734 if (overlaps) {
735 return initialPos;
736 }
737
739
740 int candidateLineStart = 0;
741 double closest = std::numeric_limits<double>::max();
742 for (int i = 0; i < d->cursorPos.size(); i++) {
743 CursorPos pos = d->cursorPos.at(i);
744 CharacterResult res = d->result.at(pos.cluster);
745 if (res.anchored_chunk) {
746 QLineF caret = res.cursorInfo.caret;
747 caret.translate(res.finalPosition);
748 QPointF cursorStart = res.finalPosition;
749 cursorStart += res.cursorInfo.offsets.value(pos.offset, res.advance);
750 double distance = kisDistance(cursorStart, point);
751 if (mode == KoSvgText::HorizontalTB) {
752 if (caret.p1().y() > point.y() && caret.p2().y() <= point.y() && closest > distance) {
753 candidateLineStart = i;
754 closest = distance;
755 }
756 } else {
757 if (caret.p2().x() > point.x() && caret.p1().x() <= point.x() && closest > distance) {
758 candidateLineStart = i;
759 closest = distance;
760 }
761 }
762 }
763 }
764
765 if (candidateLineStart > -1) {
766 int end = lineEnd(candidateLineStart);
767 initialPos = posForPoint(point, candidateLineStart, qMin(end + 1, d->cursorPos.size()));
768 }
769
770 return initialPos;
771}
772
773int KoSvgTextShape::posForIndex(int index, bool firstIndex, bool skipSynthetic) const
774{
775 int pos = -1;
776 if (d->cursorPos.isEmpty() || index < 0) {
777 return pos;
778 }
779 for (int i = 0; i< d->cursorPos.size(); i++) {
780 if (skipSynthetic && d->cursorPos.at(i).synthetic) {
781 continue;
782 }
783 if (d->cursorPos.at(i).index <= index) {
784 pos = i;
785 if (d->cursorPos.at(i).index == index && firstIndex) {
786 break;
787 }
788 } else if (d->cursorPos.at(i).index > index) {
789 break;
790 }
791 }
792
793 return pos;
794}
795
797{
798 if (d->cursorPos.isEmpty() || pos < 0) {
799 return -1;
800 }
801
802 return d->cursorPos.at(qMin(d->cursorPos.size()-1, pos)).index;
803}
804
806{
807 return d->initialTextPosition;
808}
809
810bool KoSvgTextShape::insertText(int pos, QString text)
811{
812 bool success = false;
813 int currentIndex = 0;
814
818 int elementIndex = 0;
819 int insertionIndex = 0;
820 if (pos > -1 && !d->cursorPos.isEmpty()) {
821 CursorPos cursorPos = d->cursorPos.at(pos);
822 CharacterResult res = d->result.at(cursorPos.cluster);
823 elementIndex = res.plaintTextIndex;
824 insertionIndex = cursorPos.index;
825 elementIndex = qMin(elementIndex, d->result.size()-1);
826 }
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);
831
832 d->insertTransforms(d->textData, insertionIndex, text.size(), (elementIndex == insertionIndex));
835 success = true;
836 }
837 return success;
838}
839
840bool KoSvgTextShape::removeText(int &index, int &length)
841{
842 bool success = false;
843 if (index < -1) {
844 return success;
845 }
846 int currentLength = length;
847 int endLength = 0;
848 while (currentLength > 0) {
849 int currentIndex = 0;
850
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;
858 endLength += diff;
859
860 if (index >= currentIndex) {
861 index = currentIndex + offset;
862 }
863
864 d->removeTransforms(d->textData, index, endLength);
865
866 success = true;
867 } else {
868 currentLength = -1;
869 }
870 }
871 if (success) {
872 length = endLength;
875 }
876 return success;
877}
878
879KoSvgTextProperties KoSvgTextShape::propertiesForPos(const int pos, bool inherited) const
880{
881 return propertiesForRange(pos, pos, inherited).value(0, KoSvgTextProperties());
882}
883
885 KoSvgTextProperties props = it->properties;
886 for (auto parentIt = KisForestDetail::hierarchyBegin(siblingCurrent(it));
887 parentIt != KisForestDetail::hierarchyEnd(siblingCurrent(it)); parentIt++) {
888 KoSvgTextProperties parentProps = parentIt->properties;
889 parentProps.setAllButNonInheritableProperties(props);
890 props = parentProps;
891 }
892 return props;
893}
894
895QList<KoSvgTextProperties> KoSvgTextShape::propertiesForRange(const int startPos, const int endPos, bool inherited) const
896{
898
900 if (d->isLoading) return props;
901
902 if (((startPos < 0 || startPos >= d->cursorPos.size()) && startPos == endPos) || d->cursorPos.isEmpty()) {
903 props = {KisForestDetail::size(d->textData)? d->textData.childBegin()->properties: KoSvgTextProperties()};
904 return props;
905 }
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()) {
914 if (inherited) {
915 props.append(inheritProperties(it));
916 } else {
917 props.append(it->properties);
918 }
919 } else {
920 currentIndex = 0;
921 it = d->findTextContentElementForIndex(d->textData, currentIndex, sought - 1);
922 if (it != d->textData.depthFirstTailEnd()) {
923 if (inherited) {
924 props.append(inheritProperties(it));
925 } else {
926 props.append(it->properties);
927 }
928 }
929 }
930 } else {
931 while(sought < endIndex) {
932 int currentIndex = 0;
933 auto it = d->findTextContentElementForIndex(d->textData, currentIndex, sought);
934 if (KisForestDetail::siblingCurrent(it) == d->textData.childBegin()) {
935 // If there's a selection and the search algorithm only returns the root, return empty.
936 // The root text properties should be retrieved explicitly (either by using -1 as pos, or by calling textProperties()).
937 props = {KoSvgTextProperties()};
938 return props;
939 } else if (it != d->textData.depthFirstTailEnd()) {
940 if (inherited) {
941 props.append(inheritProperties(it));
942 } else {
943 props.append(it->properties);
944 }
945 }
946 sought = currentIndex + it->numChars(false);
947 }
948 }
949
950 return props;
951}
952
954{
955 if (pos < 0 || d->cursorPos.isEmpty()) {
956 if (KisForestDetail::size(d->textData)) {
957 d->textData.childBegin()->properties = properties;
958 }
961 return;
962 }
963 CursorPos cursorPos = d->cursorPos.at(pos);
964 CharacterResult res = d->result.at(cursorPos.cluster);
965 int currentIndex = 0;
966 auto it = d->findTextContentElementForIndex(d->textData, currentIndex, res.plaintTextIndex);
967 if (it != d->textData.depthFirstTailEnd()) {
968 it->properties = properties;
971 }
972}
973
975 const int endPos,
976 const KoSvgTextProperties properties,
977 const QSet<KoSvgTextProperties::PropertyId> removeProperties)
978{
979 if ((startPos < 0 && startPos == endPos) || d->cursorPos.isEmpty()) {
980 if (KisForestDetail::size(d->textData)) {
981 Q_FOREACH(KoSvgTextProperties::PropertyId p, properties.properties()) {
982 d->textData.childBegin()->properties.setProperty(p, properties.property(p));
983 }
984 Q_FOREACH(KoSvgTextProperties::PropertyId p, removeProperties) {
985 d->textData.childBegin()->properties.removeProperty(p);
986 }
987 }
990 return;
991 }
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);
997 }
998 bool changed = false;
999 int currentIndex = 0;
1000 KoSvgText::AutoValue inlineSize = d->textData.childBegin()->properties.propertyOrDefault(KoSvgTextProperties::InlineSizeId).value<KoSvgText::AutoValue>();
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) {
1004 continue;
1005 }
1006
1007 if (currentIndex >= startIndex && currentIndex < endIndex) {
1008 Q_FOREACH(KoSvgTextProperties::PropertyId p, removeProperties) {
1010 d->textData.childBegin()->properties.removeProperty(p);
1011 } else {
1012 it->properties.removeProperty(p);
1013 }
1014 }
1015 Q_FOREACH(KoSvgTextProperties::PropertyId p, properties.properties()) {
1017 d->textData.childBegin()->properties.setProperty(p, properties.property(p));
1018 } else {
1019 it->properties.setProperty(p, properties.property(p));
1020 }
1021 }
1022
1023 changed = true;
1024 }
1025 currentIndex += it->numChars(false);
1026 }
1027
1028 if (changed){
1029 KoSvgTextShape::Private::cleanUp(d->textData);
1030 notifyChanged();
1032 if (properties.hasProperty(KoSvgTextProperties::FillId)) {
1034 }
1037 }
1038 }
1039}
1040
1041std::unique_ptr<KoSvgTextShape> KoSvgTextShape::copyRange(int index, int length) const
1042{
1043 KoSvgTextShape *clone = new KoSvgTextShape(*this);
1044 int zero = 0;
1045 int endRange = index + length;
1046 int size = KoSvgTextShape::Private::numChars(clone->d->textData.childBegin(), false) - endRange;
1047 clone->removeText(endRange, size);
1048 clone->removeText(zero, index);
1049 KoSvgTextShape::Private::cleanUp(clone->d->textData);
1050 return std::unique_ptr<KoSvgTextShape>(clone);
1051}
1052
1054{
1055 bool success = false;
1056 int currentIndex = 0;
1057 int elementIndex = 0;
1058 int insertionIndex = 0;
1059
1060 if (isEnd(richText->d->textData.childBegin())) {
1061 // rich text is empty.
1062 return success;
1063 }
1064
1065 if (pos > -1 && !d->cursorPos.isEmpty()) {
1066 CursorPos cursorPos = d->cursorPos.at(qMin(d->cursorPos.size()-1, pos));
1067 CharacterResult res = d->result.at(cursorPos.cluster);
1068 elementIndex = res.plaintTextIndex;
1069 insertionIndex = cursorPos.index;
1070 elementIndex = qMin(elementIndex, d->result.size()-1);
1071 }
1072
1073 KoSvgTextShape::Private::splitContentElement(this->d->textData, insertionIndex);
1074
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));
1078 success = true;
1079 } else {
1080 currentIndex = 0;
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)));
1084 success = true;
1085 }
1086 }
1087
1088 if (success) {
1089 notifyChanged();
1091 }
1092 return success;
1093}
1094
1096{
1097 KoSvgTextShape::Private::cleanUp(d->textData);
1098 notifyChanged();
1100}
1101
1103 for (auto child = KisForestDetail::childBegin(parent); child != KisForestDetail::childEnd(parent); child++) {
1104 if (child->properties.hasProperty(propertyId)) {
1105 return child;
1106 } else if (KisForestDetail::childBegin(child) != KisForestDetail::childEnd(child)) {
1107 auto found = findNodeIndexForPropertyIdImpl(child, propertyId);
1108 if (found != child) {
1109 return found;
1110 }
1111 }
1112 }
1113 return parent;
1114}
1115
1117{
1118 for (auto it = d->textData.childBegin(); it != d->textData.childEnd(); it++) {
1119 if (it->properties.hasProperty(propertyId)) {
1120 return Private::createTextNodeIndex(it);
1122 auto found = findNodeIndexForPropertyIdImpl(it, propertyId);
1123 if (found != it) {
1124 return Private::createTextNodeIndex(found);
1125 }
1126 }
1127 }
1128 return Private::createTextNodeIndex(d->textData.childBegin());
1129}
1130
1132{
1133 int startIndex = 0;
1134 int endIndex = 0;
1135 for (auto child = d->textData.childBegin(); child != d->textData.childEnd(); child++) {
1136 // count children
1137 d->startIndexOfIterator(child, node.d->textElement, startIndex);
1138 endIndex = d->numChars(node.d->textElement) + startIndex;
1139 }
1140 return qMakePair(posForIndex(startIndex), posForIndex(endIndex));
1141}
1142
1144{
1145 return KisForestDetail::size(d->textData)? d->textData.childBegin()->properties: KoSvgTextProperties();
1146}
1147
1156
1158{
1159 if (KisForestDetail::size(d->textData) == 0) {
1160 d->textData.insert(d->textData.childBegin(), KoSvgTextContentElement());
1161 }
1162 d->textData.childBegin()->properties.setProperty(KoSvgTextProperties::FillId,
1163 QVariant::fromValue(KoSvgText::BackgroundProperty(background)));
1164
1166 notifyChanged();
1167}
1168
1170{
1171 KoSvgTextProperties props = KisForestDetail::size(d->textData)? d->textData.childBegin()->properties: KoSvgTextProperties();
1173}
1174
1176{
1177 if (KisForestDetail::size(d->textData) == 0) {
1178 d->textData.insert(d->textData.childBegin(), KoSvgTextContentElement());
1179 }
1180 d->textData.childBegin()->properties.setProperty(KoSvgTextProperties::StrokeId,
1181 QVariant::fromValue(KoSvgText::StrokeProperty(stroke)));
1183 notifyChanged();
1184}
1185
1194
1196{
1197 if (KisForestDetail::size(d->textData) == 0) {
1198 d->textData.insert(d->textData.childBegin(), KoSvgTextContentElement());
1199 }
1200 KIS_SAFE_ASSERT_RECOVER_RETURN(first != second);
1202
1203 if (first != Fill) {
1204 if (order.at(1) == first) {
1205 order[1] = order[0];
1206 order[0] = first;
1207 } else if (order.at(2) == first) {
1208 order[2] = order[0];
1209 order[0] = first;
1210 }
1211 }
1212 if (second != first && second != Stroke) {
1213 if (order.at(2) == second) {
1214 order[2] = order[1];
1215 order[1] = second;
1216 }
1217 }
1218 d->textData.childBegin()->properties.setProperty(KoSvgTextProperties::PaintOrder,
1219 QVariant::fromValue(order));
1220 setInheritPaintOrder(false);
1221}
1222
1224{
1225 return d->plainText;
1226}
1227
1232
1234{
1235 if (d->isLoading) {
1236 return;
1237 }
1238 Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners()) {
1239 TextCursorChangeListener *cursorListener = dynamic_cast<TextCursorChangeListener*>(listener);
1240 if (cursorListener) {
1241 cursorListener->notifyCursorPosChanged(pos, anchor);
1242 }
1243 }
1244}
1245
1247{
1248 if (d->isLoading) {
1249 return;
1250 }
1251 Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners()) {
1252 TextCursorChangeListener *cursorListener = dynamic_cast<TextCursorChangeListener*>(listener);
1253 if (cursorListener) {
1254 cursorListener->notifyMarkupChanged();
1255 }
1256 }
1257}
1258
1260{
1261 const int inlineSize = writingMode() == KoSvgText::HorizontalTB? outlineRect().width(): outlineRect().height();
1262 d->applyWhiteSpace(d->textData, true);
1263 d->insertNewLinesAtAnchors(d->textData, !d->shapesInside.isEmpty());
1264 d->cleanUp(d->textData);
1265
1266 KoSvgTextProperties props = this->propertiesForPos(-1);
1267 if (makeInlineSize) {
1269 // Using QCeil here because otherwise the text will layout too tight.
1270 val.customValue = qCeil(inlineSize);
1271 val.isAuto = false;
1273 props.setProperty(KoSvgTextProperties::InlineSizeId, QVariant::fromValue(val));
1274 }
1275 } else {
1277 }
1278 // NOTE: applyWhiteSpace and insertNewLines don't notify changes,
1279 // so setProperties is the only thing triggering relayout();
1280 setPropertiesAtPos(-1, props);
1281}
1282
1284{
1285 if (d->result.isEmpty()) return;
1286 d->setTransformsFromLayout(d->textData, d->result);
1287 d->cleanUp(d->textData);
1288 //d->applyWhiteSpace(d->textData, true);
1289 KoSvgTextProperties props = this->propertiesForPos(-1);
1293
1294 setPropertiesAtPos(-1, props);
1295}
1296
1297#include "KoXmlWriter.h"
1299{
1300 bool success = false;
1301 for (auto it = d->textData.compositionBegin(); it != d->textData.compositionEnd(); it++) {
1302 if (it.state() == KisForestDetail::Enter) {
1303 bool isTextPath = false;
1304 QMap<QString, QString> shapeSpecificStyles;
1305 if (it->textPath) {
1306 isTextPath = true;
1307 }
1308 if (it == d->textData.compositionBegin()) {
1309 context.shapeWriter().startElement("text", false);
1310
1311 if (!context.strippedTextMode()) {
1312 context.shapeWriter().addAttribute("id", context.getID(this));
1313
1314 // save the version to distinguish from the buggy Krita version
1315 // 2: Wrong font-size.
1316 // 3: Wrong font-size-adjust.
1317 context.shapeWriter().addAttribute("krita:textVersion", 3);
1318
1320 SvgStyleWriter::saveSvgStyle(this, context);
1321 } else {
1322 SvgStyleWriter::saveSvgFill(this->background(), false, this->outlineRect(), this->size(), this->absoluteTransformation(), context);
1323 SvgStyleWriter::saveSvgStroke(this->stroke(), context);
1325 inheritPaintOrder(), context, true);
1326 }
1327 shapeSpecificStyles = this->shapeTypeSpecificStyles(context);
1328 } else {
1329 if (isTextPath) {
1330 context.shapeWriter().startElement("textPath", false);
1331 } else {
1332 context.shapeWriter().startElement("tspan", false);
1333 }
1334 SvgStyleWriter::saveSvgBasicStyle(it->properties.property(KoSvgTextProperties::Visiblity, true).toBool(),
1335 it->properties.property(KoSvgTextProperties::Opacity, 0).toReal(),
1336 it->properties.property(KoSvgTextProperties::PaintOrder,
1337 QVariant::fromValue(paintOrder())
1339 !it->properties.hasProperty(KoSvgTextProperties::PaintOrder), context, true);
1340
1341 }
1342
1343 success = it->saveSvg(context,
1344 it == d->textData.compositionBegin(),
1345 d->childCount(siblingCurrent(it)) == 0,
1346 shapeSpecificStyles);
1347 } else {
1348 if (it == d->textData.compositionBegin()) {
1349 SvgStyleWriter::saveMetadata(this, context);
1350 }
1351 context.shapeWriter().endElement();
1352 }
1353 }
1354 return success;
1355}
1356
1358{
1359 bool success = true;
1361 for (auto it = d->textData.compositionBegin(); it != d->textData.compositionEnd(); it++) {
1362 if (it.state() == KisForestDetail::Enter) {
1363 QMap<QString, QString> shapeSpecificStyles;
1364
1365 if (it == d->textData.compositionBegin()) {
1366 context.shapeWriter().startElement("p", false);
1367 } else {
1368 context.shapeWriter().startElement("span", false);
1369 }
1370 KoSvgTextProperties ownProperties = it->properties.ownProperties(parentProps.last(),
1371 it == d->textData.compositionBegin());
1372 parentProps.append(ownProperties);
1373 QMap<QString, QString> attributes = ownProperties.convertToSvgTextAttributes();
1374 if (it == d->textData.compositionBegin())
1375 attributes.insert(ownProperties.convertParagraphProperties());
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") {
1383 val = "center";
1384 } else if (it.value()=="end") {
1385 val = "right";
1386 } else {
1387 val = "left";
1388 }
1389 styleString.append("text-align")
1390 .append(": ")
1391 .append(val)
1392 .append(";" );
1393 } else if (QString(it.key().toLatin1().data()).contains("fill")) {
1394 styleString.append("color")
1395 .append(": ")
1396 .append(it.value())
1397 .append(";" );
1398 addedFill = true;
1399 } else if (QString(it.key().toLatin1().data()).contains("font-size")) {
1400 QString val = it.value();
1401 styleString.append(it.key().toLatin1().data())
1402 .append(": ")
1403 .append(val)
1404 .append(";" );
1405 } else {
1406 styleString.append(it.key().toLatin1().data())
1407 .append(": ")
1408 .append(it.value())
1409 .append(";" );
1410 }
1411 }
1412 if (ownProperties.hasProperty(KoSvgTextProperties::FillId) && !addedFill) {
1413 KoColorBackground *b = dynamic_cast<KoColorBackground *>(it->properties.background().data());
1414 if (b) {
1415 styleString.append("color")
1416 .append(": ")
1417 .append(b->color().name())
1418 .append(";" );
1419 }
1420 }
1421 context.shapeWriter().addAttribute("style", styleString);
1422
1423 if (d->childCount(siblingCurrent(it)) == 0) {
1424 debugFlake << "saveHTML" << this << it->text;
1425 // After adding all the styling to the <p> element, add the text
1426 context.shapeWriter().addTextNode(it->text);
1427 }
1428 }
1429 } else {
1430 parentProps.pop_back();
1431 context.shapeWriter().endElement();
1432 }
1433 }
1434 return success;
1435}
1436
1438{
1439
1440}
1441
1443{
1444
1445}
1446
1448{
1450 d->result,
1451 d->lineBoxes,
1452 d->cursorPos,
1453 d->logicalToVisualCursorPos,
1454 d->plainText,
1455 d->isBidi,
1456 d->initialTextPosition));
1457}
1458
1460{
1461 KoSvgTextShapeMementoImpl *impl = dynamic_cast<KoSvgTextShapeMementoImpl*>(memento.data());
1462 if (impl) {
1463 d->textData = impl->textData;
1464 d->result = impl->result;
1465 d->lineBoxes = impl->lineBoxes;
1466 d->cursorPos = impl->cursorPos;
1467 d->logicalToVisualCursorPos = impl->logicalToVisualCursorPos;
1468 d->plainText = impl->plainText;
1469 d->isBidi = impl->isBidi;
1470 d->initialTextPosition = impl->initialTextPosition;
1471 }
1472}
1473
1480
1481void KoSvgTextShape::setMemento(const KoSvgTextShapeMementoSP memento, int pos, int anchor)
1482{
1483 setMementoImpl(memento);
1486}
1487
1489{
1490 qDebug() << "Tree size:" << KisForestDetail::size(d->textData);
1491 QString spaces;
1492 for (auto it = compositionBegin(d->textData); it != compositionEnd(d->textData); it++) {
1493 if (it.state() == KisForestDetail::Enter) {
1494
1495 qDebug() << QString(spaces + "+") << it->text;
1496 qDebug() << QString(spaces + "|") << it->properties.convertToSvgTextAttributes();
1497 qDebug() << QString(spaces + "| PropertyType:") << it->properties.property(KoSvgTextProperties::KraTextStyleType).toString();
1498 qDebug() << QString(spaces + "| Fill set: ") << it->properties.hasProperty(KoSvgTextProperties::FillId);
1499 qDebug() << QString(spaces + "| Stroke set: ") << it->properties.hasProperty(KoSvgTextProperties::StrokeId);
1500 qDebug() << QString(spaces + "| Opacity: ") << it->properties.property(KoSvgTextProperties::Opacity);
1501 qDebug() << QString(spaces + "| PaintOrder: ") << it->properties.hasProperty(KoSvgTextProperties::PaintOrder);
1502 qDebug() << QString(spaces + "| Visibility set: ") << it->properties.hasProperty(KoSvgTextProperties::Visiblity);
1503 qDebug() << QString(spaces + "| TextPath set: ") << (!it->textPath.isNull());
1504 qDebug() << QString(spaces + "| Transforms set: ") << it->localTransformations;
1505 spaces.append(" ");
1506 }
1507
1508 if (it.state() == KisForestDetail::Leave) {
1509 spaces.chop(1);
1510 }
1511 }
1512}
1513
1515{
1516 d->isLoading = disable;
1517}
1518
1520{
1521 return d->isLoading;
1522}
1523
1525{
1526 d->disableFontMatching = disable;
1527}
1528
1530{
1531 return d->disableFontMatching;
1532}
1533
1534void KoSvgTextShape::paint(QPainter &painter) const
1535{
1536 painter.save();
1538 if (textRendering == KoSvgText::RenderingOptimizeSpeed || !painter.testRenderHint(QPainter::Antialiasing)) {
1539 // also apply antialiasing only if antialiasing is active on provided target QPainter
1540 painter.setRenderHint(QPainter::Antialiasing, false);
1541 painter.setRenderHint(QPainter::SmoothPixmapTransform, false);
1542 } else {
1543 painter.setRenderHint(QPainter::Antialiasing, true);
1544 painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
1545 }
1546
1547 QPainterPath chunk;
1548 int currentIndex = 0;
1549 if (!d->result.isEmpty()) {
1550 QPainterPath rootBounds;
1551 rootBounds.addRect(this->outline().boundingRect());
1552 d->paintTextDecoration(painter, rootBounds, this, KoSvgText::DecorationUnderline, textRendering);
1553 d->paintTextDecoration(painter, rootBounds, this, KoSvgText::DecorationOverline, textRendering);
1554 d->paintPaths(painter, rootBounds, this, d->result, textRendering, chunk, currentIndex);
1555 d->paintTextDecoration(painter, rootBounds, this, KoSvgText::DecorationLineThrough, textRendering);
1556 }
1557#if 0 // Debug
1558 Q_FOREACH (KoShape *child, this->shapes()) {
1559 const KoSvgTextChunkShape *textPathChunk = dynamic_cast<const KoSvgTextChunkShape *>(child);
1560 if (textPathChunk) {
1561 painter.save();
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));
1571 }
1572 painter.restore();
1573 }
1574 }
1575#endif
1576#if 0 // Debug
1577 Q_FOREACH (KoShape *shapeInside, d->shapesInside) {
1578 QPainterPath p = shapeInside->outline();
1579 p = shapeInside->transformation().map(p);
1580 painter.strokePath(p, QPen(Qt::green));
1581 }
1582 Q_FOREACH (KoShape *shapeInside, d->shapesSubtract) {
1583 QPainterPath p = shapeInside->outline();
1584 p = shapeInside->transformation().map(p);
1585 painter.strokePath(p, QPen(Qt::red));
1586 }
1587#endif
1588
1589 painter.restore();
1590}
1591
1592void KoSvgTextShape::paintStroke(QPainter &painter) const
1593{
1594 Q_UNUSED(painter);
1595 // do nothing! everything is painted in paint()
1596}
1597
1598QPainterPath KoSvgTextShape::outline() const {
1599 QPainterPath result;
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));
1604 }
1605
1606 }
1607 return result;
1608}
1610{
1611 return outline().boundingRect();
1612}
1613
1615{
1616 QRectF result;
1617 QList<KoShapeStrokeModelSP> parentStrokes;
1618 for (auto it = d->textData.compositionBegin(); it != d->textData.compositionEnd(); it++) {
1619 if (it.state() == KisForestDetail::Enter) {
1620 if (it->properties.hasProperty(KoSvgTextProperties::StrokeId)) {
1621 parentStrokes.append(it->properties.property(KoSvgTextProperties::StrokeId).value<KoSvgText::StrokeProperty>().property);
1622 }
1623 } else {
1624 KoShapeStrokeModelSP stroke = parentStrokes.size() > 0? parentStrokes.last(): nullptr;
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();
1629 }
1630 if (!bb.isEmpty()) {
1631 if (stroke) {
1632 KoInsets insets;
1633 stroke->strokeInsets(this, insets);
1634 result |= bb.adjusted(-insets.left, -insets.top, insets.right, insets.bottom);
1635 } else {
1636 result |= bb;
1637 }
1638 }
1639 if (it->properties.hasProperty(KoSvgTextProperties::StrokeId)) {
1640 // reset stroke to use parent stroke.
1641 parentStrokes.pop_back();
1642 }
1643 }
1644 }
1645 return this->absoluteTransformation().mapRect(result);
1646}
1647
1648void KoSvgTextShape::paintDebug(QPainter &painter, const DebugElements elements) const
1649{
1650 if (elements & DebugElement::CharBbox) {
1651 int currentIndex = 0;
1652 if (!d->result.isEmpty()) {
1653 QPainterPath rootBounds;
1654 rootBounds.addRect(this->outline().boundingRect());
1655 d->paintDebug(painter, d->result, currentIndex);
1656 }
1657 }
1658
1659 if (elements & DebugElement::LineBox) {
1660 Q_FOREACH (LineBox lineBox, d->lineBoxes) {
1661 Q_FOREACH (const LineChunk &chunk, lineBox.chunks) {
1662 QPen pen;
1663 pen.setCosmetic(true);
1664 pen.setWidth(2);
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);
1671 painter.drawRect(chunk.boundingBox);
1672
1673 pen.setColor(QColor(255, 0, 0, 128));
1674 pen.setStyle(Qt::DashDotDotLine);
1675 painter.setPen(pen);
1676 painter.drawLine(chunk.length.translated(lineBox.baselineTop));
1677 pen.setColor(QColor(0, 128, 0, 128));
1678 pen.setStyle(Qt::DashDotLine);
1679 painter.setPen(pen);
1680 painter.drawLine(chunk.length.translated(lineBox.baselineBottom));
1681 }
1682 }
1683 }
1684}
1685
1687{
1688 KoShape *shape;
1689 int currentIndex = 0;
1690 if (!d->result.empty()) {
1691 shape = d->collectPaths(this, d->result, currentIndex);
1692 }
1693
1694 return shape;
1695}
1696
1698{
1699 KoSvgText::AutoValue inlineSize = d->textData.childBegin()->properties.propertyOrDefault(KoSvgTextProperties::InlineSizeId).value<KoSvgText::AutoValue>();
1700 if (!d->shapesInside.isEmpty()) {
1701 return TextType::TextInShape;
1702 } else if (!inlineSize.isAuto) {
1703 return TextType::InlineWrap;
1704 } else {
1705 bool textSpaceCollapse = false;
1706 for (auto it = d->textData.depthFirstTailBegin(); it != d->textData.depthFirstTailEnd(); it++) {
1707 KoSvgText::TextSpaceCollapse collapse = KoSvgText::TextSpaceCollapse(it->properties.propertyOrDefault(KoSvgTextProperties::TextCollapseId).toInt());
1708 if (collapse == KoSvgText::Collapse || collapse == KoSvgText::PreserveSpaces) {
1709 textSpaceCollapse = true;
1710 break;
1711 }
1712 }
1714 }
1715}
1716
1718{
1719 d->shapesInside = shapesInside;
1720}
1721
1723{
1724 return d->shapesInside;
1725}
1726
1728{
1729 d->shapesSubtract = shapesSubtract;
1730}
1731
1733{
1734 return d->shapesSubtract;
1735}
1736
1737QMap<QString, QString> KoSvgTextShape::shapeTypeSpecificStyles(SvgSavingContext &context) const
1738{
1739 QMap<QString, QString> map = this->textProperties().convertParagraphProperties();
1740 if (!d->shapesInside.isEmpty()) {
1741 QStringList shapesInsideList;
1742 Q_FOREACH(KoShape* shape, d->shapesInside) {
1743 QString id = SvgStyleWriter::embedShape(shape, context);
1744 shapesInsideList.append(QString("url(#%1)").arg(id));
1745 }
1746 map.insert("shape-inside", shapesInsideList.join(" "));
1747 }
1748 if (!d->shapesSubtract.isEmpty()) {
1749 QStringList shapesInsideList;
1750 Q_FOREACH(KoShape* shape, d->shapesSubtract) {
1751 QString id = SvgStyleWriter::embedShape(shape, context);
1752 shapesInsideList.append(QString("url(#%1)").arg(id));
1753 }
1754 map.insert("shape-subtract", shapesInsideList.join(" "));
1755 }
1756
1757 return map;
1758}
1759
1761{
1762 d->relayout();
1763}
1764
1766 : KoShapeFactoryBase(KoSvgTextShape_SHAPEID, i18nc("Text label in SVG Text Tool", "Text"))
1767{
1768 setToolTip(i18n("SVG Text Shape"));
1769 setIconName(koIconNameCStr("x-shape-text"));
1772
1774 t.name = i18n("SVG Text");
1775 t.iconName = koIconName("x-shape-text");
1776 t.toolTip = i18n("SVG Text Shape");
1777 addTemplate(t);
1778}
1779
1781{
1782 Q_UNUSED(documentResources);
1783 debugFlake << "Create default svg text shape";
1784
1785 KoSvgTextShape *shape = new KoSvgTextShape();
1787 shape->insertText(0, i18nc("Default text for the text shape", "Placeholder Text"));
1788
1789 return shape;
1790}
1791
1793{
1794 KoSvgTextShape *shape = new KoSvgTextShape();
1796
1797 QString svgText = params->stringProperty("svgText", i18nc("Default text for the text shape", "<text>Placeholder Text</text>"));
1798 QString defs = params->stringProperty("defs" , "<defs/>");
1799 QRectF shapeRect = QRectF(0, 0, 200, 60);
1800 QVariant rect = params->property("shapeRect");
1801 QVariant origin = params->property("origin");
1802
1803 if (rect.type()==QVariant::RectF) {
1804 shapeRect = rect.toRectF();
1805 }
1806
1807 KoSvgTextShapeMarkupConverter converter(shape);
1808 converter.convertFromSvg(svgText,
1809 defs,
1810 shapeRect,
1811 documentResources->documentResolution());
1812 if (origin.type() == QVariant::PointF) {
1813 shape->setPosition(origin.toPointF());
1814 } else {
1815 shape->setPosition(shapeRect.topLeft());
1816 }
1818
1819 return shape;
1820}
1821
1822bool KoSvgTextShapeFactory::supports(const QDomElement &/*e*/, KoShapeLoadingContext &/*context*/) const
1823{
1824 return false;
1825}
1826
1828{
1829 Q_UNUSED(type);
1830 Q_UNUSED(shape);
1831}
1832
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
#define debugFlake
Definition FlakeDebug.h:15
float value(const T *src, size_t ch)
const Params2D p
QPointF p2
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.
QString stringProperty(const QString &name, const QString &defaultValue=QString()) const
bool property(const QString &name, QVariant &value) const
void addTemplate(const KoShapeTemplate &params)
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
Definition KoShape_p.h:84
QScopedPointer< Private > d
Definition KoShape.h:1135
virtual QSizeF size() const
Get the size of the shape in pt.
Definition KoShape.cpp:820
virtual QPainterPath outline() const
Definition KoShape.cpp:630
KoShapeAnchor * anchor() const
virtual QVector< PaintOrder > paintOrder() const
paintOrder
Definition KoShape.cpp:773
static QVector< PaintOrder > defaultPaintOrder()
default paint order as per SVG specification
Definition KoShape.cpp:784
virtual void shapeChanged(ChangeType type, KoShape *shape=0)
Definition KoShape.cpp:1253
void shapeChangedPriv(KoShape::ChangeType type)
Definition KoShape.cpp:132
QTransform absoluteTransformation() const
Definition KoShape.cpp:382
ChangeType
Used by shapeChanged() to select which change was made.
Definition KoShape.h:95
@ StrokeChanged
the shapes stroke has changed
Definition KoShape.h:105
@ ContentChanged
the content of the shape changed e.g. a new image inside a pixmap/text change inside a textshape
Definition KoShape.h:110
@ BackgroundChanged
the shapes background has changed
Definition KoShape.h:106
QTransform transformation() const
Returns the shapes local transformation matrix.
Definition KoShape.cpp:424
bool inheritPaintOrder() const
inheritPaintOrder
Definition KoShape.cpp:795
virtual void setPosition(const QPointF &position)
Set the position of the shape in pt.
Definition KoShape.cpp:295
@ Stroke
Definition KoShape.h:147
void notifyChanged()
Definition KoShape.cpp:698
void setShapeId(const QString &id)
Definition KoShape.cpp:1062
void setInheritPaintOrder(bool value)
setInheritPaintOrder set inherit paint order.
Definition KoShape.cpp:790
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
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
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
void relayout() const
QRectF outlineRect() const override
int nextIndex(int pos)
nextIndex Return the first cursor position with a higher string index.
KoSvgTextProperties textProperties() const
static const QString svg
Definition KoXmlNS.h:39
void startElement(const char *tagName, bool indentInside=true)
void addTextNode(const QString &str)
void endElement()
void addAttribute(const char *attrName, const QString &value)
Definition KoXmlWriter.h:61
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.
Definition SvgUtil.cpp:124
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
qreal kisDistance(const QPointF &pt1, const QPointF &pt2)
Definition kis_global.h:190
#define koIconNameCStr(name)
Definition kis_icon.h:28
#define koIconName(name)
Definition kis_icon.h:27
ResultIterator hierarchyBegin(Iterator it)
Definition KisForest.h:419
ChildIterator< value_type, is_const > childBegin(const ChildIterator< value_type, is_const > &it)
Definition KisForest.h:290
ResultIterator hierarchyEnd(Iterator it)
Definition KisForest.h:427
ChildIterator< value_type, is_const > siblingCurrent(ChildIterator< value_type, is_const > it)
Definition KisForest.h:240
int size(const Forest< T > &forest)
Definition KisForest.h:1232
ChildIterator< value_type, is_const > childEnd(const ChildIterator< value_type, is_const > &it)
Definition KisForest.h:300
TextDecorationStyle
Style of the text-decoration.
Definition KoSvgText.h:265
@ Solid
Draw a solid line.Ex: --—.
Definition KoSvgText.h:266
@ Dashed
Draw a dashed line. Ex: - - - - -.
Definition KoSvgText.h:269
@ Dotted
Draw a dotted line. Ex: .....
Definition KoSvgText.h:268
@ DecorationOverline
Definition KoSvgText.h:260
@ DecorationLineThrough
Definition KoSvgText.h:261
@ DecorationUnderline
Definition KoSvgText.h:259
Direction
Base direction used by Bidi algorithm.
Definition KoSvgText.h:48
@ DirectionRightToLeft
Definition KoSvgText.h:50
@ HorizontalTB
Definition KoSvgText.h:38
@ RenderingOptimizeSpeed
Definition KoSvgText.h:316
TextSpaceCollapse
Definition KoSvgText.h:96
@ Collapse
Collapse white space sequences into a single character.
Definition KoSvgText.h:97
@ PreserveSpaces
required for 'xml:space="preserve"' emulation.
Definition KoSvgText.h:102
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.
Definition KoInsets.h:50
qreal right
Right inset.
Definition KoInsets.h:52
qreal top
Top inset.
Definition KoInsets.h:49
qreal left
Left inset.
Definition KoInsets.h:51
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 notifyCursorPosChanged(int pos, int anchor)=0
BackgroundProperty is a special wrapper around KoShapeBackground for managing it in KoSvgTextProperti...
Definition KoSvgText.h:714
qint32 underlineThickness
underline thickness from font.
Definition KoSvgText.h:357
StrokeProperty is a special wrapper around KoShapeStrokeModel for managing it in KoSvgTextProperties.
Definition KoSvgText.h:733
The LineBox struct.
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.