File indexing completed on 2024-05-26 04:26:32
0001 /* 0002 * SPDX-FileCopyrightText: 2017 Dmitry Kazakov <dimula73@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "TestSvgText.h" 0008 0009 #include <simpletest.h> 0010 0011 #include <text/KoCssTextUtils.h> 0012 #include <text/KoFontRegistry.h> 0013 #include <text/KoSvgText.h> 0014 #include <text/KoSvgTextProperties.h> 0015 0016 #include "KoSvgTextShapeMarkupConverter.h" 0017 #include "SvgParserTestingUtils.h" 0018 0019 #include <SvgLoadingContext.h> 0020 #include <SvgGraphicContext.h> 0021 #include <QFont> 0022 0023 void addProp(SvgLoadingContext &context, 0024 KoSvgTextProperties &props, 0025 const QString &attribute, 0026 const QString &value, 0027 KoSvgTextProperties::PropertyId id, 0028 int newValue) 0029 { 0030 props.parseSvgTextAttribute(context, attribute, value); 0031 if (props.property(id).toInt() != newValue) { 0032 qDebug() << "Failed to load the property:"; 0033 qDebug() << ppVar(attribute) << ppVar(value); 0034 qDebug() << ppVar(newValue); 0035 qDebug() << ppVar(props.property(id)); 0036 QFAIL("Fail :("); 0037 } 0038 } 0039 0040 void addProp(SvgLoadingContext &context, 0041 KoSvgTextProperties &props, 0042 const QString &attribute, 0043 const QString &value, 0044 KoSvgTextProperties::PropertyId id, 0045 KoSvgText::AutoValue newValue) 0046 { 0047 props.parseSvgTextAttribute(context, attribute, value); 0048 if (props.property(id).value<KoSvgText::AutoValue>() != newValue) { 0049 qDebug() << "Failed to load the property:"; 0050 qDebug() << ppVar(attribute) << ppVar(value); 0051 qDebug() << ppVar(newValue); 0052 qDebug() << ppVar(props.property(id)); 0053 QFAIL("Fail :("); 0054 } 0055 QCOMPARE(props.property(id), QVariant::fromValue(newValue)); 0056 } 0057 0058 void addProp(SvgLoadingContext &context, 0059 KoSvgTextProperties &props, 0060 const QString &attribute, 0061 const QString &value, 0062 KoSvgTextProperties::PropertyId id, 0063 qreal newValue) 0064 { 0065 props.parseSvgTextAttribute(context, attribute, value); 0066 if (props.property(id).toReal() != newValue) { 0067 qDebug() << "Failed to load the property:"; 0068 qDebug() << ppVar(attribute) << ppVar(value); 0069 qDebug() << ppVar(newValue); 0070 qDebug() << ppVar(props.property(id)); 0071 QFAIL("Fail :("); 0072 } 0073 } 0074 0075 void TestSvgText::initTestCase() 0076 { 0077 /// The test initialization function sets Qt::AA_Use96Dpi 0078 /// application attribute, but it doesn't affect the font 0079 /// that has already been set as the default application 0080 /// font. 0081 qApp->setFont(QFont("sans", 10)); 0082 0083 for (const char *const fontFile : { 0084 "fonts/DejaVuSans.ttf", 0085 "fonts/FreeSans.ttf", 0086 "fonts/Krita_Test_Unicode_Variation_A.ttf", 0087 "fonts/Krita_Test_Unicode_Variation_B.ttf", 0088 "fonts/Ahem/ahem.ttf", 0089 "fonts/krita-pixel-test.otb", 0090 "fonts/variabletest_matching.ttf", 0091 "fonts/FontWithFancyFeatures.otf", 0092 "fonts/testFontsCozens/BaselineTest-Regular-with-BASE.otf", 0093 }) { 0094 QString fileName = TestUtil::fetchDataFileLazy(fontFile); 0095 bool res = KoFontRegistry::instance()->addFontFilePathToRegistery(fileName); 0096 0097 QVERIFY2(res, QString("KoFontRegistry could not add the test font %1").arg(fontFile).toLatin1()); 0098 } 0099 0100 for (const char *const fontDir : { 0101 "fonts/CSSTest", 0102 "fonts/testFontsCozens", 0103 }) { 0104 QString fileName = TestUtil::fetchDataFileLazy(fontDir); 0105 bool res = KoFontRegistry::instance()->addFontFileDirectoryToRegistery(fileName); 0106 QVERIFY2(res, QString("KoFontRegistry could not add the directory of test fonts %1").arg(fontDir).toLatin1()); 0107 } 0108 } 0109 0110 void TestSvgText::testTextProperties() 0111 { 0112 KoDocumentResourceManager resourceManager; 0113 SvgLoadingContext context(&resourceManager); 0114 context.pushGraphicsContext(); 0115 0116 KoSvgTextProperties props; 0117 0118 addProp(context, props, "writing-mode", "tb-rl", KoSvgTextProperties::WritingModeId, KoSvgText::VerticalRL); 0119 addProp(context, props, "writing-mode", "rl", KoSvgTextProperties::WritingModeId, KoSvgText::HorizontalTB); 0120 0121 // According to https://www.w3.org/TR/css-writing-modes-3/#glyph-orientation 0122 // glyph-orientation is only to be converted to text orientation in the 0 and 90 cases. 0123 0124 // "UAs must ignore and treat as invalid any other values for the glyph-orientation-vertical 0125 // property; and treat as invalid the glyph-orientation-horizontal property in its entirety." 0126 addProp(context, props, "glyph-orientation-vertical", "auto", KoSvgTextProperties::TextOrientationId, KoSvgText::OrientationMixed); 0127 addProp(context, props, "glyph-orientation-vertical", "0", KoSvgTextProperties::TextOrientationId, KoSvgText::OrientationUpright); 0128 addProp(context, props, "glyph-orientation-vertical", "90", KoSvgTextProperties::TextOrientationId, KoSvgText::OrientationSideWays); 0129 // This is confusing, but what now happens is that the tested value is always going to be 'sideways' 0130 // because the value is ignored. 0131 int newValueForGlyphOrientation = int(KoSvgText::OrientationSideWays); 0132 addProp(context, props, "glyph-orientation-vertical", "95", KoSvgTextProperties::TextOrientationId, newValueForGlyphOrientation); 0133 addProp(context, props, "glyph-orientation-vertical", "175", KoSvgTextProperties::TextOrientationId, newValueForGlyphOrientation); 0134 addProp(context, props, "glyph-orientation-vertical", "280", KoSvgTextProperties::TextOrientationId, newValueForGlyphOrientation); 0135 addProp(context, props, "glyph-orientation-vertical", "350", KoSvgTextProperties::TextOrientationId, newValueForGlyphOrientation); 0136 addProp(context, props, "glyph-orientation-vertical", "105", KoSvgTextProperties::TextOrientationId, newValueForGlyphOrientation); 0137 0138 addProp(context, props, "direction", "rtl", KoSvgTextProperties::DirectionId, KoSvgText::DirectionRightToLeft); 0139 addProp(context, props, "unicode-bidi", "embed", KoSvgTextProperties::UnicodeBidiId, KoSvgText::BidiEmbed); 0140 addProp(context, props, "unicode-bidi", "bidi-override", KoSvgTextProperties::UnicodeBidiId, KoSvgText::BidiOverride); 0141 0142 0143 addProp(context, props, "text-anchor", "middle", KoSvgTextProperties::TextAnchorId, KoSvgText::AnchorMiddle); 0144 addProp(context, props, "dominant-baseline", "ideographic", KoSvgTextProperties::DominantBaselineId, KoSvgText::BaselineIdeographic); 0145 addProp(context, props, "alignment-baseline", "alphabetic", KoSvgTextProperties::AlignmentBaselineId, KoSvgText::BaselineAlphabetic); 0146 addProp(context, props, "baseline-shift", "sub", KoSvgTextProperties::BaselineShiftModeId, KoSvgText::ShiftSub); 0147 addProp(context, props, "baseline-shift", "super", KoSvgTextProperties::BaselineShiftModeId, KoSvgText::ShiftSuper); 0148 addProp(context, props, "baseline-shift", "baseline", KoSvgTextProperties::BaselineShiftModeId, KoSvgText::ShiftNone); 0149 0150 addProp(context, props, "baseline-shift", "10%", KoSvgTextProperties::BaselineShiftModeId, KoSvgText::ShiftPercentage); 0151 QCOMPARE(props.property(KoSvgTextProperties::BaselineShiftValueId).toDouble(), 0.1); 0152 0153 context.currentGC()->textProperties.setProperty(KoSvgTextProperties::FontSizeId, 180.0); 0154 0155 addProp(context, props, "baseline-shift", "36", KoSvgTextProperties::BaselineShiftModeId, KoSvgText::ShiftPercentage); 0156 QCOMPARE(props.property(KoSvgTextProperties::BaselineShiftValueId).toDouble(), 3.6); 0157 0158 addProp(context, props, "kerning", "auto", KoSvgTextProperties::KerningId, KoSvgText::AutoValue()); 0159 addProp(context, props, "kerning", "20", KoSvgTextProperties::KerningId, KoSvgText::AutoValue(20.0)); 0160 0161 addProp(context, props, "letter-spacing", "normal", KoSvgTextProperties::LetterSpacingId, KoSvgText::AutoValue()); 0162 addProp(context, props, "letter-spacing", "20", KoSvgTextProperties::LetterSpacingId, KoSvgText::AutoValue(20.0)); 0163 0164 addProp(context, props, "word-spacing", "normal", KoSvgTextProperties::WordSpacingId, KoSvgText::AutoValue()); 0165 addProp(context, props, "word-spacing", "20", KoSvgTextProperties::WordSpacingId, KoSvgText::AutoValue(20.0)); 0166 } 0167 0168 void TestSvgText::testDefaultTextProperties() 0169 { 0170 KoSvgTextProperties props; 0171 0172 QVERIFY(props.isEmpty()); 0173 QVERIFY(!props.hasProperty(KoSvgTextProperties::UnicodeBidiId)); 0174 0175 QVERIFY(KoSvgTextProperties::defaultProperties().hasProperty(KoSvgTextProperties::UnicodeBidiId)); 0176 QCOMPARE(KoSvgTextProperties::defaultProperties().property(KoSvgTextProperties::UnicodeBidiId).toInt(), int(KoSvgText::BidiNormal)); 0177 0178 props = KoSvgTextProperties::defaultProperties(); 0179 0180 QVERIFY(props.hasProperty(KoSvgTextProperties::UnicodeBidiId)); 0181 QCOMPARE(props.property(KoSvgTextProperties::UnicodeBidiId).toInt(), int(KoSvgText::BidiNormal)); 0182 } 0183 0184 void TestSvgText::testTextPropertiesDifference() 0185 { 0186 using namespace KoSvgText; 0187 0188 KoSvgTextProperties props; 0189 0190 props.setProperty(KoSvgTextProperties::WritingModeId, HorizontalTB); 0191 props.setProperty(KoSvgTextProperties::DirectionId, DirectionRightToLeft); 0192 props.setProperty(KoSvgTextProperties::UnicodeBidiId, BidiEmbed); 0193 props.setProperty(KoSvgTextProperties::TextAnchorId, AnchorEnd); 0194 props.setProperty(KoSvgTextProperties::DominantBaselineId, BaselineNoChange); 0195 props.setProperty(KoSvgTextProperties::AlignmentBaselineId, BaselineIdeographic); 0196 props.setProperty(KoSvgTextProperties::BaselineShiftModeId, ShiftPercentage); 0197 props.setProperty(KoSvgTextProperties::BaselineShiftValueId, 0.5); 0198 props.setProperty(KoSvgTextProperties::KerningId, fromAutoValue(AutoValue(10))); 0199 props.setProperty(KoSvgTextProperties::TextOrientationId, OrientationSideWays); 0200 props.setProperty(KoSvgTextProperties::LetterSpacingId, fromAutoValue(AutoValue(20))); 0201 props.setProperty(KoSvgTextProperties::WordSpacingId, fromAutoValue(AutoValue(30))); 0202 props.setProperty(KoSvgTextProperties::FontSizeId, 0203 KoSvgTextProperties::defaultProperties().property(KoSvgTextProperties::FontSizeId)); 0204 0205 KoSvgTextProperties newProps = props; 0206 0207 newProps.setProperty(KoSvgTextProperties::KerningId, fromAutoValue(AutoValue(11))); 0208 newProps.setProperty(KoSvgTextProperties::LetterSpacingId, fromAutoValue(AutoValue(21))); 0209 0210 KoSvgTextProperties diff = newProps.ownProperties(props); 0211 0212 QVERIFY(diff.hasProperty(KoSvgTextProperties::KerningId)); 0213 QVERIFY(diff.hasProperty(KoSvgTextProperties::LetterSpacingId)); 0214 0215 QVERIFY(!diff.hasProperty(KoSvgTextProperties::WritingModeId)); 0216 QVERIFY(!diff.hasProperty(KoSvgTextProperties::DirectionId)); 0217 0218 KoSvgTextProperties diff2 = newProps.ownProperties(props, true); 0219 0220 QVERIFY(diff2.hasProperty(KoSvgTextProperties::FontSizeId)); 0221 0222 } 0223 0224 void TestSvgText::testParseFontStyles() 0225 { 0226 const QString data = 0227 "<text x=\"7\" y=\"7\"" 0228 " font-family=\"Verdana , \'Times New Roman\', serif\" font-size=\"15\" font-style=\"oblique\" fill=\"blue\"" 0229 " font-stretch=\"extra-condensed\"" 0230 " font-size-adjust=\"0.56\"" 0231 " font=\"bold italic large Palatino, serif\"" // we don't support this right now. 0232 " font-variant=\"small-caps\" font-weight=\"600\" >" 0233 " Hello, out there" 0234 "</text>"; 0235 0236 QDomDocument doc; 0237 QVERIFY(doc.setContent(data.toLatin1())); 0238 QDomElement root = doc.documentElement(); 0239 0240 KoDocumentResourceManager resourceManager; 0241 SvgLoadingContext context(&resourceManager); 0242 context.pushGraphicsContext(); 0243 0244 SvgStyles styles = context.styleParser().collectStyles(root); 0245 context.styleParser().parseFont(styles); 0246 0247 auto getFont = [&context]() { 0248 return context.currentGC()->textProperties; 0249 }; 0250 0251 { 0252 QStringList expectedFonts = {"Verdana", "Times New Roman", "serif"}; 0253 QCOMPARE(getFont().property(KoSvgTextProperties::FontFamiliesId).toStringList(), expectedFonts); 0254 } 0255 0256 QCOMPARE(getFont().property(KoSvgTextProperties::FontSizeId).toReal(), 15.0); 0257 QCOMPARE(QFont::Style(getFont().property(KoSvgTextProperties::FontStyleId).toInt()), QFont::StyleOblique); 0258 QCOMPARE(getFont().property(KoSvgTextProperties::FontVariantCapsId).toInt(), KoSvgText::SmallCaps); 0259 QCOMPARE(getFont().property(KoSvgTextProperties::FontWeightId).toInt(), 600); 0260 0261 { 0262 SvgStyles fontModifier; 0263 fontModifier["font-weight"] = "bolder"; 0264 context.styleParser().parseFont(fontModifier); 0265 QCOMPARE(getFont().property(KoSvgTextProperties::FontWeightId).toInt(), 700); 0266 } 0267 0268 { 0269 SvgStyles fontModifier; 0270 fontModifier["font-weight"] = "lighter"; 0271 context.styleParser().parseFont(fontModifier); 0272 QCOMPARE(getFont().property(KoSvgTextProperties::FontWeightId).toInt(), 600); 0273 } 0274 0275 QCOMPARE(getFont().property(KoSvgTextProperties::FontStretchId).toInt(), int(QFont::ExtraCondensed)); 0276 0277 { 0278 SvgStyles fontModifier; 0279 fontModifier["font-stretch"] = "narrower"; 0280 context.styleParser().parseFont(fontModifier); 0281 QCOMPARE(getFont().property(KoSvgTextProperties::FontStretchId).toInt(), int(QFont::UltraCondensed)); 0282 } 0283 0284 { 0285 SvgStyles fontModifier; 0286 fontModifier["font-stretch"] = "wider"; 0287 context.styleParser().parseFont(fontModifier); 0288 QCOMPARE(getFont().property(KoSvgTextProperties::FontStretchId).toInt(), int(QFont::ExtraCondensed)); 0289 } 0290 0291 { 0292 SvgStyles fontModifier; 0293 fontModifier["text-decoration"] = "underline"; 0294 context.styleParser().parseFont(fontModifier); 0295 KoSvgText::TextDecorations deco = getFont().property(KoSvgTextProperties::TextDecorationLineId).value<KoSvgText::TextDecorations>(); 0296 QCOMPARE(deco.testFlag(KoSvgText::DecorationUnderline), true); 0297 } 0298 0299 { 0300 SvgStyles fontModifier; 0301 fontModifier["text-decoration"] = "overline"; 0302 context.styleParser().parseFont(fontModifier); 0303 KoSvgText::TextDecorations deco = getFont().property(KoSvgTextProperties::TextDecorationLineId).value<KoSvgText::TextDecorations>(); 0304 QCOMPARE(deco.testFlag(KoSvgText::DecorationOverline), true); 0305 } 0306 0307 { 0308 SvgStyles fontModifier; 0309 fontModifier["text-decoration"] = "line-through"; 0310 context.styleParser().parseFont(fontModifier); 0311 KoSvgText::TextDecorations deco = getFont().property(KoSvgTextProperties::TextDecorationLineId).value<KoSvgText::TextDecorations>(); 0312 QCOMPARE(deco.testFlag(KoSvgText::DecorationLineThrough), true); 0313 } 0314 0315 { 0316 SvgStyles fontModifier; 0317 fontModifier["text-decoration"] = " line-through overline"; 0318 context.styleParser().parseFont(fontModifier); 0319 KoSvgText::TextDecorations deco = getFont().property(KoSvgTextProperties::TextDecorationLineId).value<KoSvgText::TextDecorations>(); 0320 QCOMPARE(deco.testFlag(KoSvgText::DecorationUnderline), false); 0321 QCOMPARE(deco.testFlag(KoSvgText::DecorationLineThrough), true); 0322 QCOMPARE((deco.testFlag(KoSvgText::DecorationOverline)), true); 0323 } 0324 0325 } 0326 0327 void TestSvgText::testParseTextStyles() 0328 { 0329 const QString data = 0330 "<text x=\"7\" y=\"7\"" 0331 " font-family=\"Verdana\" font-size=\"15\" font-style=\"oblique\" fill=\"blue\"" 0332 " writing-mode=\"tb-rl\" " 0333 " glyph-orientation-vertical=\"90\" >" 0334 " Hello, out there" 0335 "</text>"; 0336 0337 QDomDocument doc; 0338 QVERIFY(doc.setContent(data.toLatin1())); 0339 QDomElement root = doc.documentElement(); 0340 0341 KoDocumentResourceManager resourceManager; 0342 SvgLoadingContext context(&resourceManager); 0343 context.pushGraphicsContext(); 0344 0345 SvgStyles styles = context.styleParser().collectStyles(root); 0346 context.styleParser().parseFont(styles); 0347 0348 auto getFont = [&context] () { 0349 return context.currentGC()->textProperties.generateFont(); 0350 }; 0351 0352 QCOMPARE(getFont().family(), QString("Verdana")); 0353 0354 KoSvgTextProperties &props = context.currentGC()->textProperties; 0355 0356 QCOMPARE(props.property(KoSvgTextProperties::WritingModeId).toInt(), int(KoSvgText::VerticalRL)); 0357 QCOMPARE(props.property(KoSvgTextProperties::TextOrientationId).toInt(), int(KoSvgText::OrientationSideWays)); 0358 } 0359 0360 #include <text/KoSvgTextShape.h> 0361 #include <text/KoSvgTextShape_p.h> 0362 #include <text/KoSvgTextContentElement.h> 0363 0364 void TestSvgText::testSimpleText() 0365 { 0366 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/text-test-simple-text.svg")); 0367 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 0368 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 0369 0370 QXmlInputSource data; 0371 data.setData(file.readAll()); 0372 0373 SvgRenderTester t(data.data()); 0374 t.setCheckQImagePremultiplied(true); 0375 t.test_standard("text_simple", QSize(140, 40), 72.0); 0376 0377 KoShape *shape = t.findShape("testRect"); 0378 KoSvgTextShape *chunkShape = dynamic_cast<KoSvgTextShape*>(shape); 0379 QVERIFY(chunkShape); 0380 0381 // root shape is not just a chunk! 0382 QVERIFY(dynamic_cast<KoSvgTextShape*>(shape)); 0383 0384 QCOMPARE(KoSvgTextShape::Private::childCount(chunkShape->d->textData.childBegin()), 0); 0385 0386 QString text = chunkShape->d->textData.childBegin()->text; 0387 QVector<bool> collapse = KoCssTextUtils::collapseSpaces(&text, KoSvgText::Collapse); 0388 QCOMPARE(collapse.count(false), 17); 0389 QCOMPARE(text, QString(" Hello, out there! ")); 0390 0391 QVector<KoSvgText::CharTransformation> transform = chunkShape->d->textData.childBegin()->localTransformations; 0392 QCOMPARE(transform.size(), 1); 0393 QVERIFY(bool(transform[0].xPos)); 0394 QVERIFY(bool(transform[0].yPos)); 0395 QVERIFY(!transform[0].dxPos); 0396 QVERIFY(!transform[0].dyPos); 0397 QVERIFY(!transform[0].rotate); 0398 0399 QCOMPARE(*transform[0].xPos, 7.0); 0400 QCOMPARE(*transform[0].yPos, 27.0); 0401 0402 bool dummy = false; 0403 QVector<SubChunk> subChunks = KoSvgTextShape::Private::collectSubChunks(chunkShape->d->textData.childBegin(), false, dummy); 0404 0405 QCOMPARE(subChunks.size(), 1); 0406 QCOMPARE(subChunks[0].text.size(), 35); 0407 //qDebug() << ppVar(subChunks[0].text); 0408 //qDebug() << ppVar(subChunks[0].transformation); 0409 //qDebug() << ppVar(subChunks[0].format); 0410 0411 } 0412 0413 void TestSvgText::testComplexText() 0414 { 0415 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/text-test-complex-text.svg")); 0416 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 0417 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 0418 0419 QXmlInputSource data; 0420 data.setData(file.readAll()); 0421 0422 SvgRenderTester t(data.data()); 0423 t.setCheckQImagePremultiplied(true); 0424 t.test_standard("text_complex", QSize(370, 56), 72.0); 0425 0426 KoSvgTextShape *baseShape = dynamic_cast<KoSvgTextShape*>(t.findShape("testRect")); 0427 QVERIFY(baseShape); 0428 KisForest<KoSvgTextContentElement>::child_iterator root = baseShape->d->textData.childBegin(); 0429 0430 QCOMPARE(KoSvgTextShape::Private::childCount(root), 5); 0431 QCOMPARE(KoSvgTextShape::Private::numChars(root, false), 64); 0432 0433 QVector<KoSvgText::CharTransformation> baseTransform = root->localTransformations; 0434 QCOMPARE(baseTransform.size(), 9); 0435 QVERIFY(bool(baseTransform[0].xPos)); 0436 QVERIFY(!bool(baseTransform[1].xPos)); 0437 QCOMPARE(baseTransform.at(0).xPos, 7.0); 0438 QVERIFY(baseTransform.at(0).xPos); // if there's a value it's always set. 0439 0440 for (int i = 0; i < 9; i++) { 0441 QVERIFY(!i || bool(baseTransform[i].dxPos)); 0442 0443 if (i) { 0444 QCOMPARE(*baseTransform[i].dxPos, qreal(i)); 0445 } 0446 } 0447 0448 KisForest<KoSvgTextContentElement>::child_iterator child = KisForestDetail::childBegin(root); 0449 { // chunk 0: "Hello, " 0450 QCOMPARE(KoSvgTextShape::Private::childCount(child), 0); 0451 0452 QString text = child->text; 0453 QVector<bool> collapse = KoCssTextUtils::collapseSpaces(&text, KoSvgText::Collapse); 0454 0455 QCOMPARE(collapse.count(false), 6); 0456 QCOMPARE(text, QString(" Hello, ")); 0457 0458 QVector<KoSvgText::CharTransformation> transform = child->localTransformations; 0459 QCOMPARE(transform.size(), 0); 0460 0461 bool dummy = false; 0462 QVector<SubChunk> subChunks = KoSvgTextShape::Private::collectSubChunks(child, false, dummy); 0463 0464 QCOMPARE(subChunks.size(), 1); // used to be 7, but we got rid of aggresive subchunking. 0465 QCOMPARE(subChunks[0].text.size(), 20); 0466 } 0467 child ++; 0468 0469 { // chunk 1: "out" 0470 0471 QCOMPARE(KoSvgTextShape::Private::childCount(child), 0); 0472 0473 QCOMPARE(KoSvgTextShape::Private::numChars(child), 4); 0474 QCOMPARE(child->text, QString("ou\nt")); 0475 0476 QVector<KoSvgText::CharTransformation> transform = child->localTransformations; 0477 QCOMPARE(transform.size(), 1); 0478 QVERIFY(bool(transform[0].xPos)); 0479 0480 bool dummy = false; 0481 QVector<SubChunk> subChunks = KoSvgTextShape::Private::collectSubChunks(child, false, dummy); 0482 0483 QCOMPARE(subChunks.size(), 1); 0484 QCOMPARE(subChunks[0].text.size(), 4); 0485 } 0486 0487 child++; 0488 0489 { // chunk 2: " there " 0490 0491 QCOMPARE(KoSvgTextShape::Private::childCount(child), 0); 0492 0493 QCOMPARE(child->numChars(), 7); 0494 QCOMPARE(child->text, QString(" there ")); 0495 0496 QVector<KoSvgText::CharTransformation> transform = child->localTransformations; 0497 QCOMPARE(transform.size(), 0); 0498 0499 bool dummy = false; 0500 QVector<SubChunk> subChunks = KoSvgTextShape::Private::collectSubChunks(child, false, dummy); 0501 0502 QCOMPARE(subChunks.size(), 1); 0503 QCOMPARE(subChunks[0].text.size(), 7); 0504 } 0505 0506 child++; 0507 0508 { // chunk 3: "cool cdata --> nice work" 0509 0510 QCOMPARE(KoSvgTextShape::Private::childCount(child), 0); 0511 0512 QCOMPARE(child->numChars(), 24); 0513 QCOMPARE(child->text, QString("cool cdata --> nice work")); 0514 0515 QVector<KoSvgText::CharTransformation> transform = child->localTransformations; 0516 QCOMPARE(transform.size(), 0); 0517 0518 bool dummy = false; 0519 QVector<SubChunk> subChunks = KoSvgTextShape::Private::collectSubChunks(child, false, dummy); 0520 0521 QCOMPARE(subChunks.size(), 1); 0522 QCOMPARE(subChunks[0].text.size(), 24); 0523 } 0524 } 0525 0526 /** 0527 * @brief TestSvgText::testHindiText 0528 * 0529 * Test complex text-shaping in Devaganari using FreeSans. 0530 * Harfbuzz takes care of all of this, but it is a core feature 0531 * we need to keep an eye on. 0532 */ 0533 void TestSvgText::testHindiText() 0534 { 0535 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/text-test-hindi-text.svg")); 0536 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 0537 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 0538 0539 QXmlInputSource data; 0540 data.setData(file.readAll()); 0541 0542 SvgRenderTester t(data.data()); 0543 0544 t.setCheckQImagePremultiplied(true); 0545 t.setFuzzyThreshold(5); 0546 0547 t.test_standard("text_hindi", QSize(200, 30), 72); 0548 } 0549 0550 /** 0551 * @brief TestSvgText::testTextBaselineShift 0552 * 0553 * This tests the baseline-shift. 0554 * TODO: Test alignment and dominant baseline? 0555 */ 0556 void TestSvgText::testTextBaselineShift() 0557 { 0558 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/text-test-baseline-shift.svg")); 0559 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 0560 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 0561 0562 QXmlInputSource data; 0563 data.setData(file.readAll()); 0564 0565 SvgRenderTester t(data.data()); 0566 0567 t.setCheckQImagePremultiplied(true); 0568 t.test_standard("text_baseline_shift", QSize(180, 40), 72); 0569 0570 KoSvgTextShape *baseShape = dynamic_cast<KoSvgTextShape*>(t.findShape("testRect")); 0571 QVERIFY(baseShape); 0572 0573 } 0574 /** 0575 * @brief TestSvgText::testTextSpacing 0576 * 0577 * This tests the letter and word spacing CSS properties, 0578 * as well as the SVG 1.1 kerning property. The latter 0579 * is considered a on-off function for CSS font-kerning 0580 * in SVG 2.0, so it will have different result in a SVG 0581 * 1.1 renderer. 0582 */ 0583 void TestSvgText::testTextSpacing() 0584 { 0585 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/test-text-spacing.svg")); 0586 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 0587 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 0588 0589 QXmlInputSource data; 0590 data.setData(file.readAll()); 0591 0592 SvgRenderTester t(data.data()); 0593 t.setCheckQImagePremultiplied(true); 0594 t.setFuzzyThreshold(5); 0595 0596 t.test_standard("text_letter_word_spacing", QSize(340, 250), 72.0); 0597 0598 KoSvgTextShape *baseShape = dynamic_cast<KoSvgTextShape*>(t.findShape("testRect")); 0599 QVERIFY(baseShape); 0600 0601 } 0602 /** 0603 * @brief TestSvgText::testTextTabSpacing 0604 * 0605 * Tests tabs being kept as well as tab-size. 0606 */ 0607 void TestSvgText::testTextTabSpacing() 0608 { 0609 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/test-text-tab-spacing.svg")); 0610 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 0611 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 0612 0613 QXmlInputSource data; 0614 data.setData(file.readAll()); 0615 0616 SvgRenderTester t(data.data()); 0617 t.setFuzzyThreshold(5); 0618 t.setCheckQImagePremultiplied(true); 0619 t.test_standard("text_tab_spacing", QSize(400, 170), 72.0); 0620 0621 KoSvgTextShape *baseShape = dynamic_cast<KoSvgTextShape*>(t.findShape("testRect")); 0622 QVERIFY(baseShape); 0623 } 0624 0625 /** 0626 * @brief TestSvgText::testTextDecorations 0627 * 0628 * tests the text-decorations, but for some reason they don't paint so it's broken :( 0629 */ 0630 void TestSvgText::testTextDecorations() 0631 { 0632 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/test-text-decorations.svg")); 0633 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 0634 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 0635 0636 QXmlInputSource data; 0637 data.setData(file.readAll()); 0638 0639 SvgRenderTester t(data.data()); 0640 t.setFuzzyThreshold(5); 0641 t.setCheckQImagePremultiplied(true); 0642 t.test_standard("text_decorations", QSize(290, 135), 72.0); 0643 0644 KoSvgTextShape *baseShape = dynamic_cast<KoSvgTextShape*>(t.findShape("testRect")); 0645 QVERIFY(baseShape); 0646 0647 } 0648 0649 void TestSvgText::testRightToLeft() 0650 { 0651 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/test-text-right-to-left.svg")); 0652 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 0653 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 0654 0655 QXmlInputSource data; 0656 data.setData(file.readAll()); 0657 0658 SvgRenderTester t(data.data()); 0659 t.setCheckQImagePremultiplied(true); 0660 t.test_standard("text_right_to_left", QSize(500, 600), 72.0); 0661 0662 KoSvgTextShape *baseShape = dynamic_cast<KoSvgTextShape*>(t.findShape("testRect")); 0663 QVERIFY(baseShape); 0664 } 0665 0666 /** 0667 * @brief TestSvgText::testRightToLeftAnchoring 0668 * 0669 * This tests how anchoring behaves when doing RTL text, 0670 * as well as text on path. This doesn't test all text- 0671 * on-path cases, but it does expose an unfortunate 0672 * edge case with bidi-reordered chunks that start and end 0673 * with latin characters: these cause holes to appear. 0674 * This is unfortunately correct according to spec. 0675 */ 0676 void TestSvgText::testRightToLeftAnchoring() 0677 { 0678 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/test-text-right-to-left-text-paths.svg")); 0679 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 0680 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 0681 0682 QXmlInputSource data; 0683 data.setData(file.readAll()); 0684 0685 SvgRenderTester t(data.data()); 0686 t.setCheckQImagePremultiplied(true); 0687 t.test_standard("text_right_to_left_anchoring", QSize(500, 500), 72.0); 0688 } 0689 0690 void TestSvgText::testVerticalText() 0691 { 0692 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/text-test-vertical-text.svg")); 0693 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 0694 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 0695 0696 QXmlInputSource data; 0697 data.setData(file.readAll()); 0698 0699 SvgRenderTester t(data.data()); 0700 t.setFuzzyThreshold(5); 0701 t.test_standard("text-test-vertical-text", QSize(80, 400), 72.0); 0702 } 0703 0704 #include <QTextLayout> 0705 #include <QPainter> 0706 #include <QPainterPath> 0707 0708 void TestSvgText::testQtBidi() 0709 { 0710 // Arabic text sample from Wikipedia: 0711 // 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 0712 0713 QStringList ltrText; 0714 ltrText << "aa bb cc dd"; 0715 ltrText << "aa bb حادثتا السفينتين بسين cc dd"; 0716 ltrText << "aa bb \u202ec1c2 d3d4\u202C ee ff"; 0717 0718 QStringList rtlText; 0719 rtlText << "حادثتا السفينتين «بسين Bassein» و«فايبر Viper»"; 0720 rtlText << "حادثتا السفينتين «بسين aa bb cc dd» و«فايبر Viper»"; 0721 0722 0723 QImage canvas(500,500,QImage::Format_ARGB32); 0724 canvas.fill(Qt::transparent); 0725 QPainter gc(&canvas); 0726 QPointF pos(15,15); 0727 0728 0729 QVector<QStringList> textSamples; 0730 textSamples << ltrText; 0731 textSamples << rtlText; 0732 0733 QVector<Qt::LayoutDirection> textDirections; 0734 textDirections << Qt::LeftToRight; 0735 textDirections << Qt::RightToLeft; 0736 0737 for (int i = 0; i < textSamples.size(); i++) { 0738 Q_FOREACH (const QString str, textSamples[i]) { 0739 QTextOption option; 0740 option.setTextDirection(textDirections[i]); 0741 option.setUseDesignMetrics(true); 0742 0743 QTextLayout layout; 0744 0745 layout.setText(str); 0746 layout.setFont(QFont("serif", 15.0)); 0747 layout.setCacheEnabled(true); 0748 layout.beginLayout(); 0749 0750 QTextLine line = layout.createLine(); 0751 line.setPosition(pos); 0752 pos.ry() += 25; 0753 layout.endLayout(); 0754 layout.draw(&gc, QPointF()); 0755 } 0756 } 0757 0758 canvas.save("test_bidi.png"); 0759 } 0760 0761 void TestSvgText::testQtDxDy() 0762 { 0763 QImage canvas(500,500,QImage::Format_ARGB32); 0764 canvas.fill(Qt::transparent); 0765 QPainter gc(&canvas); 0766 QPointF pos(15,15); 0767 0768 QTextOption option; 0769 option.setTextDirection(Qt::LeftToRight); 0770 option.setUseDesignMetrics(true); 0771 option.setWrapMode(QTextOption::WrapAnywhere); 0772 0773 QTextLayout layout; 0774 0775 layout.setText("aa bb cc dd ee ff"); 0776 layout.setFont(QFont("serif", 15.0)); 0777 layout.setCacheEnabled(true); 0778 layout.beginLayout(); 0779 layout.setTextOption(option); 0780 0781 { 0782 QTextLine line = layout.createLine(); 0783 line.setPosition(pos); 0784 line.setNumColumns(4); 0785 } 0786 pos.ry() += 25; 0787 pos.rx() += 30; 0788 { 0789 QTextLine line = layout.createLine(); 0790 line.setPosition(pos); 0791 } 0792 0793 layout.endLayout(); 0794 layout.draw(&gc, QPointF()); 0795 0796 0797 canvas.save("test_dxdy.png"); 0798 } 0799 0800 /** 0801 * @brief testTextOutlineSolid() 0802 * 0803 * Tests whether SVG strokes render correctly for SVG text. 0804 */ 0805 void TestSvgText::testTextOutlineSolid() 0806 { 0807 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/test-text-solid-stroke.svg")); 0808 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 0809 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 0810 0811 QXmlInputSource data; 0812 data.setData(file.readAll()); 0813 0814 SvgRenderTester t(data.data()); 0815 t.test_standard("text_outline_solid", QSize(30, 30), 72.0); 0816 } 0817 0818 /** 0819 * @brief testNbspHandling() 0820 * 0821 * Tests whether no-break-spaces (nbsp) are left alone. 0822 */ 0823 void TestSvgText::testNbspHandling() 0824 { 0825 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/test-text-no-break-space.svg")); 0826 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 0827 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 0828 0829 QXmlInputSource data; 0830 data.setData(file.readAll()); 0831 0832 SvgRenderTester t(data.data()); 0833 t.test_standard("text_nbsp", QSize(30, 30), 72.0); 0834 } 0835 0836 /** 0837 * @brief testMulticolorText() 0838 * 0839 * Tests whether we can have a piece of text with multiple 0840 * colors assigned to different parts of the text. 0841 * 0842 * This now tests what happens when ligatures straddle a span border. According to 0843 * SVG, all graphemes made up from multiple code-points (like ligatures) should have 0844 * the color assigned to the first code-point. 0845 */ 0846 void TestSvgText::testMulticolorText() 0847 { 0848 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/test-text-multicolor.svg")); 0849 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 0850 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 0851 0852 QXmlInputSource data; 0853 data.setData(file.readAll()); 0854 0855 SvgRenderTester t(data.data()); 0856 t.setFuzzyThreshold(5); 0857 t.test_standard("text_multicolor", QSize(100, 30), 72.0); 0858 } 0859 0860 #include <KoColorBackground.h> 0861 0862 void TestSvgText::testConvertToStrippedSvg() 0863 { 0864 const QString data = 0865 "<svg width=\"100px\" height=\"30px\"" 0866 " xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">" 0867 0868 "<g id=\"test\">" 0869 0870 " <rect id=\"boundingRect\" x=\"4\" y=\"5\" width=\"89\" height=\"19\"" 0871 " fill=\"none\" stroke=\"red\"/>" 0872 0873 " <text transform=\"translate(2)\" id=\"testRect\" x=\"2\" y=\"24\"" 0874 " font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >" 0875 " S<tspan fill=\"red\">A</tspan><![CDATA[some stuff<><><<<>]]>" 0876 " </text>" 0877 0878 "</g>" 0879 0880 "</svg>"; 0881 0882 SvgRenderTester t (data); 0883 t.parser.setResolution(QRectF(QPointF(), QSizeF(30,30)) /* px */, 72.0/* ppi */); 0884 t.run(); 0885 0886 KoSvgTextShape *baseShape = dynamic_cast<KoSvgTextShape*>(t.findShape("testRect")); 0887 QVERIFY(baseShape); 0888 0889 { 0890 KoColorBackground *bg = dynamic_cast<KoColorBackground*>(baseShape->background().data()); 0891 QVERIFY(bg); 0892 QCOMPARE(bg->color(), QColor(Qt::blue)); 0893 } 0894 0895 KoSvgTextShapeMarkupConverter converter(baseShape); 0896 0897 QString svgText; 0898 QString stylesText; 0899 QVERIFY(converter.convertToSvg(&svgText, &stylesText)); 0900 0901 QCOMPARE(stylesText, QString("<defs/>")); 0902 QCOMPARE(svgText, 0903 QString("<text text-rendering=\"auto\" fill=\"#0000ff\" stroke-opacity=\"0\" stroke=\"#000000\" stroke-width=\"0\" stroke-linecap=\"square\" " 0904 "stroke-linejoin=\"bevel\" x=\"2\" y=\"24\" style=\"font-family: DejaVu Sans;font-size: 15;\"><tspan> S</tspan><tspan " 0905 "fill=\"#ff0000\">A</tspan><tspan>some stuff<><><<<></tspan></text>")); 0906 0907 // test loading 0908 0909 svgText = "<text fill=\"#00ff00\" x=\"2\" y=\"24\" font-family=\"DejaVu Sans\" font-size=\"19\"><tspan> S</tspan><tspan fill=\"#ff0000\">A</tspan><tspan>some stuff<><><<<></tspan></text>"; 0910 0911 QVERIFY(converter.convertFromSvg(svgText, stylesText, QRectF(0,0,30,30), 72.0)); 0912 0913 { 0914 KoColorBackground *bg = dynamic_cast<KoColorBackground*>(baseShape->background().data()); 0915 QVERIFY(bg); 0916 QCOMPARE(bg->color(), QColor(Qt::green)); 0917 } 0918 0919 { 0920 KoSvgTextProperties props = baseShape->textProperties(); 0921 QVERIFY(props.hasProperty(KoSvgTextProperties::FontSizeId)); 0922 0923 const qreal fontSize = props.property(KoSvgTextProperties::FontSizeId).toReal(); 0924 QCOMPARE(fontSize, 19.0); 0925 } 0926 0927 QCOMPARE(KoSvgTextShape::Private::childCount(baseShape->d->textData.childBegin()), 3); 0928 } 0929 0930 void TestSvgText::testConvertToStrippedSvgNullOrigin() 0931 { 0932 const QString data = 0933 "<svg width=\"100px\" height=\"30px\"" 0934 " xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">" 0935 0936 "<g id=\"test\">" 0937 0938 " <rect id=\"boundingRect\" x=\"4\" y=\"5\" width=\"89\" height=\"19\"" 0939 " fill=\"none\" stroke=\"red\"/>" 0940 0941 " <text transform=\"translate(2)\" id=\"testRect\" x=\"0\" y=\"0\"" 0942 " font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >" 0943 " S<tspan fill=\"red\">A</tspan><![CDATA[some stuff<><><<<>]]>" 0944 " </text>" 0945 0946 "</g>" 0947 0948 "</svg>"; 0949 0950 SvgRenderTester t (data); 0951 t.parser.setResolution(QRectF(QPointF(), QSizeF(30,30)) /* px */, 72.0/* ppi */); 0952 t.run(); 0953 0954 KoSvgTextShape *baseShape = dynamic_cast<KoSvgTextShape*>(t.findShape("testRect")); 0955 QVERIFY(baseShape); 0956 0957 KoSvgTextShapeMarkupConverter converter(baseShape); 0958 0959 QString svgText; 0960 QString stylesText; 0961 QVERIFY(converter.convertToSvg(&svgText, &stylesText)); 0962 0963 QCOMPARE(stylesText, QString("<defs/>")); 0964 QCOMPARE(svgText, 0965 QString("<text text-rendering=\"auto\" fill=\"#0000ff\" stroke-opacity=\"0\" stroke=\"#000000\" stroke-width=\"0\" stroke-linecap=\"square\" " 0966 "stroke-linejoin=\"bevel\" x=\"0\" y=\"0\" style=\"font-family: DejaVu Sans;font-size: 15;\"><tspan> S</tspan><tspan " 0967 "fill=\"#ff0000\">A</tspan><tspan>some stuff<><><<<></tspan></text>")); 0968 } 0969 0970 void TestSvgText::testConvertFromIncorrectStrippedSvg() 0971 { 0972 QScopedPointer<KoSvgTextShape> baseShape(new KoSvgTextShape()); 0973 0974 KoSvgTextShapeMarkupConverter converter(baseShape.data()); 0975 0976 QString svgText; 0977 QString stylesText; 0978 0979 svgText = "<text>blah text</text>"; 0980 QVERIFY(converter.convertFromSvg(svgText, stylesText, QRectF(0,0,30,30), 72.0)); 0981 QCOMPARE(converter.errors().size(), 0); 0982 0983 svgText = "<text>>><<><blah text</text>"; 0984 QVERIFY(!converter.convertFromSvg(svgText, stylesText, QRectF(0,0,30,30), 72.0)); 0985 qDebug() << ppVar(converter.errors()); 0986 QCOMPARE(converter.errors().size(), 1); 0987 0988 svgText = "<notext>blah text</notext>"; 0989 QVERIFY(!converter.convertFromSvg(svgText, stylesText, QRectF(0,0,30,30), 72.0)); 0990 qDebug() << ppVar(converter.errors()); 0991 QCOMPARE(converter.errors().size(), 1); 0992 0993 svgText = "<defs/>"; 0994 QVERIFY(!converter.convertFromSvg(svgText, stylesText, QRectF(0,0,30,30), 72.0)); 0995 qDebug() << ppVar(converter.errors()); 0996 QCOMPARE(converter.errors().size(), 1); 0997 } 0998 0999 void TestSvgText::testEmptyTextChunk() 1000 { 1001 const QString data = 1002 "<svg width=\"100px\" height=\"30px\"" 1003 " xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">" 1004 1005 "<g id=\"test\">" 1006 1007 " <rect id=\"boundingRect\" x=\"4\" y=\"5\" width=\"89\" height=\"19\"" 1008 " fill=\"none\" stroke=\"red\"/>" 1009 1010 " <text id=\"testRect\" x=\"2\" y=\"24\"" 1011 " font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >" 1012 " " // no actual text! should not crash! 1013 " </text>" 1014 1015 "</g>" 1016 1017 "</svg>"; 1018 1019 SvgRenderTester t (data); 1020 1021 // it just shouldn't assert or fail when seeing an empty text block 1022 t.parser.setResolution(QRectF(QPointF(), QSizeF(30,30)) /* px */, 72.0/* ppi */); 1023 t.run(); 1024 } 1025 1026 void TestSvgText::testTrailingWhitespace() 1027 { 1028 QStringList chunkA; 1029 chunkA << "aaa"; 1030 chunkA << " aaa"; 1031 chunkA << "aaa "; 1032 chunkA << " aaa "; 1033 1034 QStringList chunkB; 1035 chunkB << "bbb"; 1036 chunkB << " bbb"; 1037 chunkB << "bbb "; 1038 chunkB << " bbb "; 1039 1040 QStringList linkChunk; 1041 linkChunk << ""; 1042 linkChunk << " "; 1043 linkChunk << "<tspan></tspan>"; 1044 linkChunk << "<tspan> </tspan>"; 1045 1046 1047 const QString dataTemplate = 1048 "<svg width=\"100px\" height=\"30px\"" 1049 " xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">" 1050 1051 "<g id=\"test\">" 1052 1053 " <rect id=\"boundingRect\" x=\"4\" y=\"5\" width=\"89\" height=\"19\"" 1054 " fill=\"none\" stroke=\"red\"/>" 1055 1056 " <text id=\"testRect\" x=\"2\" y=\"24\"" 1057 " font-family=\"DejaVu Sans\" font-size=\"15\" fill=\"blue\" >" 1058 " <tspan>%1</tspan>%2<tspan>%3</tspan>" 1059 " </text>" 1060 1061 "</g>" 1062 1063 "</svg>"; 1064 1065 for (auto itL = linkChunk.constBegin(); itL != linkChunk.constEnd(); ++itL) { 1066 for (auto itA = chunkA.constBegin(); itA != chunkA.constEnd(); ++itA) { 1067 for (auto itB = chunkB.constBegin(); itB != chunkB.constEnd(); ++itB) { 1068 if (itA->rightRef(1) != " " && 1069 itB->leftRef(1) != " " && 1070 *itL != " " && 1071 *itL != linkChunk.last()) continue; 1072 1073 QString cleanLink = *itL; 1074 cleanLink.replace('/', '_'); 1075 1076 qDebug() << "Testcase:" << *itA << cleanLink << *itB; 1077 1078 const QString data = dataTemplate.arg(*itA, *itL, *itB); 1079 SvgRenderTester t (data); 1080 t.setFuzzyThreshold(5); 1081 //t.test_standard(QString("text_trailing_%1_%2_%3").arg(*itA).arg(cleanLink).arg(*itB), QSize(70, 30), 72.0); 1082 1083 // all files should look exactly the same! 1084 t.test_standard(QString("text_whitespace"), QSize(70, 30), 72.0); 1085 } 1086 } 1087 } 1088 } 1089 1090 void TestSvgText::testWhiteSpaceRules() 1091 { 1092 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/text-test-white-space.svg")); 1093 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 1094 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 1095 1096 QXmlInputSource data; 1097 data.setData(file.readAll()); 1098 1099 SvgRenderTester t(data.data()); 1100 t.setFuzzyThreshold(5); 1101 t.test_standard("text-test-white-space", QSize(400, 320), 72.0); 1102 } 1103 1104 void TestSvgText::testConvertHtmlToSvg() 1105 { 1106 const QString html = 1107 "<?xml version=\"1.0\"?>" 1108 "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">" 1109 "<html>" 1110 "<head>" 1111 "<meta name=\"qrichtext\" content=\"1\"/>" 1112 "<style type=\"text/css\">p, li { white-space: pre-wrap; }</style>" 1113 "</head>" 1114 "<body style=\" font-family:'Droid Sans'; font-size:9pt; font-weight:400; font-style:normal;\">" 1115 "<p style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">" 1116 " <span style=\" font-family:'Times'; font-size:20pt;\">Lorem ipsum dolor</span>" 1117 "</p>" 1118 "<p style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">sit am" 1119 "<span style=\" font-weight:600;\">et, consectetur adipis</span>cing </p>" 1120 "<p style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">" 1121 " <span style=\" font-style:italic;\">elit. </span>" 1122 "</p>" 1123 "</body>" 1124 "</html>"; 1125 1126 KoSvgTextShape shape; 1127 KoSvgTextShapeMarkupConverter converter(&shape); 1128 1129 QString svg; 1130 QString defs; 1131 1132 converter.convertFromHtml(html, &svg, &defs); 1133 1134 1135 bool r = converter.convertToSvg(&svg, &defs); 1136 1137 qDebug() << r << svg << defs; 1138 1139 } 1140 1141 void TestSvgText::testTextWithMultipleRelativeOffsets() 1142 { 1143 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/text-test-multiple-relative-offsets.svg")); 1144 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 1145 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 1146 1147 QXmlInputSource data; 1148 data.setData(file.readAll()); 1149 1150 SvgRenderTester t(data.data()); 1151 t.setFuzzyThreshold(5); 1152 t.test_standard("text_multiple_relative_offsets", QSize(300, 80), 72.0); 1153 } 1154 1155 void TestSvgText::testTextWithMultipleAbsoluteOffsetsArabic() 1156 { 1157 /** 1158 * According to the SVG 1.1 standard, each **absolute** offset 1159 * defines a new text chunk, therefore, in SVG 1.1 the arabic text 1160 * would become ltr reordered 1161 * 1162 * SVG 2.0 gets rid of this, because an SVG text is treated as a 1163 * single paragraph, and it's not expected that such a thing happens 1164 * inside a single paragraph. 1165 */ 1166 1167 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/text-test-multiple-absolute-offsets-arabic.svg")); 1168 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 1169 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 1170 1171 QXmlInputSource data; 1172 data.setData(file.readAll()); 1173 1174 SvgRenderTester t(data.data()); 1175 t.setFuzzyThreshold(5); 1176 t.test_standard("text_multiple_absolute_offsets_arabic", QSize(530, 70), 72.0); 1177 } 1178 1179 void TestSvgText::testTextWithMultipleRelativeOffsetsArabic() 1180 { 1181 /** 1182 * According to the standard, **relative** offsets must not define a new 1183 * text chunk, therefore, the arabic text must be written in native rtl order, 1184 * even though the individual letters are split. 1185 * 1186 * Mind, for SVG 2.0 this difference between absolute and relative 1187 * has been removed. 1188 */ 1189 1190 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/text-test-multiple-relative-offsets-arabic.svg")); 1191 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 1192 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 1193 1194 QXmlInputSource data; 1195 data.setData(file.readAll()); 1196 1197 SvgRenderTester t(data.data()); 1198 1199 // we cannot expect more than one failure 1200 /*#ifndef USE_ROUND_TRIP 1201 QEXPECT_FAIL("", "WARNING: in Krita relative offsets also define a new text chunk, that doesn't comply with SVG standard and must be fixed", 1202 Continue);*/ 1203 t.setFuzzyThreshold(5); 1204 t.test_standard("text_multiple_relative_offsets_arabic", QSize(530, 70), 72.0); 1205 // #endif 1206 } 1207 /** 1208 * @brief TestSvgText::testTextWithMultipleRelativeOffsetsVertical 1209 * 1210 * This tests vertical rotation. 1211 */ 1212 void TestSvgText::testTextWithMultipleRelativeOffsetsVertical() 1213 { 1214 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/text-test-multiple-relative-offsets-vertical.svg")); 1215 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 1216 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 1217 1218 QXmlInputSource data; 1219 data.setData(file.readAll()); 1220 1221 SvgRenderTester t(data.data()); 1222 t.setFuzzyThreshold(5); 1223 t.test_standard("text_multiple_relative_offsets_vertical", QSize(80, 400), 72.0); 1224 } 1225 1226 /** 1227 * @brief TestSvgText::testTextWithMultipleRotations 1228 * 1229 * This tests the rotation property, which rotates a glyph 1230 * around it's own axis. 1231 */ 1232 void TestSvgText::testTextWithMultipleRotations() 1233 { 1234 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/text-test-multiple-rotations.svg")); 1235 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 1236 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 1237 1238 QXmlInputSource data; 1239 data.setData(file.readAll()); 1240 1241 SvgRenderTester t(data.data()); 1242 t.setFuzzyThreshold(5); 1243 t.test_standard("text_multiple_rotations", QSize(340, 400), 72.0); 1244 } 1245 1246 void TestSvgText::testTextOutline() 1247 { 1248 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/test-text-outline.svg")); 1249 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 1250 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 1251 1252 QXmlInputSource data; 1253 data.setData(file.readAll()); 1254 1255 SvgRenderTester t(data.data()); 1256 1257 QRect renderRect(0, 0, 450, 40); 1258 1259 t.setFuzzyThreshold(5); 1260 t.setCheckQImagePremultiplied(true); 1261 t.test_standard("text_outline", renderRect.size(), 72.0); 1262 1263 KoShape *shape = t.findShape("testRect"); 1264 1265 KoSvgTextShape *textShape = dynamic_cast<KoSvgTextShape*>(shape); 1266 1267 QImage canvas(renderRect.size(), QImage::Format_ARGB32); 1268 canvas.fill(0); 1269 QPainter gc(&canvas); 1270 gc.setPen(Qt::NoPen); 1271 gc.setBrush(Qt::black); 1272 gc.setRenderHint(QPainter::Antialiasing, true); 1273 for (KoShape *shape : textShape->textOutline()) { 1274 KoPathShape *outline = dynamic_cast<KoPathShape *>(shape); 1275 if (shape) { 1276 gc.drawPath(outline->outline()); 1277 } 1278 } 1279 1280 QVERIFY(TestUtil::checkQImage(canvas, "svg_render", "load_text_outline", "converted_to_path", 3, 5)); 1281 } 1282 1283 void testTextFontSizeHelper(QString filename, int dpi, bool pixelSize) 1284 { 1285 // ENTER_FUNCTION() << ppVar(dpi) << ppVar(filename) << ppVar(pixelSize); 1286 1287 QFont testFont("DejaVu Sans"); 1288 if (!QFontInfo(testFont).exactMatch()) { 1289 qWarning() << "DejaVu Sans is *not* found! Text rendering might be broken!"; 1290 } 1291 1292 if (pixelSize) { 1293 testFont.setPixelSize(20); 1294 } else { 1295 testFont.setPointSize(20); 1296 } 1297 1298 QTextLayout layout("Chy QTextLayout", testFont); 1299 1300 QFontMetricsF fontMetrics(testFont); 1301 int leading = fontMetrics.leading(); 1302 qreal height = 0; 1303 layout.setCacheEnabled(true); 1304 layout.beginLayout(); 1305 qreal lineWidth = 1000; 1306 while (1) { 1307 QTextLine line = layout.createLine(); 1308 if (!line.isValid()) 1309 break; 1310 1311 line.setLineWidth(lineWidth); 1312 height += leading; 1313 line.setPosition(QPointF(0, height)); 1314 height += line.height(); 1315 } 1316 layout.endLayout(); 1317 1318 // ENTER_FUNCTION() << ppVar(layout.boundingRect()); 1319 QImage image(QSize(200, 100), QImage::Format_ARGB32); 1320 // 72 dpi => ~2834 dpm 1321 qreal inchesInMeter = 39.37007874; 1322 qreal dpm = dpi*inchesInMeter; 1323 1324 image.setDotsPerMeterX((int)dpm); 1325 image.setDotsPerMeterY((int)dpm); 1326 1327 // ENTER_FUNCTION() << ppVar(image.dotsPerMeterX()) << ppVar(image.dotsPerMeterY()) << ppVar(image.devicePixelRatioF()) 1328 // << ppVar(image.devicePixelRatioFScale()) << ppVar(image.logicalDpiX()) << ppVar(image.logicalDpiY()) 1329 // << ppVar(image.logicalDpiX()) << ppVar(image.physicalDpiX())<< ppVar(image.physicalDpiY()); 1330 1331 image.fill(Qt::white); 1332 QPainter painter(&image); 1333 //painter.se 1334 layout.draw(&painter, QPointF(0, 0)); 1335 1336 1337 QBrush brush(Qt::red); 1338 QPen pen(Qt::red); 1339 painter.setBrush(brush); 1340 painter.setPen(pen); 1341 painter.drawLine(QPoint(0, 20), QPoint(200, 20)); 1342 painter.drawLine(QPoint(0, 40), QPoint(200, 40)); 1343 painter.drawLine(QPoint(0, 60), QPoint(200, 60)); 1344 painter.drawLine(QPoint(0, 80), QPoint(200, 80)); 1345 1346 QBrush brush2(Qt::blue); 1347 QPen pen2(Qt::blue); 1348 painter.setBrush(brush2); 1349 painter.setPen(pen2); 1350 1351 painter.setFont(testFont); 1352 //painter.drawText(QPointF(0, 0), "Chy QPainter"); 1353 painter.drawText(QRectF(0, 40, 200, 100), "Chy QPainter"); 1354 1355 // ENTER_FUNCTION() << ppVar(painter.fontMetrics().height()) << ppVar(painter.fontMetrics().xHeight()); 1356 // ENTER_FUNCTION() << ppVar(QFontMetrics(testFont).height()) << ppVar(QFontMetrics(testFont).xHeight()); 1357 1358 QString filenameSuffix = (pixelSize ? "pixel_" : "point_") + QString::number(dpi); 1359 1360 image.save(QString(FILES_OUTPUT_DIR) + '/' + filename + "_" + filenameSuffix + ".png"); 1361 1362 } 1363 1364 void TestSvgText::testTextFontSize() 1365 { 1366 QString filename = "testTextFontSize"; 1367 1368 testTextFontSizeHelper(filename, 72, true); 1369 testTextFontSizeHelper(filename, 72, false); 1370 testTextFontSizeHelper(filename, 4*72, true); 1371 testTextFontSizeHelper(filename, 4*72, false); 1372 1373 testTextFontSizeHelper(filename, 96, true); 1374 testTextFontSizeHelper(filename, 96, false); 1375 1376 } 1377 1378 /** 1379 * @brief TestSvgText::testAddingTestFont 1380 * 1381 * This test tests whether we can add a font to the font registery 1382 * and retrieve it afterwards. Without this, we won't be able to 1383 * write reliable machine tests given how much of text rendering is 1384 * font specific. 1385 */ 1386 void TestSvgText::testAddingTestFont() 1387 { 1388 QString fontName = "Ahem"; 1389 1390 QVector<int> lengths; 1391 QMap<QString, qreal> axisSettings; 1392 const std::vector<FT_FaceUP> faces = KoFontRegistry::instance()->facesForCSSValues({fontName}, lengths, axisSettings); 1393 1394 bool res = false; 1395 for (const FT_FaceUP &face : faces) { 1396 // qDebug() << face->family_name; 1397 if (face->family_name == fontName) { 1398 res = true; 1399 break; 1400 } 1401 } 1402 QVERIFY2(res, QString("KoFontRegistry could not find the added test font %1").arg(fontName).toLatin1()); 1403 } 1404 1405 /** 1406 * @testUnicodeGraphemeClusters 1407 * This tests KoCssTextUtils::textToUnicodeGraphemeClusters, 1408 * which is a prerequisite to doing robust font-charmap-matching. 1409 * 1410 * We'll be testing a number of texts and see if they break up 1411 * correctly. 1412 */ 1413 void TestSvgText::testUnicodeGraphemeClusters() 1414 { 1415 QString langCode = ""; 1416 QString test; 1417 QStringList expectedResult; 1418 QStringList result; 1419 1420 // Simple test. 1421 1422 test = "123ABC"; 1423 expectedResult.clear(); 1424 expectedResult << "1" 1425 << "2" 1426 << "3" 1427 << "A" 1428 << "B" 1429 << "C"; 1430 1431 result = KoCssTextUtils::textToUnicodeGraphemeClusters(test, langCode); 1432 1433 QVERIFY2(result == expectedResult, 1434 QString("Text to unicode clusters for %1 is incorrect.\n Result:\t %2\n Expected:\t %3") 1435 .arg(test) 1436 .arg(result.join(", ")) 1437 .arg(expectedResult.join(", ")) 1438 .toLatin1()); 1439 1440 // Testing text + combining marks. 1441 1442 test = "K\u0304r\u0330i\u1dd1\u1ab2ta\u20d4"; 1443 expectedResult.clear(); 1444 expectedResult << "K\u0304" 1445 << "r\u0330" 1446 << "i\u1dd1\u1ab2" 1447 << "t" 1448 << "a\u20d4"; 1449 1450 result = KoCssTextUtils::textToUnicodeGraphemeClusters(test, langCode); 1451 1452 QVERIFY2(result == expectedResult, 1453 QString("Text to unicode clusters for %1 is incorrect.\n Result:\t %2\n Expected:\t %3") 1454 .arg(test) 1455 .arg(result.join(", ")) 1456 .arg(expectedResult.join(", ")) 1457 .toLatin1()); 1458 1459 // Testing text + emoji sequence 1460 // This tests the fitzpatrick modifiers (woman+black), a zero-width joiner (black woman+fire engine) 1461 // as well as the regional indicators which is how flags are handled. 1462 1463 test = "Fire:\U0001F469\U0001F3FF\u200D\U0001F692 US:\U0001F1FA\U0001F1F8"; 1464 expectedResult.clear(); 1465 expectedResult << "F" 1466 << "i" 1467 << "r" 1468 << "e" 1469 << ":" 1470 << "\U0001F469\U0001F3FF\u200D\U0001F692" 1471 << " " 1472 << "U" 1473 << "S" 1474 << ":" 1475 << "\U0001F1FA\U0001F1F8"; 1476 1477 result = KoCssTextUtils::textToUnicodeGraphemeClusters(test, langCode); 1478 1479 QVERIFY2(result == expectedResult, 1480 QString("Text to unicode clusters for %1 is incorrect.\n Result:\t %2\n Expected:\t %3") 1481 .arg(test) 1482 .arg(result.join(", ")) 1483 .arg(expectedResult.join(", ")) 1484 .toLatin1()); 1485 1486 // Testing variation selector. 1487 // These represent alternate forms of a glyph which may need to be selected for certain purposes. 1488 // For example a person's name and a place name may use the same character, 1489 // but will need different versions of that character. 1490 1491 test = "Ashi:\u82A6\uFE03 or \u82A6"; 1492 expectedResult.clear(); 1493 expectedResult << "A" 1494 << "s" 1495 << "h" 1496 << "i" 1497 << ":" 1498 << "\u82A6\uFE03" 1499 << " " 1500 << "o" 1501 << "r" 1502 << " " 1503 << "\u82A6"; 1504 1505 result = KoCssTextUtils::textToUnicodeGraphemeClusters(test, langCode); 1506 1507 QVERIFY2(result == expectedResult, 1508 QString("Text to unicode clusters for %1 is incorrect.\n Result:\t %2\n Expected:\t %3") 1509 .arg(test) 1510 .arg(result.join(", ")) 1511 .arg(expectedResult.join(", ")) 1512 .toLatin1()); 1513 } 1514 1515 /** 1516 * @brief TestSvgText::testFontSelectionForText 1517 * 1518 * This tests whether we are selecting appropriate fonts for a given text. 1519 * Things we want to test amongst others are: mixed script, emoji selection, 1520 * unicode variation selection, combination marks and support for the unicode 1521 * supplementary (and above) planes. 1522 */ 1523 void TestSvgText::testFontSelectionForText() 1524 { 1525 // Test the letter a. 1526 1527 QString test = "a"; 1528 QMap<QString, qreal> axisSettings; 1529 1530 // First we verify that we can find the test fonts. 1531 1532 QVector<int> lengths; 1533 const std::vector<FT_FaceUP> faces = KoFontRegistry::instance()->facesForCSSValues({"CSSTest Verify"}, lengths, axisSettings, test); 1534 1535 QVERIFY2(lengths.size() == 1, QString("KoFontRegistry selected the wrong amount of fonts for the following text: %1").arg(test).toLatin1()); 1536 1537 // Test combination marks. We should prefer combination marks to be using the same glyphs as the font. 1538 1539 test = "K\u0304r\u0330ita"; 1540 QStringList fontFamilies; 1541 QStringList foundFonts; 1542 QStringList expectedFonts; 1543 fontFamilies << "CSSTest Verify" 1544 << "DejaVu Sans"; 1545 expectedFonts << "DejaVu Sans" 1546 << "CSSTest Verify"; 1547 QVector<int> expectedLengths; 1548 expectedLengths << 4 << 3; 1549 1550 const std::vector<FT_FaceUP> faces2 = KoFontRegistry::instance()->facesForCSSValues(fontFamilies, lengths, axisSettings, test); 1551 QVERIFY2(lengths == expectedLengths, QString("KoFontRegistry returns the wrong lengths for string %1").arg(test).toLatin1()); 1552 for (const FT_FaceUP &face : faces2) { 1553 // qDebug() << face->family_name; 1554 foundFonts.append(face->family_name); 1555 } 1556 QVERIFY2(foundFonts == expectedFonts, 1557 QString("KoFontRegistry returns the wrong fonts for string %1" 1558 "\nResult:\t%2\nExpected:\t%3") 1559 .arg(test) 1560 .arg(foundFonts.join(", ")) 1561 .arg(expectedFonts.join(", ")) 1562 .toLatin1()); 1563 1564 // Test emoji 1565 1566 test = "Hand:\u270d\U0001F3FF etc."; 1567 1568 const std::vector<FT_FaceUP> faces3 = KoFontRegistry::instance()->facesForCSSValues(fontFamilies, lengths, axisSettings, test); 1569 expectedLengths.clear(); 1570 expectedLengths << 5 << 3 << 5; 1571 // we can only test the lengths here because dejavu sans doesn't 1572 // have the fitzpatrick emoji selectors, so on a regular 1573 // desktop the families would pick a proper emoji font for this. 1574 QVERIFY2(lengths == expectedLengths, QString("KoFontRegistry returns the wrong lengths for string %1").arg(test).toLatin1()); 1575 1576 // Test variation selector (with and without graceful fallback). 1577 // What we want to do here is check whether if we have a font with a character 1578 // but not the variation selector, it will treat this as the fallback and select 1579 // when there's no better font. May not work on non-testing systems? 1580 1581 test = "Ashi:\u82A6\uFE03 or \u82A6"; 1582 fontFamilies << "Krita_Test_Unicode_Variation_A"; 1583 expectedLengths.clear(); 1584 expectedLengths << 5 << 2 << 4 << 1; 1585 1586 foundFonts.clear(); 1587 expectedFonts.clear(); 1588 expectedFonts << "CSSTest Verify" 1589 << "Krita_Test_Unicode_Variation_A" 1590 << "CSSTest Verify" 1591 << "Krita_Test_Unicode_Variation_A"; 1592 const std::vector<FT_FaceUP> faces4 = KoFontRegistry::instance()->facesForCSSValues(fontFamilies, lengths, axisSettings, test); 1593 QVERIFY2(lengths == expectedLengths, QString("KoFontRegistry returns the wrong lengths for string %1").arg(test).toLatin1()); 1594 for (const FT_FaceUP &face : faces4) { 1595 foundFonts.append(face->family_name); 1596 } 1597 QVERIFY2(foundFonts == expectedFonts, 1598 QString("KoFontRegistry returns the wrong fonts for string %1" 1599 "\nResult:\t%2\nExpected:\t%3") 1600 .arg(test) 1601 .arg(foundFonts.join(", ")) 1602 .arg(expectedFonts.join(", ")) 1603 .toLatin1()); 1604 1605 // What we want to do here is check whether if we have a font with a character and a selector, 1606 // it will select that font over others that may only have the base character. 1607 1608 foundFonts.clear(); 1609 expectedFonts.clear(); 1610 expectedFonts << "CSSTest Verify" 1611 << "Krita_Test_Unicode_Variation_B" 1612 << "CSSTest Verify" 1613 << "Krita_Test_Unicode_Variation_B"; 1614 fontFamilies.clear(); 1615 fontFamilies << "CSSTest Verify" 1616 << "Krita_Test_Unicode_Variation_B" 1617 << "Krita_Test_Unicode_Variation_A"; 1618 1619 const std::vector<FT_FaceUP> faces5 = KoFontRegistry::instance()->facesForCSSValues(fontFamilies, lengths, axisSettings, test); 1620 QVERIFY2(lengths == expectedLengths, QString("KoFontRegistry returns the wrong lengths for string %1").arg(test).toLatin1()); 1621 for (const FT_FaceUP &face : faces5) { 1622 // qDebug() << face->family_name; 1623 foundFonts.append(face->family_name); 1624 } 1625 QVERIFY2(foundFonts == expectedFonts, 1626 QString("KoFontRegistry returns the wrong fonts for string %1" 1627 "\nResult:\t%2\nExpected:\t%3") 1628 .arg(test) 1629 .arg(foundFonts.join(", ")) 1630 .arg(expectedFonts.join(", ")) 1631 .toLatin1()); 1632 1633 // Test Arabic + English + CJK 1634 // This is just a generic test to see if we can have mixed script without things blowing up. 1635 1636 test = "Lo rem اللغة العربية المعيارية الحديثة ip あああ sum"; 1637 fontFamilies << "DejaVu Sans"; 1638 foundFonts.clear(); 1639 expectedLengths.clear(); 1640 expectedLengths << 7 << 5 << 1 << 7 << 1 << 9 << 1 << 7 << 4 << 3 << 4; 1641 expectedFonts.clear(); 1642 expectedFonts << "CSSTest Verify" 1643 << "DejaVu Sans" 1644 << "CSSTest Verify" 1645 << "DejaVu Sans" 1646 << "CSSTest Verify" 1647 << "DejaVu Sans" 1648 << "CSSTest Verify" 1649 << "DejaVu Sans" 1650 << "CSSTest Verify" 1651 << "Krita_Test_Unicode_Variation_B" 1652 << "CSSTest Verify"; 1653 const std::vector<FT_FaceUP> faces6 = KoFontRegistry::instance()->facesForCSSValues(fontFamilies, lengths, axisSettings, test); 1654 QVERIFY2(lengths == expectedLengths, QString("KoFontRegistry returns the wrong lengths for string %1").arg(test).toLatin1()); 1655 for (const FT_FaceUP &face : faces6) { 1656 // qDebug() << face->family_name; 1657 foundFonts.append(face->family_name); 1658 } 1659 QVERIFY2(foundFonts == expectedFonts, 1660 QString("KoFontRegistry returns the wrong fonts for string %1" 1661 "\nResult:\t%2\nExpected:\t%3") 1662 .arg(test) 1663 .arg(foundFonts.join(", ")) 1664 .arg(expectedFonts.join(", ")) 1665 .toLatin1()); 1666 1667 // Test supplementary plane code points. 1668 1669 // Jack of diamonds is U+1f0cb and is part of DejaVu Sans 1670 test = "Jack:🃋"; 1671 const std::vector<FT_FaceUP> faces7 = KoFontRegistry::instance()->facesForCSSValues(fontFamilies, lengths, axisSettings, test); 1672 foundFonts.clear(); 1673 expectedLengths.clear(); 1674 expectedLengths << 5 << 2; 1675 expectedFonts.clear(); 1676 expectedFonts << "CSSTest Verify" 1677 << "DejaVu Sans"; 1678 QVERIFY2(lengths == expectedLengths, QString("KoFontRegistry returns the wrong lengths for string %1").arg(test).toLatin1()); 1679 for (const FT_FaceUP &face : faces7) { 1680 // qDebug() << face->family_name; 1681 foundFonts.append(face->family_name); 1682 } 1683 QVERIFY2(foundFonts == expectedFonts, 1684 QString("KoFontRegistry returns the wrong fonts for string %1" 1685 "\nResult:\t%2\nExpected:\t%3") 1686 .arg(test) 1687 .arg(foundFonts.join(", ")) 1688 .arg(expectedFonts.join(", ")) 1689 .toLatin1()); 1690 } 1691 1692 /** 1693 * @brief TestSvgText::testFontStyleSelection 1694 * 1695 * This tests whether the font registery is selecting things like bold or italics correctly. 1696 */ 1697 void TestSvgText::testFontStyleSelection() 1698 { 1699 QString verifyCSSTest = "CSSTest Verify"; 1700 QString test = "A"; 1701 QMap<QString, qreal> axisSettings; 1702 1703 // First we verify that we can find the test fonts. 1704 1705 { 1706 QVector<int> lengths; 1707 const std::vector<FT_FaceUP> faces = KoFontRegistry::instance()->facesForCSSValues({verifyCSSTest}, lengths, axisSettings, test); 1708 1709 bool res = false; 1710 for (const FT_FaceUP &face : faces) { 1711 // qDebug() << face->family_name; 1712 if (face->family_name == verifyCSSTest) { 1713 res = true; 1714 break; 1715 } 1716 } 1717 QVERIFY2(res, QString("KoFontRegistry did not return the expected test font %1").arg(verifyCSSTest).toLatin1()); 1718 1719 // Now we go through a table of font-weights for the given test fonts. 1720 // This test is an adaptation of web-platform-test font-weight-bolder-001.xht 1721 // Note: when comparing to 1722 // https://github.com/web-platform-tests/wpt/blob/master/css/css-fonts/support/font-weight-bolder-001-ref.png 1723 // our implementation leaves things to be desired, but at the least it's using the correct fonts. 1724 1725 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/font-weight-bolder-001.svg")); 1726 res = file.open(QIODevice::ReadOnly | QIODevice::Text); 1727 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 1728 1729 QXmlInputSource data; 1730 data.setData(file.readAll()); 1731 1732 QRect renderRect(0, 0, 300, 150); 1733 1734 SvgRenderTester t(data.data()); 1735 t.setFuzzyThreshold(5); 1736 t.setCheckQImagePremultiplied(true); 1737 t.test_standard("font-weight-bolder-001", renderRect.size(), 72.0); 1738 } 1739 1740 { 1741 QString testItalic = "CSS Test Basic"; 1742 QVector<int> lengths; 1743 const std::vector<FT_FaceUP> faces = 1744 KoFontRegistry::instance()->facesForCSSValues({testItalic}, lengths, axisSettings, test, 72, 72, 1, 1.0, 400, 100, true); 1745 1746 bool res = false; 1747 for (const FT_FaceUP &face : faces) { 1748 // qDebug() << face->family_name; 1749 if (face->style_flags == FT_STYLE_FLAG_ITALIC) { 1750 res = true; 1751 break; 1752 } 1753 } 1754 QVERIFY2(res, QString("KoFontRegistry did not return a font with italics as requested.").toLatin1()); 1755 } 1756 } 1757 /** 1758 * @brief TestSvgText::testFontSizeConfiguration 1759 * 1760 * This tests setting the font size. 1761 */ 1762 void TestSvgText::testFontSizeConfiguration() 1763 { 1764 QString fontName = "Ahem"; 1765 qreal freetypefontfactor = 64.0; 1766 QMap<QString, qreal> axisSettings; 1767 1768 { 1769 QVector<int> lengths; 1770 qreal sizePt = 15.0; 1771 const std::vector<FT_FaceUP> faces = KoFontRegistry::instance()->facesForCSSValues({fontName}, lengths, axisSettings, QString(), 72, 72, sizePt); 1772 1773 int size = faces.front()->size->metrics.height; 1774 QVERIFY2(size == (sizePt * freetypefontfactor), 1775 QString("Configured value for Ahem at 15 pt is not returning as %1, instead %2") 1776 .arg(QString::number(sizePt * freetypefontfactor)) 1777 .arg(QString::number(size)) 1778 .toLatin1()); 1779 } 1780 1781 // Test pixel font. 1782 // The krita test font has support for 4, 8, and 12, so we'll test 4, 8, *10* and 12 :) 1783 1784 fontName = "krita-pixel-test"; 1785 1786 { 1787 QVector<qreal> testSizes; 1788 testSizes << 4.0 << 8.0 << 10.0 << 12.0; 1789 1790 for (qreal sizePt : testSizes) { 1791 QVector<int> lengths; 1792 const std::vector<FT_FaceUP> faces = KoFontRegistry::instance()->facesForCSSValues({fontName}, lengths, axisSettings, QString(), 72, 72, sizePt); 1793 1794 // With 10.0, we mostly want to test that it returns a valid value. 1795 if (sizePt == 10.0) { 1796 sizePt = 8.0; 1797 } 1798 1799 int size = faces.front()->size->metrics.height; 1800 QVERIFY2(size == (sizePt * freetypefontfactor), 1801 QString("Configured value for %1 at %2 pt is not returning as %3, instead %4") 1802 .arg(fontName) 1803 .arg(QString::number(sizePt)) 1804 .arg(QString::number(sizePt * freetypefontfactor)) 1805 .arg(QString::number(size)) 1806 .toLatin1()); 1807 } 1808 } 1809 1810 // Test font-size-adjust. 1811 1812 { 1813 QVector<int> lengths; 1814 qreal sizePt = 15.0; 1815 qreal fontSizeAdjust = 0.8; 1816 const std::vector<FT_FaceUP> faces = 1817 KoFontRegistry::instance()->facesForCSSValues({fontName}, lengths, axisSettings, QString(), 72, 72, sizePt, fontSizeAdjust); 1818 1819 int size = faces.front()->size->metrics.height; 1820 QVERIFY2(size == 768, 1821 QString("Configured value for Ahem at 15 pt with font-size adjust 0.8 is not returning as %1, instead %2") 1822 .arg(QString::number(768)) 1823 .arg(QString::number(size)) 1824 .toLatin1()); 1825 } 1826 } 1827 1828 /** 1829 * @brief TestSvgText::testFontSizeRender 1830 * 1831 * Test whether we can set different font 1832 * sizes and they render correctly. 1833 */ 1834 void TestSvgText::testFontSizeRender() 1835 { 1836 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/font-test-sizes-rendering.svg")); 1837 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 1838 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 1839 1840 QXmlInputSource data; 1841 data.setData(file.readAll()); 1842 1843 SvgRenderTester t(data.data()); 1844 t.setCheckQImagePremultiplied(true); 1845 t.test_standard("font-sizes", QSize(140, 40), 72.0); 1846 } 1847 1848 /** 1849 * @brief TestSvgText::testFontOpenTypeVariationsConfiguration 1850 * 1851 * test whether we can succefully confgiure the axes for an opentype 1852 * variation font. This test is an adaptation of web-platform-test 1853 * style-ranges-over-weight-direction.html 1854 */ 1855 void TestSvgText::testFontOpenTypeVariationsConfiguration() 1856 { 1857 QString fontName = "Variable Test Axis Matching"; 1858 1859 // Testing rendering. 1860 1861 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/font-opentype-variations.svg")); 1862 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 1863 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 1864 1865 QXmlInputSource data; 1866 data.setData(file.readAll()); 1867 1868 QRect renderRect(0, 0, 300, 150); 1869 1870 SvgRenderTester t(data.data()); 1871 t.setFuzzyThreshold(5); 1872 t.test_standard("font-opentype-variations", renderRect.size(), 72.0); 1873 } 1874 1875 /** 1876 * Testing color font rendering. 1877 * 1878 * This right now only tests COLRv0 fonts, beecause we don't support COLRv1 and SVG-in-opentype yet, 1879 * and I have no idea what to expect from SBX. 1880 * 1881 * TODO: Still searching for a CBDT font. 1882 */ 1883 void TestSvgText::testFontColorRender() 1884 { 1885 QStringList testFonts; 1886 testFonts << "CFF Outlines and COLR"; 1887 1888 // testFonts << "CFF Outlines and SBIX" << "CFF Outlines and SVG" << "CFF COLR and SVG"; 1889 1890 const QString dataFront = 1891 "<svg width=\"70px\" height=\"45px\"" 1892 " xmlns=\"http://www.w3.org/2000/svg\" version=\"2.0\">" 1893 "<g id=\"testRect\">"; 1894 const QString dataBack = 1895 "AB</text>" 1896 "</g>" 1897 "</svg>"; 1898 1899 for (QString test : testFonts) { 1900 const QString dataMiddle = QString("<text font-size=\"30\" x=\"5\" y=\"40\" font-family=\"%1\">").arg(test); 1901 const QString data = dataFront + dataMiddle + dataBack; 1902 1903 const QString testName = "test_font_" + test.split(" ").join("_"); 1904 SvgRenderTester t(data); 1905 t.setFuzzyThreshold(5); 1906 t.test_standard(testName, QSize(70, 45), 72.0); 1907 } 1908 } 1909 1910 /** 1911 * @brief TestSvgText::testCssFontVariants 1912 * 1913 * This tests css 3 font-variants, which are equivelant to opentype features, 1914 * and should not be confused with opentype variations 1915 * (or with unicode variation selectors for that matter). 1916 */ 1917 void TestSvgText::testCssFontVariants() 1918 { 1919 QString fontName = "FontWithFeaturesOTF"; 1920 1921 QMap<QString, QRect> testFiles; 1922 testFiles.insert("font-test-font-variant-basic", QRect(0, 0, 230, 200)); 1923 testFiles.insert("font-test-font-variant-caps", QRect(0, 0, 100, 370)); 1924 testFiles.insert("font-test-font-variant-east-asian", QRect(0, 0, 260, 260)); 1925 testFiles.insert("font-test-font-variant-ligatures", QRect(0, 0, 160, 200)); 1926 testFiles.insert("font-test-font-variant-numeric", QRect(0, 0, 370, 160)); 1927 testFiles.insert("font-test-font-variant-position", QRect(0, 0, 160, 70)); 1928 for (QString testFile : testFiles.keys()) { 1929 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/" + testFile + ".svg")); 1930 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 1931 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 1932 1933 QXmlInputSource data; 1934 data.setData(file.readAll()); 1935 1936 SvgRenderTester t(data.data()); 1937 t.setFuzzyThreshold(5); 1938 t.test_standard(testFile, testFiles.value(testFile).size(), 72.0); 1939 } 1940 } 1941 /** 1942 * Tests all relevant permutations of the textLength 1943 * property. This includes increase in spacing, 1944 * decrease in spacing, squashing and stratching 1945 * and finally, nested textLengths. 1946 */ 1947 void TestSvgText::testTextLength() 1948 { 1949 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/text-test-textLength.svg")); 1950 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 1951 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 1952 1953 QXmlInputSource data; 1954 data.setData(file.readAll()); 1955 1956 SvgRenderTester t(data.data()); 1957 t.setFuzzyThreshold(5); 1958 t.setCheckQImagePremultiplied(true); 1959 t.test_standard("text-test-textLength", QSize(360, 210), 72.0); 1960 1961 QMap<QString, int> testWidths; 1962 // Test 1 (Blue) is very simple and should work in all cases. 1963 testWidths.insert("test1", 250); 1964 testWidths.insert("test1rtl", 250); 1965 testWidths.insert("test1ttb", 200); 1966 1967 // Test 2 (Cyan) will have different results with different fonts and different strings, 1968 // due to the last of the whole text glyph being subtracted from the width to 1969 // determine the delta. 1970 testWidths.insert("test2", 127); // 125 1971 testWidths.insert("test2rtl", 126); // 125 1972 testWidths.insert("test2ttb", 94); // 100 1973 1974 // Test 3 (green) is test 1 but then smaller instead of bigger and should always work. 1975 testWidths.insert("test3", 100); 1976 testWidths.insert("test3rtl", 100); 1977 testWidths.insert("test3ttb", 95); 1978 1979 // Test 4 (light green) is a spacing-and-glyphs test, make sure to include the last character 1980 // when deciding the delta for the stretch. 1981 testWidths.insert("test4", 100); 1982 testWidths.insert("test4rtl", 100); 1983 testWidths.insert("test4ttb", 95); 1984 1985 // Test 5 (magenta) is like 4 but then strtch instead of squashing. 1986 testWidths.insert("test5", 250); 1987 testWidths.insert("test5rtl", 250); 1988 testWidths.insert("test5ttb", 200); 1989 1990 // Test 6 (orange) is a nested text-length test. 1991 testWidths.insert("test6", 250); 1992 testWidths.insert("test6rtl", 250); 1993 testWidths.insert("test6ttb", 200); 1994 for (QString testID : testWidths.keys()) { 1995 KoSvgTextShape *baseShape = dynamic_cast<KoSvgTextShape *>(t.findShape(testID)); 1996 if (baseShape) { 1997 int expectedSize = testWidths.value(testID); 1998 int givenSize = testID.endsWith("ttb") ? round(baseShape->boundingRect().height()) : round(baseShape->boundingRect().width()); 1999 2000 QVERIFY2( 2001 givenSize == expectedSize, 2002 QString("Size of %1 is incorrect: %2, expected %3").arg(testID).arg(QString::number(givenSize)).arg(QString::number(expectedSize)).toLatin1()); 2003 } 2004 } 2005 } 2006 /** 2007 * This tests basic features of textPath, so text-on-path, 2008 * side, method="stretch", startOffset, and what happens when 2009 * there's a single closed path. 2010 */ 2011 void TestSvgText::testTextPathBasic() 2012 { 2013 QMap<QString, QRect> testFiles; 2014 // Basic text path. 2015 testFiles.insert("textPath-test-basic", QRect(0, 0, 230, 170)); 2016 // Tests switching the side. 2017 testFiles.insert("textPath-test-side", QRect(0, 0, 230, 170)); 2018 // Tests the startOffset attribute. 2019 testFiles.insert("textPath-test-offset", QRect(0, 0, 350, 190)); 2020 // Tests closed paths, these need to wrap around. 2021 testFiles.insert("textPath-test-closed", QRect(0, 0, 460, 270)); 2022 // Tests the stretch method. 2023 testFiles.insert("textPath-test-method", QRect(0, 0, 460, 270)); 2024 for (QString testFile : testFiles.keys()) { 2025 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/" + testFile + ".svg")); 2026 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 2027 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 2028 2029 QXmlInputSource data; 2030 data.setData(file.readAll()); 2031 2032 SvgRenderTester t(data.data()); 2033 t.setFuzzyThreshold(5); 2034 t.test_standard(testFile, testFiles.value(testFile).size(), 72.0); 2035 } 2036 } 2037 /** 2038 * This tests some of the more intricate parts of textPath, 2039 * some of which don't have a consistent solution (like mixed 2040 * tspans and textpath, especially rtl), or are unusual 2041 * to Krita (text-decoration). 2042 */ 2043 void TestSvgText::testTextPathComplex() 2044 { 2045 QMap<QString, QRect> testFiles; 2046 // Tests what happens if you apply transforms on text paths. 2047 testFiles.insert("textPath-test-transforms", QRect(0, 0, 300, 240)); 2048 // Tests multiple textPaths. 2049 testFiles.insert("textPath-test-multiple", QRect(0, 0, 230, 170)); 2050 // Tests the case where there's a textPath surrounded by tspans. 2051 testFiles.insert("textPath-test-mix-tspans", QRect(0, 0, 230, 170)); 2052 // Tests text-decoration inside a path. 2053 testFiles.insert("textPath-test-text-decoration", QRect(0, 0, 230, 170)); 2054 for (QString testFile : testFiles.keys()) { 2055 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/" + testFile + ".svg")); 2056 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 2057 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 2058 2059 QXmlInputSource data; 2060 data.setData(file.readAll()); 2061 2062 SvgRenderTester t(data.data()); 2063 t.setFuzzyThreshold(5); 2064 t.setCheckQImagePremultiplied(true); 2065 t.test_standard(testFile, testFiles.value(testFile).size(), 72.0); 2066 } 2067 } 2068 2069 /** 2070 * Tests the text-transform in KoCssTextUtils. 2071 * 2072 * The Web-platform-tests for this are far more thorough, 2073 * however I am unsure how to adapt them for the non-ascii values. 2074 */ 2075 void TestSvgText::testCssTextTransform() 2076 { 2077 // Basic test of upper/lower and capitalize. The last one is particularly the one we're testing, as the others just use qlocale. 2078 QString lower = "aaa bbb ccc ddd eee fff ggg hhh iii jjj kkk lll mmm nnn ooo ppp qqq rrr sss ttt uuu vvv www xxx yyy zzz"; 2079 QString capitalize = "Aaa Bbb Ccc Ddd Eee Fff Ggg Hhh Iii Jjj Kkk Lll Mmm Nnn Ooo Ppp Qqq Rrr Sss Ttt Uuu Vvv Www Xxx Yyy Zzz"; 2080 QString uppercase = "AAA BBB CCC DDD EEE FFF GGG HHH III JJJ KKK LLL MMM NNN OOO PPP QQQ RRR SSS TTT UUU VVV WWW XXX YYY ZZZ"; 2081 2082 QVector<QPair<int, int>> positions; 2083 QVERIFY2(KoCssTextUtils::transformTextToLowerCase(capitalize, "", positions) == lower, QString("Transform to lower case does not match lowercase string").toLatin1()); 2084 QVERIFY2(KoCssTextUtils::transformTextToUpperCase(capitalize, "", positions) == uppercase, 2085 QString("Transform to upper case does not match uppercase string").toLatin1()); 2086 QVERIFY2(KoCssTextUtils::transformTextCapitalize(lower, "", positions) == capitalize, 2087 QString("Capitalization transform does not match capitalized string").toLatin1()); 2088 QVERIFY2(KoCssTextUtils::transformTextCapitalize(uppercase, "", positions) == uppercase, 2089 QString("Capitalization transform on uppercase string does not match uppercase string").toLatin1()); 2090 2091 // Turkish differentiates between İ and I, little details like these are why we use QLocale, and in effect, this tests whether the QLocale support is 2092 // lacking on whichever system we're building for. 2093 QString uppercaseTurkish = "AAA BBB CCC DDD EEE FFF GGG HHH Iİİ JJJ KKK LLL MMM NNN OOO PPP QQQ RRR SSS TTT UUU VVV WWW XXX YYY ZZZ"; 2094 QVERIFY2(KoCssTextUtils::transformTextToUpperCase(capitalize, "tr", positions) == uppercaseTurkish, 2095 QString("Transform to upper case in Turkish locale does not match reference string, QLocale might not be able to provide good text transforms") 2096 .toLatin1()); 2097 2098 // Adapted from the web-platform tests text-transform-full-size-kana-##.html 2099 QString kanaSmall = 2100 "ぁ ぃ ぅ ぇ ぉ ゕ ゖ っ ゃ ゅ ょ ゎ " 2101 "ァ ィ ゥ ェ ォ ヵ ㇰ ヶ ㇱ ㇲ ッ ㇳ ㇴ " 2102 "ㇵ ㇶ ㇷ ㇸ ㇹ ㇺ ャ ュ ョ ㇻ ㇼ ㇽ ㇾ ㇿ ヮ " 2103 "ァ ィ ゥ ェ ォ ッ ャ ュ ョ"; 2104 QString kanaLarge = 2105 "あ い う え お か け つ や ゆ よ わ " 2106 "ア イ ウ エ オ カ ク ケ シ ス ツ ト ヌ " 2107 "ハ ヒ フ ヘ ホ ム ヤ ユ ヨ ラ リ ル レ ロ ワ " 2108 "ア イ ウ エ オ ツ ヤ ユ ヨ"; 2109 QVERIFY2(KoCssTextUtils::transformTextFullSizeKana(kanaSmall) == kanaLarge, 2110 QString("Transform to full size kana does not match full size kana string").toLatin1()); 2111 2112 // Half width to full width tests. 2113 2114 QString halfWidth = "012 ABC abc % ァィゥ アイウ ᆱᄆᄋ ← ○"; 2115 QString fullWidth = "012 ABC abc % ァィゥ アイウ ㄻㅁㅇ ← ○"; 2116 2117 QVERIFY2(KoCssTextUtils::transformTextFullWidth(halfWidth) == fullWidth, 2118 QString("Transform to full width kana does not match full width string").toLatin1()); 2119 2120 // Adapted from web platform test text-transform-tailoring-001.html 2121 2122 QString ijDigraphTest = "ijsland"; 2123 QString ijDigraphRef = "IJsland"; 2124 2125 QVERIFY2(KoCssTextUtils::transformTextCapitalize(ijDigraphTest, "nl", positions) == ijDigraphRef, QString("IJ disgraph tailor test is failing").toLatin1()); 2126 2127 // Adapted from web platform test text-transform-tailoring-002.html 2128 QString greekTonosTest = "καλημέρα αύριο"; 2129 QString greekTonosRef = "ΚΑΛΗΜΕΡΑ ΑΥΡΙΟ"; 2130 QVERIFY2(KoCssTextUtils::transformTextToUpperCase(greekTonosTest, "el", positions) == greekTonosRef, QString("Greek tonos tailor test is failing").toLatin1()); 2131 2132 // Adapted from web platform test text-transform-tailoring-002a.html 2133 greekTonosTest = "θεϊκό"; 2134 greekTonosRef = "ΘΕΪΚΟ"; 2135 2136 QVERIFY2(KoCssTextUtils::transformTextToUpperCase(greekTonosTest, "el", positions) == greekTonosRef, 2137 QString("Greek tonos tailor test for dialytika is failing").toLatin1()); 2138 2139 // Adapted from web platform test text-transform-tailoring-003.html 2140 greekTonosTest = "ευφυΐα Νεράιδα"; 2141 greekTonosRef = "ΕΥΦΥΪΑ ΝΕΡΑΪΔΑ"; 2142 2143 QVERIFY2(KoCssTextUtils::transformTextToUpperCase(greekTonosTest, "el", positions) == greekTonosRef, 2144 QString("Greek tonos tailor test number 3 is failing.").toLatin1()); 2145 2146 // Adapted from web platform test text-transform-tailoring-004.html 2147 // "[Exploratory] the brower tailors text-transform: capitalize such that a stressed vowel that is the first syllable of a Greek sentence keeps its tonos 2148 // diacritic." 2149 2150 /* This needs someone who can actually read greek, because I am unsure what 'tonos' means, like, is it all diacritics or just a few unicode values? 2151 greekTonosTest = "όμηρος"; 2152 greekTonosRef = "Όμηρος"; 2153 qDebug() << KoCssTextUtils::transformTextCapitalize(greekTonosTest, "el"); 2154 QVERIFY2(KoCssTextUtils::transformTextCapitalize(greekTonosTest, "el") == greekTonosRef, QString("Greek tonos tailor test number 4 is failing").toLatin1()); 2155 */ 2156 2157 // Adapted from web platform test text-transform-tailoring-004.html 2158 2159 greekTonosTest = "ήσουν ή εγώ ή εσύ"; 2160 greekTonosRef = "ΗΣΟΥΝ Ή ΕΓΩ Ή ΕΣΥ"; 2161 positions.clear(); 2162 QVERIFY2(KoCssTextUtils::transformTextToUpperCase(greekTonosTest, "el", positions) == greekTonosRef, 2163 QString("Greek tonos tailor test number 5 is failing").toLatin1()); 2164 // This particular transformation also has a difference in characters between the before and after, 2165 // so let's test the positions too. 2166 QVector<QPair<int, int>> refPositions; 2167 refPositions << QPair(0,0) << QPair(1,1) << QPair(2,2) << QPair(3,3) << QPair(4,4) << QPair(5,5) 2168 << QPair(6,6) << QPair(-1,7) << QPair(7,8) << QPair(8,9) << QPair(9,10) 2169 << QPair(10,11) << QPair(11,12) << QPair(12,13) << QPair(-1,14) << QPair(13,15) 2170 << QPair(14,16) << QPair(15,17) << QPair(16,18); 2171 QVERIFY2(positions == refPositions, 2172 QString("positions returned by Greek Tonos test number 5 are incorrect.").toLatin1()); 2173 } 2174 2175 /* 2176 * This is a basic test of inline-size with different teext-anchors, 2177 * directions and writing modes. These interact in very fundamental 2178 * ways, so it doesn't make sense to test them seperately. 2179 */ 2180 void TestSvgText::testTextInlineSize() 2181 { 2182 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/text-test-inline-size-basic-anchoring.svg")); 2183 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 2184 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 2185 2186 QXmlInputSource data; 2187 data.setData(file.readAll()); 2188 2189 SvgRenderTester t(data.data()); 2190 t.setFuzzyThreshold(5); 2191 t.test_standard("text-test-inline-size-anchoring", QSize(420, 200), 72.0); 2192 2193 QMap<QString, int> testWidths; 2194 2195 testWidths.insert("test1", 100); 2196 testWidths.insert("test2", 100); 2197 testWidths.insert("test3", 100); 2198 2199 testWidths.insert("test1rtl", 100); 2200 testWidths.insert("test2rtl", 100); 2201 testWidths.insert("test3rtl", 100); 2202 2203 testWidths.insert("test1ttb", 60); 2204 testWidths.insert("test2ttb", 60); 2205 testWidths.insert("test3ttb", 60); 2206 2207 testWidths.insert("test1-lr-ttb", 60); 2208 testWidths.insert("test2-lr-ttb", 60); 2209 testWidths.insert("test3-lr-ttb", 60); 2210 2211 for (QString testID : testWidths.keys()) { 2212 KoSvgTextShape *baseShape = dynamic_cast<KoSvgTextShape *>(t.findShape(testID)); 2213 if (baseShape) { 2214 int maxSize = testWidths.value(testID); 2215 int givenSize = testID.endsWith("ttb") ? round(baseShape->boundingRect().height()) : round(baseShape->boundingRect().width()); 2216 2217 QVERIFY2( 2218 givenSize <= maxSize, 2219 QString("Size of %1 is too large: %2, maximum is %3").arg(testID).arg(QString::number(givenSize)).arg(QString::number(maxSize)).toLatin1()); 2220 } 2221 } 2222 } 2223 2224 void TestSvgText::testTextWrap() 2225 { 2226 QMap<QString, QRect> testFiles; 2227 // Tests differrent line-height configurations. 2228 testFiles.insert("textWrap-test-css-line-height", QRect(0, 0, 120, 180)); 2229 // Tests overflow wrap behaviour options. 2230 testFiles.insert("textWrap-test-css-overflow-wrap", QRect(0, 0, 120, 220)); 2231 // Tests hanging punctuation. 2232 testFiles.insert("textWrap-test-css-hanging-punctuation", QRect(0, 0, 420, 100)); 2233 // Tests text-indent 2234 testFiles.insert("textWrap-test-css-text-indent", QRect(0, 0, 420, 200)); 2235 // Integration test of sorts, tests font-sizes, color difference, 2236 // unicode supplementary plane, bidirectional wrrapping and text decorations. 2237 testFiles.insert("textWrap-test-css-mixed-markup", QRect(0, 0, 420, 100)); 2238 for (QString testFile : testFiles.keys()) { 2239 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/" + testFile + ".svg")); 2240 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 2241 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 2242 2243 QXmlInputSource data; 2244 data.setData(file.readAll()); 2245 2246 SvgRenderTester t(data.data()); 2247 t.setFuzzyThreshold(5); 2248 t.test_standard(testFile, testFiles.value(testFile).size(), 72.0); 2249 } 2250 } 2251 /** 2252 * Test baseline alignment. Within CSS text this is defined in CSS3-Inline, 2253 * however, it was originally part of SVG 1.1, and we implement that version 2254 * as it has the clearest implementation explanation. 2255 * 2256 * This relies on different font-sizes, because otherwise all the baseline tables 2257 * are exactly the same. 2258 */ 2259 void TestSvgText::testTextBaselineAlignment() 2260 { 2261 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/test-text-baseline-alignment.svg")); 2262 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 2263 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 2264 2265 QXmlInputSource data; 2266 data.setData(file.readAll()); 2267 2268 SvgRenderTester t(data.data()); 2269 t.setFuzzyThreshold(5); 2270 t.test_standard("test-text-baseline-alignment", QSize(90, 51), 72.0); 2271 } 2272 2273 /** 2274 * Tests the loading of CSS shapes by comparing the loaded shapes with their reference shapes. 2275 */ 2276 void TestSvgText::testCssShapeParsing() 2277 { 2278 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/textShape-test-css-basic-shapes.svg")); 2279 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 2280 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 2281 2282 QXmlInputSource data; 2283 data.setData(file.readAll()); 2284 2285 SvgTester t(data.data()); 2286 t.parser.setResolution(QRectF(0, 0, 380, 380) /* px */, 72 /* ppi */); 2287 t.run(); 2288 2289 QStringList tests = {"circle" , "ellipse", "polygon", "path", "uri"}; 2290 2291 Q_FOREACH(const QString test, tests) { 2292 KoPathShape *refShape = dynamic_cast<KoPathShape*>(t.findShape("ref-"+test)); 2293 if (!refShape) { 2294 // there's an oddity with <use> elements right now that results in their id being lost, so as a work-around, we 2295 // instead check the shape that is being referenced. 2296 refShape = dynamic_cast<KoPathShape*>(t.findShape("bubble")); 2297 } 2298 QVERIFY(refShape); 2299 KoSvgTextShape *textShape = dynamic_cast<KoSvgTextShape*>(t.findShape("test-"+test)); 2300 QVERIFY(textShape); 2301 2302 KoPathShape *testShape = dynamic_cast<KoPathShape*>(textShape->shapesInside().at(0)); 2303 QVERIFY(testShape); 2304 2305 QVERIFY2(refShape->outline() == testShape->outline(), QString("Outline mismatch for CSS Shape type %1").arg(test).toLatin1()); 2306 } 2307 2308 } 2309 2310 void TestSvgText::testShapeInsideRender() 2311 { 2312 QMap<QString, QRect> testFiles; 2313 testFiles.insert("textShape-test-complex-shapes", QRect(0, 0, 380, 380)); 2314 // Tests basic text align. 2315 testFiles.insert("textShape-test-text-align", QRect(0, 0, 550, 700)); 2316 // Tests justification. 2317 testFiles.insert("textShape-test-text-align-justify", QRect(0, 0, 550, 550)); 2318 // Tests padding and margin 2319 testFiles.insert("textShape-test-shape-padding-margin", QRect(0, 0, 250, 255)); 2320 // Tests multiple shapes inside and subtract 2321 testFiles.insert("textShape-test-shape-inside-subtract", QRect(0, 0, 310, 260)); 2322 // Test hanging punctuation and text-indent. 2323 testFiles.insert("textShape-test-edge-effects", QRect(0, 0, 450, 450)); 2324 // Tests mixed markup (though only font-size changes for now. 2325 testFiles.insert("textShape-test-mixed-markup", QRect(0, 0, 200, 70)); 2326 2327 2328 for (QString testFile : testFiles.keys()) { 2329 QFile file(TestUtil::fetchDataFileLazy("fonts/textTestSvgs/" + testFile + ".svg")); 2330 bool res = file.open(QIODevice::ReadOnly | QIODevice::Text); 2331 QVERIFY2(res, QString("Cannot open test svg file.").toLatin1()); 2332 2333 QXmlInputSource data; 2334 data.setData(file.readAll()); 2335 2336 SvgRenderTester t(data.data()); 2337 t.setFuzzyThreshold(5); 2338 t.test_standard(testFile, testFiles.value(testFile).size(), 72.0); 2339 } 2340 } 2341 /** 2342 * Test text insertion. 2343 * This tests basic text insertion and inserting text at end. 2344 */ 2345 void TestSvgText::testTextInsertion() 2346 { 2347 // Insert some text. 2348 KoSvgTextShape *textShape = new KoSvgTextShape(); 2349 QString ref ("The quick brown fox"); 2350 textShape->insertText(0, ref); 2351 QVERIFY2(ref == textShape->plainText(), QString("Text shape plain text does not match inserted text.").toLatin1()); 2352 2353 // Append at end. 2354 QString ref2(" jumps over the lazy dog."); 2355 textShape->insertText(19, ref2); 2356 ref.insert(19, ref2); 2357 QVERIFY2(ref == textShape->plainText(), QString("Text shape plain text does not match reference text.").toLatin1()); 2358 } 2359 2360 void TestSvgText::testTextDeletion_data() 2361 { 2362 QTest::addColumn<QString>("text"); 2363 QTest::addColumn<int>("start"); 2364 QTest::addColumn<int>("length"); 2365 QTest::addColumn<int>("start2"); 2366 QTest::addColumn<int>("length2"); 2367 QTest::addColumn<QString>("finalText"); 2368 2369 QTest::addRow("basic") << QString("The quick brown fox jumps over the lazy dog.") 2370 << 15 << 10 << 15 << 10 2371 << QString("The quick brown over the lazy dog."); 2372 QTest::addRow("backspace-hindi") << QString("क्रिता") 2373 << 5 << 1 << 5 << 1 2374 << QString("क्रित"); 2375 QTest::addRow("backspace-zwj") << QString("\U0001F469\U0001F3FF\u200D\U0001F692") 2376 << 6 << 1 << 4 << 3 2377 << QString("\U0001F469\U0001F3FF"); 2378 QTest::addRow("backspace-emoji-vs") << QString("\U0001F469\U0001F3FF\u200D\U0001F692\U0001F469\U0001F3FF") 2379 << 10 << 1 << 7 << 4 2380 << QString("\U0001F469\U0001F3FF\u200D\U0001F692"); 2381 QTest::addRow("backspace-regular-vs") << QString("Ashi:\u82A6\uFE03") 2382 << 6 << 1 << 5 << 2 2383 << QString("Ashi:"); 2384 QTest::addRow("backspace-regional") << QString("US:\U0001F1FA\U0001F1F8") 2385 << 6 << 1 << 3 << 4 2386 << QString("US:"); 2387 QTest::addRow("delete-zwj") << QString("\U0001F469\U0001F3FF\u200D\U0001F692") 2388 << 0 << 1 << 0 << 5 2389 << QString("\U0001F692"); 2390 QTest::addRow("delete-regular-vs") << QString("\u82A6\uFE03:Ashi") 2391 << 0 << 1 << 0 << 2 2392 << QString(":Ashi"); 2393 QTest::addRow("delete-regional") << QString("\U0001F1FA\U0001F1F8\U0001F1FA\U0001F1F8:US") 2394 << 0 << 1 << 0 << 4 2395 << QString("\U0001F1FA\U0001F1F8:US"); 2396 } 2397 /** 2398 * This tests basic text deletion. 2399 */ 2400 void TestSvgText::testTextDeletion() 2401 { 2402 KoSvgTextShape *textShape = new KoSvgTextShape(); 2403 QFETCH(QString, text); 2404 textShape->insertText(0, text); 2405 2406 QFETCH(int, start); 2407 QFETCH(int, length); 2408 2409 QFETCH(QString, finalText); 2410 QFETCH(int, start2); 2411 QFETCH(int, length2); 2412 2413 textShape->removeText(start, length); 2414 2415 QCOMPARE(start2, start); 2416 QCOMPARE(length2, length); 2417 QVERIFY2(finalText == textShape->plainText(), 2418 QString("Mismatch between textShape plain text and reference for text-removal. \n Res: %1 \n Exp: %2") 2419 .arg(textShape->plainText()).arg(finalText).toLatin1()); 2420 } 2421 /** 2422 * Test the cursor navigation. In particular we're 2423 * testing the logic for the up/down/left/right pos 2424 */ 2425 void TestSvgText::testNavigation() 2426 { 2427 // Test basic left-to-right horizontal. 2428 KoSvgTextShape *textShape = new KoSvgTextShape(); 2429 QString ref ("<text style=\"inline-size:50.0; font-size:10.0;font-family:Deja Vu Sans\">The quick brown fox jumps over the lazy dog.</text>"); 2430 KoSvgTextShapeMarkupConverter converter(textShape); 2431 converter.convertFromSvg(ref, QString(), QRectF(0, 0, 300, 300), 72.0); 2432 2433 int cursorPos = 0; 2434 for (int i=0; i<4; i++) { 2435 cursorPos = textShape->posRight(cursorPos); 2436 } 2437 QCOMPARE(cursorPos, 4); 2438 for (int i=0; i<5; i++) { 2439 cursorPos = textShape->posLeft(cursorPos); 2440 } 2441 QCOMPARE(cursorPos, 0); 2442 for (int i=0; i<8; i++) { 2443 cursorPos = textShape->posRight(cursorPos); 2444 } 2445 cursorPos = textShape->posDown(cursorPos); 2446 QCOMPARE(cursorPos, 19); 2447 QCOMPARE(textShape->lineStart(cursorPos), 11); 2448 QCOMPARE(textShape->lineEnd(cursorPos), 21); 2449 2450 // Test right-to-left horizontal. 2451 QString rtlRef ("<text style=\"inline-size:50.0; font-size:10.0; direction:rtl; font-family:Deja Vu Sans\">داستان SVG 1.1 SE طولا ني است.</text>"); 2452 converter.convertFromSvg(rtlRef, QString(), QRectF(0, 0, 300, 300), 72.0); 2453 2454 cursorPos = 0; 2455 for (int i=0; i<10; i++) { 2456 cursorPos = textShape->posLeft(cursorPos, false); 2457 } 2458 QCOMPARE(cursorPos, 10); 2459 for (int i=0; i<11; i++) { 2460 cursorPos = textShape->posRight(cursorPos, false); 2461 } 2462 QCOMPARE(cursorPos, 0); 2463 2464 2465 // Test right-to-left bidi with visual. 2466 for (int i=0; i<10; i++) { 2467 cursorPos = textShape->posLeft(cursorPos, true); 2468 } 2469 QCOMPARE(cursorPos, 14); 2470 for (int i=0; i<10; i++) { 2471 cursorPos = textShape->posLeft(cursorPos, true); 2472 } 2473 QCOMPARE(cursorPos, 20); 2474 2475 // Test top-to-bottom. 2476 QString ttbRef ("<text style=\"inline-size:50.0; font-size:10.0; writing-mode:vertical-rl; font-family:Deja Vu Sans\">A B C D E F G H I J K L M N O P</text>"); 2477 converter.convertFromSvg(ttbRef, QString(), QRectF(0, 0, 300, 300), 72.0); 2478 cursorPos = 0; 2479 for (int i=0; i<5; i++) { 2480 cursorPos = textShape->posDown(cursorPos); 2481 } 2482 QCOMPARE(cursorPos, 5); 2483 for (int i=0; i<10; i++) { 2484 cursorPos = textShape->posUp(cursorPos); 2485 } 2486 QCOMPARE(cursorPos, 0); 2487 for (int i=0; i<5; i++) { 2488 cursorPos = textShape->posDown(cursorPos); 2489 } 2490 cursorPos = textShape->posLeft(cursorPos); 2491 QCOMPARE(cursorPos, 12); 2492 QCOMPARE(textShape->lineStart(cursorPos), 7); 2493 QCOMPARE(textShape->lineEnd(cursorPos), 13); 2494 2495 cursorPos = textShape->posRight(cursorPos); 2496 cursorPos = textShape->posRight(cursorPos); 2497 QCOMPARE(cursorPos, 0); 2498 2499 // Test vertical left-to-right. 2500 QString ttbRef2 ("<text style=\"inline-size:50.0; font-size:10.0; writing-mode:vertical-lr; font-family:Deja Vu Sans\">A B C D E F G H I J K L M N O P</text>"); 2501 converter.convertFromSvg(ttbRef2, QString(), QRectF(0, 0, 300, 300), 72.0); 2502 cursorPos = 0; 2503 for (int i=0; i<5; i++) { 2504 cursorPos = textShape->posDown(cursorPos); 2505 } 2506 QCOMPARE(cursorPos, 5); 2507 for (int i=0; i<10; i++) { 2508 cursorPos = textShape->posUp(cursorPos); 2509 } 2510 QCOMPARE(cursorPos, 0); 2511 for (int i=0; i<5; i++) { 2512 cursorPos = textShape->posDown(cursorPos); 2513 } 2514 cursorPos = textShape->posRight(cursorPos); 2515 QCOMPARE(cursorPos, 12); 2516 QCOMPARE(textShape->lineStart(cursorPos), 7); 2517 QCOMPARE(textShape->lineEnd(cursorPos), 13); 2518 2519 cursorPos = textShape->posLeft(cursorPos); 2520 cursorPos = textShape->posLeft(cursorPos); 2521 QCOMPARE(cursorPos, 0); 2522 } 2523 2524 #include "kistest.h" 2525 2526 2527 KISTEST_MAIN(TestSvgText)