Krita Source Code Documentation
Loading...
Searching...
No Matches
KoSvgTextShapeMarkupConverter Class Reference

#include <KoSvgTextShapeMarkupConverter.h>

Classes

struct  ExtraStyles
 
struct  Private
 

Public Types

enum class  WrappingMode { QtLegacy = 0 , WhiteSpacePre , WhiteSpacePreWrap }
 

Public Member Functions

bool convertDocumentToSvg (const QTextDocument *doc, QString *svgText)
 convertDocumentToSvg
 
bool convertFromHtml (const QString &htmlText, QString *svgText, QString *styles)
 convertFromHtml converted Qt rich text html (and no other: https://doc.qt.io/qt-5/richtext-html-subset.html) to SVG
 
bool convertFromSvg (const QString &svgText, const QString &stylesText, const QRectF &boundsInPixels, qreal pixelsPerInch)
 upload the svg representation of text into the shape
 
bool convertSvgToDocument (const QString &svgText, QTextDocument *doc)
 convertSvgToDocument
 
bool convertToHtml (QString *htmlText)
 convertToHtml convert the text in the text shape to html
 
bool convertToSvg (QString *svgText, QString *stylesText)
 
QStringList errors () const
 
QTextFormat formatDifference (QTextFormat test, QTextFormat reference)
 formatDifference A class to get the difference between two text-char formats.
 
 KoSvgTextShapeMarkupConverter (KoSvgTextShape *shape)
 
QString style (QTextCharFormat format, QTextBlockFormat blockFormat, QTextCharFormat mostCommon=QTextCharFormat(), bool includeLineHeight=false)
 style creates a style string based on the blockformat and the format.
 
QStringList warnings () const
 
 ~KoSvgTextShapeMarkupConverter ()
 

Static Public Member Functions

static std::optional< double > getInlineSize (const QTextFrameFormat &frameFormat)
 Get the inline-size from the frameFormat of the rootFrame of a QTextDocument.
 
static WrappingMode getWrappingMode (const QTextFrameFormat &frameFormat)
 Get the Wrapping Mode from the frameFormat of the rootFrame of a QTextDocument.
 
static void setInlineSize (QTextFrameFormat *frameFormat, double inlineSize)
 Set or unset the inline-size on a frameFormat to be applied to the rootFrame of a QTextDocument.
 
static void setWrappingMode (QTextFrameFormat *frameFormat, WrappingMode wrappingMode)
 Set the Wrapping Mode on a frame format to be applied to the rootFrame of a QTextDocument.
 
static QVector< QTextFormat > stylesFromString (QStringList styles, QTextCharFormat currentCharFormat, QTextBlockFormat currentBlockFormat, ExtraStyles &extraStyles)
 stylesFromString returns a qvector with two textformats: at 0 is the QTextCharFormat at 1 is the QTextBlockFormat
 

Static Public Attributes

static constexpr QTextFormat::Property InlineSizeProperty
 
static constexpr QTextFormat::Property WrappingModeProperty
 

Private Attributes

const QScopedPointer< Privated
 

Detailed Description

KoSvgTextShapeMarkupConverter is a utility class for converting a KoSvgTextShape to/from user-editable markup/svg representation.

Please note that the converted SVG is not the same as when saved into .kra! Some attributes are dropped to make the editing is easier for the user.

Definition at line 31 of file KoSvgTextShapeMarkupConverter.h.

Member Enumeration Documentation

◆ WrappingMode

The wrapping mode for the conversion between QTextDocument and SVG.

Enumerator
QtLegacy 

Use the legacy conversion with line breaks performed by setting dy on <tspan> with offsets calculated from QTextLayout.

WhiteSpacePre 

Use white-space: pre and output hard line-breaks in the SVG.

WhiteSpacePreWrap 

Use white-space: pre-wrap and output hard line-breaks in the SVG. inline-size is also set to enable automatic wrapping.

Definition at line 147 of file KoSvgTextShapeMarkupConverter.h.

Constructor & Destructor Documentation

◆ KoSvgTextShapeMarkupConverter()

KoSvgTextShapeMarkupConverter::KoSvgTextShapeMarkupConverter ( KoSvgTextShape * shape)

Definition at line 71 of file KoSvgTextShapeMarkupConverter.cpp.

72 : d(new Private(shape))
73{
74}

◆ ~KoSvgTextShapeMarkupConverter()

KoSvgTextShapeMarkupConverter::~KoSvgTextShapeMarkupConverter ( )

Definition at line 76 of file KoSvgTextShapeMarkupConverter.cpp.

77{
78}

Member Function Documentation

◆ convertDocumentToSvg()

bool KoSvgTextShapeMarkupConverter::convertDocumentToSvg ( const QTextDocument * doc,
QString * svgText )

convertDocumentToSvg

Parameters
docthe QTextDocument to convert.
svgTextthe converted svg text element
Returns
true if the conversion was successful

DIRTY-DIRTY-DIRTY HACK ALERT!!!

All the internals of QTextDocument work with the primary screen's DPI. That is, when we ask Qt about font metrics, it returns values scaled to the pixels in the current screen, that is, ptValue * qt_defaultDpi() / 72.0. We need to convert this value back into points before writing as SVG. Therefore, all the metrics returned by the document are scaled with fixQtDpi().

Official Qt's way to workaround this problem is to set logicalDpiX/Y() values on the paint device's associated with the document. But it seems like in our version of Qt (5.12.12, which is rather old) it doesn't work properly.

The alignment rule will be inverted while rendering the text in the text shape (according to the standard the alignment is defined not by "left" or "right", but by "start" and "end", which inverts for rtl text)

Definition at line 576 of file KoSvgTextShapeMarkupConverter.cpp.

577{
578 QBuffer svgBuffer;
579 svgBuffer.open(QIODevice::WriteOnly);
580
581 QXmlStreamWriter svgWriter(&svgBuffer);
582
583 // disable auto-formatting to avoid extra spaces appearing here and there
584 svgWriter.setAutoFormatting(false);
585
586
587 qreal maxParagraphWidth = 0.0;
588 QTextCharFormat mostCommonCharFormat;
589 QTextBlockFormat mostCommonBlockFormat;
590
591 struct LineInfo {
592 LineInfo() {}
593 LineInfo(QTextBlock _block, int _numSkippedLines)
594 : block(_block), numSkippedLines(_numSkippedLines)
595 {}
596
597 QTextBlock block;
598 int numSkippedLines = 0;
599 };
600
601 const WrappingMode wrappingMode = getWrappingMode(doc->rootFrame()->frameFormat());
602
619 QVector<LineInfo> lineInfoList;
620
621 {
622 QTextBlock block = doc->begin();
623
624 QList<QTextFormat> allCharFormats;
625 QList<QTextFormat> allBlockFormats;
626
627 int numSequentialEmptyLines = 0;
628
629 bool hasExplicitTextWidth = false;
630 if (wrappingMode == WrappingMode::WhiteSpacePreWrap) {
631 // If the doc is pre-wrap, we expect the inline-size to be set.
632 if (std::optional<double> inlineSize = getInlineSize(doc->rootFrame()->frameFormat())) {
633 if (*inlineSize > 0.0) {
634 hasExplicitTextWidth = true;
635 maxParagraphWidth = *inlineSize;
636 }
637 }
638 }
639
640 while (block.isValid()) {
641 if (wrappingMode != WrappingMode::QtLegacy || !block.text().trimmed().isEmpty()) {
642 lineInfoList.append(LineInfo(block, numSequentialEmptyLines));
643 numSequentialEmptyLines = 0;
644
645 if (!hasExplicitTextWidth) {
646 maxParagraphWidth = qMax(maxParagraphWidth, calcLineWidth(block));
647 }
648
649 allBlockFormats.append(block.blockFormat());
650 Q_FOREACH (const QTextLayout::FormatRange &range, block.textFormats()) {
651 QTextFormat format = range.format;
652 allCharFormats.append(format);
653 }
654 } else {
655 numSequentialEmptyLines++;
656 }
657
658 block = block.next();
659 }
660
661 mostCommonCharFormat = findMostCommonFormat(allCharFormats).toCharFormat();
662 mostCommonBlockFormat = findMostCommonFormat(allBlockFormats).toBlockFormat();
663 }
664
665 //Okay, now the actual writing.
666
667 QTextBlock block = doc->begin();
668
669 svgWriter.writeStartElement("text");
670
671 if (wrappingMode == WrappingMode::WhiteSpacePreWrap) {
672 // There can only be one text direction for pre-wrap, so take that of
673 // the first block.
674 if (block.textDirection() == Qt::RightToLeft) {
675 svgWriter.writeAttribute("direction", "rtl");
676 }
677 }
678
679 {
680 QString commonTextStyle = style(mostCommonCharFormat,
681 mostCommonBlockFormat,
682 {},
683 /*includeLineHeight=*/wrappingMode != WrappingMode::QtLegacy);
684 if (wrappingMode != WrappingMode::QtLegacy) {
685 if (!commonTextStyle.isEmpty()) {
686 commonTextStyle += "; ";
687 }
688 commonTextStyle += "white-space: pre";
689 if (wrappingMode == WrappingMode::WhiteSpacePreWrap) {
690 commonTextStyle += "-wrap;inline-size:";
691 commonTextStyle += QString::number(maxParagraphWidth);
692 }
693 }
694 if (!commonTextStyle.isEmpty()) {
695 svgWriter.writeAttribute("style", commonTextStyle);
696 }
697 }
698
699 // TODO: check if we should change into to float
700 int prevBlockRelativeLineSpacing = mostCommonBlockFormat.lineHeight();
701 int prevBlockLineType = mostCommonBlockFormat.lineHeightType();
702 qreal prevBlockAscent = 0.0;
703 qreal prevBlockDescent= 0.0;
704
705 Q_FOREACH (const LineInfo &info, lineInfoList) {
706 QTextBlock block = info.block;
707
708 const QTextBlockFormat blockFormatDiff = formatDifference(block.blockFormat(), mostCommonBlockFormat).toBlockFormat();
709 QTextCharFormat blockCharFormatDiff = QTextCharFormat();
710 const QVector<QTextLayout::FormatRange> formats = block.textFormats();
711 if (formats.size()==1) {
712 blockCharFormatDiff = formatDifference(formats.at(0).format, mostCommonCharFormat).toCharFormat();
713 if (wrappingMode == WrappingMode::WhiteSpacePreWrap) {
714 // For pre-wrap, be extra sure we are not writing text-anchor
715 // to the `tspan`s because they don't do anything.
716 blockCharFormatDiff.clearProperty(QTextBlockFormat::BlockAlignment);
717 }
718 }
719
720 const QTextLayout *layout = block.layout();
721 const QTextLine line = layout->lineAt(0);
722 if (!line.isValid()) {
723 // This layout probably has no lines at all. This can happen when
724 // wrappingMode != QtLegacy and the text doc is completely empty.
725 // It is safe to just skip the line. Trying to get its metrics will
726 // crash.
727 continue;
728 }
729
730 svgWriter.writeStartElement("tspan");
731
732 const QString text = block.text();
733
734 bool isRightToLeft;
735 switch (block.textDirection()) {
736 case Qt::LeftToRight:
737 isRightToLeft = false;
738 break;
739 case Qt::RightToLeft:
740 isRightToLeft = true;
741 break;
742 case Qt::LayoutDirectionAuto:
743 default:
744 // QTextBlock::textDirection() is not supposed to return these,
745 // but just in case...
746 isRightToLeft = guessIsRightToLeft(text);;
747 break;
748 }
749
750 if (isRightToLeft && wrappingMode != WrappingMode::WhiteSpacePreWrap) {
751 svgWriter.writeAttribute("direction", "rtl");
752 svgWriter.writeAttribute("unicode-bidi", "embed");
753 }
754
755 {
756 const QString blockStyleString = style(blockCharFormatDiff,
757 blockFormatDiff,
758 {},
759 /*includeLineHeight=*/wrappingMode != WrappingMode::QtLegacy);
760 if (!blockStyleString.isEmpty()) {
761 svgWriter.writeAttribute("style", blockStyleString);
762 }
763 }
764
765 if (wrappingMode != WrappingMode::WhiteSpacePreWrap) {
771 Qt::Alignment blockAlignment = block.blockFormat().alignment();
772 if (isRightToLeft) {
773 if (blockAlignment & Qt::AlignLeft) {
774 blockAlignment &= ~Qt::AlignLeft;
775 blockAlignment |= Qt::AlignRight;
776 } else if (blockAlignment & Qt::AlignRight) {
777 blockAlignment &= ~Qt::AlignRight;
778 blockAlignment |= Qt::AlignLeft;
779 }
780 }
781
782 if (blockAlignment & Qt::AlignHCenter) {
783 svgWriter.writeAttribute("x", KisDomUtils::toString(0.5 * maxParagraphWidth) + "pt");
784 } else if (blockAlignment & Qt::AlignRight) {
785 svgWriter.writeAttribute("x", KisDomUtils::toString(maxParagraphWidth) + "pt");
786 } else {
787 svgWriter.writeAttribute("x", "0");
788 }
789 }
790
791 if (wrappingMode == WrappingMode::QtLegacy && block.blockNumber() > 0) {
792 qreal lineHeightPt =
793 fixFromQtDpi(line.ascent()) - prevBlockAscent +
794 (prevBlockAscent + prevBlockDescent) * qreal(prevBlockRelativeLineSpacing) / 100.0;
795
796 const qreal currentLineSpacing = (info.numSkippedLines + 1) * lineHeightPt;
797 svgWriter.writeAttribute("dy", KisDomUtils::toString(currentLineSpacing) + "pt");
798 }
799
800 prevBlockRelativeLineSpacing =
801 blockFormatDiff.hasProperty(QTextFormat::LineHeight) ?
802 blockFormatDiff.lineHeight() :
803 mostCommonBlockFormat.lineHeight();
804
805 prevBlockLineType =
806 blockFormatDiff.hasProperty(QTextFormat::LineHeightType) ?
807 blockFormatDiff.lineHeightType() :
808 mostCommonBlockFormat.lineHeightType();
809
810 if (prevBlockLineType == QTextBlockFormat::SingleHeight) {
811 //single line will set lineHeight to 100%
812 prevBlockRelativeLineSpacing = 100;
813 }
814
815 prevBlockAscent = fixFromQtDpi(line.ascent());
816 prevBlockDescent = fixFromQtDpi(line.descent());
817
818
819 if (formats.size()>1) {
820 QStringList texts;
821 QVector<QTextCharFormat> charFormats;
822 for (int f=0; f<formats.size(); f++) {
823 QString chunk;
824 for (int c = 0; c<formats.at(f).length; c++) {
825 chunk.append(text.at(formats.at(f).start+c));
826 }
827 texts.append(chunk);
828 charFormats.append(formats.at(f).format);
829 }
830
831 for (int c = 0; c<texts.size(); c++) {
832 QTextCharFormat diff = formatDifference(charFormats.at(c), mostCommonCharFormat).toCharFormat();
833 const QString subStyle = style(diff, QTextBlockFormat(), mostCommonCharFormat);
834 if (!subStyle.isEmpty()) {
835 svgWriter.writeStartElement("tspan");
836 svgWriter.writeAttribute("style", subStyle);
837 svgWriter.writeCharacters(texts.at(c));
838 svgWriter.writeEndElement();
839 } else {
840 svgWriter.writeCharacters(texts.at(c));
841 }
842 }
843
844 } else {
845 svgWriter.writeCharacters(text);
846 //check format against
847 }
848
849 // Add line-breaks for `pre` modes, but not for the final line.
850 if (wrappingMode != WrappingMode::QtLegacy && &info != &lineInfoList.constLast()) {
851 svgWriter.writeCharacters(QLatin1String("\n"));
852 }
853 svgWriter.writeEndElement();
854 }
855 svgWriter.writeEndElement();//text root element.
856
857 if (svgWriter.hasError()) {
858 d->errors << i18n("Unknown error writing SVG text element");
859 return false;
860 }
861 *svgText = QString::fromUtf8(svgBuffer.data()).trimmed();
862 return true;
863}
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
QTextFormat findMostCommonFormat(const QList< QTextFormat > &allFormats)
qreal calcLineWidth(const QTextBlock &block)
qreal fixFromQtDpi(qreal value)
static bool guessIsRightToLeft(QStringView text)
QString style(QTextCharFormat format, QTextBlockFormat blockFormat, QTextCharFormat mostCommon=QTextCharFormat(), bool includeLineHeight=false)
style creates a style string based on the blockformat and the format.
static std::optional< double > getInlineSize(const QTextFrameFormat &frameFormat)
Get the inline-size from the frameFormat of the rootFrame of a QTextDocument.
QTextFormat formatDifference(QTextFormat test, QTextFormat reference)
formatDifference A class to get the difference between two text-char formats.
static WrappingMode getWrappingMode(const QTextFrameFormat &frameFormat)
Get the Wrapping Mode from the frameFormat of the rootFrame of a QTextDocument.
QString toString(const QString &value)

References calcLineWidth(), d, findMostCommonFormat(), fixFromQtDpi(), formatDifference(), getInlineSize(), getWrappingMode(), guessIsRightToLeft(), length(), QtLegacy, style(), KisDomUtils::toString(), and WhiteSpacePreWrap.

◆ convertFromHtml()

bool KoSvgTextShapeMarkupConverter::convertFromHtml ( const QString & htmlText,
QString * svgText,
QString * styles )

convertFromHtml converted Qt rich text html (and no other: https://doc.qt.io/qt-5/richtext-html-subset.html) to SVG

Parameters
htmlTextthe input html
svgTextthe converted svg text element
styles
Returns
true if the conversion was successful

Definition at line 196 of file KoSvgTextShapeMarkupConverter.cpp.

197{
198
199 debugFlake << ">>>>>>>>>>>" << htmlText;
200
201 QBuffer svgBuffer;
202 svgBuffer.open(QIODevice::WriteOnly);
203
204 QXmlStreamReader htmlReader(htmlText);
205 QXmlStreamWriter svgWriter(&svgBuffer);
206
207 svgWriter.setAutoFormatting(false);
208#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
209 QStringRef elementName;
210#else
211 QStringView elementName;
212#endif
213
214 bool newLine = false;
215 int lineCount = 0;
216 QString bodyEm = "1em";
217 QString em;
218 QString p("p");
219 //previous style string is for keeping formatting proper on linebreaks and appendstyle is for specific tags
220 QString previousStyleString;
221 QString appendStyle;
222 bool firstElement = true;
223
224 while (!htmlReader.atEnd()) {
225 QXmlStreamReader::TokenType token = htmlReader.readNext();
226 QLatin1String elName = firstElement? QLatin1String("text"): QLatin1String("tspan");
227 switch (token) {
228 case QXmlStreamReader::StartElement:
229 {
230 newLine = false;
231 if (htmlReader.name() == "br") {
232 debugFlake << "\tdoing br";
233 svgWriter.writeEndElement();
234#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
235 elementName = QStringRef(&p);
236#else
237 elementName = QStringView(p);
238#endif
239 em = bodyEm;
240 appendStyle = previousStyleString;
241 }
242 else {
243 elementName = htmlReader.name();
244 em = "";
245 }
246
247 if (elementName == "body") {
248 debugFlake << "\tstart Element" << elementName;
249 svgWriter.writeStartElement(elName);
250 firstElement = false;
251 appendStyle = QString();
252 }
253 else if (elementName == "p") {
254 // new line
255 debugFlake << "\t\tstart Element" << elementName;
256 svgWriter.writeStartElement(elName);
257 firstElement = false;
258 newLine = true;
259 if (em.isEmpty()) {
260 em = bodyEm;
261 appendStyle = QString();
262 }
263 lineCount++;
264 }
265 else if (elementName == "span") {
266 debugFlake << "\tstart Element" << elementName;
267 svgWriter.writeStartElement(elName);
268 firstElement = false;
269 appendStyle = QString();
270 }
271 else if (elementName == "b" || elementName == "strong") {
272 debugFlake << "\tstart Element" << elementName;
273 svgWriter.writeStartElement(elName);
274 firstElement = false;
275 appendStyle = "font-weight:700;";
276 }
277 else if (elementName == "i" || elementName == "em") {
278 debugFlake << "\tstart Element" << elementName;
279 svgWriter.writeStartElement(elName);
280 firstElement = false;
281 appendStyle = "font-style:italic;";
282 }
283 else if (elementName == "u") {
284 debugFlake << "\tstart Element" << elementName;
285 svgWriter.writeStartElement(elName);
286 firstElement = false;
287 appendStyle = "text-decoration:underline";
288 }
289 else if (elementName == "font") {
290 debugFlake << "\tstart Element" << elementName;
291 svgWriter.writeStartElement(elName);
292 firstElement = false;
293 appendStyle = QString();
294 if (htmlReader.attributes().hasAttribute("color")) {
295 svgWriter.writeAttribute("fill", htmlReader.attributes().value("color").toString());
296 }
297 }
298 else if (elementName == "pre") {
299 debugFlake << "\tstart Element" << elementName;
300 svgWriter.writeStartElement(elName);
301 firstElement = false;
302 appendStyle = "white-space:pre";
303 }
304
305 QXmlStreamAttributes attributes = htmlReader.attributes();
306
307 QString textAlign;
308 if (attributes.hasAttribute("align")) {
309 textAlign = attributes.value("align").toString();
310 }
311
312 if (attributes.hasAttribute("style")) {
313 QString filteredStyles;
314 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(" ");
315 QStringList styles = attributes.value("style").toString().split(";");
316 for(int i=0; i<styles.size(); i++) {
317 QStringList style = QString(styles.at(i)).split(":");
318 debugFlake<<style.at(0);
319 if (svgStyles.contains(QString(style.at(0)).trimmed())) {
320 filteredStyles.append(styles.at(i)+";");
321 }
322
323 if (QString(style.at(0)).trimmed() == "color") {
324 filteredStyles.append(" fill:"+style.at(1)+";");
325 }
326
327 if (QString(style.at(0)).trimmed() == "text-align") {
328 textAlign = QString(style.at(1)).trimmed();
329 }
330
331 if (QString(style.at(0)).trimmed() == "line-height"){
332 if (style.at(1).contains("%")) {
333 double percentage = QString(style.at(1)).remove("%").toDouble();
334 em = QString::number(percentage/100.0)+"em";
335 } else if(style.at(1).contains("em")) {
336 em = style.at(1);
337 } else if(style.at(1).contains("px")) {
338 em = style.at(1);
339 }
340 if (elementName == "body") {
341 bodyEm = em;
342 }
343 }
344 }
345
346 if (textAlign == "center") {
347 filteredStyles.append(" text-anchor:middle;");
348 } else if (textAlign == "right") {
349 filteredStyles.append(" text-anchor:end;");
350 } else if (textAlign == "left"){
351 filteredStyles.append(" text-anchor:start;");
352 }
353
354 filteredStyles.append(appendStyle);
355
356 if (!filteredStyles.isEmpty()) {
357 svgWriter.writeAttribute("style", filteredStyles);
358 previousStyleString = filteredStyles;
359 }
360
361
362 }
363 if (newLine && lineCount > 1) {
364 debugFlake << "\t\tAdvancing to the next line";
365 svgWriter.writeAttribute("x", "0");
366 svgWriter.writeAttribute("dy", em);
367 }
368 break;
369 }
370 case QXmlStreamReader::EndElement:
371 {
372 if (htmlReader.name() == "br") break;
373 if (elementName == "p" || elementName == "span" || elementName == "body") {
374 debugFlake << "\tEndElement" << htmlReader.name() << "(" << elementName << ")";
375 svgWriter.writeEndElement();
376 }
377 break;
378 }
379 case QXmlStreamReader::Characters:
380 {
381 if (elementName == "style") {
382 *styles = htmlReader.text().toString();
383 }
384 else {
385 //TODO: Think up what to do with mix of pretty-print and <BR> (what libreoffice uses).
386 //if (!htmlReader.isWhitespace()) {
387 debugFlake << "\tCharacters:" << htmlReader.text();
388 svgWriter.writeCharacters(htmlReader.text().toString());
389 //}
390 }
391 break;
392 }
393 default:
394 ;
395 }
396 }
397
398 if (htmlReader.hasError()) {
399 d->errors << htmlReader.errorString();
400 return false;
401 }
402 if (svgWriter.hasError()) {
403 d->errors << i18n("Unknown error writing SVG text element");
404 return false;
405 }
406
407 *svgText = QString::fromUtf8(svgBuffer.data());
408 return true;
409}
#define debugFlake
Definition FlakeDebug.h:15
const Params2D p

References d, debugFlake, p, and style().

◆ convertFromSvg()

bool KoSvgTextShapeMarkupConverter::convertFromSvg ( const QString & svgText,
const QString & stylesText,
const QRectF & boundsInPixels,
qreal pixelsPerInch )

upload the svg representation of text into the shape

Parameters
svgText<text> part of SVG
stylesText<defs> part of SVG (used only for gradients and patterns)
boundsInPixelsbounds of the entire image in pixel. Used for parsing percentage units.
pixelsPerInchresolution of the image where we load the shape to
Returns
true if the text was parsed successfully. Check errors() and warnings() for details.

Definition at line 111 of file KoSvgTextShapeMarkupConverter.cpp.

113{
114
115 debugFlake << "convertFromSvg. text:" << svgText << "styles:" << stylesText << "bounds:" << boundsInPixels << "ppi:" << pixelsPerInch;
116
117 d->clearErrors();
118
119 QString errorMessage;
120 int errorLine = 0;
121 int errorColumn = 0;
122
123 const QString fullText = QString("<svg>\n%1\n%2\n</svg>\n").arg(stylesText).arg(svgText);
124
125 QDomDocument doc = SvgParser::createDocumentFromSvg(fullText, &errorMessage, &errorLine, &errorColumn);
126 if (doc.isNull()) {
127 d->errors << QString("line %1, col %2: %3").arg(errorLine).arg(errorColumn).arg(errorMessage);
128 return false;
129 }
130
131 KoDocumentResourceManager resourceManager;
132 SvgParser parser(&resourceManager);
133 parser.setResolution(boundsInPixels, pixelsPerInch);
134
135 QDomElement root = doc.documentElement();
136 QDomNode node = root.firstChild();
137
138 bool textNodeFound = false;
139
140 for (; !node.isNull(); node = node.nextSibling()) {
141 QDomElement el = node.toElement();
142 if (el.isNull()) continue;
143
144 if (el.tagName() == "defs") {
145 parser.parseDefsElement(el);
146 }
147 else if (el.tagName() == "text") {
148 if (textNodeFound) {
149 d->errors << i18n("More than one 'text' node found!");
150 return false;
151 }
152
153 KoShape *shape = parser.parseTextElement(el, d->shape);
154 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape == d->shape, false);
155 textNodeFound = true;
156 break;
157 } else {
158 d->errors << i18n("Unknown node of type \'%1\' found!", el.tagName());
159 return false;
160 }
161 }
162
163 if (!textNodeFound) {
164 d->errors << i18n("No \'text\' node found!");
165 return false;
166 }
167
168 return true;
169
170}
static QDomDocument createDocumentFromSvg(QIODevice *device, QString *errorMsg=0, int *errorLine=0, int *errorColumn=0)
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129

References SvgParser::createDocumentFromSvg(), d, debugFlake, KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE, SvgParser::parseDefsElement(), SvgParser::parseTextElement(), and SvgParser::setResolution().

◆ convertSvgToDocument()

bool KoSvgTextShapeMarkupConverter::convertSvgToDocument ( const QString & svgText,
QTextDocument * doc )

convertSvgToDocument

Parameters
svgTextthe <text> element and it's children as a string.
docthe QTextDocument that the conversion is written to.
Returns
true if the conversion was successful

Definition at line 908 of file KoSvgTextShapeMarkupConverter.cpp.

909{
910 QXmlStreamReader svgReader(svgText.trimmed());
911 doc->clear();
912 QTextCursor cursor(doc);
913
914 struct BlockFormatRecord {
915 BlockFormatRecord() {}
916 BlockFormatRecord(QTextBlockFormat _blockFormat,
917 QTextCharFormat _charFormat)
918 : blockFormat(_blockFormat),
919 charFormat(_charFormat)
920 {}
921
922 QTextBlockFormat blockFormat;
923 QTextCharFormat charFormat;
924 };
925
926 QStack<BlockFormatRecord> formatStack;
927 formatStack.push(BlockFormatRecord(QTextBlockFormat(), QTextCharFormat()));
928 cursor.setCharFormat(formatStack.top().charFormat);
929 cursor.setBlockFormat(formatStack.top().blockFormat);
930
931 qreal currBlockAbsoluteLineOffset = 0.0;
932 int prevBlockCursorPosition = -1;
933 Qt::Alignment prevBlockAlignment = Qt::AlignLeft;
934 bool prevTspanHasTrailingLF = false;
935 qreal prevLineDescent = 0.0;
936 qreal prevLineAscent = 0.0;
937 // work around uninitialized memory warning, therefore, no boost::none
938 boost::optional<qreal> previousBlockAbsoluteXOffset =
939 boost::optional<qreal>(false, qreal());
940
941 std::optional<ExtraStyles> docExtraStyles;
942
943 while (!svgReader.atEnd()) {
944 QXmlStreamReader::TokenType token = svgReader.readNext();
945 switch (token) {
946 case QXmlStreamReader::StartElement:
947 {
948 prevTspanHasTrailingLF = false;
949
950 bool newBlock = false;
951 QTextBlockFormat newBlockFormat;
952 QTextCharFormat newCharFormat;
953 qreal absoluteLineOffset = 1.0;
954
955 // fetch format of the parent block and make it default
956 if (!formatStack.empty()) {
957 newBlockFormat = formatStack.top().blockFormat;
958 newCharFormat = formatStack.top().charFormat;
959 }
960
961 {
962 ExtraStyles extraStyles{};
963 const QXmlStreamAttributes elementAttributes = svgReader.attributes();
964 parseTextAttributes(elementAttributes, newCharFormat, newBlockFormat, extraStyles);
965
966 if (!docExtraStyles && svgReader.name() == QLatin1String("text")) {
967 if (extraStyles.inlineSize > 0.0) {
968 // There is a valid inline-size, forcing pre-wrap mode.
969 extraStyles.wrappingMode = WrappingMode::WhiteSpacePreWrap;
970 } else if (extraStyles.wrappingMode == WrappingMode::WhiteSpacePreWrap && extraStyles.inlineSize <= 0.0) {
971 // Without a valid inline-size, there is no point using
972 // pre-wrap, so change to pre.
973 extraStyles.wrappingMode = WrappingMode::WhiteSpacePre;
974 }
975 docExtraStyles = extraStyles;
976 }
977
978 // For WrappingMode::QtLegacy,
979 // mnemonic for a newline is (dy != 0 && (x == prevX || alignmentChanged))
980
981 // work around uninitialized memory warning, therefore, no
982 // boost::none
983 boost::optional<qreal> blockAbsoluteXOffset =
984 boost::make_optional(false, qreal());
985
986 if (elementAttributes.hasAttribute("x")) {
987 QString xString = elementAttributes.value("x").toString();
988 if (xString.contains("pt")) {
989 xString = xString.remove("pt").trimmed();
990 }
991 blockAbsoluteXOffset = fixToQtDpi(KisDomUtils::toDouble(xString));
992 }
993
994 // Get current text alignment: If current block has alignment,
995 // use it. Otherwise, try to inherit from parent block.
996 Qt::Alignment thisBlockAlignment = Qt::AlignLeft;
997 if (newBlockFormat.hasProperty(QTextBlockFormat::BlockAlignment)) {
998 thisBlockAlignment = newBlockFormat.alignment();
999 } else if (!formatStack.empty()) {
1000 thisBlockAlignment = formatStack.top().blockFormat.alignment();
1001 }
1002
1003 const auto isSameXOffset = [&]() {
1004 return previousBlockAbsoluteXOffset && blockAbsoluteXOffset
1005 && qFuzzyCompare(*previousBlockAbsoluteXOffset, *blockAbsoluteXOffset);
1006 };
1007 if ((isSameXOffset() || thisBlockAlignment != prevBlockAlignment) && svgReader.name() != "text"
1008 && elementAttributes.hasAttribute("dy")) {
1009
1010 QString dyString = elementAttributes.value("dy").toString();
1011 if (dyString.contains("pt")) {
1012 dyString = dyString.remove("pt").trimmed();
1013 }
1014
1015 KIS_SAFE_ASSERT_RECOVER_NOOP(formatStack.isEmpty() == (svgReader.name() == "text"));
1016
1017 absoluteLineOffset = fixToQtDpi(KisDomUtils::toDouble(dyString));
1018 newBlock = absoluteLineOffset > 0;
1019 }
1020
1021 if (elementAttributes.hasAttribute("x")) {
1022 previousBlockAbsoluteXOffset = blockAbsoluteXOffset;
1023 }
1024 prevBlockAlignment = thisBlockAlignment;
1025 }
1026
1027 //hack
1028 doc->setTextWidth(100);
1029 doc->setTextWidth(-1);
1030
1031 if (newBlock && absoluteLineOffset > 0) {
1032 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!formatStack.isEmpty(), false);
1033 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cursor.block().layout()->lineCount() > 0, false);
1034
1035 QTextLine line = cursor.block().layout()->lineAt(0);
1036
1037 if (prevBlockCursorPosition >= 0) {
1038 postCorrectBlockHeight(doc, line.ascent(), prevLineAscent, prevLineDescent,
1039 prevBlockCursorPosition, currBlockAbsoluteLineOffset);
1040 }
1041
1042 prevBlockCursorPosition = cursor.position();
1043 prevLineAscent = line.ascent();
1044 prevLineDescent = line.descent();
1045 currBlockAbsoluteLineOffset = absoluteLineOffset;
1046
1047 cursor.insertBlock();
1048 cursor.setCharFormat(formatStack.top().charFormat);
1049 cursor.setBlockFormat(formatStack.top().blockFormat);
1050 }
1051
1052 cursor.mergeCharFormat(newCharFormat);
1053 cursor.mergeBlockFormat(newBlockFormat);
1054
1055 formatStack.push(BlockFormatRecord(cursor.blockFormat(), cursor.charFormat()));
1056
1057 break;
1058 }
1059 case QXmlStreamReader::EndElement:
1060 {
1061 if (svgReader.name() != "text") {
1062 formatStack.pop();
1063 KIS_SAFE_ASSERT_RECOVER(!formatStack.isEmpty()) { break; }
1064
1065 cursor.setCharFormat(formatStack.top().charFormat);
1066 // For legacy wrapping mode, don't reset block format here
1067 // because this will break the block formats of the current
1068 // (last) block. The latest block format will be applied when
1069 // creating a new block.
1070 // However, resetting the block format is required for pre/pre-
1071 // wrap modes because they do not trigger the same code that
1072 // creates the new block; these modes rely on a trailing `\n`
1073 // at the end of a tspan to start a new block. At this point, if
1074 // there was indeed a trailing `\n`, this means we are already
1075 // in a new block.
1076 if (docExtraStyles && docExtraStyles->wrappingMode != WrappingMode::QtLegacy
1077 && prevTspanHasTrailingLF) {
1078 cursor.setBlockFormat(formatStack.top().blockFormat);
1079 }
1080 prevTspanHasTrailingLF = false;
1081 }
1082 break;
1083 }
1084 case QXmlStreamReader::Characters:
1085 {
1086 cursor.insertText(svgReader.text().toString());
1087 prevTspanHasTrailingLF = svgReader.text().endsWith('\n');
1088 break;
1089 }
1090 default:
1091 break;
1092 }
1093 }
1094
1095 if (prevBlockCursorPosition >= 0) {
1096 QTextLine line = cursor.block().layout()->lineAt(0);
1097 postCorrectBlockHeight(doc, line.ascent(), prevLineAscent, prevLineDescent,
1098 prevBlockCursorPosition, currBlockAbsoluteLineOffset);
1099 }
1100
1101 {
1102 if (!docExtraStyles) {
1103 docExtraStyles = ExtraStyles{};
1104 }
1105 QTextFrameFormat f = doc->rootFrame()->frameFormat();
1106 setWrappingMode(&f, docExtraStyles->wrappingMode);
1107 setInlineSize(&f, docExtraStyles->inlineSize);
1108 doc->rootFrame()->setFrameFormat(f);
1109 }
1110
1111 if (svgReader.hasError()) {
1112 d->errors << svgReader.errorString();
1113 return false;
1114 }
1115 doc->setModified(false);
1116 return true;
1117}
void parseTextAttributes(const QXmlStreamAttributes &elementAttributes, QTextCharFormat &charFormat, QTextBlockFormat &blockFormat, KoSvgTextShapeMarkupConverter::ExtraStyles &extraStyles)
qreal fixToQtDpi(qreal value)
void postCorrectBlockHeight(QTextDocument *doc, qreal currLineAscent, qreal prevLineAscent, qreal prevLineDescent, int prevBlockCursorPosition, qreal currentBlockAbsoluteLineOffset)
static void setWrappingMode(QTextFrameFormat *frameFormat, WrappingMode wrappingMode)
Set the Wrapping Mode on a frame format to be applied to the rootFrame of a QTextDocument.
static void setInlineSize(QTextFrameFormat *frameFormat, double inlineSize)
Set or unset the inline-size on a frameFormat to be applied to the rootFrame of a QTextDocument.
static bool qFuzzyCompare(half p1, half p2)
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
double toDouble(const QString &str, bool *ok=nullptr)

References d, fixToQtDpi(), KIS_SAFE_ASSERT_RECOVER, KIS_SAFE_ASSERT_RECOVER_NOOP, KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE, parseTextAttributes(), postCorrectBlockHeight(), qFuzzyCompare(), QtLegacy, setInlineSize(), setWrappingMode(), KisDomUtils::toDouble(), WhiteSpacePre, and WhiteSpacePreWrap.

◆ convertToHtml()

bool KoSvgTextShapeMarkupConverter::convertToHtml ( QString * htmlText)

convertToHtml convert the text in the text shape to html

Parameters
htmlTextwill be filled with correct html representing the text in the shape
Returns
true on success

Definition at line 172 of file KoSvgTextShapeMarkupConverter.cpp.

173{
174 d->clearErrors();
175
176 QBuffer shapesBuffer;
177 shapesBuffer.open(QIODevice::WriteOnly);
178 {
179 HtmlWriter writer({d->shape});
180 if (!writer.save(shapesBuffer)) {
181 d->errors = writer.errors();
182 d->warnings = writer.warnings();
183 return false;
184 }
185 }
186
187 shapesBuffer.close();
188
189 *htmlText = QString(shapesBuffer.data());
190
191 debugFlake << "\t\t" << *htmlText;
192
193 return true;
194}
QStringList errors() const

References d, debugFlake, and HtmlWriter::errors().

◆ convertToSvg()

bool KoSvgTextShapeMarkupConverter::convertToSvg ( QString * svgText,
QString * stylesText )

Convert the text shape into two strings: text and styles. Styles string is non-empty only when the text has some gradient/pattern attached. It is intended to be places into a separate tab in the GUI.

Returns
true on success

Definition at line 85 of file KoSvgTextShapeMarkupConverter.cpp.

86{
87 d->clearErrors();
88
89 QBuffer shapesBuffer;
90 QBuffer stylesBuffer;
91
92 shapesBuffer.open(QIODevice::WriteOnly);
93 stylesBuffer.open(QIODevice::WriteOnly);
94
95 {
96 SvgSavingContext savingContext(shapesBuffer, stylesBuffer);
97 savingContext.setStrippedTextMode(true);
98 SvgWriter writer({d->shape});
99 writer.saveDetached(savingContext);
100 }
101
102 shapesBuffer.close();
103 stylesBuffer.close();
104
105 *svgText = QString::fromUtf8(shapesBuffer.data());
106 *stylesText = QString::fromUtf8(stylesBuffer.data());
107
108 return true;
109}
Context for saving svg files.
Implements exporting shapes to SVG.
Definition SvgWriter.h:33
bool saveDetached(QIODevice &outputDevice)

References d, SvgWriter::saveDetached(), and SvgSavingContext::setStrippedTextMode().

◆ errors()

QStringList KoSvgTextShapeMarkupConverter::errors ( ) const

A list of errors happened during loading the user's text

Definition at line 1121 of file KoSvgTextShapeMarkupConverter.cpp.

1122{
1123 return d->errors;
1124}

References d.

◆ formatDifference()

QTextFormat KoSvgTextShapeMarkupConverter::formatDifference ( QTextFormat test,
QTextFormat reference )

formatDifference A class to get the difference between two text-char formats.

Parameters
testthe format to test
referencethe format to test against.
Returns
the difference between the two.

Definition at line 1679 of file KoSvgTextShapeMarkupConverter.cpp.

1680{
1681 //copied from QTextDocument.cpp
1682 QTextFormat diff = test;
1683 //props should proly compare itself to the main text format...
1684 const QMap<int, QVariant> props = reference.properties();
1685 for (QMap<int, QVariant>::ConstIterator it = props.begin(), end = props.end();
1686 it != end; ++it)
1687 if (it.value() == test.property(it.key())) {
1688 // Some props must not be removed as default state gets in the way.
1689 switch (it.key()) {
1690 case QTextFormat::TextUnderlineStyle: // 0x2023
1691 case QTextFormat::FontLetterSpacingType: // 0x2033 in Qt5, but is 0x1FE9 in Qt6
1692 case QTextFormat::LineHeightType:
1693 continue;
1694 }
1695 diff.clearProperty(it.key());
1696 }
1697 return diff;
1698}

◆ getInlineSize()

std::optional< double > KoSvgTextShapeMarkupConverter::getInlineSize ( const QTextFrameFormat & frameFormat)
static

Get the inline-size from the frameFormat of the rootFrame of a QTextDocument.

Parameters
frameFormat
Returns
std::optional<double> the inline-size if set, or std::nullopt

Definition at line 1715 of file KoSvgTextShapeMarkupConverter.cpp.

1716{
1717 const QVariant inlineSize = frameFormat.property(InlineSizeProperty);
1718 if (inlineSize.userType() != QMetaType::Double) {
1719 return {};
1720 }
1721 const double val = inlineSize.toDouble();
1722 if (val > 0.0) {
1723 return {val};
1724 }
1725 return {};
1726}
static constexpr QTextFormat::Property InlineSizeProperty

References InlineSizeProperty.

◆ getWrappingMode()

KoSvgTextShapeMarkupConverter::WrappingMode KoSvgTextShapeMarkupConverter::getWrappingMode ( const QTextFrameFormat & frameFormat)
static

Get the Wrapping Mode from the frameFormat of the rootFrame of a QTextDocument.

Parameters
frameFormat
Returns
WrappingMode if set, or the default value WrappingMode::QtLegacy

Definition at line 1701 of file KoSvgTextShapeMarkupConverter.cpp.

1702{
1703 const QVariant wrappingMode = frameFormat.property(WrappingModeProperty);
1704 if (wrappingMode.userType() != QMetaType::Int) {
1706 }
1707 return static_cast<WrappingMode>(wrappingMode.toInt());
1708}
static constexpr QTextFormat::Property WrappingModeProperty

References QtLegacy, and WrappingModeProperty.

◆ setInlineSize()

void KoSvgTextShapeMarkupConverter::setInlineSize ( QTextFrameFormat * frameFormat,
double inlineSize )
static

Set or unset the inline-size on a frameFormat to be applied to the rootFrame of a QTextDocument.

Parameters
frameFormatpointer to the frameFormat to be modified
inlineSizethe inline-size; pass a negative value to unset the property

Definition at line 1728 of file KoSvgTextShapeMarkupConverter.cpp.

1729{
1730 if (inlineSize >= 0.0) {
1731 frameFormat->setProperty(InlineSizeProperty, inlineSize);
1732 } else {
1733 frameFormat->clearProperty(InlineSizeProperty);
1734 }
1735}

References InlineSizeProperty.

◆ setWrappingMode()

void KoSvgTextShapeMarkupConverter::setWrappingMode ( QTextFrameFormat * frameFormat,
WrappingMode wrappingMode )
static

Set the Wrapping Mode on a frame format to be applied to the rootFrame of a QTextDocument.

Parameters
frameFormatpointer to the franeFormat to be modified
wrappingMode

Definition at line 1710 of file KoSvgTextShapeMarkupConverter.cpp.

1711{
1712 frameFormat->setProperty(WrappingModeProperty, static_cast<int>(wrappingMode));
1713}

References WrappingModeProperty.

◆ style()

QString KoSvgTextShapeMarkupConverter::style ( QTextCharFormat format,
QTextBlockFormat blockFormat,
QTextCharFormat mostCommon = QTextCharFormat(),
bool includeLineHeight = false )

style creates a style string based on the blockformat and the format.

Parameters
formatthe textCharFormat of the current text.
blockFormatthe block format of the current text.
mostCommonthe most common format to compare the format to.
includeLineHeightwhether the style should include line-height.
Returns
a string that can be written into a style element.

Definition at line 1174 of file KoSvgTextShapeMarkupConverter.cpp.

1178{
1180 for(int i=0; i<format.properties().size(); i++) {
1181 QString c;
1182 int propertyId = format.properties().keys().at(i);
1183
1184 if (propertyId == QTextCharFormat::FontFamily) {
1185 const QString fontFamily = format.properties()[propertyId].toString();
1186 c.append("font-family").append(":").append(fontFamily);
1187 }
1188 if (propertyId == QTextCharFormat::FontPointSize ||
1189 propertyId == QTextCharFormat::FontPixelSize) {
1190
1191 // in Krita we unify point size and pixel size of the font
1192
1193 c.append("font-size").append(":")
1194 .append(format.properties()[propertyId].toString());
1195 }
1196 if (propertyId == QTextCharFormat::FontWeight) {
1197 // Convert from QFont::Weight range to SVG range,
1198 // as defined in qt's qfont.h
1199 int convertedWeight = 400; // Defaulting to Weight::Normal in svg scale
1200
1201 switch (format.properties()[propertyId].toInt()) {
1202 case QFont::Weight::Thin:
1203 convertedWeight = 100;
1204 break;
1205 case QFont::Weight::ExtraLight:
1206 convertedWeight = 200;
1207 break;
1208 case QFont::Weight::Light:
1209 convertedWeight = 300;
1210 break;
1211 case QFont::Weight::Normal:
1212 convertedWeight = 400;
1213 break;
1214 case QFont::Weight::Medium:
1215 convertedWeight = 500;
1216 break;
1217 case QFont::Weight::DemiBold:
1218 convertedWeight = 600;
1219 break;
1220 case QFont::Weight::Bold:
1221 convertedWeight = 700;
1222 break;
1223 case QFont::Weight::ExtraBold:
1224 convertedWeight = 800;
1225 break;
1226 case QFont::Weight::Black:
1227 convertedWeight = 900;
1228 break;
1229 default:
1230 warnFile << "WARNING: Invalid QFont::Weight value supplied to KoSvgTextShapeMarkupConverter::style.";
1231 break;
1232 }
1233
1234 c.append("font-weight").append(":")
1235 .append(QString::number(convertedWeight));
1236 }
1237 if (propertyId == QTextCharFormat::FontItalic) {
1238 QString val = "italic";
1239 if (!format.fontItalic()) {
1240 val = "normal";
1241 }
1242 c.append("font-style").append(":")
1243 .append(val);
1244 }
1245
1246 if (propertyId == QTextCharFormat::FontCapitalization) {
1247 if (format.fontCapitalization() == QFont::SmallCaps){
1248 c.append("font-variant").append(":")
1249 .append("small-caps");
1250 } else if (format.fontCapitalization() == QFont::AllUppercase) {
1251 c.append("text-transform").append(":")
1252 .append("uppercase");
1253 } else if (format.fontCapitalization() == QFont::AllLowercase) {
1254 c.append("text-transform").append(":")
1255 .append("lowercase");
1256 } else if (format.fontCapitalization() == QFont::Capitalize) {
1257 c.append("text-transform").append(":")
1258 .append("capitalize");
1259 }
1260 }
1261
1262 if (propertyId == QTextCharFormat::FontStretch) {
1263 QString valueString = QString::number(format.fontStretch(), 10);
1264 if (format.fontStretch() == QFont::ExtraCondensed) {
1265 valueString = "extra-condensed";
1266 } else if (format.fontStretch() == QFont::SemiCondensed) {
1267 valueString = "semi-condensed";
1268 } else if (format.fontStretch() == QFont::Condensed) {
1269 valueString = "condensed";
1270 } else if (format.fontStretch() == QFont::AnyStretch) {
1271 valueString = "normal";
1272 } else if (format.fontStretch() == QFont::Expanded) {
1273 valueString = "expanded";
1274 } else if (format.fontStretch() == QFont::SemiExpanded) {
1275 valueString = "semi-expanded";
1276 } else if (format.fontStretch() == QFont::ExtraExpanded) {
1277 valueString = "extra-expanded";
1278 } else if (format.fontStretch() == QFont::UltraExpanded) {
1279 valueString = "ultra-expanded";
1280 }
1281 c.append("font-stretch").append(":")
1282 .append(valueString);
1283 }
1284 if (propertyId == QTextCharFormat::FontKerning) {
1285 QString val;
1286 if (format.fontKerning()) {
1287 val = "auto";
1288 } else {
1289 val = "0";
1290 }
1291 c.append("kerning").append(":")
1292 .append(val);
1293 }
1294 if (propertyId == QTextCharFormat::FontWordSpacing) {
1295 c.append("word-spacing").append(":")
1296 .append(QString::number(format.fontWordSpacing()));
1297 }
1298 if (propertyId == QTextCharFormat::FontLetterSpacing) {
1299 QString val;
1300 if (format.fontLetterSpacingType()==QFont::AbsoluteSpacing) {
1301 val = QString::number(format.fontLetterSpacing());
1302 } else {
1303 val = QString::number(((format.fontLetterSpacing()/100)*format.fontPointSize()));
1304 }
1305 c.append("letter-spacing").append(":")
1306 .append(val);
1307 }
1308 if (propertyId == QTextCharFormat::TextOutline) {
1309 if (format.textOutline().color() != mostCommon.textOutline().color()) {
1310 c.append("stroke").append(":")
1311 .append(format.textOutline().color().name());
1312 style.append(c);
1313 c.clear();
1314 }
1315 if (format.textOutline().width() != mostCommon.textOutline().width()) {
1316 c.append("stroke-width").append(":")
1317 .append(QString::number(format.textOutline().width()));
1318 }
1319 }
1320
1321
1322 if (propertyId == QTextCharFormat::TextVerticalAlignment) {
1323 QString val = "baseline";
1324 if (format.verticalAlignment() == QTextCharFormat::AlignSubScript) {
1325 val = QLatin1String("sub");
1326 }
1327 else if (format.verticalAlignment() == QTextCharFormat::AlignSuperScript) {
1328 val = QLatin1String("super");
1329 }
1330 c.append("baseline-shift").append(":").append(val);
1331 }
1332
1333 if (propertyId == QTextCharFormat::ForegroundBrush) {
1334 QColor::NameFormat colorFormat;
1335
1336 if (format.foreground().color().alphaF() < 1.0) {
1337 colorFormat = QColor::HexArgb;
1338 } else {
1339 colorFormat = QColor::HexRgb;
1340 }
1341
1342 c.append("fill").append(":")
1343 .append(format.foreground().color().name(colorFormat));
1344 }
1345
1346 if (!c.isEmpty()) {
1347 style.append(c);
1348 }
1349 }
1350
1351 if (!compareFormatUnderlineWithMostCommon(format, mostCommon)) {
1352
1353 QString c = convertFormatUnderlineToSvg(format);
1354 if (!c.isEmpty()) {
1355 style.append(c);
1356 }
1357 }
1358
1359 if (blockFormat.hasProperty(QTextBlockFormat::BlockAlignment)) {
1360 // TODO: Alignment works incorrectly! The offsets should be calculated
1361 // according to the shape width/height!
1362
1363 QString c;
1364 QString val;
1365 if (blockFormat.alignment()==Qt::AlignRight) {
1366 val = "end";
1367 } else if (blockFormat.alignment()==Qt::AlignCenter) {
1368 val = "middle";
1369 } else {
1370 val = "start";
1371 }
1372 c.append("text-anchor").append(":")
1373 .append(val);
1374 if (!c.isEmpty()) {
1375 style.append(c);
1376 }
1377 }
1378
1379 if (includeLineHeight && blockFormat.hasProperty(QTextBlockFormat::LineHeight)) {
1380 double h = 0;
1381 if (blockFormat.lineHeightType() == QTextBlockFormat::ProportionalHeight) {
1382 h = blockFormat.lineHeight() / 100.0;
1383 } else if (blockFormat.lineHeightType() == QTextBlockFormat::SingleHeight) {
1384 h = -1.0;
1385 }
1386 QString c = "line-height:";
1387 if (h >= 0) {
1388 c += QString::number(blockFormat.lineHeight() / 100.0);
1389 } else {
1390 c += "normal";
1391 }
1392 style.append(c);
1393 }
1394
1395 return style.join("; ");
1396}
bool compareFormatUnderlineWithMostCommon(QTextCharFormat format, QTextCharFormat mostCommon)
QString convertFormatUnderlineToSvg(QTextCharFormat format)
#define warnFile
Definition kis_debug.h:95
int size(const Forest< T > &forest)
Definition KisForest.h:1232

References compareFormatUnderlineWithMostCommon(), convertFormatUnderlineToSvg(), style(), and warnFile.

◆ stylesFromString()

QVector< QTextFormat > KoSvgTextShapeMarkupConverter::stylesFromString ( QStringList styles,
QTextCharFormat currentCharFormat,
QTextBlockFormat currentBlockFormat,
ExtraStyles & extraStyles )
static

stylesFromString returns a qvector with two textformats: at 0 is the QTextCharFormat at 1 is the QTextBlockFormat

Parameters
stylesa style string split at ";"
currentCharFormatthe current charformat to compare against.
currentBlockFormatthe current blockformat to compare against.
[out]extraStylesother styles.
Returns
A QVector with at 0 a QTextCharFormat and at 1 a QBlockCharFormat.

Definition at line 1398 of file KoSvgTextShapeMarkupConverter.cpp.

1402{
1403 Q_UNUSED(currentBlockFormat);
1404
1405 QVector<QTextFormat> formats;
1406 QTextCharFormat charFormat;
1407 charFormat.setTextOutline(currentCharFormat.textOutline());
1408 QTextBlockFormat blockFormat;
1409 QScopedPointer<SvgGraphicsContext> context(new SvgGraphicsContext());
1411
1412 for (int i=0; i<styles.size(); i++) {
1413 if (!styles.at(i).isEmpty()){
1414 QStringList style = styles.at(i).split(":");
1415 // ignore the property instead of crashing,
1416 // if user forgets to separate property name and value with ':'.
1417 if (style.size() < 2) {
1418 continue;
1419 }
1420
1421 QString property = style.at(0).trimmed();
1422 QString value = style.at(1).trimmed();
1423
1424 if (property == "font-family") {
1425 charFormat.setFontFamily(value);
1426 }
1427
1428 if (property == "font-size") {
1429 qreal val = SvgUtil::parseUnitX(context.data(), resolved, value);
1430 charFormat.setFontPointSize(val);
1431 }
1432
1433 if (property == "font-variant") {
1434 if (value=="small-caps") {
1435 charFormat.setFontCapitalization(QFont::SmallCaps);
1436 } else {
1437 charFormat.setFontCapitalization(QFont::MixedCase);
1438 }
1439 }
1440
1441 if (property == "font-style") {
1442 if (value=="italic" || value=="oblique") {
1443 charFormat.setFontItalic(true);
1444 } else {
1445 charFormat.setFontItalic(false);
1446 }
1447 }
1448
1449 if (property == "font-stretch") {
1450 if (value == "ultra-condensed") {
1451 charFormat.setFontStretch(QFont::UltraCondensed);
1452 } else if (value == "condensed") {
1453 charFormat.setFontStretch(QFont::Condensed);
1454 } else if (value == "semi-condensed") {
1455 charFormat.setFontStretch(QFont::SemiCondensed);
1456 } else if (value == "normal") {
1457 charFormat.setFontStretch(100);
1458 } else if (value == "semi-expanded") {
1459 charFormat.setFontStretch(QFont::SemiExpanded);
1460 } else if (value == "expanded") {
1461 charFormat.setFontStretch(QFont::Expanded);
1462 } else if (value == "extra-expanded") {
1463 charFormat.setFontStretch(QFont::ExtraExpanded);
1464 } else if (value == "ultra-expanded") {
1465 charFormat.setFontStretch(QFont::UltraExpanded);
1466 } else { // "normal"
1467 charFormat.setFontStretch(value.toInt());
1468 }
1469 }
1470
1471 if (property == "font-weight") {
1472 // Convert from SVG range to QFont::Weight range,
1473 // as defined in qt's qfont.h
1474 int convertedWeight = QFont::Weight::Normal; // Defaulting to Weight::Normal
1475
1476 switch (value.toInt()) {
1477 case 100:
1478 convertedWeight = QFont::Weight::Thin;
1479 break;
1480 case 200:
1481 convertedWeight = QFont::Weight::ExtraLight;
1482 break;
1483 case 300:
1484 convertedWeight = QFont::Weight::Light;
1485 break;
1486 case 400:
1487 convertedWeight = QFont::Weight::Normal;
1488 break;
1489 case 500:
1490 convertedWeight = QFont::Weight::Medium;
1491 break;
1492 case 600:
1493 convertedWeight = QFont::Weight::DemiBold;
1494 break;
1495 case 700:
1496 convertedWeight = QFont::Weight::Bold;
1497 break;
1498 case 800:
1499 convertedWeight = QFont::Weight::ExtraBold;
1500 break;
1501 case 900:
1502 convertedWeight = QFont::Weight::Black;
1503 break;
1504 default:
1505 warnFile << "WARNING: Invalid weight value supplied to KoSvgTextShapeMarkupConverter::stylesFromString.";
1506 break;
1507 }
1508
1509 charFormat.setFontWeight(convertedWeight);
1510 }
1511
1512 if (property == "text-decoration") {
1513 charFormat.setFontUnderline(false);
1514 charFormat.setFontOverline(false);
1515 charFormat.setFontStrikeOut(false);
1516 QStringList values = value.split(" ");
1517 if (values.contains("line-through")) {
1518 charFormat.setFontStrikeOut(true);
1519 }
1520 if (values.contains("overline")) {
1521 charFormat.setFontOverline(true);
1522 }
1523 if(values.contains("underline")){
1524 charFormat.setFontUnderline(true);
1525 }
1526 }
1527
1528 if (property == "text-transform") {
1529 if (value == "uppercase") {
1530 charFormat.setFontCapitalization(QFont::AllUppercase);
1531 } else if (value == "lowercase") {
1532 charFormat.setFontCapitalization(QFont::AllLowercase);
1533 } else if (value == "capitalize") {
1534 charFormat.setFontCapitalization(QFont::Capitalize);
1535 } else{
1536 charFormat.setFontCapitalization(QFont::MixedCase);
1537 }
1538 }
1539
1540 if (property == "letter-spacing") {
1541 qreal val = SvgUtil::parseUnitX(context.data(), resolved, value);
1542 charFormat.setFontLetterSpacingType(QFont::AbsoluteSpacing);
1543 charFormat.setFontLetterSpacing(val);
1544 }
1545
1546 if (property == "word-spacing") {
1547 qreal val = SvgUtil::parseUnitX(context.data(), resolved, value);
1548 charFormat.setFontWordSpacing(val);
1549 }
1550
1551 if (property == "kerning") {
1552 if (value == "auto") {
1553 charFormat.setFontKerning(true);
1554 } else {
1555 qreal val = SvgUtil::parseUnitX(context.data(), resolved, value);
1556 charFormat.setFontKerning(false);
1557 charFormat.setFontLetterSpacingType(QFont::AbsoluteSpacing);
1558 charFormat.setFontLetterSpacing(charFormat.fontLetterSpacing() + val);
1559 }
1560 }
1561
1562 if (property == "stroke") {
1563 QPen pen = charFormat.textOutline();
1564 QColor color;
1565 color.setNamedColor(value);
1566 pen.setColor(color);
1567 charFormat.setTextOutline(pen);
1568 }
1569
1570 if (property == "stroke-width") {
1571 QPen pen = charFormat.textOutline();
1572 pen.setWidth(value.toInt());
1573 charFormat.setTextOutline(pen);
1574 }
1575
1576 if (property == "fill") {
1577 QColor color;
1578 color.setNamedColor(value);
1579
1580 // avoid assertion failure in `KoColor` later
1581 if (!color.isValid()) {
1582 continue;
1583 }
1584
1585 // default color is #ff000000, so default alpha will be 1.0
1586 qreal currentAlpha = charFormat.foreground().color().alphaF();
1587
1588 // if alpha was already defined by `fill-opacity` prop
1589 if (currentAlpha < 1.0) {
1590 // and `fill` doesn't have alpha component
1591 if (color.alphaF() < 1.0) {
1592 color.setAlphaF(currentAlpha);
1593 }
1594 }
1595
1596 charFormat.setForeground(color);
1597 }
1598
1599 if (property == "fill-opacity") {
1600 QColor color = charFormat.foreground().color();
1601 bool ok = true;
1602 qreal alpha = qBound(0.0, SvgUtil::fromPercentage(value, &ok), 1.0);
1603
1604 // if conversion fails due to non-numeric input,
1605 // it defaults to 0.0, default to current alpha instead
1606 if (!ok) {
1607 alpha = color.alphaF();
1608 }
1609 color.setAlphaF(alpha);
1610 charFormat.setForeground(color);
1611 }
1612
1613 if (property == "text-anchor") {
1614 if (value == "end") {
1615 blockFormat.setAlignment(Qt::AlignRight);
1616 } else if (value == "middle") {
1617 blockFormat.setAlignment(Qt::AlignCenter);
1618 } else {
1619 blockFormat.setAlignment(Qt::AlignLeft);
1620 }
1621 }
1622
1623 if (property == "baseline-shift") {
1624 if (value == "super") {
1625 charFormat.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
1626 } else if (value == "sub") {
1627 charFormat.setVerticalAlignment(QTextCharFormat::AlignSubScript);
1628 } else {
1629 charFormat.setVerticalAlignment(QTextCharFormat::AlignNormal);
1630 }
1631 }
1632
1633 if (property == "line-height") {
1634 double lineHeightPercent = -1.0;
1635 bool ok = false;
1636 if (value.endsWith('%')) {
1637 // Note: Percentage line-height behaves differently than
1638 // unitless number in case of nested descendant elements,
1639 // but here we pretend they are the same.
1640 lineHeightPercent = value.left(value.length() - 1).toDouble(&ok);
1641 if (!ok) {
1642 lineHeightPercent = -1.0;
1643 }
1644 } else if(const double unitless = value.toDouble(&ok); ok) {
1645 lineHeightPercent = unitless * 100.0;
1646 } else if (value == QLatin1String("normal")) {
1647 lineHeightPercent = -1.0;
1648 blockFormat.setLineHeight(1, QTextBlockFormat::SingleHeight);
1649 }
1650 if (lineHeightPercent >= 0) {
1651 blockFormat.setLineHeight(lineHeightPercent, QTextBlockFormat::ProportionalHeight);
1652 }
1653 }
1654
1655 if (property == "inline-size") {
1656 const qreal val = SvgUtil::parseUnitX(context.data(), resolved, value);
1657 if (val > 0.0) {
1658 extraStyles.inlineSize = val;
1659 }
1660 }
1661
1662 if (property == "white-space") {
1663 if (value == QLatin1String("pre")) {
1664 extraStyles.wrappingMode = WrappingMode::WhiteSpacePre;
1665 } else if (value == QLatin1String("pre-wrap")) {
1666 extraStyles.wrappingMode = WrappingMode::WhiteSpacePreWrap;
1667 } else {
1668 extraStyles.wrappingMode = WrappingMode::QtLegacy;
1669 }
1670 }
1671 }
1672 }
1673
1674 formats.append(charFormat);
1675 formats.append(blockFormat);
1676 return formats;
1677}
float value(const T *src, size_t ch)
static const KoSvgTextProperties & defaultProperties()
static qreal parseUnitX(SvgGraphicsContext *gc, const KoSvgTextProperties &resolved, const QString &unit)
parses a length attribute in x-direction
Definition SvgUtil.cpp:304
static double fromPercentage(QString s, bool *ok=nullptr)
Definition SvgUtil.cpp:64

References KoSvgTextProperties::defaultProperties(), SvgUtil::fromPercentage(), KoSvgTextShapeMarkupConverter::ExtraStyles::inlineSize, SvgUtil::parseUnitX(), QtLegacy, style(), value(), warnFile, WhiteSpacePre, WhiteSpacePreWrap, and KoSvgTextShapeMarkupConverter::ExtraStyles::wrappingMode.

◆ warnings()

QStringList KoSvgTextShapeMarkupConverter::warnings ( ) const

A list of warnings produced during loading the user's text

Definition at line 1126 of file KoSvgTextShapeMarkupConverter.cpp.

1127{
1128 return d->warnings;
1129}

References d.

Member Data Documentation

◆ d

const QScopedPointer<Private> KoSvgTextShapeMarkupConverter::d
private

Definition at line 135 of file KoSvgTextShapeMarkupConverter.h.

◆ InlineSizeProperty

constexpr QTextFormat::Property KoSvgTextShapeMarkupConverter::InlineSizeProperty
staticconstexpr
Initial value:
=
static_cast<QTextFormat::Property>(WrappingModeProperty + 1)

Definition at line 166 of file KoSvgTextShapeMarkupConverter.h.

◆ WrappingModeProperty

constexpr QTextFormat::Property KoSvgTextShapeMarkupConverter::WrappingModeProperty
staticconstexpr
Initial value:
=
static_cast<QTextFormat::Property>(QTextFormat::UserProperty + 56784)

Definition at line 164 of file KoSvgTextShapeMarkupConverter.h.


The documentation for this class was generated from the following files: