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 580 of file KoSvgTextShapeMarkupConverter.cpp.

581{
582 QBuffer svgBuffer;
583 svgBuffer.open(QIODevice::WriteOnly);
584
585 QXmlStreamWriter svgWriter(&svgBuffer);
586
587 // disable auto-formatting to avoid extra spaces appearing here and there
588 svgWriter.setAutoFormatting(false);
589
590
591 qreal maxParagraphWidth = 0.0;
592 QTextCharFormat mostCommonCharFormat;
593 QTextBlockFormat mostCommonBlockFormat;
594
595 struct LineInfo {
596 LineInfo() {}
597 LineInfo(QTextBlock _block, int _numSkippedLines)
598 : block(_block), numSkippedLines(_numSkippedLines)
599 {}
600
601 QTextBlock block;
602 int numSkippedLines = 0;
603 };
604
605 const WrappingMode wrappingMode = getWrappingMode(doc->rootFrame()->frameFormat());
606
623 QVector<LineInfo> lineInfoList;
624
625 {
626 QTextBlock block = doc->begin();
627
628 QList<QTextFormat> allCharFormats;
629 QList<QTextFormat> allBlockFormats;
630
631 int numSequentialEmptyLines = 0;
632
633 bool hasExplicitTextWidth = false;
634 if (wrappingMode == WrappingMode::WhiteSpacePreWrap) {
635 // If the doc is pre-wrap, we expect the inline-size to be set.
636 if (std::optional<double> inlineSize = getInlineSize(doc->rootFrame()->frameFormat())) {
637 if (*inlineSize > 0.0) {
638 hasExplicitTextWidth = true;
639 maxParagraphWidth = *inlineSize;
640 }
641 }
642 }
643
644 while (block.isValid()) {
645 if (wrappingMode != WrappingMode::QtLegacy || !block.text().trimmed().isEmpty()) {
646 lineInfoList.append(LineInfo(block, numSequentialEmptyLines));
647 numSequentialEmptyLines = 0;
648
649 if (!hasExplicitTextWidth) {
650 maxParagraphWidth = qMax(maxParagraphWidth, calcLineWidth(block));
651 }
652
653 allBlockFormats.append(block.blockFormat());
654 Q_FOREACH (const QTextLayout::FormatRange &range, block.textFormats()) {
655 QTextFormat format = range.format;
656 allCharFormats.append(format);
657 }
658 } else {
659 numSequentialEmptyLines++;
660 }
661
662 block = block.next();
663 }
664
665 mostCommonCharFormat = findMostCommonFormat(allCharFormats).toCharFormat();
666 mostCommonBlockFormat = findMostCommonFormat(allBlockFormats).toBlockFormat();
667 }
668
669 //Okay, now the actual writing.
670
671 QTextBlock block = doc->begin();
672
673 svgWriter.writeStartElement("text");
674
675 if (wrappingMode == WrappingMode::WhiteSpacePreWrap) {
676 // There can only be one text direction for pre-wrap, so take that of
677 // the first block.
678 if (block.textDirection() == Qt::RightToLeft) {
679 svgWriter.writeAttribute("direction", "rtl");
680 }
681 }
682
683 {
684 QString commonTextStyle = style(mostCommonCharFormat,
685 mostCommonBlockFormat,
686 {},
687 /*includeLineHeight=*/wrappingMode != WrappingMode::QtLegacy);
688 if (wrappingMode != WrappingMode::QtLegacy) {
689 if (!commonTextStyle.isEmpty()) {
690 commonTextStyle += "; ";
691 }
692 commonTextStyle += "white-space: pre";
693 if (wrappingMode == WrappingMode::WhiteSpacePreWrap) {
694 commonTextStyle += "-wrap;inline-size:";
695 commonTextStyle += QString::number(maxParagraphWidth);
696 }
697 }
698 if (!commonTextStyle.isEmpty()) {
699 svgWriter.writeAttribute("style", commonTextStyle);
700 }
701 }
702
703 // TODO: check if we should change into to float
704 int prevBlockRelativeLineSpacing = mostCommonBlockFormat.lineHeight();
705 int prevBlockLineType = mostCommonBlockFormat.lineHeightType();
706 qreal prevBlockAscent = 0.0;
707 qreal prevBlockDescent= 0.0;
708
709 Q_FOREACH (const LineInfo &info, lineInfoList) {
710 QTextBlock block = info.block;
711
712 const QTextBlockFormat blockFormatDiff = formatDifference(block.blockFormat(), mostCommonBlockFormat).toBlockFormat();
713 QTextCharFormat blockCharFormatDiff = QTextCharFormat();
714 const QVector<QTextLayout::FormatRange> formats = block.textFormats();
715 if (formats.size()==1) {
716 blockCharFormatDiff = formatDifference(formats.at(0).format, mostCommonCharFormat).toCharFormat();
717 if (wrappingMode == WrappingMode::WhiteSpacePreWrap) {
718 // For pre-wrap, be extra sure we are not writing text-anchor
719 // to the `tspan`s because they don't do anything.
720 blockCharFormatDiff.clearProperty(QTextBlockFormat::BlockAlignment);
721 }
722 }
723
724 const QTextLayout *layout = block.layout();
725 const QTextLine line = layout->lineAt(0);
726 if (!line.isValid()) {
727 // This layout probably has no lines at all. This can happen when
728 // wrappingMode != QtLegacy and the text doc is completely empty.
729 // It is safe to just skip the line. Trying to get its metrics will
730 // crash.
731 continue;
732 }
733
734 svgWriter.writeStartElement("tspan");
735
736 const QString text = block.text();
737
738 bool isRightToLeft;
739 switch (block.textDirection()) {
740 case Qt::LeftToRight:
741 isRightToLeft = false;
742 break;
743 case Qt::RightToLeft:
744 isRightToLeft = true;
745 break;
746 case Qt::LayoutDirectionAuto:
747 default:
748 // QTextBlock::textDirection() is not supposed to return these,
749 // but just in case...
750 isRightToLeft = guessIsRightToLeft(text);;
751 break;
752 }
753
754 if (isRightToLeft && wrappingMode != WrappingMode::WhiteSpacePreWrap) {
755 svgWriter.writeAttribute("direction", "rtl");
756 svgWriter.writeAttribute("unicode-bidi", "embed");
757 }
758
759 {
760 const QString blockStyleString = style(blockCharFormatDiff,
761 blockFormatDiff,
762 {},
763 /*includeLineHeight=*/wrappingMode != WrappingMode::QtLegacy);
764 if (!blockStyleString.isEmpty()) {
765 svgWriter.writeAttribute("style", blockStyleString);
766 }
767 }
768
769 if (wrappingMode != WrappingMode::WhiteSpacePreWrap) {
775 Qt::Alignment blockAlignment = block.blockFormat().alignment();
776 if (isRightToLeft) {
777 if (blockAlignment & Qt::AlignLeft) {
778 blockAlignment &= ~Qt::AlignLeft;
779 blockAlignment |= Qt::AlignRight;
780 } else if (blockAlignment & Qt::AlignRight) {
781 blockAlignment &= ~Qt::AlignRight;
782 blockAlignment |= Qt::AlignLeft;
783 }
784 }
785
786 if (blockAlignment & Qt::AlignHCenter) {
787 svgWriter.writeAttribute("x", KisDomUtils::toString(0.5 * maxParagraphWidth) + "pt");
788 } else if (blockAlignment & Qt::AlignRight) {
789 svgWriter.writeAttribute("x", KisDomUtils::toString(maxParagraphWidth) + "pt");
790 } else {
791 svgWriter.writeAttribute("x", "0");
792 }
793 }
794
795 if (wrappingMode == WrappingMode::QtLegacy && block.blockNumber() > 0) {
796 qreal lineHeightPt =
797 fixFromQtDpi(line.ascent()) - prevBlockAscent +
798 (prevBlockAscent + prevBlockDescent) * qreal(prevBlockRelativeLineSpacing) / 100.0;
799
800 const qreal currentLineSpacing = (info.numSkippedLines + 1) * lineHeightPt;
801 svgWriter.writeAttribute("dy", KisDomUtils::toString(currentLineSpacing) + "pt");
802 }
803
804 prevBlockRelativeLineSpacing =
805 blockFormatDiff.hasProperty(QTextFormat::LineHeight) ?
806 blockFormatDiff.lineHeight() :
807 mostCommonBlockFormat.lineHeight();
808
809 prevBlockLineType =
810 blockFormatDiff.hasProperty(QTextFormat::LineHeightType) ?
811 blockFormatDiff.lineHeightType() :
812 mostCommonBlockFormat.lineHeightType();
813
814 if (prevBlockLineType == QTextBlockFormat::SingleHeight) {
815 //single line will set lineHeight to 100%
816 prevBlockRelativeLineSpacing = 100;
817 }
818
819 prevBlockAscent = fixFromQtDpi(line.ascent());
820 prevBlockDescent = fixFromQtDpi(line.descent());
821
822
823 if (formats.size()>1) {
824 QStringList texts;
825 QVector<QTextCharFormat> charFormats;
826 for (int f=0; f<formats.size(); f++) {
827 QString chunk;
828 for (int c = 0; c<formats.at(f).length; c++) {
829 chunk.append(text.at(formats.at(f).start+c));
830 }
831 texts.append(chunk);
832 charFormats.append(formats.at(f).format);
833 }
834
835 for (int c = 0; c<texts.size(); c++) {
836 QTextCharFormat diff = formatDifference(charFormats.at(c), mostCommonCharFormat).toCharFormat();
837 const QString subStyle = style(diff, QTextBlockFormat(), mostCommonCharFormat);
838 if (!subStyle.isEmpty()) {
839 svgWriter.writeStartElement("tspan");
840 svgWriter.writeAttribute("style", subStyle);
841 svgWriter.writeCharacters(texts.at(c));
842 svgWriter.writeEndElement();
843 } else {
844 svgWriter.writeCharacters(texts.at(c));
845 }
846 }
847
848 } else {
849 svgWriter.writeCharacters(text);
850 //check format against
851 }
852
853 // Add line-breaks for `pre` modes, but not for the final line.
854 if (wrappingMode != WrappingMode::QtLegacy && &info != &lineInfoList.constLast()) {
855 svgWriter.writeCharacters(QLatin1String("\n"));
856 }
857 svgWriter.writeEndElement();
858 }
859 svgWriter.writeEndElement();//text root element.
860
861 if (svgWriter.hasError()) {
862 d->errors << i18n("Unknown error writing SVG text element");
863 return false;
864 }
865 *svgText = QString::fromUtf8(svgBuffer.data()).trimmed();
866 return true;
867}
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
223 const QStringList spanLikes = {
224 "span", "font", "b", "strong", "em", "i", "pre", "u"
225 };
226 bool firstElement = true;
227
228 while (!htmlReader.atEnd()) {
229 QXmlStreamReader::TokenType token = htmlReader.readNext();
230 QLatin1String elName = firstElement? QLatin1String("text"): QLatin1String("tspan");
231 switch (token) {
232 case QXmlStreamReader::StartElement:
233 {
234 newLine = false;
235 if (htmlReader.name() == "br") {
236 debugFlake << "\tdoing br";
237 svgWriter.writeEndElement();
238#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
239 elementName = QStringRef(&p);
240#else
241 elementName = QStringView(p);
242#endif
243 em = bodyEm;
244 appendStyle = previousStyleString;
245 }
246 else {
247 elementName = htmlReader.name();
248 em = "";
249 }
250
251 if (elementName == "body") {
252 debugFlake << "\tstart Element" << elementName;
253 svgWriter.writeStartElement(elName);
254 firstElement = false;
255 appendStyle = QString();
256 }
257 else if (elementName == "p") {
258 // new line
259 debugFlake << "\t\tstart Element" << elementName;
260 svgWriter.writeStartElement(elName);
261 firstElement = false;
262 newLine = true;
263 if (em.isEmpty()) {
264 em = bodyEm;
265 appendStyle = QString();
266 }
267 lineCount++;
268 }
269 else if (elementName == "span") {
270 debugFlake << "\tstart Element" << elementName;
271 svgWriter.writeStartElement(elName);
272 firstElement = false;
273 appendStyle = QString();
274 }
275 else if (elementName == "b" || elementName == "strong") {
276 debugFlake << "\tstart Element" << elementName;
277 svgWriter.writeStartElement(elName);
278 firstElement = false;
279 appendStyle = "font-weight:700;";
280 }
281 else if (elementName == "i" || elementName == "em") {
282 debugFlake << "\tstart Element" << elementName;
283 svgWriter.writeStartElement(elName);
284 firstElement = false;
285 appendStyle = "font-style:italic;";
286 }
287 else if (elementName == "u") {
288 debugFlake << "\tstart Element" << elementName;
289 svgWriter.writeStartElement(elName);
290 firstElement = false;
291 appendStyle = "text-decoration:underline";
292 }
293 else if (elementName == "font") {
294 debugFlake << "\tstart Element" << elementName;
295 svgWriter.writeStartElement(elName);
296 firstElement = false;
297 appendStyle = QString();
298 if (htmlReader.attributes().hasAttribute("color")) {
299 svgWriter.writeAttribute("fill", htmlReader.attributes().value("color").toString());
300 }
301 }
302 else if (elementName == "pre") {
303 debugFlake << "\tstart Element" << elementName;
304 svgWriter.writeStartElement(elName);
305 firstElement = false;
306 appendStyle = "white-space:pre";
307 }
308
309 QXmlStreamAttributes attributes = htmlReader.attributes();
310
311 QString textAlign;
312 if (attributes.hasAttribute("align")) {
313 textAlign = attributes.value("align").toString();
314 }
315
316 if (attributes.hasAttribute("style") || !appendStyle.isEmpty()) {
317 QString filteredStyles;
318 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(" ");
319 QStringList styles = attributes.value("style").toString().split(";");
320 for(int i=0; i<styles.size(); i++) {
321 QStringList style = QString(styles.at(i)).split(":");
322 debugFlake<<style.at(0);
323 if (svgStyles.contains(QString(style.at(0)).trimmed())) {
324 filteredStyles.append(styles.at(i)+";");
325 }
326
327 if (QString(style.at(0)).trimmed() == "color") {
328 filteredStyles.append(" fill:"+style.at(1)+";");
329 }
330
331 if (QString(style.at(0)).trimmed() == "text-align") {
332 textAlign = QString(style.at(1)).trimmed();
333 }
334
335 if (QString(style.at(0)).trimmed() == "line-height"){
336 if (style.at(1).contains("%")) {
337 double percentage = QString(style.at(1)).remove("%").toDouble();
338 em = QString::number(percentage/100.0)+"em";
339 } else if(style.at(1).contains("em")) {
340 em = style.at(1);
341 } else if(style.at(1).contains("px")) {
342 em = style.at(1);
343 }
344 if (elementName == "body") {
345 bodyEm = em;
346 }
347 }
348 }
349
350 if (textAlign == "center") {
351 filteredStyles.append(" text-anchor:middle;");
352 } else if (textAlign == "right") {
353 filteredStyles.append(" text-anchor:end;");
354 } else if (textAlign == "left"){
355 filteredStyles.append(" text-anchor:start;");
356 }
357
358 filteredStyles.append(appendStyle);
359
360 if (!filteredStyles.isEmpty()) {
361 svgWriter.writeAttribute("style", filteredStyles);
362 previousStyleString = filteredStyles;
363 }
364
365
366 }
367 if (newLine && lineCount > 1) {
368 debugFlake << "\t\tAdvancing to the next line";
369 svgWriter.writeAttribute("x", "0");
370 svgWriter.writeAttribute("dy", em);
371 }
372 break;
373 }
374 case QXmlStreamReader::EndElement:
375 {
376 if (htmlReader.name() == "br") break;
377 if (elementName == "p" || spanLikes.contains(elementName) || elementName == "body") {
378 debugFlake << "\tEndElement" << htmlReader.name() << "(" << elementName << ")";
379 svgWriter.writeEndElement();
380 }
381 break;
382 }
383 case QXmlStreamReader::Characters:
384 {
385 if (elementName == "style") {
386 *styles = htmlReader.text().toString();
387 }
388 else {
389 //TODO: Think up what to do with mix of pretty-print and <BR> (what libreoffice uses).
390 //if (!htmlReader.isWhitespace()) {
391 debugFlake << "\tCharacters:" << htmlReader.text();
392 svgWriter.writeCharacters(htmlReader.text().toString());
393 //}
394 }
395 break;
396 }
397 default:
398 ;
399 }
400 }
401
402 if (htmlReader.hasError()) {
403 d->errors << htmlReader.errorString();
404 return false;
405 }
406 if (svgWriter.hasError()) {
407 d->errors << i18n("Unknown error writing SVG text element");
408 return false;
409 }
410
411 *svgText = QString::fromUtf8(svgBuffer.data());
412 return true;
413}
#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 912 of file KoSvgTextShapeMarkupConverter.cpp.

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

1126{
1127 return d->errors;
1128}

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 1683 of file KoSvgTextShapeMarkupConverter.cpp.

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

◆ 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 1719 of file KoSvgTextShapeMarkupConverter.cpp.

1720{
1721 const QVariant inlineSize = frameFormat.property(InlineSizeProperty);
1722 if (inlineSize.userType() != QMetaType::Double) {
1723 return {};
1724 }
1725 const double val = inlineSize.toDouble();
1726 if (val > 0.0) {
1727 return {val};
1728 }
1729 return {};
1730}
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 1705 of file KoSvgTextShapeMarkupConverter.cpp.

1706{
1707 const QVariant wrappingMode = frameFormat.property(WrappingModeProperty);
1708 if (wrappingMode.userType() != QMetaType::Int) {
1710 }
1711 return static_cast<WrappingMode>(wrappingMode.toInt());
1712}
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 1732 of file KoSvgTextShapeMarkupConverter.cpp.

1733{
1734 if (inlineSize >= 0.0) {
1735 frameFormat->setProperty(InlineSizeProperty, inlineSize);
1736 } else {
1737 frameFormat->clearProperty(InlineSizeProperty);
1738 }
1739}

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 1714 of file KoSvgTextShapeMarkupConverter.cpp.

1715{
1716 frameFormat->setProperty(WrappingModeProperty, static_cast<int>(wrappingMode));
1717}

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 1178 of file KoSvgTextShapeMarkupConverter.cpp.

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

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

1131{
1132 return d->warnings;
1133}

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: