1 /*
2  *  Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "TestSvgText.h"
20 
21 #include <QTest>
22 
23 #include "SvgParserTestingUtils.h"
24 #include <text/KoSvgText.h>
25 #include <text/KoSvgTextProperties.h>
26 #include "KoSvgTextShapeMarkupConverter.h"
27 
28 #include <SvgLoadingContext.h>
29 #include <SvgGraphicContext.h>
30 #include <QFont>
31 
addProp(SvgLoadingContext & context,KoSvgTextProperties & props,const QString & attribute,const QString & value,KoSvgTextProperties::PropertyId id,int newValue)32 void addProp(SvgLoadingContext &context,
33              KoSvgTextProperties &props,
34              const QString &attribute,
35              const QString &value,
36              KoSvgTextProperties::PropertyId id,
37              int newValue)
38 {
39     props.parseSvgTextAttribute(context, attribute, value);
40     if (props.property(id).toInt() != newValue) {
41         qDebug() << "Failed to load the property:";
42         qDebug() << ppVar(attribute) << ppVar(value);
43         qDebug() << ppVar(newValue);
44         qDebug() << ppVar(props.property(id));
45         QFAIL("Fail :(");
46     }
47 }
48 
addProp(SvgLoadingContext & context,KoSvgTextProperties & props,const QString & attribute,const QString & value,KoSvgTextProperties::PropertyId id,KoSvgText::AutoValue newValue)49 void addProp(SvgLoadingContext &context,
50              KoSvgTextProperties &props,
51              const QString &attribute,
52              const QString &value,
53              KoSvgTextProperties::PropertyId id,
54              KoSvgText::AutoValue newValue)
55 {
56     props.parseSvgTextAttribute(context, attribute, value);
57     if (props.property(id).value<KoSvgText::AutoValue>() != newValue) {
58         qDebug() << "Failed to load the property:";
59         qDebug() << ppVar(attribute) << ppVar(value);
60         qDebug() << ppVar(newValue);
61         qDebug() << ppVar(props.property(id));
62         QFAIL("Fail :(");
63     }
64     QCOMPARE(props.property(id), QVariant::fromValue(newValue));
65 }
66 
addProp(SvgLoadingContext & context,KoSvgTextProperties & props,const QString & attribute,const QString & value,KoSvgTextProperties::PropertyId id,qreal newValue)67 void addProp(SvgLoadingContext &context,
68              KoSvgTextProperties &props,
69              const QString &attribute,
70              const QString &value,
71              KoSvgTextProperties::PropertyId id,
72              qreal newValue)
73 {
74     props.parseSvgTextAttribute(context, attribute, value);
75     if (props.property(id).toReal() != newValue) {
76         qDebug() << "Failed to load the property:";
77         qDebug() << ppVar(attribute) << ppVar(value);
78         qDebug() << ppVar(newValue);
79         qDebug() << ppVar(props.property(id));
80         QFAIL("Fail :(");
81     }
82 }
83 
84 
testTextProperties()85 void TestSvgText::testTextProperties()
86 {
87     KoDocumentResourceManager resourceManager;
88     SvgLoadingContext context(&resourceManager);
89     context.pushGraphicsContext();
90 
91     KoSvgTextProperties props;
92 
93     addProp(context, props, "writing-mode", "tb-rl", KoSvgTextProperties::WritingModeId, KoSvgText::TopToBottom);
94     addProp(context, props, "writing-mode", "rl", KoSvgTextProperties::WritingModeId, KoSvgText::RightToLeft);
95 
96     addProp(context, props, "glyph-orientation-vertical", "auto", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue());
97     addProp(context, props, "glyph-orientation-vertical", "0", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(0));
98     addProp(context, props, "glyph-orientation-vertical", "90", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(M_PI_2));
99     addProp(context, props, "glyph-orientation-vertical", "95", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(M_PI_2));
100     addProp(context, props, "glyph-orientation-vertical", "175", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(M_PI));
101     addProp(context, props, "glyph-orientation-vertical", "280", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(3 * M_PI_2));
102     addProp(context, props, "glyph-orientation-vertical", "350", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(0));
103     addProp(context, props, "glyph-orientation-vertical", "105", KoSvgTextProperties::GlyphOrientationVerticalId, KoSvgText::AutoValue(M_PI_2));
104 
105     addProp(context, props, "glyph-orientation-horizontal", "0", KoSvgTextProperties::GlyphOrientationHorizontalId, 0.0);
106     addProp(context, props, "glyph-orientation-horizontal", "90", KoSvgTextProperties::GlyphOrientationHorizontalId, M_PI_2);
107     addProp(context, props, "glyph-orientation-horizontal", "95", KoSvgTextProperties::GlyphOrientationHorizontalId, M_PI_2);
108     addProp(context, props, "glyph-orientation-horizontal", "175", KoSvgTextProperties::GlyphOrientationHorizontalId, M_PI);
109     addProp(context, props, "glyph-orientation-horizontal", "280", KoSvgTextProperties::GlyphOrientationHorizontalId, 3 * M_PI_2);
110 
111     addProp(context, props, "direction", "rtl", KoSvgTextProperties::WritingModeId, KoSvgText::DirectionRightToLeft);
112     addProp(context, props, "unicode-bidi", "embed", KoSvgTextProperties::UnicodeBidiId, KoSvgText::BidiEmbed);
113     addProp(context, props, "unicode-bidi", "bidi-override", KoSvgTextProperties::UnicodeBidiId, KoSvgText::BidiOverride);
114 
115 
116     addProp(context, props, "text-anchor", "middle", KoSvgTextProperties::TextAnchorId, KoSvgText::AnchorMiddle);
117     addProp(context, props, "dominant-baseline", "ideographic", KoSvgTextProperties::DominantBaselineId, KoSvgText::DominantBaselineIdeographic);
118     addProp(context, props, "alignment-baseline", "alphabetic", KoSvgTextProperties::AlignmentBaselineId, KoSvgText::AlignmentBaselineAlphabetic);
119     addProp(context, props, "baseline-shift", "sub", KoSvgTextProperties::BaselineShiftModeId, KoSvgText::ShiftSub);
120     addProp(context, props, "baseline-shift", "super", KoSvgTextProperties::BaselineShiftModeId, KoSvgText::ShiftSuper);
121     addProp(context, props, "baseline-shift", "baseline", KoSvgTextProperties::BaselineShiftModeId, KoSvgText::ShiftNone);
122 
123     addProp(context, props, "baseline-shift", "10%", KoSvgTextProperties::BaselineShiftModeId, KoSvgText::ShiftPercentage);
124     QCOMPARE(props.property(KoSvgTextProperties::BaselineShiftValueId).toDouble(), 0.1);
125 
126     context.currentGC()->font.setPointSizeF(180);
127 
128     addProp(context, props, "baseline-shift", "36", KoSvgTextProperties::BaselineShiftModeId, KoSvgText::ShiftPercentage);
129     QCOMPARE(props.property(KoSvgTextProperties::BaselineShiftValueId).toDouble(), 0.2);
130 
131     addProp(context, props, "kerning", "auto", KoSvgTextProperties::KerningId, KoSvgText::AutoValue());
132     addProp(context, props, "kerning", "20", KoSvgTextProperties::KerningId, KoSvgText::AutoValue(20.0));
133 
134     addProp(context, props, "letter-spacing", "normal", KoSvgTextProperties::LetterSpacingId, KoSvgText::AutoValue());
135     addProp(context, props, "letter-spacing", "20", KoSvgTextProperties::LetterSpacingId, KoSvgText::AutoValue(20.0));
136 
137     addProp(context, props, "word-spacing", "normal", KoSvgTextProperties::WordSpacingId, KoSvgText::AutoValue());
138     addProp(context, props, "word-spacing", "20", KoSvgTextProperties::WordSpacingId, KoSvgText::AutoValue(20.0));
139 }
140 
testDefaultTextProperties()141 void TestSvgText::testDefaultTextProperties()
142 {
143     KoSvgTextProperties props;
144 
145     QVERIFY(props.isEmpty());
146     QVERIFY(!props.hasProperty(KoSvgTextProperties::UnicodeBidiId));
147 
148     QVERIFY(KoSvgTextProperties::defaultProperties().hasProperty(KoSvgTextProperties::UnicodeBidiId));
149     QCOMPARE(KoSvgTextProperties::defaultProperties().property(KoSvgTextProperties::UnicodeBidiId).toInt(), int(KoSvgText::BidiNormal));
150 
151     props = KoSvgTextProperties::defaultProperties();
152 
153     QVERIFY(props.hasProperty(KoSvgTextProperties::UnicodeBidiId));
154     QCOMPARE(props.property(KoSvgTextProperties::UnicodeBidiId).toInt(), int(KoSvgText::BidiNormal));
155 }
156 
testTextPropertiesDifference()157 void TestSvgText::testTextPropertiesDifference()
158 {
159     using namespace KoSvgText;
160 
161     KoSvgTextProperties props;
162 
163     props.setProperty(KoSvgTextProperties::WritingModeId, RightToLeft);
164     props.setProperty(KoSvgTextProperties::DirectionId, DirectionRightToLeft);
165     props.setProperty(KoSvgTextProperties::UnicodeBidiId, BidiEmbed);
166     props.setProperty(KoSvgTextProperties::TextAnchorId, AnchorEnd);
167     props.setProperty(KoSvgTextProperties::DominantBaselineId, DominantBaselineNoChange);
168     props.setProperty(KoSvgTextProperties::AlignmentBaselineId, AlignmentBaselineIdeographic);
169     props.setProperty(KoSvgTextProperties::BaselineShiftModeId, ShiftPercentage);
170     props.setProperty(KoSvgTextProperties::BaselineShiftValueId, 0.5);
171     props.setProperty(KoSvgTextProperties::KerningId, fromAutoValue(AutoValue(10)));
172     props.setProperty(KoSvgTextProperties::GlyphOrientationVerticalId, fromAutoValue(AutoValue(90)));
173     props.setProperty(KoSvgTextProperties::GlyphOrientationHorizontalId, fromAutoValue(AutoValue(180)));
174     props.setProperty(KoSvgTextProperties::LetterSpacingId, fromAutoValue(AutoValue(20)));
175     props.setProperty(KoSvgTextProperties::WordSpacingId, fromAutoValue(AutoValue(30)));
176 
177     KoSvgTextProperties newProps = props;
178 
179     newProps.setProperty(KoSvgTextProperties::KerningId, fromAutoValue(AutoValue(11)));
180     newProps.setProperty(KoSvgTextProperties::LetterSpacingId, fromAutoValue(AutoValue(21)));
181 
182     KoSvgTextProperties diff = newProps.ownProperties(props);
183 
184     QVERIFY(diff.hasProperty(KoSvgTextProperties::KerningId));
185     QVERIFY(diff.hasProperty(KoSvgTextProperties::LetterSpacingId));
186 
187     QVERIFY(!diff.hasProperty(KoSvgTextProperties::WritingModeId));
188     QVERIFY(!diff.hasProperty(KoSvgTextProperties::DirectionId));
189 
190 
191 }
192 
testParseFontStyles()193 void TestSvgText::testParseFontStyles()
194 {
195     const QString data =
196             "<text x=\"7\" y=\"7\""
197             "    font-family=\"Verdana , \'Times New Roman\', serif\" font-size=\"15\" font-style=\"oblique\" fill=\"blue\""
198             "    font-stretch=\"extra-condensed\""
199             "    font-size-adjust=\"0.56\"" //// not implemented! should issue a warning!
200             "    font=\"bold italic large Palatino, serif\"" //// not implemented! should issue a warning!
201             "    font-variant=\"small-caps\" font-weight=\"600\" >"
202             "    Hello, out there"
203             "</text>";
204 
205     KoXmlDocument doc;
206     QVERIFY(doc.setContent(data.toLatin1()));
207     KoXmlElement root = doc.documentElement();
208 
209     KoDocumentResourceManager resourceManager;
210     SvgLoadingContext context(&resourceManager);
211     context.pushGraphicsContext();
212 
213     SvgStyles styles = context.styleParser().collectStyles(root);
214     context.styleParser().parseFont(styles);
215 
216     //QCOMPARE(styles.size(), 3);
217 
218     // TODO: multiple fonts!
219     QCOMPARE(context.currentGC()->font.family(), QString("Verdana"));
220 
221     {
222         QStringList expectedFonts = {"Verdana", "Times New Roman", "serif"};
223         QCOMPARE(context.currentGC()->fontFamiliesList, expectedFonts);
224     }
225 
226     QCOMPARE(context.currentGC()->font.pointSizeF(), 15.0);
227     QCOMPARE(context.currentGC()->font.style(), QFont::StyleOblique);
228     QCOMPARE(context.currentGC()->font.capitalization(), QFont::SmallCaps);
229     QCOMPARE(context.currentGC()->font.weight(), 66);
230 
231     {
232         SvgStyles fontModifier;
233         fontModifier["font-weight"] = "bolder";
234         context.styleParser().parseFont(fontModifier);
235         QCOMPARE(context.currentGC()->font.weight(), 75);
236     }
237 
238     {
239         SvgStyles fontModifier;
240         fontModifier["font-weight"] = "lighter";
241         context.styleParser().parseFont(fontModifier);
242         QCOMPARE(context.currentGC()->font.weight(), 66);
243     }
244 
245     QCOMPARE(context.currentGC()->font.stretch(), int(QFont::ExtraCondensed));
246 
247     {
248         SvgStyles fontModifier;
249         fontModifier["font-stretch"] = "narrower";
250         context.styleParser().parseFont(fontModifier);
251         QCOMPARE(context.currentGC()->font.stretch(), int(QFont::UltraCondensed));
252     }
253 
254     {
255         SvgStyles fontModifier;
256         fontModifier["font-stretch"] = "wider";
257         context.styleParser().parseFont(fontModifier);
258         QCOMPARE(context.currentGC()->font.stretch(), int(QFont::ExtraCondensed));
259     }
260 
261     {
262         SvgStyles fontModifier;
263         fontModifier["text-decoration"] = "underline";
264         context.styleParser().parseFont(fontModifier);
265         QCOMPARE(context.currentGC()->font.underline(), true);
266     }
267 
268     {
269         SvgStyles fontModifier;
270         fontModifier["text-decoration"] = "overline";
271         context.styleParser().parseFont(fontModifier);
272         QCOMPARE(context.currentGC()->font.overline(), true);
273     }
274 
275     {
276         SvgStyles fontModifier;
277         fontModifier["text-decoration"] = "line-through";
278         context.styleParser().parseFont(fontModifier);
279         QCOMPARE(context.currentGC()->font.strikeOut(), true);
280     }
281 
282     {
283         SvgStyles fontModifier;
284         fontModifier["text-decoration"] = " line-through overline";
285         context.styleParser().parseFont(fontModifier);
286         QCOMPARE(context.currentGC()->font.underline(), false);
287         QCOMPARE(context.currentGC()->font.strikeOut(), true);
288         QCOMPARE(context.currentGC()->font.overline(), true);
289     }
290 
291 }
292 
testParseTextStyles()293 void TestSvgText::testParseTextStyles()
294 {
295     const QString data =
296             "<text x=\"7\" y=\"7\""
297             "    font-family=\"Verdana\" font-size=\"15\" font-style=\"oblique\" fill=\"blue\""
298             "    writing-mode=\"tb-rl\" "
299             "    glyph-orientation-vertical=\"90\" >"
300             "    Hello, out there"
301             "</text>";
302 
303     KoXmlDocument doc;
304     QVERIFY(doc.setContent(data.toLatin1()));
305     KoXmlElement root = doc.documentElement();
306 
307     KoDocumentResourceManager resourceManager;
308     SvgLoadingContext context(&resourceManager);
309     context.pushGraphicsContext();
310 
311     SvgStyles styles = context.styleParser().collectStyles(root);
312     context.styleParser().parseFont(styles);
313 
314     QCOMPARE(context.currentGC()->font.family(), QString("Verdana"));
315 
316     KoSvgTextProperties &props = context.currentGC()->textProperties;
317 
318     QCOMPARE(props.property(KoSvgTextProperties::WritingModeId).toInt(), int(KoSvgText::TopToBottom));
319     QCOMPARE(props.property(KoSvgTextProperties::GlyphOrientationVerticalId).value<KoSvgText::AutoValue>(), KoSvgText::AutoValue(M_PI_2));
320 
321 }
322 
323 #include <text/KoSvgTextShape.h>
324 #include <text/KoSvgTextChunkShape.h>
325 #include <text/KoSvgTextChunkShapeLayoutInterface.h>
326 
testSimpleText()327 void TestSvgText::testSimpleText()
328 {
329     const QString data =
330             "<svg width=\"100px\" height=\"30px\""
331             "    xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
332 
333             "<g id=\"test\">"
334 
335             "    <rect id=\"boundingRect\" x=\"4\" y=\"5\" width=\"89\" height=\"19\""
336             "        fill=\"none\" stroke=\"red\"/>"
337 
338             "    <text id=\"testRect\" x=\"7\" y=\"27\""
339             "        font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >"
340             "        Hello, out there!"
341             "    </text>"
342 
343             "</g>"
344 
345             "</svg>";
346 
347     QFont testFont("DejaVu Sans");
348     if (!QFontInfo(testFont).exactMatch()) {
349         QEXPECT_FAIL(0, "DejaVu Sans is *not* found! Text rendering might be broken!", Continue);
350     }
351 
352     SvgRenderTester t (data);
353     t.test_standard("text_simple", QSize(175, 40), 72.0);
354 
355     KoShape *shape = t.findShape("testRect");
356     KoSvgTextChunkShape *chunkShape = dynamic_cast<KoSvgTextChunkShape*>(shape);
357     QVERIFY(chunkShape);
358 
359     // root shape is not just a chunk!
360     QVERIFY(dynamic_cast<KoSvgTextShape*>(shape));
361 
362     QCOMPARE(chunkShape->shapeCount(), 0);
363     QCOMPARE(chunkShape->layoutInterface()->isTextNode(), true);
364 
365     QCOMPARE(chunkShape->layoutInterface()->numChars(), 17);
366     QCOMPARE(chunkShape->layoutInterface()->nodeText(), QString("Hello, out there!"));
367 
368     QVector<KoSvgText::CharTransformation> transform = chunkShape->layoutInterface()->localCharTransformations();
369     QCOMPARE(transform.size(), 1);
370     QVERIFY(bool(transform[0].xPos));
371     QVERIFY(bool(transform[0].yPos));
372     QVERIFY(!transform[0].dxPos);
373     QVERIFY(!transform[0].dyPos);
374     QVERIFY(!transform[0].rotate);
375 
376     QCOMPARE(*transform[0].xPos, 7.0);
377     QCOMPARE(*transform[0].yPos, 27.0);
378 
379     QVector<KoSvgTextChunkShapeLayoutInterface::SubChunk> subChunks =
380             chunkShape->layoutInterface()->collectSubChunks();
381 
382     QCOMPARE(subChunks.size(), 1);
383     QCOMPARE(subChunks[0].text.size(), 17);
384     //qDebug() << ppVar(subChunks[0].text);
385     //qDebug() << ppVar(subChunks[0].transformation);
386     //qDebug() << ppVar(subChunks[0].format);
387 
388 }
389 
toChunkShape(KoShape * shape)390 inline KoSvgTextChunkShape* toChunkShape(KoShape *shape) {
391     KoSvgTextChunkShape *chunkShape = dynamic_cast<KoSvgTextChunkShape*>(shape);
392     KIS_ASSERT(chunkShape);
393     return chunkShape;
394 }
395 
testComplexText()396 void TestSvgText::testComplexText()
397 {
398     const QString data =
399             "<svg width=\"100px\" height=\"30px\""
400             "    xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
401 
402             "<g id=\"test\">"
403 
404             "    <rect id=\"boundingRect\" x=\"5\" y=\"5\" width=\"89\" height=\"19\""
405             "        fill=\"none\" stroke=\"red\"/>"
406 
407             "    <text id=\"testRect\" x=\"7\" y=\"27\" dx=\"0,1,2,3,4,5,6,7,8\""
408             "        font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >"
409             "        Hello, <tspan fill=\"red\" x=\"20\" y=\"46\" text-anchor=\"start\">ou"
410             "t</tspan> there <![CDATA[cool cdata --> nice work]]>"
411             "    </text>"
412 
413             "</g>"
414 
415             "</svg>";
416 
417     SvgRenderTester t (data);
418     t.test_standard("text_complex", QSize(385, 56), 72.0);
419 
420     KoSvgTextChunkShape *baseShape = toChunkShape(t.findShape("testRect"));
421     QVERIFY(baseShape);
422 
423     // root shape is not just a chunk!
424     QVERIFY(dynamic_cast<KoSvgTextShape*>(baseShape));
425 
426     QCOMPARE(baseShape->shapeCount(), 4);
427     QCOMPARE(baseShape->layoutInterface()->isTextNode(), false);
428     QCOMPARE(baseShape->layoutInterface()->numChars(), 41);
429 
430     {   // chunk 0: "Hello, "
431         KoSvgTextChunkShape *chunk = toChunkShape(baseShape->shapes()[0]);
432 
433         QCOMPARE(chunk->shapeCount(), 0);
434         QCOMPARE(chunk->layoutInterface()->isTextNode(), true);
435 
436         QCOMPARE(chunk->layoutInterface()->numChars(), 7);
437         QCOMPARE(chunk->layoutInterface()->nodeText(), QString("Hello, "));
438 
439         QVector<KoSvgText::CharTransformation> transform = chunk->layoutInterface()->localCharTransformations();
440         QCOMPARE(transform.size(), 7);
441         QVERIFY(bool(transform[0].xPos));
442         QVERIFY(!bool(transform[1].xPos));
443 
444         for (int i = 0; i < 7; i++) {
445             QVERIFY(!i || bool(transform[i].dxPos));
446 
447             if (i) {
448                 QCOMPARE(*transform[i].dxPos, qreal(i));
449             }
450         }
451 
452         QVector<KoSvgTextChunkShapeLayoutInterface::SubChunk> subChunks =
453                 chunk->layoutInterface()->collectSubChunks();
454 
455         QCOMPARE(subChunks.size(), 7);
456         QCOMPARE(subChunks[0].text.size(), 1);
457         QCOMPARE(*subChunks[0].transformation.xPos, 7.0);
458         QVERIFY(!subChunks[1].transformation.xPos);
459     }
460 
461     {   // chunk 1: "out"
462         KoSvgTextChunkShape *chunk = toChunkShape(baseShape->shapes()[1]);
463 
464         QCOMPARE(chunk->shapeCount(), 0);
465         QCOMPARE(chunk->layoutInterface()->isTextNode(), true);
466 
467         QCOMPARE(chunk->layoutInterface()->numChars(), 3);
468         QCOMPARE(chunk->layoutInterface()->nodeText(), QString("out"));
469 
470         QVector<KoSvgText::CharTransformation> transform = chunk->layoutInterface()->localCharTransformations();
471         QCOMPARE(transform.size(), 2);
472         QVERIFY(bool(transform[0].xPos));
473         QVERIFY(!bool(transform[1].xPos));
474 
475         for (int i = 0; i < 2; i++) {
476             QVERIFY(bool(transform[i].dxPos));
477             QCOMPARE(*transform[i].dxPos, qreal(i + 7));
478         }
479 
480         QVector<KoSvgTextChunkShapeLayoutInterface::SubChunk> subChunks =
481                 chunk->layoutInterface()->collectSubChunks();
482 
483         QCOMPARE(subChunks.size(), 2);
484         QCOMPARE(subChunks[0].text.size(), 1);
485         QCOMPARE(subChunks[1].text.size(), 2);
486     }
487 
488     {   // chunk 2: " there "
489         KoSvgTextChunkShape *chunk = toChunkShape(baseShape->shapes()[2]);
490 
491         QCOMPARE(chunk->shapeCount(), 0);
492         QCOMPARE(chunk->layoutInterface()->isTextNode(), true);
493 
494         QCOMPARE(chunk->layoutInterface()->numChars(), 7);
495         QCOMPARE(chunk->layoutInterface()->nodeText(), QString(" there "));
496 
497         QVector<KoSvgText::CharTransformation> transform = chunk->layoutInterface()->localCharTransformations();
498         QCOMPARE(transform.size(), 0);
499 
500         QVector<KoSvgTextChunkShapeLayoutInterface::SubChunk> subChunks =
501                 chunk->layoutInterface()->collectSubChunks();
502 
503         QCOMPARE(subChunks.size(), 1);
504         QCOMPARE(subChunks[0].text.size(), 7);
505     }
506 
507     {   // chunk 3: "cool cdata --> nice work"
508         KoSvgTextChunkShape *chunk = toChunkShape(baseShape->shapes()[3]);
509 
510         QCOMPARE(chunk->shapeCount(), 0);
511         QCOMPARE(chunk->layoutInterface()->isTextNode(), true);
512 
513         QCOMPARE(chunk->layoutInterface()->numChars(), 24);
514         QCOMPARE(chunk->layoutInterface()->nodeText(), QString("cool cdata --> nice work"));
515 
516         QVector<KoSvgText::CharTransformation> transform = chunk->layoutInterface()->localCharTransformations();
517         QCOMPARE(transform.size(), 0);
518 
519         QVector<KoSvgTextChunkShapeLayoutInterface::SubChunk> subChunks =
520                 chunk->layoutInterface()->collectSubChunks();
521 
522         QCOMPARE(subChunks.size(), 1);
523         QCOMPARE(subChunks[0].text.size(), 24);
524     }
525 }
526 
testHindiText()527 void TestSvgText::testHindiText()
528 {
529     const QString data =
530             "<svg width=\"100px\" height=\"30px\""
531             "    xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
532 
533             "<g id=\"test\">"
534 
535             "    <rect id=\"boundingRect\" x=\"4\" y=\"5\" width=\"89\" height=\"19\""
536             "        fill=\"none\" stroke=\"red\"/>"
537 
538             "    <text id=\"testRect\" x=\"4\" y=\"24\""
539             "        font-family=\"FreeSans\" font-size=\"15\" fill=\"blue\" >"
540             "मौखिक रूप से हिंदी के काफी सामान"
541             "    </text>"
542 
543             "</g>"
544 
545             "</svg>";
546 
547     SvgRenderTester t (data);
548 
549     QFont testFont("FreeSans");
550     if (!QFontInfo(testFont).exactMatch()) {
551 #ifdef USE_ROUND_TRIP
552             return;
553 #else
554             QEXPECT_FAIL(0, "FreeSans found is *not* found! Hindi rendering might be broken!", Continue);
555 #endif
556     }
557 
558     t.test_standard("text_hindi", QSize(260, 30), 72);
559 }
560 
testTextBaselineShift()561 void TestSvgText::testTextBaselineShift()
562 {
563     const QString data =
564             "<svg width=\"100px\" height=\"30px\""
565             "    xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
566 
567             "<g id=\"test\">"
568 
569             "    <rect id=\"boundingRect\" x=\"5\" y=\"5\" width=\"89\" height=\"19\""
570             "        fill=\"none\" stroke=\"red\"/>"
571 
572             "    <text id=\"testRect\" x=\"4\" y=\"29\" "
573             "        font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >"
574 
575             "        <tspan>text<tspan baseline-shift=\"super\">super </tspan>normal<tspan baseline-shift=\"sub\">sub</tspan></tspan>"
576 
577             "    </text>"
578 
579             "</g>"
580 
581             "</svg>";
582 
583     SvgRenderTester t (data);
584 
585     t.test_standard("text_baseline_shift", QSize(180, 40), 72);
586 
587     KoSvgTextChunkShape *baseShape = toChunkShape(t.findShape("testRect"));
588     QVERIFY(baseShape);
589 
590     // root shape is not just a chunk!
591     QVERIFY(dynamic_cast<KoSvgTextShape*>(baseShape));
592 
593 }
594 
testTextSpacing()595 void TestSvgText::testTextSpacing()
596 {
597     const QString data =
598             "<svg width=\"100px\" height=\"30px\""
599             "    xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
600 
601             "<g id=\"test\">"
602 
603             "    <rect id=\"boundingRect\" x=\"5\" y=\"5\" width=\"89\" height=\"19\""
604             "        fill=\"none\" stroke=\"red\"/>"
605 
606             "    <text id=\"testRect\" x=\"5\" y=\"24\" "
607             "        font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >"
608 
609             "        <tspan x=\"5\" dy=\"0.0em\">Lorem ipsum</tspan>"
610             "        <tspan x=\"5\" dy=\"1.5em\" letter-spacing=\"4.0\">Lorem ipsum (ls=4)</tspan>"
611             "        <tspan x=\"5\" dy=\"1.5em\" letter-spacing=\"-2.0\">Lorem ipsum (ls=-2)</tspan>"
612 
613             "        <tspan x=\"5\" dy=\"2.0em\">Lorem ipsum</tspan>"
614             "        <tspan x=\"5\" dy=\"1.5em\" word-spacing=\"4.0\">Lorem ipsum (ws=4)</tspan>"
615             "        <tspan x=\"5\" dy=\"1.5em\" word-spacing=\"-2.0\">Lorem ipsum (ws=-2)</tspan>"
616 
617             "        <tspan x=\"5\" dy=\"2.0em\">Lorem ipsum</tspan>"
618             "        <tspan x=\"5\" dy=\"1.5em\" kerning=\"0.0\">Lorem ipsum (k=0)</tspan>"
619             "        <tspan x=\"5\" dy=\"1.5em\" kerning=\"2.0\">Lorem ipsum (k=2)</tspan>"
620             "        <tspan x=\"5\" dy=\"1.5em\" kerning=\"2.0\" letter-spacing=\"2.0\">Lorem ipsum (k=2,ls=2)</tspan>"
621 
622             "    </text>"
623 
624             "</g>"
625 
626             "</svg>";
627 
628     SvgRenderTester t (data);
629     t.setFuzzyThreshold(5);
630     t.test_standard("text_letter_word_spacing", QSize(340, 250), 72.0);
631 
632     KoSvgTextChunkShape *baseShape = toChunkShape(t.findShape("testRect"));
633     QVERIFY(baseShape);
634 
635     // root shape is not just a chunk!
636     QVERIFY(dynamic_cast<KoSvgTextShape*>(baseShape));
637 
638 }
639 
testTextTabSpacing()640 void TestSvgText::testTextTabSpacing()
641 {
642     const QString data =
643             "<svg width=\"100px\" height=\"30px\""
644             "    xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
645 
646             "<g id=\"test\">"
647 
648             "    <rect id=\"boundingRect\" x=\"5\" y=\"5\" width=\"89\" height=\"120\""
649             "        fill=\"none\" stroke=\"red\"/>"
650 
651             "    <text id=\"testRect\" x=\"5\" y=\"24\" "
652             "        font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >"
653 
654             "        <tspan x=\"10\" dy=\"1.0em\">  Lorem</tspan>"
655             "        <tspan x=\"10\" dy=\"2.0em\">	ipsum</tspan>"
656             "        <tspan x=\"10\" dy=\"2.0em\">dolor  sit	amet,</tspan>"
657             "        <tspan x=\"10\" dy=\"2.0em\">		consectetur adipiscing elit.</tspan>"
658 
659             "    </text>"
660 
661             "</g>"
662 
663             "</svg>";
664 
665     SvgRenderTester t (data);
666     t.setFuzzyThreshold(5);
667     t.test_standard("text_tab_spacing", QSize(400, 170), 72.0);
668 
669     KoSvgTextChunkShape *baseShape = toChunkShape(t.findShape("testRect"));
670     QVERIFY(baseShape);
671 
672     // root shape is not just a chunk!
673     QVERIFY(dynamic_cast<KoSvgTextShape*>(baseShape));
674 }
675 
testTextDecorations()676 void TestSvgText::testTextDecorations()
677 {
678     const QString data =
679             "<svg width=\"100px\" height=\"30px\""
680             "    xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
681 
682             "<g id=\"test\">"
683 
684             "    <rect id=\"boundingRect\" x=\"5\" y=\"5\" width=\"89\" height=\"19\""
685             "        fill=\"none\" stroke=\"red\"/>"
686 
687             "    <text id=\"testRect\" x=\"4\" y=\"24\" "
688             "        font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >"
689 
690             "        <tspan x=\"20\" dy=\"0.0em\" text-decoration=\"underline\">Lorem ipsum</tspan>"
691             "        <tspan x=\"20\" dy=\"2.5em\" text-decoration=\"overline\">Lorem ipsum</tspan>"
692             "        <tspan x=\"20\" dy=\"2.0em\" text-decoration=\"line-through\">Lorem ipsum</tspan>"
693             "        <tspan x=\"20\" dy=\"2.0em\" text-decoration=\"underline\">Lorem <tspan fill=\"red\">ipsum</tspan> (WRONG!!!)</tspan>"
694 
695             "    </text>"
696 
697             "</g>"
698 
699             "</svg>";
700 
701     SvgRenderTester t (data);
702     t.setFuzzyThreshold(5);
703     t.test_standard("text_decorations", QSize(290, 135), 72.0);
704 
705     KoSvgTextChunkShape *baseShape = toChunkShape(t.findShape("testRect"));
706     QVERIFY(baseShape);
707 
708     // root shape is not just a chunk!
709     QVERIFY(dynamic_cast<KoSvgTextShape*>(baseShape));
710 
711 }
712 
testRightToLeft()713 void TestSvgText::testRightToLeft()
714 {
715     const QString data =
716             "<svg width=\"100px\" height=\"30px\""
717             "    xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
718 
719             "<g id=\"test\">"
720 
721             "    <rect id=\"boundingRect\" x=\"5\" y=\"5\" width=\"89\" height=\"19\""
722             "        fill=\"none\" stroke=\"red\"/>"
723 
724             "    <text id=\"testRect\" x=\"20\" y=\"34\" "
725             "        font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" text-anchor=\"end\">"
726 
727             "        <tspan x=\"250\" dy=\"0.0em\" text-anchor=\"middle\" direction=\"rtl\">aa bb cc dd</tspan>"
728             "        <tspan x=\"250\" dy=\"2.0em\" text-anchor=\"middle\" direction=\"rtl\">حادثتا السفينتين «بسين Bassein» و«فايبر Viper»</tspan>"
729 
730             "        <tspan x=\"250\" dy=\"2.0em\" text-anchor=\"middle\">*</tspan>"
731 
732             "        <tspan x=\"150\" dy=\"2.0em\" text-anchor=\"start\" direction=\"ltr\">aa bb حادثتا السفينتين بسين cc dd </tspan>"
733             "        <tspan x=\"350\" dy=\"2.0em\" text-anchor=\"start\" direction=\"rtl\">aa bb حادثتا السفينتين بسين cc dd </tspan>"
734 
735             "        <tspan x=\"250\" dy=\"2.0em\" text-anchor=\"middle\">*</tspan>"
736 
737             "        <tspan x=\"150\" dy=\"2.0em\" text-anchor=\"start\" direction=\"ltr\">aa bb <tspan text-decoration=\"underline\">حادثتا</tspan> السفينتين بسين cc dd </tspan>"
738             "        <tspan x=\"250\" dy=\"2.0em\" text-anchor=\"middle\" direction=\"ltr\">aa bb <tspan text-decoration=\"underline\">حادثتا</tspan> السفينتين بسين cc dd </tspan>"
739             "        <tspan x=\"350\" dy=\"2.0em\" text-anchor=\"end\" direction=\"ltr\">aa bb <tspan text-decoration=\"underline\">حادثتا</tspan> السفينتين بسين cc dd </tspan>"
740 
741             "        <tspan x=\"250\" dy=\"2.0em\" text-anchor=\"middle\">*</tspan>"
742 
743             "        <tspan x=\"250\" dy=\"2.0em\" text-anchor=\"middle\" direction=\"ltr\">الناطقون: 295 مليون - 422 مليون</tspan>"
744             "        <tspan x=\"250\" dy=\"2.0em\" text-anchor=\"middle\" direction=\"ltr\">Spoken: <tspan direction=\"rtl\" unicode-bidi=\"embed\">295 مليون - 422 مليون </tspan></tspan>"
745 
746             "        <tspan x=\"250\" dy=\"2.0em\" text-anchor=\"middle\">*</tspan>"
747 
748             "        <tspan x=\"250\" dy=\"2.0em\" text-anchor=\"middle\" direction=\"ltr\">aa bb <tspan direction=\"rtl\" unicode-bidi=\"bidi-override\">c1 c2 c3 c4</tspan> dd ee</tspan>"
749 
750 
751 
752             "    </text>"
753 
754             "</g>"
755 
756             "</svg>";
757 
758     SvgRenderTester t (data);
759     t.test_standard("text_right_to_left", QSize(500,450), 72.0);
760 
761     KoSvgTextChunkShape *baseShape = toChunkShape(t.findShape("testRect"));
762     QVERIFY(baseShape);
763 
764     // root shape is not just a chunk!
765     QVERIFY(dynamic_cast<KoSvgTextShape*>(baseShape));
766 
767 }
768 
769 #include <QTextLayout>
770 #include <QPainter>
771 #include <QPainterPath>
772 
testQtBidi()773 void TestSvgText::testQtBidi()
774 {
775     // Arabic text sample from Wikipedia:
776     // https://ar.wikipedia.org/wiki/%D8%A5%D9%85%D8%A7%D8%B1%D8%A7%D8%AA_%D8%A7%D9%84%D8%B3%D8%A7%D8%AD%D9%84_%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D8%A7%D9%84%D8%AD
777 
778     QStringList ltrText;
779     ltrText << "aa bb cc dd";
780     ltrText << "aa bb حادثتا السفينتين بسين cc dd";
781     ltrText << "aa bb \u202ec1c2 d3d4\u202C ee ff";
782 
783     QStringList rtlText;
784     rtlText << "حادثتا السفينتين «بسين Bassein» و«فايبر Viper»";
785     rtlText << "حادثتا السفينتين «بسين aa bb cc dd» و«فايبر Viper»";
786 
787 
788     QImage canvas(500,500,QImage::Format_ARGB32);
789     QPainter gc(&canvas);
790     QPointF pos(15,15);
791 
792 
793     QVector<QStringList> textSamples;
794     textSamples << ltrText;
795     textSamples << rtlText;
796 
797     QVector<Qt::LayoutDirection> textDirections;
798     textDirections << Qt::LeftToRight;
799     textDirections << Qt::RightToLeft;
800 
801     for (int i = 0; i < textSamples.size(); i++) {
802         Q_FOREACH (const QString str, textSamples[i]) {
803             QTextOption option;
804             option.setTextDirection(textDirections[i]);
805             option.setUseDesignMetrics(true);
806 
807             QTextLayout layout;
808 
809             layout.setText(str);
810             layout.setFont(QFont("serif", 15.0));
811             layout.setCacheEnabled(true);
812             layout.beginLayout();
813 
814             QTextLine line = layout.createLine();
815             line.setPosition(pos);
816             pos.ry() += 25;
817             layout.endLayout();
818             layout.draw(&gc, QPointF());
819         }
820     }
821 
822     canvas.save("test_bidi.png");
823 }
824 
testQtDxDy()825 void TestSvgText::testQtDxDy()
826 {
827     QImage canvas(500,500,QImage::Format_ARGB32);
828     QPainter gc(&canvas);
829     QPointF pos(15,15);
830 
831     QTextOption option;
832     option.setTextDirection(Qt::LeftToRight);
833     option.setUseDesignMetrics(true);
834     option.setWrapMode(QTextOption::WrapAnywhere);
835 
836     QTextLayout layout;
837 
838     layout.setText("aa bb cc dd ee ff");
839     layout.setFont(QFont("serif", 15.0));
840     layout.setCacheEnabled(true);
841     layout.beginLayout();
842     layout.setTextOption(option);
843 
844     {
845         QTextLine line = layout.createLine();
846         line.setPosition(pos);
847         line.setNumColumns(4);
848     }
849     pos.ry() += 25;
850     pos.rx() += 30;
851     {
852         QTextLine line = layout.createLine();
853         line.setPosition(pos);
854     }
855 
856     layout.endLayout();
857     layout.draw(&gc, QPointF());
858 
859 
860     canvas.save("test_dxdy.png");
861 }
862 
863 
testTextOutlineSolid()864 void TestSvgText::testTextOutlineSolid()
865 {
866     const QString data =
867             "<svg width=\"100px\" height=\"30px\""
868             "    xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
869 
870             "<g id=\"test\">"
871 
872             "    <rect id=\"boundingRect\" x=\"4\" y=\"5\" width=\"89\" height=\"19\""
873             "        fill=\"none\" stroke=\"red\"/>"
874 
875             "    <text id=\"testRect\" x=\"2\" y=\"24\""
876             "        font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" stroke=\"red\" stroke-width=\"1\">"
877             "        SA"
878             "    </text>"
879 
880             "</g>"
881 
882             "</svg>";
883 
884     SvgRenderTester t (data);
885     t.test_standard("text_outline_solid", QSize(30, 30), 72.0);
886 }
887 
testNbspHandling()888 void TestSvgText::testNbspHandling()
889 {
890     const QString data =
891             "<svg width=\"100px\" height=\"30px\""
892             "    xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
893 
894             "<g id=\"test\">"
895 
896             "    <rect id=\"boundingRect\" x=\"4\" y=\"5\" width=\"89\" height=\"19\""
897             "        fill=\"none\" stroke=\"red\"/>"
898 
899             "    <text id=\"testRect\" x=\"2\" y=\"24\""
900             "        font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" stroke=\"red\" stroke-width=\"1\">"
901             "        S\u00A0A"
902             "    </text>"
903 
904             "</g>"
905 
906             "</svg>";
907 
908     SvgRenderTester t (data);
909     t.test_standard("text_nbsp", QSize(30, 30), 72.0);
910 }
911 
testMulticolorText()912 void TestSvgText::testMulticolorText()
913 {
914     const QString data =
915             "<svg width=\"100px\" height=\"30px\""
916             "    xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
917 
918             "<g id=\"test\">"
919 
920             "    <rect id=\"boundingRect\" x=\"4\" y=\"5\" width=\"89\" height=\"19\""
921             "        fill=\"none\" stroke=\"red\"/>"
922 
923             "    <text id=\"testRect\" x=\"2\" y=\"24\""
924             "        font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >"
925             "        S<tspan fill=\"red\">A</tspan>"
926             "    </text>"
927 
928             "</g>"
929 
930             "</svg>";
931 
932     SvgRenderTester t (data);
933     t.setFuzzyThreshold(5);
934     t.test_standard("text_multicolor", QSize(30, 30), 72.0);
935 }
936 
937 #include <KoColorBackground.h>
938 
testConvertToStrippedSvg()939 void TestSvgText::testConvertToStrippedSvg()
940 {
941     const QString data =
942             "<svg width=\"100px\" height=\"30px\""
943             "    xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
944 
945             "<g id=\"test\">"
946 
947             "    <rect id=\"boundingRect\" x=\"4\" y=\"5\" width=\"89\" height=\"19\""
948             "        fill=\"none\" stroke=\"red\"/>"
949 
950             "    <text transform=\"translate(2)\" id=\"testRect\" x=\"2\" y=\"24\""
951             "        font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >"
952             "        S<tspan fill=\"red\">A</tspan><![CDATA[some stuff<><><<<>]]>"
953             "    </text>"
954 
955             "</g>"
956 
957             "</svg>";
958 
959     SvgRenderTester t (data);
960     t.parser.setResolution(QRectF(QPointF(), QSizeF(30,30)) /* px */, 72.0/* ppi */);
961     t.run();
962 
963     KoSvgTextShape *baseShape = dynamic_cast<KoSvgTextShape*>(t.findShape("testRect"));
964     QVERIFY(baseShape);
965 
966     {
967         KoColorBackground *bg = dynamic_cast<KoColorBackground*>(baseShape->background().data());
968         QVERIFY(bg);
969         QCOMPARE(bg->color(), QColor(Qt::blue));
970     }
971 
972     KoSvgTextShapeMarkupConverter converter(baseShape);
973 
974     QString svgText;
975     QString stylesText;
976     QVERIFY(converter.convertToSvg(&svgText, &stylesText));
977 
978     QCOMPARE(stylesText, QString("<defs/>"));
979     QCOMPARE(svgText, QString("<text fill=\"#0000ff\" font-family=\"DejaVu Sans\" font-size=\"15\"><tspan x=\"2\" y=\"24\">S</tspan><tspan fill=\"#ff0000\">A</tspan><tspan>some stuff&lt;&gt;&lt;&gt;&lt;&lt;&lt;&gt;</tspan></text>"));
980 
981     // test loading
982 
983     svgText = "<text fill=\"#00ff00\" font-family=\"DejaVu Sans\" font-size=\"19\"><tspan x=\"2\" y=\"24\">S</tspan><tspan fill=\"#ff0000\">A</tspan><tspan>some stuff&lt;&gt;&lt;&gt;&lt;&lt;&lt;&gt;</tspan></text>";
984 
985     QVERIFY(converter.convertFromSvg(svgText, stylesText, QRectF(0,0,30,30), 72.0));
986 
987     {
988         KoColorBackground *bg = dynamic_cast<KoColorBackground*>(baseShape->background().data());
989         QVERIFY(bg);
990         QCOMPARE(bg->color(), QColor(Qt::green));
991     }
992 
993     {
994         KoSvgTextProperties props = baseShape->textProperties();
995         QVERIFY(props.hasProperty(KoSvgTextProperties::FontSizeId));
996 
997         const qreal fontSize = props.property(KoSvgTextProperties::FontSizeId).toReal();
998         QCOMPARE(fontSize, 19.0);
999     }
1000 
1001     QCOMPARE(baseShape->shapeCount(), 3);
1002 }
1003 
testConvertToStrippedSvgNullOrigin()1004 void TestSvgText::testConvertToStrippedSvgNullOrigin()
1005 {
1006     const QString data =
1007             "<svg width=\"100px\" height=\"30px\""
1008             "    xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
1009 
1010             "<g id=\"test\">"
1011 
1012             "    <rect id=\"boundingRect\" x=\"4\" y=\"5\" width=\"89\" height=\"19\""
1013             "        fill=\"none\" stroke=\"red\"/>"
1014 
1015             "    <text transform=\"translate(2)\" id=\"testRect\" x=\"0\" y=\"0\""
1016             "        font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >"
1017             "        S<tspan fill=\"red\">A</tspan><![CDATA[some stuff<><><<<>]]>"
1018             "    </text>"
1019 
1020             "</g>"
1021 
1022             "</svg>";
1023 
1024     SvgRenderTester t (data);
1025     t.parser.setResolution(QRectF(QPointF(), QSizeF(30,30)) /* px */, 72.0/* ppi */);
1026     t.run();
1027 
1028     KoSvgTextShape *baseShape = dynamic_cast<KoSvgTextShape*>(t.findShape("testRect"));
1029     QVERIFY(baseShape);
1030 
1031     KoSvgTextShapeMarkupConverter converter(baseShape);
1032 
1033     QString svgText;
1034     QString stylesText;
1035     QVERIFY(converter.convertToSvg(&svgText, &stylesText));
1036 
1037     QCOMPARE(stylesText, QString("<defs/>"));
1038     QCOMPARE(svgText, QString("<text fill=\"#0000ff\" font-family=\"DejaVu Sans\" font-size=\"15\"><tspan x=\"0\" y=\"0\">S</tspan><tspan fill=\"#ff0000\">A</tspan><tspan>some stuff&lt;&gt;&lt;&gt;&lt;&lt;&lt;&gt;</tspan></text>"));
1039 }
1040 
testConvertFromIncorrectStrippedSvg()1041 void TestSvgText::testConvertFromIncorrectStrippedSvg()
1042 {
1043     QScopedPointer<KoSvgTextShape> baseShape(new KoSvgTextShape());
1044 
1045     KoSvgTextShapeMarkupConverter converter(baseShape.data());
1046 
1047     QString svgText;
1048     QString stylesText;
1049 
1050     svgText = "<text>blah text</text>";
1051     QVERIFY(converter.convertFromSvg(svgText, stylesText, QRectF(0,0,30,30), 72.0));
1052     QCOMPARE(converter.errors().size(), 0);
1053 
1054     svgText = "<text>>><<><blah text</text>";
1055     QVERIFY(!converter.convertFromSvg(svgText, stylesText, QRectF(0,0,30,30), 72.0));
1056     qDebug() << ppVar(converter.errors());
1057     QCOMPARE(converter.errors().size(), 1);
1058 
1059     svgText = "<notext>blah text</notext>";
1060     QVERIFY(!converter.convertFromSvg(svgText, stylesText, QRectF(0,0,30,30), 72.0));
1061     qDebug() << ppVar(converter.errors());
1062     QCOMPARE(converter.errors().size(), 1);
1063 
1064     svgText = "<defs/>";
1065     QVERIFY(!converter.convertFromSvg(svgText, stylesText, QRectF(0,0,30,30), 72.0));
1066     qDebug() << ppVar(converter.errors());
1067     QCOMPARE(converter.errors().size(), 1);
1068 }
1069 
testEmptyTextChunk()1070 void TestSvgText::testEmptyTextChunk()
1071 {
1072     const QString data =
1073             "<svg width=\"100px\" height=\"30px\""
1074             "    xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
1075 
1076             "<g id=\"test\">"
1077 
1078             "    <rect id=\"boundingRect\" x=\"4\" y=\"5\" width=\"89\" height=\"19\""
1079             "        fill=\"none\" stroke=\"red\"/>"
1080 
1081             "    <text id=\"testRect\" x=\"2\" y=\"24\""
1082             "        font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >"
1083             "        " // no actual text! should not crash!
1084             "    </text>"
1085 
1086             "</g>"
1087 
1088             "</svg>";
1089 
1090     SvgRenderTester t (data);
1091 
1092     // it just shouldn't assert or fail when seeing an empty text block
1093     t.parser.setResolution(QRectF(QPointF(), QSizeF(30,30)) /* px */, 72.0/* ppi */);
1094     t.run();
1095 }
1096 
testTrailingWhitespace()1097 void TestSvgText::testTrailingWhitespace()
1098 {
1099     QStringList chunkA;
1100     chunkA << "aaa";
1101     chunkA << " aaa";
1102     chunkA << "aaa ";
1103     chunkA << " aaa ";
1104 
1105     QStringList chunkB;
1106     chunkB << "bbb";
1107     chunkB << " bbb";
1108     chunkB << "bbb ";
1109     chunkB << " bbb ";
1110 
1111     QStringList linkChunk;
1112     linkChunk << "";
1113     linkChunk << " ";
1114     linkChunk << "<tspan></tspan>";
1115     linkChunk << "<tspan> </tspan>";
1116 
1117 
1118     const QString dataTemplate =
1119             "<svg width=\"100px\" height=\"30px\""
1120             "    xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
1121 
1122             "<g id=\"test\">"
1123 
1124             "    <rect id=\"boundingRect\" x=\"4\" y=\"5\" width=\"89\" height=\"19\""
1125             "        fill=\"none\" stroke=\"red\"/>"
1126 
1127             "    <text id=\"testRect\" x=\"2\" y=\"24\""
1128             "        font-family=\"DejaVu Sans\" font-size=\"10\" fill=\"blue\" >"
1129             "        <tspan>%1</tspan>%2<tspan>%3</tspan>"
1130             "    </text>"
1131 
1132             "</g>"
1133 
1134             "</svg>";
1135 
1136     for (auto itL = linkChunk.constBegin(); itL != linkChunk.constEnd(); ++itL) {
1137         for (auto itA = chunkA.constBegin(); itA != chunkA.constEnd(); ++itA) {
1138             for (auto itB = chunkB.constBegin(); itB != chunkB.constEnd(); ++itB) {
1139                 if (itA->rightRef(1) != " " &&
1140                     itB->leftRef(1) != " " &&
1141                     *itL != " " &&
1142                     *itL != linkChunk.last()) continue;
1143 
1144                 QString cleanLink = *itL;
1145                 cleanLink.replace('/', '_');
1146 
1147                 qDebug() << "Testcase:" << *itA << cleanLink << *itB;
1148 
1149                 const QString data = dataTemplate.arg(*itA, *itL, *itB);
1150                 SvgRenderTester t (data);
1151                 t.setFuzzyThreshold(5);
1152                 //t.test_standard(QString("text_trailing_%1_%2_%3").arg(*itA).arg(cleanLink).arg(*itB), QSize(70, 30), 72.0);
1153 
1154                 // all files should look exactly the same!
1155                 t.test_standard(QString("text_whitespace"), QSize(70, 30), 72.0);
1156             }
1157         }
1158     }
1159 }
1160 
testConvertHtmlToSvg()1161 void TestSvgText::testConvertHtmlToSvg()
1162 {
1163     const QString html =
1164             "<?xml version=\"1.0\"?>"
1165             "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">"
1166             "<html>"
1167               "<head>"
1168                 "<meta name=\"qrichtext\" content=\"1\"/>"
1169                 "<style type=\"text/css\">p, li { white-space: pre-wrap; }</style>"
1170               "</head>"
1171               "<body style=\" font-family:'Droid Sans'; font-size:9pt; font-weight:400; font-style:normal;\">"
1172                 "<p style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">"
1173                 "  <span style=\" font-family:'Times'; font-size:20pt;\">Lorem ipsum dolor</span>"
1174                 "</p>"
1175                 "<p style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">sit am"
1176                 "<span style=\" font-weight:600;\">et, consectetur adipis</span>cing </p>"
1177                 "<p style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">"
1178                 "  <span style=\" font-style:italic;\">elit. </span>"
1179                 "</p>"
1180               "</body>"
1181             "</html>";
1182 
1183     KoSvgTextShape shape;
1184     KoSvgTextShapeMarkupConverter converter(&shape);
1185 
1186     QString svg;
1187     QString defs;
1188 
1189     converter.convertFromHtml(html, &svg, &defs);
1190 
1191 
1192     bool r = converter.convertToSvg(&svg, &defs);
1193 
1194     qDebug() << r << svg << defs;
1195 
1196 }
1197 
testTextWithMultipleRelativeOffsets()1198 void TestSvgText::testTextWithMultipleRelativeOffsets()
1199 {
1200     const QString data =
1201             "<svg width=\"100px\" height=\"30px\""
1202             "    xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
1203 
1204             "<g id=\"test\">"
1205             "    <text id=\"testRect\" x=\"10\" y=\"40\""
1206             "        font-family=\"DejaVu Sans\" font-size=\"15\" "
1207             "        dy=\"0 -3 -3 -3 -3 3 3 3 3 0 -3 -3 -3 -3 3 3 3 3 0 -3 -3 -3 -3 3 3 3 3 0 -3 -3 -3 -3 3 3 3 3 0\">"
1208             "        Lorem ipsum dolor sit amet"
1209             "    </text>"
1210 
1211             "</g>"
1212 
1213             "</svg>";
1214 
1215     SvgRenderTester t (data);
1216     t.setFuzzyThreshold(5);
1217     t.test_standard("text_multiple_relative_offsets", QSize(300, 80), 72.0);
1218 }
1219 
testTextWithMultipleAbsoluteOffsetsArabic()1220 void TestSvgText::testTextWithMultipleAbsoluteOffsetsArabic()
1221 {
1222     /**
1223      * According to the standard, each **absolute** offset defines a
1224      * new text chunk, therefore, the arabic text must become
1225      * ltr reordered
1226      */
1227 
1228     const QString data =
1229             "<svg width=\"100px\" height=\"30px\""
1230             "    xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
1231 
1232             "<g id=\"test\">"
1233             "    <text id=\"testRect\" x=\"10\" y=\"40\""
1234             "        font-family=\"DejaVu Sans\" font-size=\"15\" "
1235             "        y=\"40 45 50 55 50 45 40 35 30 25 30 35 40 45 50 55 50 45 40 35 30 25 30 35 40 45 50 55 50 45 40 35 30 25 30 35 40 45 50 55 50 45 40 35 30 25 30 35 40 45 50 55 50 45 40 35 30 25 30 35 40 45 50 55 50 45 40 35 30 25 30 35 \">"
1236             "        Lo rem اللغة العربية المعيارية الحديثة ip sum"
1237             "    </text>"
1238 
1239             "</g>"
1240 
1241             "</svg>";
1242 
1243     SvgRenderTester t (data);
1244     t.test_standard("text_multiple_absolute_offsets_arabic", QSize(530, 70), 72.0);
1245 }
1246 
testTextWithMultipleRelativeOffsetsArabic()1247 void TestSvgText::testTextWithMultipleRelativeOffsetsArabic()
1248 {
1249     /**
1250      * According to the standard, **relative** offsets must not define a new
1251      * text chunk, therefore, the arabic text must be written in native rtl order,
1252      * even though the individual letters are split.
1253      */
1254 
1255     const QString data =
1256             "<svg width=\"100px\" height=\"30px\""
1257             "    xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
1258 
1259             "<g id=\"test\">"
1260             "    <text id=\"testRect\" x=\"10\" y=\"40\""
1261             "        font-family=\"DejaVu Sans\" font-size=\"15\" "
1262             "        dy=\"0 -3 -3 -3 -3 3 3 3 3 0 -3 -3 -3 -3 3 3 3 3 0 -3 -3 -3 -3 3 3 3 3 0 -3 -3 -3 -3 3 3 3 3 0 -3 -3 -3 -3 3 3 3 3 0\">"
1263             "        Lo rem اللغة العربية المعيارية الحديثة ip sum"
1264             "    </text>"
1265 
1266             "</g>"
1267 
1268             "</svg>";
1269 
1270     SvgRenderTester t (data);
1271 
1272     // we cannot expect more than one failure
1273 #ifndef USE_ROUND_TRIP
1274     QEXPECT_FAIL("", "WARNING: in Krita relative offsets also define a new text chunk, that doesn't comply with SVG standard and must be fixed", Continue);
1275     t.test_standard("text_multiple_relative_offsets_arabic", QSize(530, 70), 72.0);
1276 #endif
1277 }
1278 
testTextOutline()1279 void TestSvgText::testTextOutline()
1280 {
1281     const QString data =
1282             "<svg width=\"100px\" height=\"30px\""
1283             "    xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
1284 
1285             "<g id=\"test\">"
1286 
1287             "    <rect id=\"boundingRect\" x=\"4\" y=\"5\" width=\"89\" height=\"19\""
1288             "        fill=\"none\" stroke=\"red\"/>"
1289 
1290             "    <text id=\"testRect\" x=\"7\" y=\"27\""
1291             " "
1292             "        font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >"
1293             "        normal "
1294             "        <tspan text-decoration=\"line-through\">strikethrough</tspan>"
1295             "        <tspan text-decoration=\"overline\">overline</tspan>"
1296             "        <tspan text-decoration=\"underline\">underline</tspan>"
1297             "    </text>"
1298 
1299             "</g>"
1300 
1301             "</svg>";
1302 
1303     QRect renderRect(0, 0, 450, 40);
1304 
1305     SvgRenderTester t (data);
1306     t.setFuzzyThreshold(5);
1307     t.test_standard("text_outline", renderRect.size(), 72.0);
1308 
1309     KoShape *shape = t.findShape("testRect");
1310     KoSvgTextChunkShape *chunkShape = dynamic_cast<KoSvgTextChunkShape*>(shape);
1311     QVERIFY(chunkShape);
1312 
1313     KoSvgTextShape *textShape = dynamic_cast<KoSvgTextShape*>(shape);
1314 
1315     QImage canvas(renderRect.size(), QImage::Format_ARGB32);
1316     canvas.fill(0);
1317     QPainter gc(&canvas);
1318     gc.setPen(Qt::NoPen);
1319     gc.setBrush(Qt::black);
1320     gc.setRenderHint(QPainter::Antialiasing, true);
1321     gc.drawPath(textShape->textOutline());
1322 
1323     QVERIFY(TestUtil::checkQImage(canvas, "svg_render", "load_text_outline", "converted_to_path", 3, 5));
1324 }
1325 
1326 
1327 
1328 
1329 QTEST_MAIN(TestSvgText)
1330