File indexing completed on 2024-05-26 04:26:26

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 2002 Lars Siebold <khandha5@gmx.net>
0003    SPDX-FileCopyrightText: 2002-2003, 2005 Rob Buis <buis@kde.org>
0004    SPDX-FileCopyrightText: 2002, 2005-2006 David Faure <faure@kde.org>
0005    SPDX-FileCopyrightText: 2002 Werner Trobin <trobin@kde.org>
0006    SPDX-FileCopyrightText: 2002 Lennart Kudling <kudling@kde.org>
0007    SPDX-FileCopyrightText: 2004 Nicolas Goutte <nicolasg@snafu.de>
0008    SPDX-FileCopyrightText: 2005 Boudewijn Rempt <boud@valdyas.org>
0009    SPDX-FileCopyrightText: 2005 Raphael Langerhorst <raphael.langerhorst@kdemail.net>
0010    SPDX-FileCopyrightText: 2005 Thomas Zander <zander@kde.org>
0011    SPDX-FileCopyrightText: 2005, 2007-2008 Jan Hambrecht <jaham@gmx.net>
0012    SPDX-FileCopyrightText: 2006 Inge Wallin <inge@lysator.liu.se>
0013    SPDX-FileCopyrightText: 2006 Martin Pfeiffer <hubipete@gmx.net>
0014    SPDX-FileCopyrightText: 2006 Gábor Lehel <illissius@gmail.com>
0015    SPDX-FileCopyrightText: 2006 Laurent Montel <montel@kde.org>
0016    SPDX-FileCopyrightText: 2006 Christian Mueller <cmueller@gmx.de>
0017    SPDX-FileCopyrightText: 2006 Ariya Hidayat <ariya@kde.org>
0018    SPDX-FileCopyrightText: 2010 Thorsten Zachmann <zachmann@kde.org>
0019 
0020    SPDX-License-Identifier: LGPL-2.0-or-later
0021 */
0022 
0023 #include "SvgStyleWriter.h"
0024 #include "SvgSavingContext.h"
0025 #include "SvgUtil.h"
0026 
0027 #include <KoShape.h>
0028 #include <KoPathShape.h>
0029 #include <KoPathSegment.h>
0030 #include <KoFilterEffect.h>
0031 #include <KoFilterEffectStack.h>
0032 #include <KoColorBackground.h>
0033 #include <KoGradientBackground.h>
0034 #include <KoMeshGradientBackground.h>
0035 #include <KoPatternBackground.h>
0036 #include <KoVectorPatternBackground.h>
0037 #include <KoShapeStroke.h>
0038 #include <KoClipPath.h>
0039 #include <KoClipMask.h>
0040 #include <KoMarker.h>
0041 #include <KoXmlWriter.h>
0042 
0043 #include <QBuffer>
0044 #include <QGradient>
0045 #include <QLinearGradient>
0046 #include <QRadialGradient>
0047 #include <KisMimeDatabase.h>
0048 #include "kis_dom_utils.h"
0049 #include "kis_algebra_2d.h"
0050 #include <SvgWriter.h>
0051 #include <KoFlakeCoordinateSystem.h>
0052 
0053 
0054 void SvgStyleWriter::saveSvgStyle(KoShape *shape, SvgSavingContext &context)
0055 {
0056     saveSvgBasicStyle(shape->isVisible(false), shape->transparency(false), shape->paintOrder(), shape->inheritPaintOrder(), context);
0057 
0058     KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
0059     bool fillRule = pathShape && pathShape->background() && pathShape->fillRule() == Qt::OddEvenFill? true: false;
0060     saveSvgFill(shape->background(), fillRule, shape->outlineRect(), shape->size(), shape->absoluteTransformation(), context);
0061     saveSvgStroke(shape->stroke(), context);
0062 
0063     saveSvgEffects(shape, context);
0064     saveSvgClipping(shape, context);
0065     saveSvgMasking(shape, context);
0066     saveSvgMarkers(shape, context);
0067 }
0068 
0069 void SvgStyleWriter::saveSvgBasicStyle(const bool isVisible, const qreal transparency, const QVector<KoShape::PaintOrder> paintOrder, bool inheritPaintorder, SvgSavingContext &context, bool textShape)
0070 {
0071     if (!isVisible) {
0072         context.shapeWriter().addAttribute("display", "none");
0073     } else if (transparency > 0.0) {
0074         context.shapeWriter().addAttribute("opacity", 1.0 - transparency);
0075     }
0076     if (!paintOrder.isEmpty() && !inheritPaintorder) {
0077 
0078         bool notDefault = paintOrder.value(0, KoShape::Fill) != KoShape::Fill && paintOrder.value(1, KoShape::Stroke) != KoShape::Stroke;
0079 
0080         if ((!textShape && notDefault) || textShape) {
0081             QStringList order;
0082             Q_FOREACH(const KoShape::PaintOrder p, paintOrder) {
0083                 if (p == KoShape::Fill) {
0084                     order.append("fill");
0085                 } else if (p == KoShape::Stroke) {
0086                     order.append("stroke");
0087                 } else if (p == KoShape::Markers) {
0088                     order.append("markers");
0089                 }
0090             }
0091             context.shapeWriter().addAttribute("paint-order", order.join(" "));
0092         }
0093     }
0094 
0095 }
0096 
0097 void SvgStyleWriter::saveSvgFill(QSharedPointer<KoShapeBackground> background, const bool fillRuleEvenOdd, const QRectF outlineRect, const QSizeF size, const QTransform absoluteTransform, SvgSavingContext &context)
0098 {
0099     if (! background) {
0100         context.shapeWriter().addAttribute("fill", "none");
0101     }
0102 
0103     QBrush fill(Qt::NoBrush);
0104     QSharedPointer<KoColorBackground>  cbg = qSharedPointerDynamicCast<KoColorBackground>(background);
0105     if (cbg) {
0106         context.shapeWriter().addAttribute("fill", cbg->color().name());
0107         if (cbg->color().alphaF() < 1.0)
0108             context.shapeWriter().addAttribute("fill-opacity", cbg->color().alphaF());
0109     }
0110     QSharedPointer<KoGradientBackground>  gbg = qSharedPointerDynamicCast<KoGradientBackground>(background);
0111     if (gbg) {
0112         QString gradientId = saveSvgGradient(gbg->gradient(), gbg->transform(), context);
0113         context.shapeWriter().addAttribute("fill", "url(#" + gradientId + ")");
0114     }
0115     QSharedPointer<KoMeshGradientBackground> mgbg = qSharedPointerDynamicCast<KoMeshGradientBackground>(background);
0116     if (mgbg) {
0117         QString gradientId = saveSvgMeshGradient(mgbg->gradient(), mgbg->transform(), context);
0118         context.shapeWriter().addAttribute("fill", "url(#" + gradientId + ")");
0119     }
0120     QSharedPointer<KoPatternBackground>  pbg = qSharedPointerDynamicCast<KoPatternBackground>(background);
0121     if (pbg) {
0122         const QString patternId = saveSvgPattern(pbg, size, absoluteTransform, context);
0123         context.shapeWriter().addAttribute("fill", "url(#" + patternId + ")");
0124     }
0125     QSharedPointer<KoVectorPatternBackground>  vpbg = qSharedPointerDynamicCast<KoVectorPatternBackground>(background);
0126     if (vpbg) {
0127         const QString patternId = saveSvgVectorPattern(vpbg, outlineRect, context);
0128         context.shapeWriter().addAttribute("fill", "url(#" + patternId + ")");
0129     }
0130 
0131     // non-zero is default, so only write fillrule if evenodd is set
0132     if (fillRuleEvenOdd)
0133         context.shapeWriter().addAttribute("fill-rule", "evenodd");
0134 }
0135 
0136 void SvgStyleWriter::saveSvgStroke(KoShapeStrokeModelSP stroke, SvgSavingContext &context)
0137 {
0138     const QSharedPointer<KoShapeStroke> lineBorder = qSharedPointerDynamicCast<KoShapeStroke>(stroke);
0139 
0140     if (! lineBorder)
0141         return;
0142 
0143     QString strokeStr("none");
0144     if (lineBorder->lineBrush().gradient()) {
0145         QString gradientId = saveSvgGradient(lineBorder->lineBrush().gradient(), lineBorder->lineBrush().transform(), context);
0146         strokeStr = "url(#" + gradientId + ")";
0147     } else {
0148         if (lineBorder->color().isValid()) {
0149             strokeStr = lineBorder->color().name();
0150         }
0151         if (lineBorder->color().alphaF() < 1.0) {
0152             context.shapeWriter().addAttribute("stroke-opacity", lineBorder->color().alphaF());
0153         }
0154     }
0155     if (!strokeStr.isEmpty())
0156         context.shapeWriter().addAttribute("stroke", strokeStr);
0157 
0158     context.shapeWriter().addAttribute("stroke-width", SvgUtil::toUserSpace(lineBorder->lineWidth()));
0159 
0160     if (lineBorder->capStyle() == Qt::FlatCap)
0161         context.shapeWriter().addAttribute("stroke-linecap", "butt");
0162     else if (lineBorder->capStyle() == Qt::RoundCap)
0163         context.shapeWriter().addAttribute("stroke-linecap", "round");
0164     else if (lineBorder->capStyle() == Qt::SquareCap)
0165         context.shapeWriter().addAttribute("stroke-linecap", "square");
0166 
0167     if (lineBorder->joinStyle() == Qt::MiterJoin) {
0168         context.shapeWriter().addAttribute("stroke-linejoin", "miter");
0169         context.shapeWriter().addAttribute("stroke-miterlimit", lineBorder->miterLimit());
0170     } else if (lineBorder->joinStyle() == Qt::RoundJoin)
0171         context.shapeWriter().addAttribute("stroke-linejoin", "round");
0172     else if (lineBorder->joinStyle() == Qt::BevelJoin)
0173         context.shapeWriter().addAttribute("stroke-linejoin", "bevel");
0174 
0175     // dash
0176     if (lineBorder->lineStyle() > Qt::SolidLine) {
0177         qreal dashFactor = lineBorder->lineWidth();
0178 
0179         if (lineBorder->dashOffset() != 0)
0180             context.shapeWriter().addAttribute("stroke-dashoffset", dashFactor * lineBorder->dashOffset());
0181 
0182         QString dashStr;
0183         const QVector<qreal> dashes = lineBorder->lineDashes();
0184         int dashCount = dashes.size();
0185         for (int i = 0; i < dashCount; ++i) {
0186             if (i > 0)
0187                 dashStr += ",";
0188             dashStr += QString("%1").arg(KisDomUtils::toString(dashes[i] * dashFactor));
0189         }
0190         context.shapeWriter().addAttribute("stroke-dasharray", dashStr);
0191     }
0192 }
0193 
0194 void SvgStyleWriter::saveSvgEffects(KoShape *shape, SvgSavingContext &context)
0195 {
0196     KoFilterEffectStack * filterStack = shape->filterEffectStack();
0197     if (!filterStack)
0198         return;
0199 
0200     QList<KoFilterEffect*> filterEffects = filterStack->filterEffects();
0201     if (!filterEffects.count())
0202         return;
0203 
0204     const QString uid = context.createUID("filter");
0205 
0206     filterStack->save(context.styleWriter(), uid);
0207 
0208     context.shapeWriter().addAttribute("filter", "url(#" + uid + ")");
0209 }
0210 
0211 void embedShapes(const QList<KoShape*> &shapes, KoXmlWriter &outWriter)
0212 {
0213     QBuffer buffer;
0214     buffer.open(QIODevice::WriteOnly);
0215     {
0216         SvgWriter shapesWriter(shapes);
0217         shapesWriter.saveDetached(buffer);
0218     }
0219     buffer.close();
0220     outWriter.addCompleteElement(&buffer);
0221 }
0222 
0223 QString SvgStyleWriter::embedShape(const KoShape *shape, SvgSavingContext &context)
0224 {
0225     QList<KoShape *> shapes;
0226     KoShape* clonedShape = shape->cloneShape();
0227     if (!clonedShape) {
0228         return QString();
0229     }
0230     const QString uid = context.createUID("path");
0231     clonedShape->setName(uid);
0232     shapes.append(clonedShape);
0233     embedShapes(shapes, context.styleWriter());
0234     return uid;
0235 }
0236 
0237 void SvgStyleWriter::saveSvgClipping(KoShape *shape, SvgSavingContext &context)
0238 {
0239     KoClipPath *clipPath = shape->clipPath();
0240     if (!clipPath)
0241         return;
0242 
0243     const QString uid = context.createUID("clippath");
0244 
0245     context.styleWriter().startElement("clipPath");
0246     context.styleWriter().addAttribute("id", uid);
0247     context.styleWriter().addAttribute("clipPathUnits", KoFlake::coordinateToString(clipPath->coordinates()));
0248 
0249     embedShapes(clipPath->clipShapes(), context.styleWriter());
0250 
0251     context.styleWriter().endElement(); // clipPath
0252 
0253     context.shapeWriter().addAttribute("clip-path", "url(#" + uid + ")");
0254     if (clipPath->clipRule() != Qt::WindingFill)
0255         context.shapeWriter().addAttribute("clip-rule", "evenodd");
0256 }
0257 
0258 void SvgStyleWriter::saveSvgMasking(KoShape *shape, SvgSavingContext &context)
0259 {
0260     KoClipMask*clipMask = shape->clipMask();
0261     if (!clipMask)
0262         return;
0263 
0264     const QString uid = context.createUID("clipmask");
0265 
0266     context.styleWriter().startElement("mask");
0267     context.styleWriter().addAttribute("id", uid);
0268     context.styleWriter().addAttribute("maskUnits", KoFlake::coordinateToString(clipMask->coordinates()));
0269     context.styleWriter().addAttribute("maskContentUnits", KoFlake::coordinateToString(clipMask->contentCoordinates()));
0270 
0271     const QRectF rect = clipMask->maskRect();
0272 
0273     context.styleWriter().addAttribute("x", rect.x());
0274     context.styleWriter().addAttribute("y", rect.y());
0275     context.styleWriter().addAttribute("width", rect.width());
0276     context.styleWriter().addAttribute("height", rect.height());
0277 
0278     embedShapes(clipMask->shapes(), context.styleWriter());
0279 
0280     context.styleWriter().endElement(); // clipMask
0281 
0282     context.shapeWriter().addAttribute("mask", "url(#" + uid + ")");
0283 }
0284 
0285 namespace {
0286 void writeMarkerStyle(KoXmlWriter &styleWriter, const KoMarker *marker, const QString &assignedId) {
0287 
0288     styleWriter.startElement("marker");
0289     styleWriter.addAttribute("id", assignedId);
0290     styleWriter.addAttribute("markerUnits", KoMarker::coordinateSystemToString(marker->coordinateSystem()));
0291 
0292     const QPointF refPoint = marker->referencePoint();
0293     styleWriter.addAttribute("refX", refPoint.x());
0294     styleWriter.addAttribute("refY", refPoint.y());
0295 
0296     const QSizeF refSize = marker->referenceSize();
0297     styleWriter.addAttribute("markerWidth", refSize.width());
0298     styleWriter.addAttribute("markerHeight", refSize.height());
0299 
0300 
0301     if (marker->hasAutoOrientation()) {
0302         styleWriter.addAttribute("orient", "auto");
0303     } else {
0304         // no suffix means 'degrees'
0305         styleWriter.addAttribute("orient", kisRadiansToDegrees(marker->explicitOrientation()));
0306     }
0307 
0308     embedShapes(marker->shapes(), styleWriter);
0309 
0310     styleWriter.endElement(); // marker
0311 }
0312 
0313 void tryEmbedMarker(const KoPathShape *pathShape,
0314                     const QString &markerTag,
0315                     KoFlake::MarkerPosition markerPosition,
0316                     SvgSavingContext &context)
0317 {
0318     KoMarker *marker = pathShape->marker(markerPosition);
0319 
0320     if (marker) {
0321         const QString uid = context.createUID("lineMarker");
0322         writeMarkerStyle(context.styleWriter(), marker, uid);
0323         context.shapeWriter().addAttribute(markerTag.toLatin1().data(), "url(#" + uid + ")");
0324     }
0325 }
0326 
0327 }
0328 
0329 void SvgStyleWriter::saveSvgMarkers(KoShape *shape, SvgSavingContext &context)
0330 {
0331     KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
0332     if (!pathShape || !pathShape->hasMarkers()) return;
0333 
0334 
0335     tryEmbedMarker(pathShape, "marker-start", KoFlake::StartMarker, context);
0336     tryEmbedMarker(pathShape, "marker-mid", KoFlake::MidMarker, context);
0337     tryEmbedMarker(pathShape, "marker-end", KoFlake::EndMarker, context);
0338 
0339     if (pathShape->autoFillMarkers()) {
0340         context.shapeWriter().addAttribute("krita:marker-fill-method", "auto");
0341     }
0342 }
0343 
0344 void SvgStyleWriter::saveSvgColorStops(const QGradientStops &colorStops, SvgSavingContext &context)
0345 {
0346     Q_FOREACH (const QGradientStop &stop, colorStops) {
0347         context.styleWriter().startElement("stop");
0348         context.styleWriter().addAttribute("stop-color", stop.second.name());
0349         context.styleWriter().addAttribute("offset", stop.first);
0350         context.styleWriter().addAttribute("stop-opacity", stop.second.alphaF());
0351         context.styleWriter().endElement();
0352     }
0353 }
0354 
0355 inline QString convertGradientMode(QGradient::CoordinateMode mode) {
0356     KIS_ASSERT_RECOVER_NOOP(mode != QGradient::StretchToDeviceMode);
0357 
0358     return
0359         mode == QGradient::ObjectBoundingMode ?
0360         "objectBoundingBox" :
0361         "userSpaceOnUse";
0362 
0363 }
0364 
0365 QString SvgStyleWriter::saveSvgGradient(const QGradient *gradient, const QTransform &gradientTransform, SvgSavingContext &context)
0366 {
0367     if (! gradient)
0368         return QString();
0369 
0370     const QString spreadMethod[3] = {
0371         QString("pad"),
0372         QString("reflect"),
0373         QString("repeat")
0374     };
0375 
0376     const QString uid = context.createUID("gradient");
0377 
0378     if (gradient->type() == QGradient::LinearGradient) {
0379         const QLinearGradient * g = static_cast<const QLinearGradient*>(gradient);
0380         context.styleWriter().startElement("linearGradient");
0381         context.styleWriter().addAttribute("id", uid);
0382         SvgUtil::writeTransformAttributeLazy("gradientTransform", gradientTransform, context.styleWriter());
0383         context.styleWriter().addAttribute("gradientUnits", convertGradientMode(g->coordinateMode()));
0384         context.styleWriter().addAttribute("x1", g->start().x());
0385         context.styleWriter().addAttribute("y1", g->start().y());
0386         context.styleWriter().addAttribute("x2", g->finalStop().x());
0387         context.styleWriter().addAttribute("y2", g->finalStop().y());
0388         context.styleWriter().addAttribute("spreadMethod", spreadMethod[g->spread()]);
0389         // color stops
0390         saveSvgColorStops(gradient->stops(), context);
0391         context.styleWriter().endElement();
0392     } else if (gradient->type() == QGradient::RadialGradient) {
0393         const QRadialGradient * g = static_cast<const QRadialGradient*>(gradient);
0394         context.styleWriter().startElement("radialGradient");
0395         context.styleWriter().addAttribute("id", uid);
0396         SvgUtil::writeTransformAttributeLazy("gradientTransform", gradientTransform, context.styleWriter());
0397         context.styleWriter().addAttribute("gradientUnits", convertGradientMode(g->coordinateMode()));
0398         context.styleWriter().addAttribute("cx", g->center().x());
0399         context.styleWriter().addAttribute("cy", g->center().y());
0400         context.styleWriter().addAttribute("fx", g->focalPoint().x());
0401         context.styleWriter().addAttribute("fy", g->focalPoint().y());
0402         context.styleWriter().addAttribute("r", g->radius());
0403         context.styleWriter().addAttribute("spreadMethod", spreadMethod[g->spread()]);
0404         // color stops
0405         saveSvgColorStops(gradient->stops(), context);
0406         context.styleWriter().endElement();
0407     } else if (gradient->type() == QGradient::ConicalGradient) {
0408         //const QConicalGradient * g = static_cast<const QConicalGradient*>( gradient );
0409         // fake conical grad as radial.
0410         // fugly but better than data loss.
0411         /*
0412         printIndentation( m_defs, m_indent2 );
0413         *m_defs << "<radialGradient id=\"" << uid << "\" ";
0414         *m_defs << "gradientUnits=\"userSpaceOnUse\" ";
0415         *m_defs << "cx=\"" << g->center().x() << "\" ";
0416         *m_defs << "cy=\"" << g->center().y() << "\" ";
0417         *m_defs << "fx=\"" << grad.focalPoint().x() << "\" ";
0418         *m_defs << "fy=\"" << grad.focalPoint().y() << "\" ";
0419         double r = sqrt( pow( grad.vector().x() - grad.origin().x(), 2 ) + pow( grad.vector().y() - grad.origin().y(), 2 ) );
0420         *m_defs << "r=\"" << QString().setNum( r ) << "\" ";
0421         *m_defs << spreadMethod[g->spread()];
0422         *m_defs << ">" << endl;
0423 
0424         // color stops
0425         getColorStops( gradient->stops() );
0426 
0427         printIndentation( m_defs, m_indent2 );
0428         *m_defs << "</radialGradient>" << endl;
0429         *m_body << "url(#" << uid << ")";
0430         */
0431     }
0432 
0433     return uid;
0434 }
0435 
0436 QString SvgStyleWriter::saveSvgMeshGradient(SvgMeshGradient *gradient,
0437                                             const QTransform& transform,
0438                                             SvgSavingContext &context)
0439 {
0440     if (!gradient || !gradient->isValid())
0441         return QString();
0442 
0443     const QString uid = context.createUID("meshgradient");
0444     context.styleWriter().startElement("meshgradient");
0445     context.styleWriter().addAttribute("id", uid);
0446 
0447     if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) {
0448         context.styleWriter().addAttribute("gradientUnits", "objectBoundingBox");
0449     } else {
0450         context.styleWriter().addAttribute("gradientUnits", "userSpaceOnUse");
0451     }
0452 
0453     SvgUtil::writeTransformAttributeLazy("transform", transform, context.styleWriter());
0454 
0455     SvgMeshArray *mesharray = gradient->getMeshArray().data();
0456     QPointF start = mesharray->getPatch(0, 0)->getStop(SvgMeshPatch::Top).point;
0457 
0458     context.styleWriter().addAttribute("x", start.x());
0459     context.styleWriter().addAttribute("y", start.y());
0460 
0461     if (gradient->type() == SvgMeshGradient::BILINEAR) {
0462         context.styleWriter().addAttribute("type", "bilinear");
0463     } else {
0464         context.styleWriter().addAttribute("type", "bicubic");
0465     }
0466 
0467     for (int row = 0; row < mesharray->numRows(); ++row) {
0468 
0469         const QString uid = context.createUID("meshrow");
0470         context.styleWriter().startElement("meshrow");
0471         context.styleWriter().addAttribute("id", uid);
0472 
0473         for (int col = 0; col < mesharray->numColumns(); ++col) {
0474 
0475             const QString uid = context.createUID("meshpatch");
0476             context.styleWriter().startElement("meshpatch");
0477             context.styleWriter().addAttribute("id", uid);
0478 
0479             SvgMeshPatch *patch = mesharray->getPatch(row, col);
0480 
0481             for (int s = 0; s < 4; ++s) {
0482                 SvgMeshPatch::Type type = static_cast<SvgMeshPatch::Type> (s);
0483 
0484                 // only first row and first col have Top and Left stop, respectively
0485                 if ((row != 0 && s == SvgMeshPatch::Top) ||
0486                     (col != 0 && s == SvgMeshPatch::Left)) {
0487                     continue;
0488                 }
0489 
0490                 context.styleWriter().startElement("stop");
0491 
0492                 std::array<QPointF, 4> segment = patch->getSegment(type);
0493 
0494                 QString pathstr;
0495                 QTextStream stream(&pathstr);
0496                 stream.setCodec("UTF-8");
0497 
0498                 stream.setRealNumberPrecision(10);
0499                 // TODO: other path type?
0500                 stream << "C "
0501                        << segment[1].x() << "," << segment[1].y() << " "
0502                        << segment[2].x() << "," << segment[2].y() << " "
0503                        << segment[3].x() << "," << segment[3].y(); // I don't see any harm, inkscape does this too
0504 
0505                 context.styleWriter().addAttribute("path", pathstr);
0506 
0507                 // don't add color/opacity if stop is in first row and stop == Top (or)
0508                 // don't add color/opacity if stop is not in first row and stop == Right
0509                 if ((row != 0 || col == 0 || s != SvgMeshPatch::Top) &&
0510                     (row == 0 || s != SvgMeshPatch::Right)) {
0511 
0512                     SvgMeshStop stop = patch->getStop(type);
0513                     context.styleWriter().addAttribute("stop-color", stop.color.name());
0514                     context.styleWriter().addAttribute("stop-opacity", stop.color.alphaF());
0515                 }
0516 
0517                 context.styleWriter().endElement(); // stop
0518             }
0519 
0520             context.styleWriter().endElement();  // meshpatch
0521         }
0522         context.styleWriter().endElement(); // meshrow
0523     }
0524     context.styleWriter().endElement(); // meshgradient
0525 
0526     return uid;
0527 }
0528 
0529 QString SvgStyleWriter::saveSvgPattern(QSharedPointer<KoPatternBackground> pattern, const QSizeF shapeSize, const QTransform absoluteTransform, SvgSavingContext &context)
0530 {
0531     const QString uid = context.createUID("pattern");
0532 
0533     const QSizeF patternSize = pattern->patternDisplaySize();
0534     const QSize imageSize = pattern->pattern().size();
0535 
0536     // calculate offset in point
0537     QPointF offset = pattern->referencePointOffset();
0538     offset.rx() = 0.01 * offset.x() * patternSize.width();
0539     offset.ry() = 0.01 * offset.y() * patternSize.height();
0540 
0541     // now take the reference point into account
0542     switch (pattern->referencePoint()) {
0543     case KoPatternBackground::TopLeft:
0544         break;
0545     case KoPatternBackground::Top:
0546         offset += QPointF(0.5 * shapeSize.width(), 0.0);
0547         break;
0548     case KoPatternBackground::TopRight:
0549         offset += QPointF(shapeSize.width(), 0.0);
0550         break;
0551     case KoPatternBackground::Left:
0552         offset += QPointF(0.0, 0.5 * shapeSize.height());
0553         break;
0554     case KoPatternBackground::Center:
0555         offset += QPointF(0.5 * shapeSize.width(), 0.5 * shapeSize.height());
0556         break;
0557     case KoPatternBackground::Right:
0558         offset += QPointF(shapeSize.width(), 0.5 * shapeSize.height());
0559         break;
0560     case KoPatternBackground::BottomLeft:
0561         offset += QPointF(0.0, shapeSize.height());
0562         break;
0563     case KoPatternBackground::Bottom:
0564         offset += QPointF(0.5 * shapeSize.width(), shapeSize.height());
0565         break;
0566     case KoPatternBackground::BottomRight:
0567         offset += QPointF(shapeSize.width(), shapeSize.height());
0568         break;
0569     }
0570 
0571     offset = absoluteTransform.map(offset);
0572 
0573     context.styleWriter().startElement("pattern");
0574     context.styleWriter().addAttribute("id", uid);
0575     context.styleWriter().addAttribute("x", SvgUtil::toUserSpace(offset.x()));
0576     context.styleWriter().addAttribute("y", SvgUtil::toUserSpace(offset.y()));
0577 
0578     if (pattern->repeat() == KoPatternBackground::Stretched) {
0579         context.styleWriter().addAttribute("width", "100%");
0580         context.styleWriter().addAttribute("height", "100%");
0581         context.styleWriter().addAttribute("patternUnits", "objectBoundingBox");
0582     } else {
0583         context.styleWriter().addAttribute("width", SvgUtil::toUserSpace(patternSize.width()));
0584         context.styleWriter().addAttribute("height", SvgUtil::toUserSpace(patternSize.height()));
0585         context.styleWriter().addAttribute("patternUnits", "userSpaceOnUse");
0586     }
0587 
0588     context.styleWriter().addAttribute("viewBox", QString("0 0 %1 %2").arg(KisDomUtils::toString(imageSize.width())).arg(KisDomUtils::toString(imageSize.height())));
0589     //*m_defs << " patternContentUnits=\"userSpaceOnUse\"";
0590 
0591     context.styleWriter().startElement("image");
0592     context.styleWriter().addAttribute("x", "0");
0593     context.styleWriter().addAttribute("y", "0");
0594     context.styleWriter().addAttribute("width", QString("%1px").arg(KisDomUtils::toString(imageSize.width())));
0595     context.styleWriter().addAttribute("height", QString("%1px").arg(KisDomUtils::toString(imageSize.height())));
0596 
0597     QBuffer buffer;
0598     buffer.open(QIODevice::WriteOnly);
0599     if (pattern->pattern().save(&buffer, "PNG")) {
0600         const QString mimeType = KisMimeDatabase::mimeTypeForSuffix("*.png");
0601         context.styleWriter().addAttribute("xlink:href", "data:"+ mimeType + ";base64," + buffer.data().toBase64());
0602     }
0603 
0604     context.styleWriter().endElement(); // image
0605     context.styleWriter().endElement(); // pattern
0606 
0607     return uid;
0608 }
0609 
0610 QString SvgStyleWriter::saveSvgVectorPattern(QSharedPointer<KoVectorPatternBackground> pattern, const QRectF outlineRect, SvgSavingContext &context)
0611 {
0612     const QString uid = context.createUID("pattern");
0613 
0614     context.styleWriter().startElement("pattern");
0615     context.styleWriter().addAttribute("id", uid);
0616 
0617     context.styleWriter().addAttribute("patternUnits", KoFlake::coordinateToString(pattern->referenceCoordinates()));
0618     context.styleWriter().addAttribute("patternContentUnits", KoFlake::coordinateToString(pattern->contentCoordinates()));
0619 
0620     const QRectF rect = pattern->referenceRect();
0621 
0622     context.styleWriter().addAttribute("x", rect.x());
0623     context.styleWriter().addAttribute("y", rect.y());
0624     context.styleWriter().addAttribute("width", rect.width());
0625     context.styleWriter().addAttribute("height", rect.height());
0626 
0627     SvgUtil::writeTransformAttributeLazy("patternTransform", pattern->patternTransform(), context.styleWriter());
0628 
0629     if (pattern->contentCoordinates() == KoFlake::ObjectBoundingBox) {
0630         // TODO: move this normalization into the KoVectorPatternBackground itself
0631 
0632         QList<KoShape*> shapes = pattern->shapes();
0633         QList<KoShape*> clonedShapes;
0634 
0635         const QTransform relativeToShape = KisAlgebra2D::mapToRect(outlineRect);
0636         const QTransform shapeToRelative = relativeToShape.inverted();
0637 
0638         Q_FOREACH (KoShape *shape, shapes) {
0639             KoShape *clone = shape->cloneShape();
0640             clone->applyAbsoluteTransformation(shapeToRelative);
0641             clonedShapes.append(clone);
0642         }
0643 
0644         embedShapes(clonedShapes, context.styleWriter());
0645         qDeleteAll(clonedShapes);
0646 
0647     } else {
0648         QList<KoShape*> shapes = pattern->shapes();
0649         embedShapes(shapes, context.styleWriter());
0650     }
0651 
0652     context.styleWriter().endElement(); // pattern
0653 
0654     return uid;
0655 }