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<><><<<></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<><><<<></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<><><<<></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