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