diff --git a/libs/flake/svg/SvgUtil.cpp b/libs/flake/svg/SvgUtil.cpp index 653168ee17..892b39db88 100644 --- a/libs/flake/svg/SvgUtil.cpp +++ b/libs/flake/svg/SvgUtil.cpp @@ -1,528 +1,527 @@ /* This file is part of the KDE project * Copyright (C) 2009 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SvgUtil.h" #include "SvgGraphicContext.h" #include #include #include #include #include #include #include #include #include "kis_debug.h" #include "kis_global.h" #include #include "kis_dom_utils.h" #define DPI 72.0 #define DEG2RAD(degree) degree/180.0*M_PI double SvgUtil::fromUserSpace(double value) { return value; } double SvgUtil::toUserSpace(double value) { return value; } double SvgUtil::ptToPx(SvgGraphicsContext *gc, double value) { return value * gc->pixelsPerInch / DPI; } QPointF SvgUtil::toUserSpace(const QPointF &point) { return QPointF(toUserSpace(point.x()), toUserSpace(point.y())); } QRectF SvgUtil::toUserSpace(const QRectF &rect) { return QRectF(toUserSpace(rect.topLeft()), toUserSpace(rect.size())); } QSizeF SvgUtil::toUserSpace(const QSizeF &size) { return QSizeF(toUserSpace(size.width()), toUserSpace(size.height())); } QString SvgUtil::toPercentage(qreal value) { return KisDomUtils::toString(value * 100.0) + "%"; } double SvgUtil::fromPercentage(QString s) { if (s.endsWith('%')) return KisDomUtils::toDouble(s.remove('%')) / 100.0; else return KisDomUtils::toDouble(s); } QPointF SvgUtil::objectToUserSpace(const QPointF &position, const QRectF &objectBound) { qreal x = objectBound.left() + position.x() * objectBound.width(); qreal y = objectBound.top() + position.y() * objectBound.height(); return QPointF(x, y); } QSizeF SvgUtil::objectToUserSpace(const QSizeF &size, const QRectF &objectBound) { qreal w = size.width() * objectBound.width(); qreal h = size.height() * objectBound.height(); return QSizeF(w, h); } QPointF SvgUtil::userSpaceToObject(const QPointF &position, const QRectF &objectBound) { qreal x = 0.0; if (objectBound.width() != 0) x = (position.x() - objectBound.x()) / objectBound.width(); qreal y = 0.0; if (objectBound.height() != 0) y = (position.y() - objectBound.y()) / objectBound.height(); return QPointF(x, y); } QSizeF SvgUtil::userSpaceToObject(const QSizeF &size, const QRectF &objectBound) { qreal w = objectBound.width() != 0 ? size.width() / objectBound.width() : 0.0; qreal h = objectBound.height() != 0 ? size.height() / objectBound.height() : 0.0; return QSizeF(w, h); } QString SvgUtil::transformToString(const QTransform &transform) { if (transform.isIdentity()) return QString(); if (transform.type() == QTransform::TxTranslate) { return QString("translate(%1, %2)") .arg(KisDomUtils::toString(toUserSpace(transform.dx()))) .arg(KisDomUtils::toString(toUserSpace(transform.dy()))); } else { return QString("matrix(%1 %2 %3 %4 %5 %6)") .arg(KisDomUtils::toString(transform.m11())) .arg(KisDomUtils::toString(transform.m12())) .arg(KisDomUtils::toString(transform.m21())) .arg(KisDomUtils::toString(transform.m22())) .arg(KisDomUtils::toString(toUserSpace(transform.dx()))) .arg(KisDomUtils::toString(toUserSpace(transform.dy()))); } } void SvgUtil::writeTransformAttributeLazy(const QString &name, const QTransform &transform, KoXmlWriter &shapeWriter) { const QString value = transformToString(transform); if (!value.isEmpty()) { shapeWriter.addAttribute(name.toLatin1().data(), value); } } bool SvgUtil::parseViewBox(const KoXmlElement &e, const QRectF &elementBounds, QRectF *_viewRect, QTransform *_viewTransform) { KIS_ASSERT(_viewRect); KIS_ASSERT(_viewTransform); QString viewBoxStr = e.attribute("viewBox"); if (viewBoxStr.isEmpty()) return false; bool result = false; QRectF viewBoxRect; // this is a workaround for bug 260429 for a file generated by blender // who has px in the viewbox which is wrong. // reported as bug http://projects.blender.org/tracker/?group_id=9&atid=498&func=detail&aid=30971 viewBoxStr.remove("px"); QStringList points = viewBoxStr.replace(',', ' ').simplified().split(' '); if (points.count() == 4) { viewBoxRect.setX(SvgUtil::fromUserSpace(points[0].toFloat())); viewBoxRect.setY(SvgUtil::fromUserSpace(points[1].toFloat())); viewBoxRect.setWidth(SvgUtil::fromUserSpace(points[2].toFloat())); viewBoxRect.setHeight(SvgUtil::fromUserSpace(points[3].toFloat())); result = true; } else { // TODO: WARNING! } if (!result) return false; QTransform viewBoxTransform = QTransform::fromTranslate(-viewBoxRect.x(), -viewBoxRect.y()) * QTransform::fromScale(elementBounds.width() / viewBoxRect.width(), elementBounds.height() / viewBoxRect.height()) * QTransform::fromTranslate(elementBounds.x(), elementBounds.y()); const QString aspectString = e.attribute("preserveAspectRatio"); - if (!aspectString.isEmpty()) { - PreserveAspectRatioParser p(aspectString); - parseAspectRatio(p, elementBounds, viewBoxRect, &viewBoxTransform); - } + // give initial value if value not defined + PreserveAspectRatioParser p( (!aspectString.isEmpty())? aspectString : QString("xMidYMid meet")); + parseAspectRatio(p, elementBounds, viewBoxRect, &viewBoxTransform); *_viewRect = viewBoxRect; *_viewTransform = viewBoxTransform; return result; } void SvgUtil::parseAspectRatio(const PreserveAspectRatioParser &p, const QRectF &elementBounds, const QRectF &viewBoxRect, QTransform *_viewTransform) { if (p.mode != Qt::IgnoreAspectRatio) { QTransform viewBoxTransform = *_viewTransform; const qreal tan1 = viewBoxRect.height() / viewBoxRect.width(); const qreal tan2 = elementBounds.height() / elementBounds.width(); const qreal uniformScale = (p.mode == Qt::KeepAspectRatioByExpanding) ^ (tan1 > tan2) ? elementBounds.height() / viewBoxRect.height() : elementBounds.width() / viewBoxRect.width(); viewBoxTransform = QTransform::fromTranslate(-viewBoxRect.x(), -viewBoxRect.y()) * QTransform::fromScale(uniformScale, uniformScale) * QTransform::fromTranslate(elementBounds.x(), elementBounds.y()); const QPointF viewBoxAnchor = viewBoxTransform.map(p.rectAnchorPoint(viewBoxRect)); const QPointF elementAnchor = p.rectAnchorPoint(elementBounds); const QPointF offset = elementAnchor - viewBoxAnchor; viewBoxTransform = viewBoxTransform * QTransform::fromTranslate(offset.x(), offset.y()); *_viewTransform = viewBoxTransform; } } qreal SvgUtil::parseUnit(SvgGraphicsContext *gc, const QString &unit, bool horiz, bool vert, const QRectF &bbox) { if (unit.isEmpty()) return 0.0; QByteArray unitLatin1 = unit.toLatin1(); // TODO : percentage? const char *start = unitLatin1.data(); if (!start) { return 0.0; } qreal value = 0.0; const char *end = parseNumber(start, value); if (int(end - start) < unit.length()) { if (unit.right(2) == "px") value = SvgUtil::fromUserSpace(value); else if (unit.right(2) == "pt") value = ptToPx(gc, value); else if (unit.right(2) == "cm") value = ptToPx(gc, CM_TO_POINT(value)); else if (unit.right(2) == "pc") value = ptToPx(gc, PI_TO_POINT(value)); else if (unit.right(2) == "mm") value = ptToPx(gc, MM_TO_POINT(value)); else if (unit.right(2) == "in") value = ptToPx(gc, INCH_TO_POINT(value)); else if (unit.right(2) == "em") // NOTE: all the fonts should be created with 'pt' size, not px! value = ptToPx(gc, value * gc->font.pointSize()); else if (unit.right(2) == "ex") { QFontMetrics metrics(gc->font); value = ptToPx(gc, value * metrics.xHeight()); } else if (unit.right(1) == "%") { if (horiz && vert) value = (value / 100.0) * (sqrt(pow(bbox.width(), 2) + pow(bbox.height(), 2)) / sqrt(2.0)); else if (horiz) value = (value / 100.0) * bbox.width(); else if (vert) value = (value / 100.0) * bbox.height(); } } else { value = SvgUtil::fromUserSpace(value); } /*else { if( m_gc.top() ) { if( horiz && vert ) value *= sqrt( pow( m_gc.top()->matrix.m11(), 2 ) + pow( m_gc.top()->matrix.m22(), 2 ) ) / sqrt( 2.0 ); else if( horiz ) value /= m_gc.top()->matrix.m11(); else if( vert ) value /= m_gc.top()->matrix.m22(); } }*/ //value *= 90.0 / DPI; return value; } qreal SvgUtil::parseUnitX(SvgGraphicsContext *gc, const QString &unit) { if (gc->forcePercentage) { return SvgUtil::fromPercentage(unit) * gc->currentBoundingBox.width(); } else { return SvgUtil::parseUnit(gc, unit, true, false, gc->currentBoundingBox); } } qreal SvgUtil::parseUnitY(SvgGraphicsContext *gc, const QString &unit) { if (gc->forcePercentage) { return SvgUtil::fromPercentage(unit) * gc->currentBoundingBox.height(); } else { return SvgUtil::parseUnit(gc, unit, false, true, gc->currentBoundingBox); } } qreal SvgUtil::parseUnitXY(SvgGraphicsContext *gc, const QString &unit) { if (gc->forcePercentage) { const qreal value = SvgUtil::fromPercentage(unit); return value * sqrt(pow(gc->currentBoundingBox.width(), 2) + pow(gc->currentBoundingBox.height(), 2)) / sqrt(2.0); } else { return SvgUtil::parseUnit(gc, unit, true, true, gc->currentBoundingBox); } } qreal SvgUtil::parseUnitAngular(SvgGraphicsContext *gc, const QString &unit) { Q_UNUSED(gc); qreal value = 0.0; if (unit.isEmpty()) return value; QByteArray unitLatin1 = unit.toLower().toLatin1(); const char *start = unitLatin1.data(); if (!start) return value; const char *end = parseNumber(start, value); if (int(end - start) < unit.length()) { if (unit.right(3) == "deg") { value = kisDegreesToRadians(value); } else if (unit.right(4) == "grad") { value *= M_PI / 200; } else if (unit.right(3) == "rad") { // noop! } else { value = kisDegreesToRadians(value); } } else { value = kisDegreesToRadians(value); } return value; } qreal SvgUtil::parseNumber(const QString &string) { qreal value = 0.0; if (string.isEmpty()) return value; QByteArray unitLatin1 = string.toLatin1(); const char *start = unitLatin1.data(); if (!start) return value; const char *end = parseNumber(start, value); KIS_SAFE_ASSERT_RECOVER_NOOP(int(end - start) == string.length()); return value; } const char * SvgUtil::parseNumber(const char *ptr, qreal &number) { int integer, exponent; qreal decimal, frac; int sign, expsign; exponent = 0; integer = 0; frac = 1.0; decimal = 0; sign = 1; expsign = 1; // read the sign if (*ptr == '+') { ptr++; } else if (*ptr == '-') { ptr++; sign = -1; } // read the integer part while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') integer = (integer * 10) + *(ptr++) - '0'; if (*ptr == '.') { // read the decimals ptr++; while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') decimal += (*(ptr++) - '0') * (frac *= 0.1); } if (*ptr == 'e' || *ptr == 'E') { // read the exponent part ptr++; // read the sign of the exponent if (*ptr == '+') { ptr++; } else if (*ptr == '-') { ptr++; expsign = -1; } exponent = 0; while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') { exponent *= 10; exponent += *ptr - '0'; ptr++; } } number = integer + decimal; number *= sign * pow((double)10, double(expsign * exponent)); return ptr; } QString SvgUtil::mapExtendedShapeTag(const QString &tagName, const KoXmlElement &element) { QString result = tagName; if (tagName == "path") { QString kritaType = element.attribute("krita:type", ""); QString sodipodiType = element.attribute("sodipodi:type", ""); if (kritaType == "arc") { result = "krita:arc"; } else if (sodipodiType == "arc") { result = "sodipodi:arc"; } } return result; } QStringList SvgUtil::simplifyList(const QString &str) { QString attribute = str; attribute.replace(',', ' '); attribute.remove('\r'); attribute.remove('\n'); return attribute.simplified().split(' ', QString::SkipEmptyParts); } SvgUtil::PreserveAspectRatioParser::PreserveAspectRatioParser(const QString &str) { QRegExp rexp("(defer)?\\s*(none|(x(Min|Max|Mid)Y(Min|Max|Mid)))\\s*(meet|slice)?", Qt::CaseInsensitive); int index = rexp.indexIn(str.toLower()); if (index >= 0) { if (rexp.cap(1) == "defer") { defer = true; } if (rexp.cap(2) != "none") { xAlignment = alignmentFromString(rexp.cap(4)); yAlignment = alignmentFromString(rexp.cap(5)); mode = rexp.cap(6) == "slice" ? Qt::KeepAspectRatioByExpanding : Qt::KeepAspectRatio; } } } QPointF SvgUtil::PreserveAspectRatioParser::rectAnchorPoint(const QRectF &rc) const { return QPointF(alignedValue(rc.x(), rc.x() + rc.width(), xAlignment), alignedValue(rc.y(), rc.y() + rc.height(), yAlignment)); } QString SvgUtil::PreserveAspectRatioParser::toString() const { QString result; if (!defer && xAlignment == Middle && yAlignment == Middle && mode == Qt::KeepAspectRatio) { return result; } if (defer) { result += "defer "; } if (mode == Qt::IgnoreAspectRatio) { result += "none"; } else { result += QString("x%1Y%2") .arg(alignmentToString(xAlignment)) .arg(alignmentToString(yAlignment)); if (mode == Qt::KeepAspectRatioByExpanding) { result += " slice"; } } return result; } SvgUtil::PreserveAspectRatioParser::Alignment SvgUtil::PreserveAspectRatioParser::alignmentFromString(const QString &str) const { return str == "max" ? Max : str == "mid" ? Middle : Min; } QString SvgUtil::PreserveAspectRatioParser::alignmentToString(SvgUtil::PreserveAspectRatioParser::Alignment alignment) const { return alignment == Max ? "Max" : alignment == Min ? "Min" : "Mid"; } qreal SvgUtil::PreserveAspectRatioParser::alignedValue(qreal min, qreal max, SvgUtil::PreserveAspectRatioParser::Alignment alignment) { qreal result = min; switch (alignment) { case Min: result = min; break; case Middle: result = 0.5 * (min + max); break; case Max: result = max; break; } return result; } diff --git a/libs/flake/tests/TestSvgParser.cpp b/libs/flake/tests/TestSvgParser.cpp index f7c3399e55..b03ac84435 100644 --- a/libs/flake/tests/TestSvgParser.cpp +++ b/libs/flake/tests/TestSvgParser.cpp @@ -1,3219 +1,3226 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "TestSvgParser.h" #include #include #include #include "SvgParserTestingUtils.h" #include "../../sdk/tests/qimage_test_util.h" #ifdef USE_ROUND_TRIP #include "SvgWriter.h" #include #include #endif void TestSvgParser::testUnitPx() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,10,20), 0.5)); QCOMPARE(shape->absoluteTransformation(0), QTransform()); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(10,20)); } void TestSvgParser::testUnitPxResolution() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,5,10), 0.25)); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(5,10)); } void TestSvgParser::testUnitPt() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 666 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,10,20), 0.5)); QCOMPARE(shape->absoluteTransformation(0), QTransform()); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(10,20)); } void TestSvgParser::testUnitIn() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 666 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,720,1440), 36)); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromScale(72, 72)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(720,1440)); } void TestSvgParser::testUnitPercentInitial() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 80, 80) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,5,10), 0.25)); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(5,10)); } void TestSvgParser::testScalingViewport() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18)); } void TestSvgParser::testScalingViewportKeepMeet1() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18)); } void TestSvgParser::testScalingViewportKeepMeet2() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18)); } void TestSvgParser::testScalingViewportKeepMeetAlign() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 24) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,12)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,12)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,28)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,28)); } void TestSvgParser::testScalingViewportKeepSlice1() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18)); } void TestSvgParser::testScalingViewportKeepSlice2() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18)); } void TestSvgParser::testScalingViewportResolution() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.25, 0.25)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(1,1)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(4,1)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(1,9)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(4,9)); } void TestSvgParser::testScalingViewportPercentInternal() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18)); } void TestSvgParser::testParsePreserveAspectRatio() { { SvgUtil::PreserveAspectRatioParser p(" defer xMinYMax meet"); QCOMPARE(p.defer, true); QCOMPARE(p.mode, Qt::KeepAspectRatio); QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Min); QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Max); } { SvgUtil::PreserveAspectRatioParser p(" xMinYMid slice"); QCOMPARE(p.defer, false); QCOMPARE(p.mode, Qt::KeepAspectRatioByExpanding); QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Min); QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Middle); } { SvgUtil::PreserveAspectRatioParser p(" xmidYMid "); QCOMPARE(p.defer, false); QCOMPARE(p.mode, Qt::KeepAspectRatio); QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Middle); QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Middle); } { SvgUtil::PreserveAspectRatioParser p(" NoNe "); QCOMPARE(p.defer, false); QCOMPARE(p.mode, Qt::IgnoreAspectRatio); QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Min); QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Min); } { SvgUtil::PreserveAspectRatioParser p("defer NoNe "); QCOMPARE(p.defer, true); QCOMPARE(p.mode, Qt::IgnoreAspectRatio); QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Min); QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Min); } { SvgUtil::PreserveAspectRatioParser p("sweet brown fox jumps over a nice svg file"); QCOMPARE(p.defer, false); QCOMPARE(p.mode, Qt::IgnoreAspectRatio); QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Min); QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Min); } } #include "parsers/SvgTransformParser.h" void TestSvgParser::testParseTransform() { { QString str("translate(-111.0, 33) translate(-111.0, 33) matrix (1 1 0 0 1, 3), translate(1)" "scale(0.5) rotate(10) rotate(10, 3 3) skewX(1) skewY(2)"); SvgTransformParser p(str); QCOMPARE(p.isValid(), true); } { // forget about one brace QString str("translate(-111.0, 33) translate(-111.0, 33 matrix (1 1 0 0 1, 3), translate(1)" "scale(0.5) rotate(10) rotate(10, 3 3) skewX(1) skewY(2)"); SvgTransformParser p(str); QCOMPARE(p.isValid(), false); } { SvgTransformParser p("translate(100, 50)"); QCOMPARE(p.isValid(), true); QCOMPARE(p.transform(), QTransform::fromTranslate(100, 50)); } { SvgTransformParser p("translate(100 50)"); QCOMPARE(p.isValid(), true); QCOMPARE(p.transform(), QTransform::fromTranslate(100, 50)); } { SvgTransformParser p("translate(100)"); QCOMPARE(p.isValid(), true); QCOMPARE(p.transform(), QTransform::fromTranslate(100, 0)); } { SvgTransformParser p("scale(100, 50)"); QCOMPARE(p.isValid(), true); QCOMPARE(p.transform(), QTransform::fromScale(100, 50)); } { SvgTransformParser p("scale(100)"); QCOMPARE(p.isValid(), true); QCOMPARE(p.transform(), QTransform::fromScale(100, 100)); } { SvgTransformParser p("rotate(90 70 74.0)"); QCOMPARE(p.isValid(), true); QTransform t; t.rotate(90); t = QTransform::fromTranslate(-70, -74) * t * QTransform::fromTranslate(70, 74); qDebug() << ppVar(p.transform()); QCOMPARE(p.transform(), t); } } void TestSvgParser::testScalingViewportTransform() { /** * Note: 'transform' affects all the attributes of the *current* * element, while 'viewBox' affects only the descendants! */ const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(10, 4) * QTransform::fromScale(0.5, 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(5,2)); QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(11,2)); QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(5,18)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(11,18)); } void TestSvgParser::testTransformNesting() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), QRectF(10 - 1,10 - 0.5, 20 + 2, 20 + 1)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(10,10)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(30,30)); } void TestSvgParser::testTransformNestingGroups() { const QString data = "" "" " " "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), QRectF(10 - 1,10 - 0.5, 20 + 2, 20 + 1)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(10,10)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(30,30)); } void TestSvgParser::testTransformRotation1() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(-20,0,20,10), 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(-20,10)); } void TestSvgParser::testTransformRotation2() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(5,5,20,10), 0.5)); QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20)); QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(5,15)); QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(25,5)); } void TestSvgParser::testRenderStrokeNone() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_none"); } void TestSvgParser::testRenderStrokeColorName() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeColorHex3() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeColorHex6() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeColorRgbValues() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeColorRgbPercent() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeColorCurrent() { const QString data = "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeColorNonexistentIri() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue"); } void TestSvgParser::testRenderStrokeWidth() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_width_2"); } void TestSvgParser::testRenderStrokeZeroWidth() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_none"); } void TestSvgParser::testRenderStrokeOpacity() { const QString data = "" "" ""; SvgRenderTester t (data); t.setFuzzyThreshold(1); t.test_standard_30px_72ppi("stroke_blue_0_3_opacity"); } void TestSvgParser::testRenderStrokeJointRound() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_join_round"); } void TestSvgParser::testRenderStrokeLinecap() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_linecap_round"); } void TestSvgParser::testRenderStrokeMiterLimit() { // TODO:seems like doesn't work!! qWarning() << "WARNING: Miter limit test is skipped!!!"; return; const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_miter_limit"); } void TestSvgParser::testRenderStrokeDashArrayEven() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_dasharray_even"); } void TestSvgParser::testRenderStrokeDashArrayEvenOffset() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_dasharray_even_offset"); } void TestSvgParser::testRenderStrokeDashArrayOdd() { // SVG 1.1: if the dasharray is odd, repeat it const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_dasharray_odd"); } void TestSvgParser::testRenderStrokeDashArrayRelative() { // SVG 1.1: relative to view box // (40 x 50) * sqrt(2) => dash length = 5 px const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_dasharray_relative"); } void TestSvgParser::testRenderFillDefault() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_black"); } void TestSvgParser::testRenderFillRuleNonZero() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_non_zero"); } void TestSvgParser::testRenderFillRuleEvenOdd() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_even_odd"); } void TestSvgParser::testRenderFillOpacity() { const QString data = "" "" ""; SvgRenderTester t (data); t.setFuzzyThreshold(1); t.test_standard_30px_72ppi("fill_opacity_0_3"); } void TestSvgParser::testRenderDisplayAttribute() { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->isVisible(), false); } void TestSvgParser::testRenderVisibilityAttribute() { { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->isVisible(), true); } { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->isVisible(), false); } { const QString data = "" "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->isVisible(), false); } } void TestSvgParser::testRenderVisibilityInheritance() { const QString data = "" "" " " "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->isVisible(false), true); QCOMPARE(shape->isVisible(true), false); } void TestSvgParser::testRenderDisplayInheritance() { const QString data = "" "" " " "" ""; SvgTester t (data); t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */); t.run(); KoShape *shape = t.findShape("testRect"); QVERIFY(shape); QCOMPARE(shape->isVisible(false), true); QEXPECT_FAIL("", "TODO: Fix 'display' attribute not to be inherited in shapes hierarchy!", Continue); QCOMPARE(shape->isVisible(true), true); } void TestSvgParser::testRenderStrokeWithInlineStyle() { const QString data = "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_blue_width_2"); } void TestSvgParser::testIccColor() { const QString data = "" "" " " " " " " "" ""; SvgRenderTester t (data); int numFetches = 0; t.parser.setFileFetcher( [&numFetches](const QString &name) { numFetches++; const QString fileName = TestUtil::fetchDataFileLazy(name); QFile file(fileName); KIS_ASSERT(file.exists()); file.open(QIODevice::ReadOnly); return file.readAll(); }); t.test_standard_30px_72ppi("stroke_blue_width_2"); QCOMPARE(numFetches, 1); } void TestSvgParser::testRenderFillLinearGradientRelativePercent() { const QString data = "" "" " " " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient"); } void TestSvgParser::testRenderFillLinearGradientRelativePortion() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient"); } void TestSvgParser::testRenderFillLinearGradientUserCoord() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient"); } void TestSvgParser::testRenderFillLinearGradientStopPortion() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient"); } void TestSvgParser::testRenderFillLinearGradientTransform() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient_vertical"); } void TestSvgParser::testRenderFillLinearGradientTransformUserCoord() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient_vertical_in_user"); } void TestSvgParser::testRenderFillLinearGradientRotatedShape() { // DK: I'm not sure I fully understand if it is a correct transformation, // but inkscape opens the file in the same way... const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient_shape_rotated", false); } void TestSvgParser::testRenderFillLinearGradientRotatedShapeUserCoord() { // DK: I'm not sure I fully understand if it is a correct transformation, // but inkscape opens the file in the same way... const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient_shape_rotated_in_user", false); } void TestSvgParser::testRenderFillRadialGradient() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.setFuzzyThreshold(1); t.test_standard_30px_72ppi("fill_gradient_radial"); } void TestSvgParser::testRenderFillRadialGradientUserCoord() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.setFuzzyThreshold(1); t.test_standard_30px_72ppi("fill_gradient_radial_in_user"); } void TestSvgParser::testRenderFillLinearGradientUserCoordPercent() { const QString data = "" "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_gradient"); } void TestSvgParser::testRenderStrokeLinearGradient() { const QString data = "" "" " " " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("stroke_gradient_dashed"); } QTransform rotateTransform(qreal degree, const QPointF ¢er) { QTransform rotate; rotate.rotate(degree); return QTransform::fromTranslate(-center.x(), -center.y()) * rotate * QTransform::fromTranslate(center.x(), center.y()); } QTransform viewTransform(const QRectF &src, const QRectF &dst) { return QTransform::fromTranslate(-src.x(), -src.y()) * QTransform::fromScale(dst.width() / src.width(), dst.height() / src.height()) * QTransform::fromTranslate(dst.x(), dst.y()); } QPainterPath bakeShape(const QPainterPath &path, const QTransform &bakeTransform, bool contentIsObb = false, const QRectF &shapeBoundingRect = QRectF(), bool contentIsViewBox = false, const QRectF &viewBoxRect= QRectF(), const QRectF &refRect = QRectF()) { const QTransform relativeToShape(shapeBoundingRect.width(), 0, 0, shapeBoundingRect.height(), shapeBoundingRect.x(), shapeBoundingRect.y()); QTransform newTransform = bakeTransform; if (contentIsObb) { newTransform = relativeToShape * newTransform; } if (contentIsViewBox) { newTransform = viewTransform(viewBoxRect, refRect) * newTransform; } return newTransform.map(path); } #include void renderBakedPath(QPainter &painter, const QPainterPath &bakedFillPath, const QTransform &bakedTransform, const QRect &shapeOutline, const QTransform &shapeTransform, const QRectF &referenceRect, bool contentIsObb, const QRectF &bakedShapeBoundingRect, bool referenceIsObb, const QTransform &patternTransform, QImage *stampResult) { painter.setTransform(QTransform()); painter.setPen(Qt::NoPen); QPainterPath shapeOutlinePath; shapeOutlinePath.addRect(shapeOutline); KoBakedShapeRenderer renderer( shapeOutlinePath, shapeTransform, bakedTransform, referenceRect, contentIsObb, bakedShapeBoundingRect, referenceIsObb, patternTransform); QPainter *patchPainter = renderer.bakeShapePainter(); patchPainter->fillPath(bakedFillPath, Qt::blue); patchPainter->end(); renderer.renderShape(painter); if (stampResult) { *stampResult = renderer.patchImage(); } } void TestSvgParser::testManualRenderPattern_ContentUser_RefObb() { const QRectF referenceRect(0, 0, 1.0, 0.5); QPainterPath fillPath; fillPath.addRect(QRect(2, 2, 6, 6)); fillPath.addRect(QRect(8, 4, 3, 2)); QTransform bakedTransform = QTransform::fromTranslate(10, 10) * QTransform::fromScale(2, 2); QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform); QRect shape1OutlineRect(0,0,10,20); QImage stampResult; QImage fillResult(QSize(60,60), QImage::Format_ARGB32); QPainter gc(&fillResult); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape1OutlineRect, bakedTransform, referenceRect, false, QRectF(), true, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_user_r_obb_patch1")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_user_r_obb_fill1")); QRect shape2OutlineRect(5,5,20,10); QTransform shape2Transform = QTransform::fromScale(2, 2) * QTransform::fromTranslate(5, 5); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape2OutlineRect, shape2Transform, referenceRect, false, QRectF(), true, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_user_r_obb_patch2")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_user_r_obb_fill2")); } void TestSvgParser::testManualRenderPattern_ContentObb_RefObb() { const QRectF referenceRect(0.3, 0.3, 0.4, 0.4); QPainterPath fillPath; fillPath.addRect(QRectF(0.4, 0.4, 0.2, 0.2)); fillPath.addRect(QRectF(0.6, 0.5, 0.1, 0.1)); fillPath.addRect(QRectF(0.3, 0.4, 0.1, 0.1)); const QRect bakedShapeRect(2,2,10,10); QTransform bakedTransform = QTransform::fromTranslate(10, 10) * QTransform::fromScale(2, 2); QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform, true, bakedShapeRect); QImage stampResult; QImage fillResult(QSize(60,60), QImage::Format_ARGB32); QPainter gc(&fillResult); // Round trip to the same shape fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, bakedShapeRect, bakedTransform, referenceRect, true, bakedShapeRect, true, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_obb_r_obb_patch1")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_obb_r_obb_fill1")); // Move to a different shape QRect shape2OutlineRect(5,5,20,10); QTransform shape2Transform = QTransform::fromScale(2, 2) * QTransform::fromTranslate(5, 5); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape2OutlineRect, shape2Transform, referenceRect, true, bakedShapeRect, true, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_obb_r_obb_patch2")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_obb_r_obb_fill2")); } void TestSvgParser::testManualRenderPattern_ContentUser_RefUser() { const QRectF referenceRect(5, 2, 8, 8); QPainterPath fillPath; fillPath.addRect(QRect(2, 2, 6, 6)); fillPath.addRect(QRect(8, 4, 3, 2)); QTransform bakedTransform = QTransform::fromTranslate(10, 10) * QTransform::fromScale(2, 2); QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform); QRect shape1OutlineRect(0,0,10,20); QImage stampResult; QImage fillResult(QSize(60,60), QImage::Format_ARGB32); QPainter gc(&fillResult); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape1OutlineRect, bakedTransform, referenceRect, false, QRectF(), false, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_user_r_user_patch1")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_user_r_user_fill1")); QRect shape2OutlineRect(5,5,20,10); QTransform shape2Transform = QTransform::fromScale(2, 2) * QTransform::fromTranslate(5, 5); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape2OutlineRect, shape2Transform, referenceRect, false, QRectF(), false, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_user_r_user_patch2")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_user_r_user_fill2")); } void TestSvgParser::testManualRenderPattern_ContentObb_RefObb_Transform_Rotate() { const QRectF referenceRect(0.0, 0.0, 0.4, 0.2); QPainterPath fillPath; fillPath.addRect(QRectF(0.0, 0.0, 0.5, 0.1)); fillPath.addRect(QRectF(0.0, 0.1, 0.1, 0.1)); const QRect bakedShapeRect(2,1,10,10); QTransform bakedTransform = QTransform::fromScale(2, 2) * QTransform::fromTranslate(10,10); QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform, true, bakedShapeRect); QImage stampResult; QImage fillResult(QSize(60,60), QImage::Format_ARGB32); QPainter gc(&fillResult); QTransform patternTransform; patternTransform.rotate(90); patternTransform = patternTransform * QTransform::fromTranslate(0.5, 0.0); // Round trip to the same shape fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, bakedShapeRect, bakedTransform, referenceRect, true, bakedShapeRect, true, patternTransform, &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_obb_r_obb_rotate_patch1")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_obb_r_obb_rotate_fill1")); QRect shape2OutlineRect(5,5,20,10); QTransform shape2Transform = QTransform::fromScale(2, 2) * QTransform::fromTranslate(5, 5); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape2OutlineRect, shape2Transform, referenceRect, true, bakedShapeRect, true, patternTransform, &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_obb_r_obb_rotate_patch2")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_obb_r_obb_rotate_fill2")); } void TestSvgParser::testManualRenderPattern_ContentView_RefObb() { const QRectF referenceRect(0, 0, 0.5, 1.0/3.0); const QRectF viewRect(10,10,60,90); QPainterPath fillPath; fillPath.addRect(QRect(30, 10, 20, 60)); fillPath.addRect(QRect(50, 40, 20, 30)); QRect shape1OutlineRect(10,20,40,120); QTransform bakedTransform = QTransform::fromScale(2, 0.5) * QTransform::fromTranslate(40,30); QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform, true, shape1OutlineRect, true, viewRect, referenceRect); QImage stampResult; QImage fillResult(QSize(220,160), QImage::Format_ARGB32); QPainter gc(&fillResult); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape1OutlineRect, bakedTransform, referenceRect, true, shape1OutlineRect, true, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_view_r_obb_patch1")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_view_r_obb_fill1")); QRect shape2OutlineRect(20,10,60,90); QTransform shape2Transform = QTransform::fromScale(2, 1) * QTransform::fromTranslate(50, 50); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape2OutlineRect, shape2Transform, referenceRect, true, shape1OutlineRect, true, rotateTransform(90, QPointF(0, 1.0 / 3.0)), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_view_r_obb_patch2")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_view_r_obb_fill2")); } void TestSvgParser::testManualRenderPattern_ContentView_RefUser() { const QRectF referenceRect(60, 0, 30, 20); const QRectF viewRect(10,10,60,90); QPainterPath fillPath; fillPath.addRect(QRect(30, 10, 20, 60)); fillPath.addRect(QRect(50, 40, 20, 30)); QRect shape1OutlineRect(10,20,40,120); QTransform bakedTransform = QTransform::fromScale(2, 0.5) * QTransform::fromTranslate(40,30); QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform, false, shape1OutlineRect, true, viewRect, referenceRect); QImage stampResult; QImage fillResult(QSize(220,160), QImage::Format_ARGB32); QPainter gc(&fillResult); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape1OutlineRect, bakedTransform, referenceRect, false, shape1OutlineRect, false, QTransform(), &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_view_r_user_patch1")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_view_r_user_fill1")); QRect shape2OutlineRect(20,10,60,90); QTransform shape2Transform = QTransform::fromScale(2, 1) * QTransform::fromTranslate(50, 50); QTransform patternTransform2 = rotateTransform(90, QPointF()) * QTransform::fromTranslate(40, 10); fillResult.fill(0); renderBakedPath(gc, bakedFillPath, bakedTransform, shape2OutlineRect, shape2Transform, referenceRect, false, shape1OutlineRect, false, patternTransform2, &stampResult); QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_view_r_user_patch2")); QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_view_r_user_fill2")); } void TestSvgParser::testRenderPattern_r_User_c_User() { const QString data = "" "" " " " " " " " " " " " " "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_pattern_base", false, QSize(160, 160)); } void TestSvgParser::testRenderPattern_InfiniteRecursionWhenInherited() { const QString data = "" "" " " " " " " " " " " " " "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_pattern_base_black", false, QSize(160, 160)); } void TestSvgParser::testRenderPattern_r_User_c_View() { const QString data = "" "" " " " " " " // y is changed to 39 from 40 to fix a rounding issue! " " " " " " "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_pattern_base", false, QSize(160, 160)); } void TestSvgParser::testRenderPattern_r_User_c_Obb() { const QString data = "" "" " " " " " " " " " " " " "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_pattern_base", false, QSize(160, 160)); } void TestSvgParser::testRenderPattern_r_User_c_View_Rotated() { const QString data = "" "" " " " " " " " " " " " " "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_pattern_rotated", false, QSize(220, 160)); } void TestSvgParser::testRenderPattern_r_Obb_c_View_Rotated() { /** * This test case differs from any application existent in the world :( * * Chrome and Firefox premultiply the patternTransform instead of doing post- * multiplication. Photoshop forgets to multiply the reference rect on it. * * So... */ const QString data = "" "" " " " " " " " " " " " " "" "" " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("fill_pattern_rotated_odd", false, QSize(220, 160)); } #include #include #include #include void TestSvgParser::testKoClipPathRendering() { QPainterPath path1; path1.addRect(QRect(5,5,15,15)); QPainterPath path2; path2.addRect(QRect(10,10,15,15)); QPainterPath clipPath1; clipPath1.addRect(QRect(10, 0, 10, 30)); QPainterPath clipPath2; clipPath2.moveTo(0,7); clipPath2.lineTo(30,7); clipPath2.lineTo(15,30); clipPath2.lineTo(0,7); QScopedPointer shape1(KoPathShape::createShapeFromPainterPath(path1)); shape1->setBackground(QSharedPointer(new KoColorBackground(Qt::blue))); QScopedPointer shape2(KoPathShape::createShapeFromPainterPath(path2)); shape2->setBackground(QSharedPointer(new KoColorBackground(Qt::red))); QScopedPointer clipShape1(KoPathShape::createShapeFromPainterPath(clipPath1)); KoClipPath *koClipPath1 = new KoClipPath({clipShape1.take()}, KoFlake::UserSpaceOnUse); koClipPath1->setClipRule(Qt::WindingFill); shape1->setClipPath(koClipPath1); QScopedPointer group(new KoShapeGroup()); { QList shapes({shape1.take(), shape2.take()}); KoShapeGroupCommand cmd(group.data(), shapes, false); cmd.redo(); } QScopedPointer clipShape2(KoPathShape::createShapeFromPainterPath(clipPath2)); KoClipPath *koClipPath2 = new KoClipPath({clipShape2.take()}, KoFlake::UserSpaceOnUse); koClipPath2->setClipRule(Qt::WindingFill); group->setClipPath(koClipPath2); SvgRenderTester::testRender(group.take(), "load", "clip_render_test", QSize(30,30)); } void TestSvgParser::testKoClipPathRelativeRendering() { QPainterPath path1; path1.addRect(QRect(5,5,15,15)); QPainterPath path2; path2.addRect(QRect(10,10,15,15)); QPainterPath clipPath1; clipPath1.addRect(QRect(10, 0, 10, 30)); QPainterPath clipPath2; clipPath2.moveTo(0,0); clipPath2.lineTo(1,0); clipPath2.lineTo(0.5,1); clipPath2.lineTo(0,0); QScopedPointer shape1(KoPathShape::createShapeFromPainterPath(path1)); shape1->setBackground(QSharedPointer(new KoColorBackground(Qt::blue))); QScopedPointer shape2(KoPathShape::createShapeFromPainterPath(path2)); shape2->setBackground(QSharedPointer(new KoColorBackground(Qt::red))); QScopedPointer clipShape1(KoPathShape::createShapeFromPainterPath(clipPath1)); KoClipPath *koClipPath1 = new KoClipPath({clipShape1.take()}, KoFlake::UserSpaceOnUse); koClipPath1->setClipRule(Qt::WindingFill); shape1->setClipPath(koClipPath1); QScopedPointer group(new KoShapeGroup()); { QList shapes({shape1.take(), shape2.take()}); KoShapeGroupCommand cmd(group.data(), shapes, false); cmd.redo(); } QScopedPointer clipShape2(KoPathShape::createShapeFromPainterPath(clipPath2)); KoClipPath *koClipPath2 = new KoClipPath({clipShape2.take()}, KoFlake::ObjectBoundingBox); koClipPath2->setClipRule(Qt::WindingFill); group->setClipPath(koClipPath2); SvgRenderTester::testRender(group.take(), "load", "relative_clip_render_test", QSize(30,30)); } void TestSvgParser::testRenderClipPath_User() { const QString data = "" "" " " " " "" "" " " "" "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("clip_render_test", false); } void TestSvgParser::testRenderClipPath_Obb() { const QString data = "" "" " " " " "" "" " " "" "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("relative_clip_render_test", false); } void TestSvgParser::testRenderClipPath_Obb_Transform() { const QString data = "" "" " " " " "" "" " " "" "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("clip_render_test_rotated", false); } void TestSvgParser::testRenderClipMask_Obb() { const QString data = "" //"" " " " " " " " " " " " " " " //"" "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("clip_mask_obb", false); } void TestSvgParser::testRenderClipMask_User_Clip_Obb() { const QString data = "" //"" " " " " " " " " " " " " " " //"" "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("clip_mask_obb", false); } void TestSvgParser::testRenderClipMask_User_Clip_User() { const QString data = "" //"" " " " " " " " " " " " " " " //"" "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("clip_mask_obb", false); } QByteArray fileFetcherFunc(const QString &name) { const QString fileName = TestUtil::fetchDataFileLazy(name); QFile file(fileName); KIS_ASSERT(file.exists()); file.open(QIODevice::ReadOnly); return file.readAll(); } void TestSvgParser::testRenderImage_AspectDefault() { const QString data = "" "" " " " " " " " My image" " " "" ""; SvgRenderTester t (data); t.parser.setFileFetcher(fileFetcherFunc); t.test_standard_30px_72ppi("image_aspect_default", false); } void TestSvgParser::testRenderImage_AspectNone() { const QString data = "" "" " " " " " " " My image" " " "" ""; SvgRenderTester t (data); t.setFuzzyThreshold(5); t.parser.setFileFetcher(fileFetcherFunc); t.test_standard_30px_72ppi("image_aspect_none", false); } void TestSvgParser::testRenderImage_AspectMeet() { const QString data = "" "" " " " " " " " My image" " " "" ""; SvgRenderTester t (data); t.parser.setFileFetcher(fileFetcherFunc); t.test_standard_30px_72ppi("image_aspect_meet", false); } void TestSvgParser::testRectShapeRoundUniformX() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("rect_5_5", false); } void TestSvgParser::testRectShapeRoundUniformY() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("rect_5_5", false); } void TestSvgParser::testRectShapeRoundXY() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("rect_5_10", false); } void TestSvgParser::testRectShapeRoundXYOverflow() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("rect_5_10", false); } void TestSvgParser::testCircleShape() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("circle", false); } void TestSvgParser::testEllipseShape() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("ellipse", false); } void TestSvgParser::testLineShape() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("line", false); } void TestSvgParser::testPolylineShape() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("polyline", false); } void TestSvgParser::testPolygonShape() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("polygon", false); } void TestSvgParser::testPathShape() { const QString data = "" " " ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("polygon", false); } void TestSvgParser::testDefsHidden() { const QString data = "" "" " " " " " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("test_defs_hidden", false); } void TestSvgParser::testDefsUseInheritance() { const QString data = "" "" " " " " " " "" /** * NOTES: * 1) width/height attributes for are not implemented yet * 2) x and y are summed up * 3) stroke="white" is overridden by the original templated object * 4) fill="green" attribute from is not inherited */ "" " " " " "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("defs_use_inheritance", false); } void TestSvgParser::testUseWithoutDefs() { const QString data = "" // technical rect for rendering "" "" " " "" /** * NOTES: * 1) width/height attributes for are not implemented yet * 2) x and y are summed up * 3) stroke="white" is overridden by the original templated object * 4) fill="green" attribute from is not inherited */ "" " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("use_without_defs", false); } void TestSvgParser::testMarkersAutoOrientation() { const QString data = "" "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers", false); } void TestSvgParser::testMarkersAutoOrientationScaled() { const QString data = "" "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_scaled", false); } void TestSvgParser::testMarkersAutoOrientationScaledUserCoordinates() { const QString data = "" "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_user_coordinates", false); } void TestSvgParser::testMarkersCustomOrientation() { const QString data = "" "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_custom_orientation", false); } void TestSvgParser::testMarkersDifferent() { const QString data = "" "" " " " " " " "" "" " " " " " " "" "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_different", false); } void TestSvgParser::testMarkersFillAsShape() { const QString data = "" "" " " " " " " " " " " " " " " " " " " "" "" //" d=\"M5,15 L25,15\"/>" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_scaled_fill_as_shape", false); } void TestSvgParser::testMarkersOnClosedPath() { const QString data = "" "" " " " " " " "" "" " " " " " " "" "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_on_closed_path", false); } void TestSvgParser::testGradientRecoveringTransform() { // used for experimenting purposes only! QImage image(100,100,QImage::Format_ARGB32); image.fill(0); QPainter painter(&image); painter.setPen(QPen(Qt::black, 0)); QLinearGradient gradient(0, 0.5, 1, 0.5); gradient.setCoordinateMode(QGradient::ObjectBoundingMode); //QLinearGradient gradient(0, 50, 100, 50); //gradient.setCoordinateMode(QGradient::LogicalMode); gradient.setColorAt(0.0, Qt::red); gradient.setColorAt(1.0, Qt::blue); QTransform gradientTransform; gradientTransform.shear(0.2, 0); { QBrush brush(gradient); brush.setTransform(gradientTransform); painter.setBrush(brush); } QRect mainShape(3,3,94,94); painter.drawRect(mainShape); QTransform gradientToUser(mainShape.width(), 0, 0, mainShape.height(), mainShape.x(), mainShape.y()); QRect smallShape(0,0,20,20); QTransform smallShapeTransform; { smallShapeTransform = QTransform::fromTranslate(-smallShape.center().x(), -smallShape.center().y()); QTransform r; r.rotate(90); smallShapeTransform *= r; smallShapeTransform *= QTransform::fromTranslate(mainShape.center().x(), mainShape.center().y()); } { gradient.setCoordinateMode(QGradient::LogicalMode); QBrush brush(gradient); brush.setTransform(gradientTransform * gradientToUser * smallShapeTransform.inverted()); painter.setBrush(brush); painter.setPen(Qt::NoPen); } painter.setTransform(smallShapeTransform); painter.drawRect(smallShape); //image.save("gradient_recovering_transform.png"); } void TestSvgParser::testMarkersAngularUnits() { const QString data = "" // start "" " " " " " " "" // end "" " " " " " " "" // mid "" " " " " " " "" "" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("markers_angular_units", false); } #include "KoParameterShape.h" void TestSvgParser::testSodipodiArcShape() { const QString data = "" "" " d=\" some weird unparsable text \" />" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("sodipodi_closed_arc", false); KoShape *shape = t.findShape("testRect"); QVERIFY(dynamic_cast(shape)); } void TestSvgParser::testSodipodiArcShapeOpen() { const QString data = "" "" " d=\" some weird unparsable text \" />" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("sodipodi_open_arc", false); KoShape *shape = t.findShape("testRect"); QVERIFY(dynamic_cast(shape)); } void TestSvgParser::testKritaChordShape() { const QString data = "" "" " d=\" some weird unparsable text \" />" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("sodipodi_chord_arc", false); KoShape *shape = t.findShape("testRect"); QVERIFY(dynamic_cast(shape)); } void TestSvgParser::testSodipodiChordShape() { const QString data = "" "" " d=\" some weird unparsable text \" />" ""; SvgRenderTester t (data); t.test_standard_30px_72ppi("sodipodi_chord_arc", false); KoShape *shape = t.findShape("testRect"); QVERIFY(dynamic_cast(shape)); } QTEST_MAIN(TestSvgParser)