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&lt;&gt;&lt;&gt;&lt;&lt;&lt;&gt;</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&lt;&gt;&lt;&gt;&lt;&lt;&lt;&gt;</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&lt;&gt;&lt;&gt;&lt;&lt;&lt;&gt;</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)