1 /* This file is part of the KDE project
2    Copyright (C) 2002 Lars Siebold <khandha5@gmx.net>
3    Copyright (C) 2002-2003,2005 Rob Buis <buis@kde.org>
4    Copyright (C) 2002,2005-2006 David Faure <faure@kde.org>
5    Copyright (C) 2002 Werner Trobin <trobin@kde.org>
6    Copyright (C) 2002 Lennart Kudling <kudling@kde.org>
7    Copyright (C) 2004 Nicolas Goutte <nicolasg@snafu.de>
8    Copyright (C) 2005 Boudewijn Rempt <boud@valdyas.org>
9    Copyright (C) 2005 Raphael Langerhorst <raphael.langerhorst@kdemail.net>
10    Copyright (C) 2005 Thomas Zander <zander@kde.org>
11    Copyright (C) 2005,2007-2008 Jan Hambrecht <jaham@gmx.net>
12    Copyright (C) 2006 Inge Wallin <inge@lysator.liu.se>
13    Copyright (C) 2006 Martin Pfeiffer <hubipete@gmx.net>
14    Copyright (C) 2006 Gábor Lehel <illissius@gmail.com>
15    Copyright (C) 2006 Laurent Montel <montel@kde.org>
16    Copyright (C) 2006 Christian Mueller <cmueller@gmx.de>
17    Copyright (C) 2006 Ariya Hidayat <ariya@kde.org>
18    Copyright (C) 2010 Thorsten Zachmann <zachmann@kde.org>
19 
20    This library is free software; you can redistribute it and/or
21    modify it under the terms of the GNU Library General Public
22    License as published by the Free Software Foundation; either
23    version 2 of the License, or (at your option) any later version.
24 
25    This library is distributed in the hope that it will be useful,
26    but WITHOUT ANY WARRANTY; without even the implied warranty of
27    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
28    Library General Public License for more details.
29 
30    You should have received a copy of the GNU Library General Public License
31    along with this library; see the file COPYING.LIB.  If not, write to
32    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
33  * Boston, MA 02110-1301, USA.
34 */
35 
36 #include "SvgStyleWriter.h"
37 #include "SvgSavingContext.h"
38 #include "SvgUtil.h"
39 
40 #include <KoShape.h>
41 #include <KoPathShape.h>
42 #include <KoPathSegment.h>
43 #include <KoFilterEffect.h>
44 #include <KoFilterEffectStack.h>
45 #include <KoColorBackground.h>
46 #include <KoGradientBackground.h>
47 #include <KoMeshGradientBackground.h>
48 #include <KoPatternBackground.h>
49 #include <KoVectorPatternBackground.h>
50 #include <KoShapeStroke.h>
51 #include <KoClipPath.h>
52 #include <KoClipMask.h>
53 #include <KoMarker.h>
54 #include <KoXmlWriter.h>
55 
56 #include <QBuffer>
57 #include <QGradient>
58 #include <QLinearGradient>
59 #include <QRadialGradient>
60 #include <KisMimeDatabase.h>
61 #include "kis_dom_utils.h"
62 #include "kis_algebra_2d.h"
63 #include <SvgWriter.h>
64 #include <KoFlakeCoordinateSystem.h>
65 
66 
saveSvgStyle(KoShape * shape,SvgSavingContext & context)67 void SvgStyleWriter::saveSvgStyle(KoShape *shape, SvgSavingContext &context)
68 {
69     saveSvgBasicStyle(shape, context);
70 
71     saveSvgFill(shape, context);
72     saveSvgStroke(shape, context);
73 
74     saveSvgEffects(shape, context);
75     saveSvgClipping(shape, context);
76     saveSvgMasking(shape, context);
77     saveSvgMarkers(shape, context);
78 }
79 
saveSvgBasicStyle(KoShape * shape,SvgSavingContext & context)80 void SvgStyleWriter::saveSvgBasicStyle(KoShape *shape, SvgSavingContext &context)
81 {
82     if (!shape->isVisible(false)) {
83         context.shapeWriter().addAttribute("display", "none");
84     } else if (shape->transparency() > 0.0) {
85         context.shapeWriter().addAttribute("opacity", 1.0 - shape->transparency());
86     }
87 }
88 
saveSvgFill(KoShape * shape,SvgSavingContext & context)89 void SvgStyleWriter::saveSvgFill(KoShape *shape, SvgSavingContext &context)
90 {
91     if (! shape->background()) {
92         context.shapeWriter().addAttribute("fill", "none");
93     }
94 
95     QBrush fill(Qt::NoBrush);
96     QSharedPointer<KoColorBackground>  cbg = qSharedPointerDynamicCast<KoColorBackground>(shape->background());
97     if (cbg) {
98         context.shapeWriter().addAttribute("fill", cbg->color().name());
99         if (cbg->color().alphaF() < 1.0)
100             context.shapeWriter().addAttribute("fill-opacity", cbg->color().alphaF());
101     }
102     QSharedPointer<KoGradientBackground>  gbg = qSharedPointerDynamicCast<KoGradientBackground>(shape->background());
103     if (gbg) {
104         QString gradientId = saveSvgGradient(gbg->gradient(), gbg->transform(), context);
105         context.shapeWriter().addAttribute("fill", "url(#" + gradientId + ")");
106     }
107     QSharedPointer<KoMeshGradientBackground> mgbg = qSharedPointerDynamicCast<KoMeshGradientBackground>(shape->background());
108     if (mgbg) {
109         QString gradientId = saveSvgMeshGradient(mgbg->gradient(), mgbg->transform(), context);
110         context.shapeWriter().addAttribute("fill", "url(#" + gradientId + ")");
111     }
112     QSharedPointer<KoPatternBackground>  pbg = qSharedPointerDynamicCast<KoPatternBackground>(shape->background());
113     if (pbg) {
114         const QString patternId = saveSvgPattern(pbg, shape, context);
115         context.shapeWriter().addAttribute("fill", "url(#" + patternId + ")");
116     }
117     QSharedPointer<KoVectorPatternBackground>  vpbg = qSharedPointerDynamicCast<KoVectorPatternBackground>(shape->background());
118     if (vpbg) {
119         const QString patternId = saveSvgVectorPattern(vpbg, shape, context);
120         context.shapeWriter().addAttribute("fill", "url(#" + patternId + ")");
121     }
122 
123     KoPathShape * path = dynamic_cast<KoPathShape*>(shape);
124     if (path && shape->background()) {
125         // non-zero is default, so only write fillrule if evenodd is set
126         if (path->fillRule() == Qt::OddEvenFill)
127             context.shapeWriter().addAttribute("fill-rule", "evenodd");
128     }
129 }
130 
saveSvgStroke(KoShape * shape,SvgSavingContext & context)131 void SvgStyleWriter::saveSvgStroke(KoShape *shape, SvgSavingContext &context)
132 {
133     const QSharedPointer<KoShapeStroke> lineBorder = qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke());
134 
135     if (! lineBorder)
136         return;
137 
138     QString strokeStr("none");
139     if (lineBorder->lineBrush().gradient()) {
140         QString gradientId = saveSvgGradient(lineBorder->lineBrush().gradient(), lineBorder->lineBrush().transform(), context);
141         strokeStr = "url(#" + gradientId + ")";
142     } else {
143         strokeStr = lineBorder->color().name();
144     }
145     if (!strokeStr.isEmpty())
146         context.shapeWriter().addAttribute("stroke", strokeStr);
147 
148     if (lineBorder->color().alphaF() < 1.0)
149         context.shapeWriter().addAttribute("stroke-opacity", lineBorder->color().alphaF());
150     context.shapeWriter().addAttribute("stroke-width", SvgUtil::toUserSpace(lineBorder->lineWidth()));
151 
152     if (lineBorder->capStyle() == Qt::FlatCap)
153         context.shapeWriter().addAttribute("stroke-linecap", "butt");
154     else if (lineBorder->capStyle() == Qt::RoundCap)
155         context.shapeWriter().addAttribute("stroke-linecap", "round");
156     else if (lineBorder->capStyle() == Qt::SquareCap)
157         context.shapeWriter().addAttribute("stroke-linecap", "square");
158 
159     if (lineBorder->joinStyle() == Qt::MiterJoin) {
160         context.shapeWriter().addAttribute("stroke-linejoin", "miter");
161         context.shapeWriter().addAttribute("stroke-miterlimit", lineBorder->miterLimit());
162     } else if (lineBorder->joinStyle() == Qt::RoundJoin)
163         context.shapeWriter().addAttribute("stroke-linejoin", "round");
164     else if (lineBorder->joinStyle() == Qt::BevelJoin)
165         context.shapeWriter().addAttribute("stroke-linejoin", "bevel");
166 
167     // dash
168     if (lineBorder->lineStyle() > Qt::SolidLine) {
169         qreal dashFactor = lineBorder->lineWidth();
170 
171         if (lineBorder->dashOffset() != 0)
172             context.shapeWriter().addAttribute("stroke-dashoffset", dashFactor * lineBorder->dashOffset());
173 
174         QString dashStr;
175         const QVector<qreal> dashes = lineBorder->lineDashes();
176         int dashCount = dashes.size();
177         for (int i = 0; i < dashCount; ++i) {
178             if (i > 0)
179                 dashStr += ",";
180             dashStr += QString("%1").arg(KisDomUtils::toString(dashes[i] * dashFactor));
181         }
182         context.shapeWriter().addAttribute("stroke-dasharray", dashStr);
183     }
184 }
185 
saveSvgEffects(KoShape * shape,SvgSavingContext & context)186 void SvgStyleWriter::saveSvgEffects(KoShape *shape, SvgSavingContext &context)
187 {
188     KoFilterEffectStack * filterStack = shape->filterEffectStack();
189     if (!filterStack)
190         return;
191 
192     QList<KoFilterEffect*> filterEffects = filterStack->filterEffects();
193     if (!filterEffects.count())
194         return;
195 
196     const QString uid = context.createUID("filter");
197 
198     filterStack->save(context.styleWriter(), uid);
199 
200     context.shapeWriter().addAttribute("filter", "url(#" + uid + ")");
201 }
202 
embedShapes(const QList<KoShape * > & shapes,KoXmlWriter & outWriter)203 void embedShapes(const QList<KoShape*> &shapes, KoXmlWriter &outWriter)
204 {
205     QBuffer buffer;
206     buffer.open(QIODevice::WriteOnly);
207     {
208         SvgWriter shapesWriter(shapes);
209         shapesWriter.saveDetached(buffer);
210     }
211     buffer.close();
212     outWriter.addCompleteElement(&buffer);
213 }
214 
215 
saveSvgClipping(KoShape * shape,SvgSavingContext & context)216 void SvgStyleWriter::saveSvgClipping(KoShape *shape, SvgSavingContext &context)
217 {
218     KoClipPath *clipPath = shape->clipPath();
219     if (!clipPath)
220         return;
221 
222     const QString uid = context.createUID("clippath");
223 
224     context.styleWriter().startElement("clipPath");
225     context.styleWriter().addAttribute("id", uid);
226     context.styleWriter().addAttribute("clipPathUnits", KoFlake::coordinateToString(clipPath->coordinates()));
227 
228     embedShapes(clipPath->clipShapes(), context.styleWriter());
229 
230     context.styleWriter().endElement(); // clipPath
231 
232     context.shapeWriter().addAttribute("clip-path", "url(#" + uid + ")");
233     if (clipPath->clipRule() != Qt::WindingFill)
234         context.shapeWriter().addAttribute("clip-rule", "evenodd");
235 }
236 
saveSvgMasking(KoShape * shape,SvgSavingContext & context)237 void SvgStyleWriter::saveSvgMasking(KoShape *shape, SvgSavingContext &context)
238 {
239     KoClipMask*clipMask = shape->clipMask();
240     if (!clipMask)
241         return;
242 
243     const QString uid = context.createUID("clipmask");
244 
245     context.styleWriter().startElement("mask");
246     context.styleWriter().addAttribute("id", uid);
247     context.styleWriter().addAttribute("maskUnits", KoFlake::coordinateToString(clipMask->coordinates()));
248     context.styleWriter().addAttribute("maskContentUnits", KoFlake::coordinateToString(clipMask->contentCoordinates()));
249 
250     const QRectF rect = clipMask->maskRect();
251 
252     context.styleWriter().addAttribute("x", rect.x());
253     context.styleWriter().addAttribute("y", rect.y());
254     context.styleWriter().addAttribute("width", rect.width());
255     context.styleWriter().addAttribute("height", rect.height());
256 
257     embedShapes(clipMask->shapes(), context.styleWriter());
258 
259     context.styleWriter().endElement(); // clipMask
260 
261     context.shapeWriter().addAttribute("mask", "url(#" + uid + ")");
262 }
263 
264 namespace {
writeMarkerStyle(KoXmlWriter & styleWriter,const KoMarker * marker,const QString & assignedId)265 void writeMarkerStyle(KoXmlWriter &styleWriter, const KoMarker *marker, const QString &assignedId) {
266 
267     styleWriter.startElement("marker");
268     styleWriter.addAttribute("id", assignedId);
269     styleWriter.addAttribute("markerUnits", KoMarker::coordinateSystemToString(marker->coordinateSystem()));
270 
271     const QPointF refPoint = marker->referencePoint();
272     styleWriter.addAttribute("refX", refPoint.x());
273     styleWriter.addAttribute("refY", refPoint.y());
274 
275     const QSizeF refSize = marker->referenceSize();
276     styleWriter.addAttribute("markerWidth", refSize.width());
277     styleWriter.addAttribute("markerHeight", refSize.height());
278 
279 
280     if (marker->hasAutoOtientation()) {
281         styleWriter.addAttribute("orient", "auto");
282     } else {
283         // no suffix means 'degrees'
284         styleWriter.addAttribute("orient", kisRadiansToDegrees(marker->explicitOrientation()));
285     }
286 
287     embedShapes(marker->shapes(), styleWriter);
288 
289     styleWriter.endElement(); // marker
290 }
291 
tryEmbedMarker(const KoPathShape * pathShape,const QString & markerTag,KoFlake::MarkerPosition markerPosition,SvgSavingContext & context)292 void tryEmbedMarker(const KoPathShape *pathShape,
293                     const QString &markerTag,
294                     KoFlake::MarkerPosition markerPosition,
295                     SvgSavingContext &context)
296 {
297     KoMarker *marker = pathShape->marker(markerPosition);
298 
299     if (marker) {
300         const QString uid = context.createUID("lineMarker");
301         writeMarkerStyle(context.styleWriter(), marker, uid);
302         context.shapeWriter().addAttribute(markerTag.toLatin1().data(), "url(#" + uid + ")");
303     }
304 }
305 
306 }
307 
saveSvgMarkers(KoShape * shape,SvgSavingContext & context)308 void SvgStyleWriter::saveSvgMarkers(KoShape *shape, SvgSavingContext &context)
309 {
310     KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
311     if (!pathShape || !pathShape->hasMarkers()) return;
312 
313 
314     tryEmbedMarker(pathShape, "marker-start", KoFlake::StartMarker, context);
315     tryEmbedMarker(pathShape, "marker-mid", KoFlake::MidMarker, context);
316     tryEmbedMarker(pathShape, "marker-end", KoFlake::EndMarker, context);
317 
318     if (pathShape->autoFillMarkers()) {
319         context.shapeWriter().addAttribute("krita:marker-fill-method", "auto");
320     }
321 }
322 
saveSvgColorStops(const QGradientStops & colorStops,SvgSavingContext & context)323 void SvgStyleWriter::saveSvgColorStops(const QGradientStops &colorStops, SvgSavingContext &context)
324 {
325     Q_FOREACH (const QGradientStop &stop, colorStops) {
326         context.styleWriter().startElement("stop");
327         context.styleWriter().addAttribute("stop-color", stop.second.name());
328         context.styleWriter().addAttribute("offset", stop.first);
329         context.styleWriter().addAttribute("stop-opacity", stop.second.alphaF());
330         context.styleWriter().endElement();
331     }
332 }
333 
convertGradientMode(QGradient::CoordinateMode mode)334 inline QString convertGradientMode(QGradient::CoordinateMode mode) {
335     KIS_ASSERT_RECOVER_NOOP(mode != QGradient::StretchToDeviceMode);
336 
337     return
338         mode == QGradient::ObjectBoundingMode ?
339         "objectBoundingBox" :
340         "userSpaceOnUse";
341 
342 }
343 
saveSvgGradient(const QGradient * gradient,const QTransform & gradientTransform,SvgSavingContext & context)344 QString SvgStyleWriter::saveSvgGradient(const QGradient *gradient, const QTransform &gradientTransform, SvgSavingContext &context)
345 {
346     if (! gradient)
347         return QString();
348 
349     const QString spreadMethod[3] = {
350         QString("pad"),
351         QString("reflect"),
352         QString("repeat")
353     };
354 
355     const QString uid = context.createUID("gradient");
356 
357     if (gradient->type() == QGradient::LinearGradient) {
358         const QLinearGradient * g = static_cast<const QLinearGradient*>(gradient);
359         context.styleWriter().startElement("linearGradient");
360         context.styleWriter().addAttribute("id", uid);
361         SvgUtil::writeTransformAttributeLazy("gradientTransform", gradientTransform, context.styleWriter());
362         context.styleWriter().addAttribute("gradientUnits", convertGradientMode(g->coordinateMode()));
363         context.styleWriter().addAttribute("x1", g->start().x());
364         context.styleWriter().addAttribute("y1", g->start().y());
365         context.styleWriter().addAttribute("x2", g->finalStop().x());
366         context.styleWriter().addAttribute("y2", g->finalStop().y());
367         context.styleWriter().addAttribute("spreadMethod", spreadMethod[g->spread()]);
368         // color stops
369         saveSvgColorStops(gradient->stops(), context);
370         context.styleWriter().endElement();
371     } else if (gradient->type() == QGradient::RadialGradient) {
372         const QRadialGradient * g = static_cast<const QRadialGradient*>(gradient);
373         context.styleWriter().startElement("radialGradient");
374         context.styleWriter().addAttribute("id", uid);
375         SvgUtil::writeTransformAttributeLazy("gradientTransform", gradientTransform, context.styleWriter());
376         context.styleWriter().addAttribute("gradientUnits", convertGradientMode(g->coordinateMode()));
377         context.styleWriter().addAttribute("cx", g->center().x());
378         context.styleWriter().addAttribute("cy", g->center().y());
379         context.styleWriter().addAttribute("fx", g->focalPoint().x());
380         context.styleWriter().addAttribute("fy", g->focalPoint().y());
381         context.styleWriter().addAttribute("r", g->radius());
382         context.styleWriter().addAttribute("spreadMethod", spreadMethod[g->spread()]);
383         // color stops
384         saveSvgColorStops(gradient->stops(), context);
385         context.styleWriter().endElement();
386     } else if (gradient->type() == QGradient::ConicalGradient) {
387         //const QConicalGradient * g = static_cast<const QConicalGradient*>( gradient );
388         // fake conical grad as radial.
389         // fugly but better than data loss.
390         /*
391         printIndentation( m_defs, m_indent2 );
392         *m_defs << "<radialGradient id=\"" << uid << "\" ";
393         *m_defs << "gradientUnits=\"userSpaceOnUse\" ";
394         *m_defs << "cx=\"" << g->center().x() << "\" ";
395         *m_defs << "cy=\"" << g->center().y() << "\" ";
396         *m_defs << "fx=\"" << grad.focalPoint().x() << "\" ";
397         *m_defs << "fy=\"" << grad.focalPoint().y() << "\" ";
398         double r = sqrt( pow( grad.vector().x() - grad.origin().x(), 2 ) + pow( grad.vector().y() - grad.origin().y(), 2 ) );
399         *m_defs << "r=\"" << QString().setNum( r ) << "\" ";
400         *m_defs << spreadMethod[g->spread()];
401         *m_defs << ">" << endl;
402 
403         // color stops
404         getColorStops( gradient->stops() );
405 
406         printIndentation( m_defs, m_indent2 );
407         *m_defs << "</radialGradient>" << endl;
408         *m_body << "url(#" << uid << ")";
409         */
410     }
411 
412     return uid;
413 }
414 
saveSvgMeshGradient(SvgMeshGradient * gradient,const QTransform & transform,SvgSavingContext & context)415 QString SvgStyleWriter::saveSvgMeshGradient(SvgMeshGradient *gradient,
416                                             const QTransform& transform,
417                                             SvgSavingContext &context)
418 {
419     if (!gradient || !gradient->isValid())
420         return QString();
421 
422     const QString uid = context.createUID("meshgradient");
423     context.styleWriter().startElement("meshgradient");
424     context.styleWriter().addAttribute("id", uid);
425 
426     if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) {
427         context.styleWriter().addAttribute("gradientUnits", "objectBoundingBox");
428     } else {
429         context.styleWriter().addAttribute("gradientUnits", "userSpaceOnUse");
430     }
431 
432     SvgUtil::writeTransformAttributeLazy("transform", transform, context.styleWriter());
433 
434     SvgMeshArray *mesharray = gradient->getMeshArray().data();
435     QPointF start = mesharray->getPatch(0, 0)->getStop(SvgMeshPatch::Top).point;
436 
437     context.styleWriter().addAttribute("x", start.x());
438     context.styleWriter().addAttribute("y", start.y());
439 
440     if (gradient->type() == SvgMeshGradient::BILINEAR) {
441         context.styleWriter().addAttribute("type", "bilinear");
442     } else {
443         context.styleWriter().addAttribute("type", "bicubic");
444     }
445 
446     for (int row = 0; row < mesharray->numRows(); ++row) {
447 
448         const QString uid = context.createUID("meshrow");
449         context.styleWriter().startElement("meshrow");
450         context.styleWriter().addAttribute("id", uid);
451 
452         for (int col = 0; col < mesharray->numColumns(); ++col) {
453 
454             const QString uid = context.createUID("meshpatch");
455             context.styleWriter().startElement("meshpatch");
456             context.styleWriter().addAttribute("id", uid);
457 
458             SvgMeshPatch *patch = mesharray->getPatch(row, col);
459 
460             for (int s = 0; s < 4; ++s) {
461                 SvgMeshPatch::Type type = static_cast<SvgMeshPatch::Type> (s);
462 
463                 // only first row and first col have Top and Left stop, respectively
464                 if ((row != 0 && s == SvgMeshPatch::Top) ||
465                     (col != 0 && s == SvgMeshPatch::Left)) {
466                     continue;
467                 }
468 
469                 context.styleWriter().startElement("stop");
470 
471                 std::array<QPointF, 4> segment = patch->getSegment(type);
472 
473                 QString pathstr;
474                 QTextStream stream(&pathstr);
475                 stream.setRealNumberPrecision(10);
476                 // TODO: other path type?
477                 stream << "C "
478                        << segment[1].x() << "," << segment[1].y() << " "
479                        << segment[2].x() << "," << segment[2].y() << " "
480                        << segment[3].x() << "," << segment[3].y(); // I don't see any harm, inkscape does this too
481 
482                 context.styleWriter().addAttribute("path", pathstr);
483 
484                 // don't add color/opacity if stop is in first row and stop == Top (or)
485                 // don't add color/opacity if stop is not in first row and stop == Right
486                 if ((row != 0 || col == 0 || s != SvgMeshPatch::Top) &&
487                     (row == 0 || s != SvgMeshPatch::Right)) {
488 
489                     SvgMeshStop stop = patch->getStop(type);
490                     context.styleWriter().addAttribute("stop-color", stop.color.name());
491                     context.styleWriter().addAttribute("stop-opacity", stop.color.alphaF());
492                 }
493 
494                 context.styleWriter().endElement(); // stop
495             }
496 
497             context.styleWriter().endElement();  // meshpatch
498         }
499         context.styleWriter().endElement(); // meshrow
500     }
501     context.styleWriter().endElement(); // meshgradient
502 
503     return uid;
504 }
505 
saveSvgPattern(QSharedPointer<KoPatternBackground> pattern,KoShape * shape,SvgSavingContext & context)506 QString SvgStyleWriter::saveSvgPattern(QSharedPointer<KoPatternBackground> pattern, KoShape *shape, SvgSavingContext &context)
507 {
508     const QString uid = context.createUID("pattern");
509 
510     const QSizeF shapeSize = shape->size();
511     const QSizeF patternSize = pattern->patternDisplaySize();
512     const QSize imageSize = pattern->pattern().size();
513 
514     // calculate offset in point
515     QPointF offset = pattern->referencePointOffset();
516     offset.rx() = 0.01 * offset.x() * patternSize.width();
517     offset.ry() = 0.01 * offset.y() * patternSize.height();
518 
519     // now take the reference point into account
520     switch (pattern->referencePoint()) {
521     case KoPatternBackground::TopLeft:
522         break;
523     case KoPatternBackground::Top:
524         offset += QPointF(0.5 * shapeSize.width(), 0.0);
525         break;
526     case KoPatternBackground::TopRight:
527         offset += QPointF(shapeSize.width(), 0.0);
528         break;
529     case KoPatternBackground::Left:
530         offset += QPointF(0.0, 0.5 * shapeSize.height());
531         break;
532     case KoPatternBackground::Center:
533         offset += QPointF(0.5 * shapeSize.width(), 0.5 * shapeSize.height());
534         break;
535     case KoPatternBackground::Right:
536         offset += QPointF(shapeSize.width(), 0.5 * shapeSize.height());
537         break;
538     case KoPatternBackground::BottomLeft:
539         offset += QPointF(0.0, shapeSize.height());
540         break;
541     case KoPatternBackground::Bottom:
542         offset += QPointF(0.5 * shapeSize.width(), shapeSize.height());
543         break;
544     case KoPatternBackground::BottomRight:
545         offset += QPointF(shapeSize.width(), shapeSize.height());
546         break;
547     }
548 
549     offset = shape->absoluteTransformation().map(offset);
550 
551     context.styleWriter().startElement("pattern");
552     context.styleWriter().addAttribute("id", uid);
553     context.styleWriter().addAttribute("x", SvgUtil::toUserSpace(offset.x()));
554     context.styleWriter().addAttribute("y", SvgUtil::toUserSpace(offset.y()));
555 
556     if (pattern->repeat() == KoPatternBackground::Stretched) {
557         context.styleWriter().addAttribute("width", "100%");
558         context.styleWriter().addAttribute("height", "100%");
559         context.styleWriter().addAttribute("patternUnits", "objectBoundingBox");
560     } else {
561         context.styleWriter().addAttribute("width", SvgUtil::toUserSpace(patternSize.width()));
562         context.styleWriter().addAttribute("height", SvgUtil::toUserSpace(patternSize.height()));
563         context.styleWriter().addAttribute("patternUnits", "userSpaceOnUse");
564     }
565 
566     context.styleWriter().addAttribute("viewBox", QString("0 0 %1 %2").arg(KisDomUtils::toString(imageSize.width())).arg(KisDomUtils::toString(imageSize.height())));
567     //*m_defs << " patternContentUnits=\"userSpaceOnUse\"";
568 
569     context.styleWriter().startElement("image");
570     context.styleWriter().addAttribute("x", "0");
571     context.styleWriter().addAttribute("y", "0");
572     context.styleWriter().addAttribute("width", QString("%1px").arg(KisDomUtils::toString(imageSize.width())));
573     context.styleWriter().addAttribute("height", QString("%1px").arg(KisDomUtils::toString(imageSize.height())));
574 
575     QByteArray ba;
576     QBuffer buffer(&ba);
577     buffer.open(QIODevice::WriteOnly);
578     if (pattern->pattern().save(&buffer, "PNG")) {
579         const QString mimeType = KisMimeDatabase::mimeTypeForSuffix("*.png");
580         context.styleWriter().addAttribute("xlink:href", "data:"+ mimeType + ";base64," + ba.toBase64());
581     }
582 
583     context.styleWriter().endElement(); // image
584     context.styleWriter().endElement(); // pattern
585 
586     return uid;
587 }
588 
saveSvgVectorPattern(QSharedPointer<KoVectorPatternBackground> pattern,KoShape * parentShape,SvgSavingContext & context)589 QString SvgStyleWriter::saveSvgVectorPattern(QSharedPointer<KoVectorPatternBackground> pattern, KoShape *parentShape, SvgSavingContext &context)
590 {
591     const QString uid = context.createUID("pattern");
592 
593     context.styleWriter().startElement("pattern");
594     context.styleWriter().addAttribute("id", uid);
595 
596     context.styleWriter().addAttribute("patternUnits", KoFlake::coordinateToString(pattern->referenceCoordinates()));
597     context.styleWriter().addAttribute("patternContentUnits", KoFlake::coordinateToString(pattern->contentCoordinates()));
598 
599     const QRectF rect = pattern->referenceRect();
600 
601     context.styleWriter().addAttribute("x", rect.x());
602     context.styleWriter().addAttribute("y", rect.y());
603     context.styleWriter().addAttribute("width", rect.width());
604     context.styleWriter().addAttribute("height", rect.height());
605 
606     SvgUtil::writeTransformAttributeLazy("patternTransform", pattern->patternTransform(), context.styleWriter());
607 
608     if (pattern->contentCoordinates() == KoFlake::ObjectBoundingBox) {
609         // TODO: move this normalization into the KoVectorPatternBackground itself
610 
611         QList<KoShape*> shapes = pattern->shapes();
612         QList<KoShape*> clonedShapes;
613 
614         const QRectF dstShapeBoundingRect = parentShape->outlineRect();
615         const QTransform relativeToShape = KisAlgebra2D::mapToRect(dstShapeBoundingRect);
616         const QTransform shapeToRelative = relativeToShape.inverted();
617 
618         Q_FOREACH (KoShape *shape, shapes) {
619             KoShape *clone = shape->cloneShape();
620             clone->applyAbsoluteTransformation(shapeToRelative);
621             clonedShapes.append(clone);
622         }
623 
624         embedShapes(clonedShapes, context.styleWriter());
625         qDeleteAll(clonedShapes);
626 
627     } else {
628         QList<KoShape*> shapes = pattern->shapes();
629         embedShapes(shapes, context.styleWriter());
630     }
631 
632     context.styleWriter().endElement(); // pattern
633 
634     return uid;
635 }
636