1 /*
2  *  Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "KoSvgTextShapeMarkupConverter.h"
20 
21 #include "klocalizedstring.h"
22 #include "kis_assert.h"
23 #include "kis_debug.h"
24 
25 #include <QBuffer>
26 #include <QStringList>
27 #include <QXmlStreamReader>
28 #include <QXmlStreamWriter>
29 
30 #include <QTextBlock>
31 #include <QTextLayout>
32 #include <QTextLine>
33 #include <QFont>
34 
35 #include <QStack>
36 
37 #include <KoSvgTextShape.h>
38 #include <KoXmlWriter.h>
39 #include <KoXmlReader.h>
40 #include <KoDocumentResourceManager.h>
41 
42 #include <SvgParser.h>
43 #include <SvgWriter.h>
44 #include <SvgUtil.h>
45 #include <SvgSavingContext.h>
46 #include <SvgGraphicContext.h>
47 
48 #include <html/HtmlSavingContext.h>
49 #include <html/HtmlWriter.h>
50 
51 #include "kis_dom_utils.h"
52 #include <boost/optional.hpp>
53 
54 #include <FlakeDebug.h>
55 
56 struct KoSvgTextShapeMarkupConverter::Private {
PrivateKoSvgTextShapeMarkupConverter::Private57     Private(KoSvgTextShape *_shape) : shape(_shape) {}
58 
59     KoSvgTextShape *shape;
60 
61     QStringList errors;
62     QStringList warnings;
63 
clearErrorsKoSvgTextShapeMarkupConverter::Private64     void clearErrors() {
65         errors.clear();
66         warnings.clear();
67     }
68 };
69 
KoSvgTextShapeMarkupConverter(KoSvgTextShape * shape)70 KoSvgTextShapeMarkupConverter::KoSvgTextShapeMarkupConverter(KoSvgTextShape *shape)
71     : d(new Private(shape))
72 {
73 }
74 
~KoSvgTextShapeMarkupConverter()75 KoSvgTextShapeMarkupConverter::~KoSvgTextShapeMarkupConverter()
76 {
77 }
78 
convertToSvg(QString * svgText,QString * stylesText)79 bool KoSvgTextShapeMarkupConverter::convertToSvg(QString *svgText, QString *stylesText)
80 {
81     d->clearErrors();
82 
83     QBuffer shapesBuffer;
84     QBuffer stylesBuffer;
85 
86     shapesBuffer.open(QIODevice::WriteOnly);
87     stylesBuffer.open(QIODevice::WriteOnly);
88 
89     {
90         SvgSavingContext savingContext(shapesBuffer, stylesBuffer);
91         savingContext.setStrippedTextMode(true);
92         SvgWriter writer({d->shape});
93         writer.saveDetached(savingContext);
94     }
95 
96     shapesBuffer.close();
97     stylesBuffer.close();
98 
99     *svgText = QString::fromUtf8(shapesBuffer.data());
100     *stylesText = QString::fromUtf8(stylesBuffer.data());
101 
102     return true;
103 }
104 
convertFromSvg(const QString & svgText,const QString & stylesText,const QRectF & boundsInPixels,qreal pixelsPerInch)105 bool KoSvgTextShapeMarkupConverter::convertFromSvg(const QString &svgText, const QString &stylesText,
106                                                    const QRectF &boundsInPixels, qreal pixelsPerInch)
107 {
108 
109     debugFlake << "convertFromSvg. text:" << svgText << "styles:" << stylesText << "bounds:" << boundsInPixels << "ppi:" << pixelsPerInch;
110 
111     d->clearErrors();
112 
113     QString errorMessage;
114     int errorLine = 0;
115     int errorColumn = 0;
116 
117     const QString fullText = QString("<svg>\n%1\n%2\n</svg>\n").arg(stylesText).arg(svgText);
118 
119     KoXmlDocument doc = SvgParser::createDocumentFromSvg(fullText, &errorMessage, &errorLine, &errorColumn);
120     if (doc.isNull()) {
121         d->errors << QString("line %1, col %2: %3").arg(errorLine).arg(errorColumn).arg(errorMessage);
122         return false;
123     }
124 
125     d->shape->resetTextShape();
126 
127     KoDocumentResourceManager resourceManager;
128     SvgParser parser(&resourceManager);
129     parser.setResolution(boundsInPixels, pixelsPerInch);
130 
131     KoXmlElement root = doc.documentElement();
132     KoXmlNode node = root.firstChild();
133 
134     bool textNodeFound = false;
135 
136     for (; !node.isNull(); node = node.nextSibling()) {
137         KoXmlElement el = node.toElement();
138         if (el.isNull()) continue;
139 
140         if (el.tagName() == "defs") {
141             parser.parseDefsElement(el);
142         }
143         else if (el.tagName() == "text") {
144             if (textNodeFound) {
145                 d->errors << i18n("More than one 'text' node found!");
146                 return false;
147             }
148 
149             KoShape *shape = parser.parseTextElement(el, d->shape);
150             KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape == d->shape, false);
151             textNodeFound = true;
152             break;
153         } else {
154             d->errors << i18n("Unknown node of type \'%1\' found!", el.tagName());
155             return false;
156         }
157     }
158 
159     if (!textNodeFound) {
160         d->errors << i18n("No \'text\' node found!");
161         return false;
162     }
163 
164     return true;
165 
166 }
167 
convertToHtml(QString * htmlText)168 bool KoSvgTextShapeMarkupConverter::convertToHtml(QString *htmlText)
169 {
170     d->clearErrors();
171 
172     QBuffer shapesBuffer;
173     shapesBuffer.open(QIODevice::WriteOnly);
174     {
175         HtmlWriter writer({d->shape});
176         if (!writer.save(shapesBuffer)) {
177             d->errors = writer.errors();
178             d->warnings = writer.warnings();
179             return false;
180         }
181     }
182 
183     shapesBuffer.close();
184 
185     *htmlText = QString(shapesBuffer.data());
186 
187     debugFlake << "\t\t" << *htmlText;
188 
189     return true;
190 }
191 
convertFromHtml(const QString & htmlText,QString * svgText,QString * styles)192 bool KoSvgTextShapeMarkupConverter::convertFromHtml(const QString &htmlText, QString *svgText, QString *styles)
193 {
194 
195     debugFlake << ">>>>>>>>>>>" << htmlText;
196 
197     QBuffer svgBuffer;
198     svgBuffer.open(QIODevice::WriteOnly);
199 
200     QXmlStreamReader htmlReader(htmlText);
201     QXmlStreamWriter svgWriter(&svgBuffer);
202 
203     svgWriter.setAutoFormatting(true);
204 
205     QStringRef elementName;
206 
207     bool newLine = false;
208     int lineCount = 0;
209     QString bodyEm = "1em";
210     QString em;
211     QString p("p");
212     //previous style string is for keeping formatting proper on linebreaks and appendstyle is for specific tags
213     QString previousStyleString;
214     QString appendStyle;
215 
216     while (!htmlReader.atEnd()) {
217         QXmlStreamReader::TokenType token = htmlReader.readNext();
218         switch (token) {
219         case QXmlStreamReader::StartElement:
220         {
221             newLine = false;
222             if (htmlReader.name() == "br") {
223                 debugFlake << "\tdoing br";
224                 svgWriter.writeEndElement();
225                 elementName = QStringRef(&p);
226                 em = bodyEm;
227                 appendStyle = previousStyleString;
228             }
229             else {
230                 elementName = htmlReader.name();
231                 em = "";
232             }
233 
234             if (elementName == "body") {
235                 debugFlake << "\tstart Element" << elementName;
236                 svgWriter.writeStartElement("text");
237                 appendStyle = QString();
238             }
239             else if (elementName == "p") {
240                 // new line
241                 debugFlake << "\t\tstart Element" << elementName;
242                 svgWriter.writeStartElement("tspan");
243                 newLine = true;
244                 if (em.isEmpty()) {
245                     em = bodyEm;
246                     appendStyle = QString();
247                 }
248                 lineCount++;
249             }
250             else if (elementName == "span") {
251                 debugFlake << "\tstart Element" << elementName;
252                 svgWriter.writeStartElement("tspan");
253                 appendStyle = QString();
254             }
255             else if (elementName == "b" || elementName == "strong") {
256                 debugFlake << "\tstart Element" << elementName;
257                 svgWriter.writeStartElement("tspan");
258                 appendStyle = "font-weight:700;";
259             }
260             else if (elementName == "i" || elementName == "em") {
261                 debugFlake << "\tstart Element" << elementName;
262                 svgWriter.writeStartElement("tspan");
263                 appendStyle = "font-style:italic;";
264             }
265             else if (elementName == "u") {
266                 debugFlake << "\tstart Element" << elementName;
267                 svgWriter.writeStartElement("tspan");
268                 appendStyle = "text-decoration:underline";
269             }
270 
271             QXmlStreamAttributes attributes = htmlReader.attributes();
272 
273             QString textAlign;
274             if (attributes.hasAttribute("align")) {
275                 textAlign = attributes.value("align").toString();
276             }
277 
278             if (attributes.hasAttribute("style")) {
279                 QString filteredStyles;
280                 QStringList svgStyles = QString("font-family font-size font-weight font-variant word-spacing text-decoration font-style font-size-adjust font-stretch direction letter-spacing").split(" ");
281                 QStringList styles = attributes.value("style").toString().split(";");
282                 for(int i=0; i<styles.size(); i++) {
283                     QStringList style = QString(styles.at(i)).split(":");
284                     debugFlake<<style.at(0);
285                     if (svgStyles.contains(QString(style.at(0)).trimmed())) {
286                         filteredStyles.append(styles.at(i)+";");
287                     }
288 
289                     if (QString(style.at(0)).trimmed() == "color") {
290                         filteredStyles.append(" fill:"+style.at(1)+";");
291                     }
292 
293                     if (QString(style.at(0)).trimmed() == "text-align") {
294                         textAlign = QString(style.at(1)).trimmed();
295                     }
296 
297                     if (QString(style.at(0)).trimmed() == "line-height"){
298                         if (style.at(1).contains("%")) {
299                             double percentage = QString(style.at(1)).remove("%").toDouble();
300                             em = QString::number(percentage/100.0)+"em";
301                         } else if(style.at(1).contains("em")) {
302                             em = style.at(1);
303                         } else if(style.at(1).contains("px")) {
304                             em = style.at(1);
305                         }
306                         if (elementName == "body") {
307                             bodyEm = em;
308                         }
309                     }
310                 }
311 
312                 if (textAlign == "center") {
313                     filteredStyles.append(" text-anchor:middle;");
314                 } else if (textAlign == "right") {
315                     filteredStyles.append(" text-anchor:end;");
316                 } else if (textAlign == "left"){
317                     filteredStyles.append(" text-anchor:start;");
318                 }
319 
320                 filteredStyles.append(appendStyle);
321 
322                 if (!filteredStyles.isEmpty()) {
323                     svgWriter.writeAttribute("style", filteredStyles);
324                     previousStyleString = filteredStyles;
325                 }
326 
327 
328             }
329             if (newLine && lineCount > 1) {
330                 debugFlake << "\t\tAdvancing to the next line";
331                 svgWriter.writeAttribute("x", "0");
332                 svgWriter.writeAttribute("dy", em);
333             }
334             break;
335         }
336         case QXmlStreamReader::EndElement:
337         {
338             if (htmlReader.name() == "br") break;
339             if (elementName == "p" || elementName == "span" || elementName == "body") {
340                 debugFlake << "\tEndElement" <<  htmlReader.name() << "(" << elementName << ")";
341                 svgWriter.writeEndElement();
342             }
343             break;
344         }
345         case QXmlStreamReader::Characters:
346         {
347             if (elementName == "style") {
348                 *styles = htmlReader.text().toString();
349             }
350             else {
351                 if (!htmlReader.isWhitespace()) {
352                     debugFlake << "\tCharacters:" << htmlReader.text();
353                     svgWriter.writeCharacters(htmlReader.text().toString());
354                 }
355             }
356             break;
357         }
358         default:
359             ;
360         }
361     }
362 
363     if (htmlReader.hasError()) {
364         d->errors << htmlReader.errorString();
365         return false;
366     }
367     if (svgWriter.hasError()) {
368         d->errors << i18n("Unknown error writing SVG text element");
369         return false;
370     }
371 
372     *svgText = QString::fromUtf8(svgBuffer.data());
373     return true;
374 }
375 
postCorrectBlockHeight(QTextDocument * doc,qreal currLineAscent,qreal prevLineAscent,qreal prevLineDescent,int prevBlockCursorPosition,qreal currentBlockAbsoluteLineOffset)376 void postCorrectBlockHeight(QTextDocument *doc,
377                             qreal currLineAscent,
378                             qreal prevLineAscent,
379                             qreal prevLineDescent,
380                             int prevBlockCursorPosition,
381                             qreal currentBlockAbsoluteLineOffset)
382 {
383     KIS_SAFE_ASSERT_RECOVER_RETURN(prevBlockCursorPosition >= 0);
384 
385     QTextCursor postCorrectionCursor(doc);
386     postCorrectionCursor.setPosition(prevBlockCursorPosition);
387     if (!postCorrectionCursor.isNull()) {
388         const qreal relativeLineHeight =
389                 ((currentBlockAbsoluteLineOffset - currLineAscent + prevLineAscent) /
390                  (prevLineAscent + prevLineDescent)) * 100.0;
391 
392         QTextBlockFormat format = postCorrectionCursor.blockFormat();
393         format.setLineHeight(relativeLineHeight, QTextBlockFormat::ProportionalHeight);
394         postCorrectionCursor.setBlockFormat(format);
395         postCorrectionCursor = QTextCursor();
396     }
397 }
398 
findMostCommonFormat(const QList<QTextFormat> & allFormats)399 QTextFormat findMostCommonFormat(const QList<QTextFormat> &allFormats)
400 {
401     QTextCharFormat mostCommonFormat;
402 
403     QSet<int> propertyIds;
404 
405     /**
406      * Get all existing property ids
407      */
408     Q_FOREACH (const QTextFormat &format, allFormats) {
409         const QMap<int, QVariant> formatProperties = format.properties();
410         Q_FOREACH (int id, formatProperties.keys()) {
411             propertyIds.insert(id);
412         }
413     }
414 
415     /**
416      * Filter out properties that do not exist in some formats. Otherwise, the
417      * global format may override the default value used in these formats
418      * (and yes, we do not have access to the default values to use them
419      * in difference calculation algorithm
420      */
421     Q_FOREACH (const QTextFormat &format, allFormats) {
422         for (auto it = propertyIds.begin(); it != propertyIds.end();) {
423             if (!format.hasProperty(*it)) {
424                 it = propertyIds.erase(it);
425             } else {
426                 ++it;
427             }
428         }
429         if (propertyIds.isEmpty()) break;
430     }
431 
432     if (!propertyIds.isEmpty()) {
433         QMap<int, QMap<QVariant, int>> propertyFrequency;
434 
435         /**
436          * Calculate the frequency of values used in *all* the formats
437          */
438         Q_FOREACH (const QTextFormat &format, allFormats) {
439             const QMap<int, QVariant> formatProperties = format.properties();
440 
441             Q_FOREACH (int id, propertyIds) {
442                 KIS_SAFE_ASSERT_RECOVER_BREAK(formatProperties.contains(id));
443                 propertyFrequency[id][formatProperties.value(id)]++;
444             }
445         }
446 
447         /**
448          * Add the most popular property value to the set of most common properties
449          */
450         for (auto it = propertyFrequency.constBegin(); it != propertyFrequency.constEnd(); ++it) {
451             const int id = it.key();
452             const QMap<QVariant, int> allValues = it.value();
453 
454             int maxCount = 0;
455             QVariant maxValue;
456 
457             for (auto valIt = allValues.constBegin(); valIt != allValues.constEnd(); ++valIt) {
458                 if (valIt.value() > maxCount) {
459                     maxCount = valIt.value();
460                     maxValue = valIt.key();
461                 }
462             }
463 
464             KIS_SAFE_ASSERT_RECOVER_BREAK(maxCount > 0);
465             mostCommonFormat.setProperty(id, maxValue);
466         }
467 
468     }
469 
470     return mostCommonFormat;
471 }
472 
calcLineWidth(const QTextBlock & block)473 qreal calcLineWidth(const QTextBlock &block)
474 {
475     const QString blockText = block.text();
476 
477     QTextLayout lineLayout;
478     lineLayout.setText(blockText);
479     lineLayout.setFont(block.charFormat().font());
480     lineLayout.setFormats(block.textFormats());
481     lineLayout.setTextOption(block.layout()->textOption());
482 
483     lineLayout.beginLayout();
484     QTextLine fullLine = lineLayout.createLine();
485     if (!fullLine.isValid()) {
486         fullLine.setNumColumns(blockText.size());
487     }
488     lineLayout.endLayout();
489 
490     return lineLayout.boundingRect().width();
491 }
492 
convertDocumentToSvg(const QTextDocument * doc,QString * svgText)493 bool KoSvgTextShapeMarkupConverter::convertDocumentToSvg(const QTextDocument *doc, QString *svgText)
494 {
495     QBuffer svgBuffer;
496     svgBuffer.open(QIODevice::WriteOnly);
497 
498     QXmlStreamWriter svgWriter(&svgBuffer);
499 
500     // disable auto-formatting to avoid axtra spaces appearing here and there
501     svgWriter.setAutoFormatting(false);
502 
503 
504     qreal maxParagraphWidth = 0.0;
505     QTextCharFormat mostCommonCharFormat;
506     QTextBlockFormat mostCommonBlockFormat;
507 
508     struct LineInfo {
509         LineInfo() {}
510         LineInfo(QTextBlock _block, int _numSkippedLines)
511             : block(_block), numSkippedLines(_numSkippedLines)
512         {}
513 
514         QTextBlock block;
515         int numSkippedLines = 0;
516     };
517 
518 
519     QVector<LineInfo> lineInfoList;
520 
521     {
522         QTextBlock block = doc->begin();
523 
524         QList<QTextFormat> allCharFormats;
525         QList<QTextFormat> allBlockFormats;
526 
527         int numSequentialEmptyLines = 0;
528 
529         while (block.isValid()) {
530             if (!block.text().trimmed().isEmpty()) {
531                 lineInfoList.append(LineInfo(block, numSequentialEmptyLines));
532                 numSequentialEmptyLines = 0;
533 
534                 maxParagraphWidth = qMax(maxParagraphWidth, calcLineWidth(block));
535 
536                 allBlockFormats.append(block.blockFormat());
537                 Q_FOREACH (const QTextLayout::FormatRange &range, block.textFormats()) {
538                     QTextFormat format =  range.format;
539                     allCharFormats.append(format);
540                 }
541             } else {
542                 numSequentialEmptyLines++;
543             }
544 
545             block = block.next();
546         }
547 
548         mostCommonCharFormat = findMostCommonFormat(allCharFormats).toCharFormat();
549         mostCommonBlockFormat = findMostCommonFormat(allBlockFormats).toBlockFormat();
550     }
551 
552     //Okay, now the actual writing.
553 
554     QTextBlock block = doc->begin();
555     Q_UNUSED(block);
556 
557     svgWriter.writeStartElement("text");
558 
559     {
560         const QString commonTextStyle = style(mostCommonCharFormat, mostCommonBlockFormat);
561         if (!commonTextStyle.isEmpty()) {
562             svgWriter.writeAttribute("style", commonTextStyle);
563         }
564     }
565 
566     int prevBlockRelativeLineSpacing = mostCommonBlockFormat.lineHeight();
567     int prevBlockLineType = mostCommonBlockFormat.lineHeightType();
568     qreal prevBlockAscent = 0.0;
569     qreal prevBlockDescent= 0.0;
570 
571     Q_FOREACH (const LineInfo &info, lineInfoList) {
572         QTextBlock block = info.block;
573 
574         const QTextBlockFormat blockFormatDiff = formatDifference(block.blockFormat(), mostCommonBlockFormat).toBlockFormat();
575         QTextCharFormat blockCharFormatDiff = QTextCharFormat();
576         const QVector<QTextLayout::FormatRange> formats = block.textFormats();
577         if (formats.size()==1) {
578             blockCharFormatDiff = formatDifference(formats.at(0).format, mostCommonCharFormat).toCharFormat();
579         }
580 
581         const QTextLayout *layout = block.layout();
582         const QTextLine line = layout->lineAt(0);
583 
584         svgWriter.writeStartElement("tspan");
585 
586         const QString text = block.text();
587 
588         /**
589          * Mindblowing part: QTextEdit uses a hi-end algorithm for auto-estimation for the text
590          * directionality, so the user expects his text being saved to SVG with the same
591          * directionality. Just emulate behavior of direction="auto", which is not supported by
592          * SVG 1.1
593          *
594          * BUG: 392064
595          */
596 
597         bool isRightToLeft = false;
598         for (int i = 0; i < text.size(); i++) {
599             const QChar ch = text[i];
600             if (ch.direction() == QChar::DirR || ch.direction() == QChar::DirAL) {
601                 isRightToLeft = true;
602                 break;
603             } else if (ch.direction() == QChar::DirL) {
604                 break;
605             }
606         }
607 
608 
609         if (isRightToLeft) {
610             svgWriter.writeAttribute("direction", "rtl");
611             svgWriter.writeAttribute("unicode-bidi", "embed");
612         }
613 
614         {
615             const QString blockStyleString = style(blockCharFormatDiff, blockFormatDiff);
616             if (!blockStyleString.isEmpty()) {
617                 svgWriter.writeAttribute("style", blockStyleString);
618             }
619         }
620 
621         /**
622          * The alignment rule will be inverted while rendering the text in the text shape
623          * (according to the standard the alignment is defined not by "left" or "right",
624          * but by "start" and "end", which inverts for rtl text)
625          */
626         Qt::Alignment blockAlignment = block.blockFormat().alignment();
627         if (isRightToLeft) {
628             if (blockAlignment & Qt::AlignLeft) {
629                 blockAlignment &= ~Qt::AlignLeft;
630                 blockAlignment |= Qt::AlignRight;
631             } else if (blockAlignment & Qt::AlignRight) {
632                 blockAlignment &= ~Qt::AlignRight;
633                 blockAlignment |= Qt::AlignLeft;
634             }
635         }
636 
637         if (blockAlignment & Qt::AlignHCenter) {
638             svgWriter.writeAttribute("x", KisDomUtils::toString(0.5 * maxParagraphWidth) + "pt");
639         } else if (blockAlignment & Qt::AlignRight) {
640             svgWriter.writeAttribute("x", KisDomUtils::toString(maxParagraphWidth) + "pt");
641         } else {
642             svgWriter.writeAttribute("x", "0");
643         }
644 
645         if (block.blockNumber() > 0) {
646             const qreal lineHeightPt =
647                     line.ascent() - prevBlockAscent +
648                     (prevBlockAscent + prevBlockDescent) * qreal(prevBlockRelativeLineSpacing) / 100.0;
649 
650             const qreal currentLineSpacing = (info.numSkippedLines + 1) * lineHeightPt;
651             svgWriter.writeAttribute("dy", KisDomUtils::toString(currentLineSpacing) + "pt");
652         }
653 
654         prevBlockRelativeLineSpacing =
655                 blockFormatDiff.hasProperty(QTextFormat::LineHeight) ?
656                     blockFormatDiff.lineHeight() :
657                     mostCommonBlockFormat.lineHeight();
658 
659         prevBlockLineType =
660                 blockFormatDiff.hasProperty(QTextFormat::LineHeightType) ?
661                     blockFormatDiff.lineHeightType() :
662                     mostCommonBlockFormat.lineHeightType();
663 
664         if (prevBlockLineType == QTextBlockFormat::SingleHeight) {
665             //single line will set lineHeight to 100%
666             prevBlockRelativeLineSpacing = 100;
667         }
668 
669         prevBlockAscent = line.ascent();
670         prevBlockDescent = line.descent();
671 
672 
673         if (formats.size()>1) {
674             QStringList texts;
675             QVector<QTextCharFormat> charFormats;
676             for (int f=0; f<formats.size(); f++) {
677                 QString chunk;
678                 for (int c = 0; c<formats.at(f).length; c++) {
679                     chunk.append(text.at(formats.at(f).start+c));
680                 }
681                 texts.append(chunk);
682                 charFormats.append(formats.at(f).format);
683             }
684 
685             for (int c = 0; c<texts.size(); c++) {
686                 QTextCharFormat diff = formatDifference(charFormats.at(c), mostCommonCharFormat).toCharFormat();
687                 const QString subStyle = style(diff, QTextBlockFormat(), mostCommonCharFormat);
688                 if (!subStyle.isEmpty()) {
689                     svgWriter.writeStartElement("tspan");
690                     svgWriter.writeAttribute("style", subStyle);
691                     svgWriter.writeCharacters(texts.at(c));
692                     svgWriter.writeEndElement();
693                 } else {
694                     svgWriter.writeCharacters(texts.at(c));
695                 }
696             }
697 
698         } else {
699             svgWriter.writeCharacters(text);
700             //check format against
701         }
702         svgWriter.writeEndElement();
703     }
704     svgWriter.writeEndElement();//text root element.
705 
706     if (svgWriter.hasError()) {
707         d->errors << i18n("Unknown error writing SVG text element");
708         return false;
709     }
710     *svgText = QString::fromUtf8(svgBuffer.data()).trimmed();
711     return true;
712 }
713 
parseTextAttributes(const QXmlStreamAttributes & elementAttributes,QTextCharFormat & charFormat,QTextBlockFormat & blockFormat)714 void parseTextAttributes(const QXmlStreamAttributes &elementAttributes,
715                          QTextCharFormat &charFormat,
716                          QTextBlockFormat &blockFormat)
717 {
718     QString styleString;
719 
720     // we convert all the presentation attributes into styles
721     QString presentationAttributes;
722     for (int a = 0; a < elementAttributes.size(); a++) {
723         if (elementAttributes.at(a).name() != "style") {
724             presentationAttributes
725                     .append(elementAttributes.at(a).name().toString())
726                     .append(":")
727                     .append(elementAttributes.at(a).value().toString())
728                     .append(";");
729         }
730     }
731 
732     if (presentationAttributes.endsWith(";")) {
733         presentationAttributes.chop(1);
734     }
735 
736     if (elementAttributes.hasAttribute("style")) {
737         styleString = elementAttributes.value("style").toString();
738         if (styleString.endsWith(";")) {
739             styleString.chop(1);
740         }
741     }
742 
743     if (!styleString.isEmpty() || !presentationAttributes.isEmpty()) {
744         //add attributes to parse them as part of the style.
745         styleString.append(";")
746                 .append(presentationAttributes);
747         QStringList styles = styleString.split(";");
748         QVector<QTextFormat> formats = KoSvgTextShapeMarkupConverter::stylesFromString(styles, charFormat, blockFormat);
749 
750         charFormat = formats.at(0).toCharFormat();
751         blockFormat = formats.at(1).toBlockFormat();
752     }
753 }
754 
convertSvgToDocument(const QString & svgText,QTextDocument * doc)755 bool KoSvgTextShapeMarkupConverter::convertSvgToDocument(const QString &svgText, QTextDocument *doc)
756 {
757     QXmlStreamReader svgReader(svgText.trimmed());
758     doc->clear();
759     QTextCursor cursor(doc);
760 
761     struct BlockFormatRecord {
762         BlockFormatRecord() {}
763         BlockFormatRecord(QTextBlockFormat _blockFormat,
764                           QTextCharFormat _charFormat)
765             : blockFormat(_blockFormat),
766               charFormat(_charFormat)
767         {}
768 
769         QTextBlockFormat blockFormat;
770         QTextCharFormat charFormat;
771     };
772 
773     QStack<BlockFormatRecord> formatStack;
774     formatStack.push(BlockFormatRecord(QTextBlockFormat(), QTextCharFormat()));
775 
776     qreal currBlockAbsoluteLineOffset = 0.0;
777     int prevBlockCursorPosition = -1;
778     qreal prevLineDescent = 0.0;
779     qreal prevLineAscent = 0.0;
780     boost::optional<qreal> previousBlockAbsoluteXOffset = boost::none;
781 
782     while (!svgReader.atEnd()) {
783         QXmlStreamReader::TokenType token = svgReader.readNext();
784         switch (token) {
785         case QXmlStreamReader::StartElement:
786         {
787             bool newBlock = false;
788             QTextBlockFormat newBlockFormat;
789             QTextCharFormat newCharFormat;
790             qreal absoluteLineOffset = 1.0;
791 
792             // fetch format of the parent block and make it default
793             if (formatStack.size() >= 2) {
794                 newBlockFormat = formatStack[formatStack.size() - 2].blockFormat;
795                 newCharFormat = formatStack[formatStack.size() - 2].charFormat;
796             }
797 
798             {
799                 const QXmlStreamAttributes elementAttributes = svgReader.attributes();
800                 parseTextAttributes(elementAttributes, newCharFormat, newBlockFormat);
801 
802                 // mnemonic for a newline is (dy != 0 && x == 0)
803 
804                 boost::optional<qreal> blockAbsoluteXOffset = boost::none;
805 
806                 if (elementAttributes.hasAttribute("x")) {
807                     QString xString = elementAttributes.value("x").toString();
808                     if (xString.contains("pt")) {
809                         xString = xString.remove("pt").trimmed();
810                     }
811                     blockAbsoluteXOffset = KisDomUtils::toDouble(xString);
812                 }
813 
814 
815                 if (previousBlockAbsoluteXOffset &&
816                     blockAbsoluteXOffset &&
817                     qFuzzyCompare(*previousBlockAbsoluteXOffset, *blockAbsoluteXOffset) &&
818                     svgReader.name() != "text" &&
819                     elementAttributes.hasAttribute("dy")) {
820 
821                     QString dyString = elementAttributes.value("dy").toString();
822                     if (dyString.contains("pt")) {
823                         dyString = dyString.remove("pt").trimmed();
824                     }
825 
826                     KIS_SAFE_ASSERT_RECOVER_NOOP(formatStack.isEmpty() == (svgReader.name() == "text"));
827 
828                     absoluteLineOffset = KisDomUtils::toDouble(dyString);
829                     newBlock = absoluteLineOffset > 0;
830                 }
831 
832                 if (elementAttributes.hasAttribute("x")) {
833                     previousBlockAbsoluteXOffset = blockAbsoluteXOffset;
834                 }
835             }
836 
837             //hack
838             doc->setTextWidth(100);
839             doc->setTextWidth(-1);
840 
841             if (newBlock && absoluteLineOffset > 0) {
842                 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!formatStack.isEmpty(), false);
843                 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cursor.block().layout()->lineCount() > 0, false);
844 
845                 QTextLine line = cursor.block().layout()->lineAt(0);
846 
847                 if (prevBlockCursorPosition >= 0) {
848                     postCorrectBlockHeight(doc, line.ascent(), prevLineAscent, prevLineDescent,
849                                            prevBlockCursorPosition, currBlockAbsoluteLineOffset);
850                 }
851 
852                 prevBlockCursorPosition = cursor.position();
853                 prevLineAscent  = line.ascent();
854                 prevLineDescent = line.descent();
855                 currBlockAbsoluteLineOffset = absoluteLineOffset;
856 
857                 cursor.insertBlock();
858                 cursor.setCharFormat(formatStack.top().charFormat);
859                 cursor.setBlockFormat(formatStack.top().blockFormat);
860             }
861 
862 
863 
864             cursor.mergeCharFormat(newCharFormat);
865             cursor.mergeBlockFormat(newBlockFormat);
866 
867             formatStack.push(BlockFormatRecord(cursor.blockFormat(), cursor.charFormat()));
868 
869             break;
870         }
871         case QXmlStreamReader::EndElement:
872         {
873             if (svgReader.name() != "text") {
874                 formatStack.pop();
875                 KIS_SAFE_ASSERT_RECOVER(!formatStack.isEmpty()) { break; }
876 
877                 cursor.setCharFormat(formatStack.top().charFormat);
878                 cursor.setBlockFormat(formatStack.top().blockFormat);
879             }
880             break;
881         }
882         case QXmlStreamReader::Characters:
883         {
884             if (!svgReader.isWhitespace()) {
885                 cursor.insertText(svgReader.text().toString());
886             }
887 
888             break;
889         }
890         default:
891             break;
892         }
893     }
894 
895     if (prevBlockCursorPosition >= 0) {
896         QTextLine line = cursor.block().layout()->lineAt(0);
897         postCorrectBlockHeight(doc, line.ascent(), prevLineAscent, prevLineDescent,
898                                prevBlockCursorPosition, currBlockAbsoluteLineOffset);
899     }
900 
901     if (svgReader.hasError()) {
902         d->errors << svgReader.errorString();
903         return false;
904     }
905     doc->setModified(false);
906     return true;
907 }
908 
errors() const909 QStringList KoSvgTextShapeMarkupConverter::errors() const
910 {
911     return d->errors;
912 }
913 
warnings() const914 QStringList KoSvgTextShapeMarkupConverter::warnings() const
915 {
916     return d->warnings;
917 }
918 
compareFormatUnderlineWithMostCommon(QTextCharFormat format,QTextCharFormat mostCommon)919 bool compareFormatUnderlineWithMostCommon(QTextCharFormat format, QTextCharFormat mostCommon)
920 {
921     // color and style is not supported in rich text editor yet
922     // TODO: support color and style
923     return format.fontUnderline() == mostCommon.fontUnderline()
924             && format.fontOverline() == mostCommon.fontOverline()
925             && format.fontStrikeOut() == mostCommon.fontStrikeOut();
926 }
927 
convertFormatUnderlineToSvg(QTextCharFormat format)928 QString convertFormatUnderlineToSvg(QTextCharFormat format)
929 {
930     // color and style is not supported in rich text editor yet
931     // and text-decoration-line and -style and -color are not supported in svg render either
932     // hence we just use text-decoration
933     // TODO: support color and style
934     QStringList line;
935 
936     if (format.fontUnderline()) {
937         line.append("underline");
938         if (format.underlineStyle() != QTextCharFormat::SingleUnderline) {
939             warnFile << "Krita only supports solid underline style";
940         }
941     }
942 
943     if (format.fontOverline()) {
944         line.append("overline");
945     }
946 
947     if (format.fontStrikeOut()) {
948         line.append("line-through");
949     }
950 
951     if (line.isEmpty())
952     {
953         line.append("none");
954     }
955 
956     QString c = QString("text-decoration").append(":")
957             .append(line.join(" "));
958 
959     return c;
960 }
961 
style(QTextCharFormat format,QTextBlockFormat blockFormat,QTextCharFormat mostCommon)962 QString KoSvgTextShapeMarkupConverter::style(QTextCharFormat format,
963                                              QTextBlockFormat blockFormat,
964                                              QTextCharFormat mostCommon)
965 {
966     QStringList style;
967     for(int i=0; i<format.properties().size(); i++) {
968         QString c;
969         int propertyId = format.properties().keys().at(i);
970 
971         if (propertyId == QTextCharFormat::FontFamily) {
972             c.append("font-family").append(":")
973                     .append(format.properties()[propertyId].toString());
974         }
975         if (propertyId == QTextCharFormat::FontPointSize) {
976             c.append("font-size").append(":")
977                     .append(format.properties()[propertyId].toString()+"pt");
978             style.append(c);
979             c.clear();
980             QFontMetricsF metrics(format.fontFamily());
981             qreal xRatio = metrics.xHeight()/metrics.height();
982             c.append("font-size-adjust").append(":").append(QString::number(xRatio));
983         }
984         if (propertyId == QTextCharFormat::FontPixelSize) {
985             c.append("font-size").append(":")
986                     .append(format.properties()[propertyId].toString()+"px");
987         }
988         if (propertyId == QTextCharFormat::FontWeight) {
989             // Convert from QFont::Weight range to SVG range,
990             // as defined in qt's qfont.h
991             int convertedWeight = 400; // Defaulting to Weight::Normal in svg scale
992 
993             switch (format.properties()[propertyId].toInt()) {
994                 case QFont::Weight::Thin:
995                     convertedWeight = 100;
996                     break;
997                 case QFont::Weight::ExtraLight:
998                     convertedWeight = 200;
999                     break;
1000                 case QFont::Weight::Light:
1001                     convertedWeight = 300;
1002                     break;
1003                 case QFont::Weight::Normal:
1004                     convertedWeight = 400;
1005                     break;
1006                 case QFont::Weight::Medium:
1007                     convertedWeight = 500;
1008                     break;
1009                 case QFont::Weight::DemiBold:
1010                     convertedWeight = 600;
1011                     break;
1012                 case QFont::Weight::Bold:
1013                     convertedWeight = 700;
1014                     break;
1015                 case QFont::Weight::ExtraBold:
1016                     convertedWeight = 800;
1017                     break;
1018                 case QFont::Weight::Black:
1019                     convertedWeight = 900;
1020                     break;
1021                 default:
1022                     warnFile << "WARNING: Invalid QFont::Weight value supplied to KoSvgTextShapeMarkupConverter::style.";
1023                     break;
1024             }
1025 
1026             c.append("font-weight").append(":")
1027                     .append(QString::number(convertedWeight));
1028         }
1029         if (propertyId == QTextCharFormat::FontItalic) {
1030             QString val = "italic";
1031             if (!format.fontItalic()) {
1032                 val = "normal";
1033             }
1034             c.append("font-style").append(":")
1035                     .append(val);
1036         }
1037 
1038         if (propertyId == QTextCharFormat::FontCapitalization) {
1039             if (format.fontCapitalization() == QFont::SmallCaps){
1040                 c.append("font-variant").append(":")
1041                         .append("small-caps");
1042             } else if (format.fontCapitalization() == QFont::AllUppercase) {
1043                 c.append("text-transform").append(":")
1044                         .append("uppercase");
1045             } else if (format.fontCapitalization() == QFont::AllLowercase) {
1046                 c.append("text-transform").append(":")
1047                         .append("lowercase");
1048             } else if (format.fontCapitalization() == QFont::Capitalize) {
1049                 c.append("text-transform").append(":")
1050                         .append("capitalize");
1051             }
1052         }
1053 
1054         if (propertyId == QTextCharFormat::FontStretch) {
1055             c.append("font-stretch").append(":")
1056                     .append(format.properties()[propertyId].toString());
1057         }
1058         if (propertyId == QTextCharFormat::FontKerning) {
1059             QString val;
1060             if (format.fontKerning()) {
1061                 val = "auto";
1062             } else {
1063                 val = "0";
1064             }
1065             c.append("kerning").append(":")
1066                     .append(val);
1067         }
1068         if (propertyId == QTextCharFormat::FontWordSpacing) {
1069             c.append("word-spacing").append(":")
1070                     .append(QString::number(format.fontWordSpacing()));
1071         }
1072         if (propertyId == QTextCharFormat::FontLetterSpacing) {
1073             QString val;
1074             if (format.fontLetterSpacingType()==QFont::AbsoluteSpacing) {
1075                 val = QString::number(format.fontLetterSpacing());
1076             } else {
1077                 val = QString::number(((format.fontLetterSpacing()/100)*format.fontPointSize()));
1078             }
1079             c.append("letter-spacing").append(":")
1080                     .append(val);
1081         }
1082         if (propertyId == QTextCharFormat::TextOutline) {
1083             if (format.textOutline().color() != mostCommon.textOutline().color()) {
1084                 c.append("stroke").append(":")
1085                         .append(format.textOutline().color().name());
1086                 style.append(c);
1087                 c.clear();
1088             }
1089             if (format.textOutline().width() != mostCommon.textOutline().width()) {
1090                 c.append("stroke-width").append(":")
1091                         .append(QString::number(format.textOutline().width()));
1092             }
1093         }
1094 
1095 
1096         if (propertyId == QTextCharFormat::TextVerticalAlignment) {
1097             QString val = "baseline";
1098             if (format.verticalAlignment() == QTextCharFormat::AlignSubScript) {
1099                 val = QLatin1String("sub");
1100             }
1101             else if (format.verticalAlignment() == QTextCharFormat::AlignSuperScript) {
1102                 val = QLatin1String("super");
1103             }
1104             c.append("baseline-shift").append(":").append(val);
1105         }
1106 
1107         if (propertyId == QTextCharFormat::ForegroundBrush) {
1108             QColor::NameFormat colorFormat;
1109 
1110             if (format.foreground().color().alphaF() < 1.0) {
1111                 colorFormat = QColor::HexArgb;
1112             } else {
1113                 colorFormat = QColor::HexRgb;
1114             }
1115 
1116             c.append("fill").append(":")
1117                     .append(format.foreground().color().name(colorFormat));
1118         }
1119 
1120         if (!c.isEmpty()) {
1121             style.append(c);
1122         }
1123     }
1124 
1125     if (!compareFormatUnderlineWithMostCommon(format, mostCommon)) {
1126 
1127         QString c = convertFormatUnderlineToSvg(format);
1128         if (!c.isEmpty()) {
1129             style.append(c);
1130         }
1131     }
1132 
1133     if (blockFormat.hasProperty(QTextBlockFormat::BlockAlignment)) {
1134         // TODO: Alignment works incorrectly! The offsets should be calculated
1135         //       according to the shape width/height!
1136 
1137         QString c;
1138         QString val;
1139         if (blockFormat.alignment()==Qt::AlignRight) {
1140             val = "end";
1141         } else if (blockFormat.alignment()==Qt::AlignCenter) {
1142             val = "middle";
1143         } else {
1144             val = "start";
1145         }
1146         c.append("text-anchor").append(":")
1147                 .append(val);
1148         if (!c.isEmpty()) {
1149             style.append(c);
1150         }
1151     }
1152 
1153     return style.join("; ");
1154 }
1155 
stylesFromString(QStringList styles,QTextCharFormat currentCharFormat,QTextBlockFormat currentBlockFormat)1156 QVector<QTextFormat> KoSvgTextShapeMarkupConverter::stylesFromString(QStringList styles, QTextCharFormat currentCharFormat, QTextBlockFormat currentBlockFormat)
1157 {
1158     Q_UNUSED(currentBlockFormat);
1159 
1160     QVector<QTextFormat> formats;
1161     QTextCharFormat charFormat;
1162     charFormat.setTextOutline(currentCharFormat.textOutline());
1163     QTextBlockFormat blockFormat;
1164     QScopedPointer<SvgGraphicsContext> context(new SvgGraphicsContext());
1165 
1166     for (int i=0; i<styles.size(); i++) {
1167         if (!styles.at(i).isEmpty()){
1168             QStringList style = styles.at(i).split(":");
1169             // ignore the property instead of crashing,
1170             // if user forgets to separate property name and value with ':'.
1171             if (style.size() < 2) {
1172                 continue;
1173             }
1174 
1175             QString property = style.at(0).trimmed();
1176             QString value = style.at(1).trimmed();
1177 
1178             if (property == "font-family") {
1179                 charFormat.setFontFamily(value);
1180             }
1181 
1182             if (property == "font-size") {
1183                 qreal val = SvgUtil::parseUnitX(context.data(), value);
1184                 charFormat.setFontPointSize(val);
1185             }
1186 
1187             if (property == "font-variant") {
1188                 if (value=="small-caps") {
1189                     charFormat.setFontCapitalization(QFont::SmallCaps);
1190                 } else {
1191                     charFormat.setFontCapitalization(QFont::MixedCase);
1192                 }
1193             }
1194 
1195             if (property == "font-style") {
1196                 if (value=="italic" || value=="oblique") {
1197                     charFormat.setFontItalic(true);
1198                 } else {
1199                     charFormat.setFontItalic(false);
1200                 }
1201             }
1202 
1203             if (property == "font-stretch") {
1204                 charFormat.setFontStretch(value.toInt());
1205             }
1206 
1207             if (property == "font-weight") {
1208                 // Convert from SVG range to QFont::Weight range,
1209                 // as defined in qt's qfont.h
1210                 int convertedWeight = QFont::Weight::Normal; // Defaulting to Weight::Normal
1211 
1212                 switch (value.toInt()) {
1213                     case 100:
1214                         convertedWeight = QFont::Weight::Thin;
1215                         break;
1216                     case 200:
1217                         convertedWeight = QFont::Weight::ExtraLight;
1218                         break;
1219                     case 300:
1220                         convertedWeight = QFont::Weight::Light;
1221                         break;
1222                     case 400:
1223                         convertedWeight = QFont::Weight::Normal;
1224                         break;
1225                     case 500:
1226                         convertedWeight = QFont::Weight::Medium;
1227                         break;
1228                     case 600:
1229                         convertedWeight = QFont::Weight::DemiBold;
1230                         break;
1231                     case 700:
1232                         convertedWeight = QFont::Weight::Bold;
1233                         break;
1234                     case 800:
1235                         convertedWeight = QFont::Weight::ExtraBold;
1236                         break;
1237                     case 900:
1238                         convertedWeight = QFont::Weight::Black;
1239                         break;
1240                     default:
1241                         warnFile << "WARNING: Invalid weight value supplied to KoSvgTextShapeMarkupConverter::stylesFromString.";
1242                         break;
1243                 }
1244 
1245                 charFormat.setFontWeight(convertedWeight);
1246             }
1247 
1248             if (property == "text-decoration") {
1249                 charFormat.setFontUnderline(false);
1250                 charFormat.setFontOverline(false);
1251                 charFormat.setFontStrikeOut(false);
1252                 QStringList values = value.split(" ");
1253                 if (values.contains("line-through")) {
1254                     charFormat.setFontStrikeOut(true);
1255                 }
1256                 if (values.contains("overline")) {
1257                     charFormat.setFontOverline(true);
1258                 }
1259                 if(values.contains("underline")){
1260                     charFormat.setFontUnderline(true);
1261                 }
1262             }
1263 
1264             if (property == "text-transform") {
1265                 if (value == "uppercase") {
1266                     charFormat.setFontCapitalization(QFont::AllUppercase);
1267                 } else if (value == "lowercase") {
1268                     charFormat.setFontCapitalization(QFont::AllLowercase);
1269                 } else if (value == "capitalize") {
1270                     charFormat.setFontCapitalization(QFont::Capitalize);
1271                 } else{
1272                     charFormat.setFontCapitalization(QFont::MixedCase);
1273                 }
1274             }
1275 
1276             if (property == "letter-spacing") {
1277                 qreal val = SvgUtil::parseUnitX(context.data(), value);
1278                 charFormat.setFontLetterSpacingType(QFont::AbsoluteSpacing);
1279                 charFormat.setFontLetterSpacing(val);
1280             }
1281 
1282             if (property == "word-spacing") {
1283                 qreal val = SvgUtil::parseUnitX(context.data(), value);
1284                 charFormat.setFontWordSpacing(val);
1285             }
1286 
1287             if (property == "kerning") {
1288                 if (value == "auto") {
1289                     charFormat.setFontKerning(true);
1290                 } else {
1291                     qreal val = SvgUtil::parseUnitX(context.data(), value);
1292                     charFormat.setFontKerning(false);
1293                     charFormat.setFontLetterSpacingType(QFont::AbsoluteSpacing);
1294                     charFormat.setFontLetterSpacing(charFormat.fontLetterSpacing() + val);
1295                 }
1296             }
1297 
1298             if (property == "stroke") {
1299                 QPen pen = charFormat.textOutline();
1300                 QColor color;
1301                 color.setNamedColor(value);
1302                 pen.setColor(color);
1303                 charFormat.setTextOutline(pen);
1304             }
1305 
1306             if (property == "stroke-width") {
1307                 QPen pen = charFormat.textOutline();
1308                 pen.setWidth(value.toInt());
1309                 charFormat.setTextOutline(pen);
1310             }
1311 
1312             if (property == "fill") {
1313                 QColor color;
1314                 color.setNamedColor(value);
1315 
1316                 // avoid assertion failure in `KoColor` later
1317                 if (!color.isValid()) {
1318                     continue;
1319                 }
1320 
1321                 // default color is #ff000000, so default alpha will be 1.0
1322                 qreal currentAlpha = charFormat.foreground().color().alphaF();
1323 
1324                 // if alpha was already defined by `fill-opacity` prop
1325                 if (currentAlpha < 1.0) {
1326                     // and `fill` doesn't have alpha component
1327                     if (color.alphaF() < 1.0) {
1328                         color.setAlphaF(currentAlpha);
1329                     }
1330                 }
1331 
1332                 charFormat.setForeground(color);
1333             }
1334 
1335             if (property == "fill-opacity") {
1336                 QColor color = charFormat.foreground().color();
1337                 bool ok = true;
1338                 qreal alpha = qBound(0.0, SvgUtil::fromPercentage(value, &ok), 1.0);
1339 
1340                 // if conversion fails due to non-numeric input,
1341                 // it defaults to 0.0, default to current alpha instead
1342                 if (!ok) {
1343                     alpha = color.alphaF();
1344                 }
1345                 color.setAlphaF(alpha);
1346                 charFormat.setForeground(color);
1347             }
1348 
1349             if (property == "text-anchor") {
1350                 if (value == "end") {
1351                     blockFormat.setAlignment(Qt::AlignRight);
1352                 } else if (value == "middle") {
1353                     blockFormat.setAlignment(Qt::AlignCenter);
1354                 } else {
1355                     blockFormat.setAlignment(Qt::AlignLeft);
1356                 }
1357             }
1358 
1359             if (property == "baseline-shift") {
1360                 if (value == "super") {
1361                     charFormat.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
1362                 } else if (value == "sub") {
1363                     charFormat.setVerticalAlignment(QTextCharFormat::AlignSubScript);
1364                 } else {
1365                     charFormat.setVerticalAlignment(QTextCharFormat::AlignNormal);
1366                 }
1367             }
1368         }
1369     }
1370 
1371     formats.append(charFormat);
1372     formats.append(blockFormat);
1373     return formats;
1374 }
1375 
formatDifference(QTextFormat test,QTextFormat reference)1376 QTextFormat KoSvgTextShapeMarkupConverter::formatDifference(QTextFormat test, QTextFormat reference)
1377 {
1378     //copied from QTextDocument.cpp
1379     QTextFormat diff = test;
1380     //props should proly compare itself to the main text format...
1381     const QMap<int, QVariant> props = reference.properties();
1382     for (QMap<int, QVariant>::ConstIterator it = props.begin(), end = props.end();
1383          it != end; ++it)
1384         if (it.value() == test.property(it.key())) {
1385             // Some props must not be removed as default state gets in the way.
1386             if (it.key() == 0x2023) { // TextUnderlineStyle
1387                 continue;
1388             } else if (it.key() == 0x2033) { // FontLetterSpacingType
1389                 continue;
1390             }
1391             diff.clearProperty(it.key());
1392         }
1393     return diff;
1394 }
1395 
1396