Krita Source Code Documentation
Loading...
Searching...
No Matches
SvgStyleWriter.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 SPDX-FileCopyrightText: 2002 Lars Siebold <khandha5@gmx.net>
3 SPDX-FileCopyrightText: 2002-2003, 2005 Rob Buis <buis@kde.org>
4 SPDX-FileCopyrightText: 2002, 2005-2006 David Faure <faure@kde.org>
5 SPDX-FileCopyrightText: 2002 Werner Trobin <trobin@kde.org>
6 SPDX-FileCopyrightText: 2002 Lennart Kudling <kudling@kde.org>
7 SPDX-FileCopyrightText: 2004 Nicolas Goutte <nicolasg@snafu.de>
8 SPDX-FileCopyrightText: 2005 Boudewijn Rempt <boud@valdyas.org>
9 SPDX-FileCopyrightText: 2005 Raphael Langerhorst <raphael.langerhorst@kdemail.net>
10 SPDX-FileCopyrightText: 2005 Thomas Zander <zander@kde.org>
11 SPDX-FileCopyrightText: 2005, 2007-2008 Jan Hambrecht <jaham@gmx.net>
12 SPDX-FileCopyrightText: 2006 Inge Wallin <inge@lysator.liu.se>
13 SPDX-FileCopyrightText: 2006 Martin Pfeiffer <hubipete@gmx.net>
14 SPDX-FileCopyrightText: 2006 Gábor Lehel <illissius@gmail.com>
15 SPDX-FileCopyrightText: 2006 Laurent Montel <montel@kde.org>
16 SPDX-FileCopyrightText: 2006 Christian Mueller <cmueller@gmx.de>
17 SPDX-FileCopyrightText: 2006 Ariya Hidayat <ariya@kde.org>
18 SPDX-FileCopyrightText: 2010 Thorsten Zachmann <zachmann@kde.org>
19
20 SPDX-License-Identifier: LGPL-2.0-or-later
21*/
22
23#include "SvgStyleWriter.h"
24#include "SvgSavingContext.h"
25#include "SvgUtil.h"
26
27#include <KoShape.h>
28#include <KoPathShape.h>
29#include <KoPathSegment.h>
30#include <KoFilterEffect.h>
31#include <KoFilterEffectStack.h>
32#include <KoColorBackground.h>
35#include <KoPatternBackground.h>
37#include <KoShapeStroke.h>
38#include <KoClipPath.h>
39#include <KoClipMask.h>
40#include <KoMarker.h>
41#include <KoXmlWriter.h>
42
43#include <QBuffer>
44#include <QGradient>
45#include <QLinearGradient>
46#include <QRadialGradient>
47#include <KisMimeDatabase.h>
48#include "kis_dom_utils.h"
49#include "kis_algebra_2d.h"
50#include <SvgWriter.h>
52
53
55{
56 saveSvgBasicStyle(shape->isVisible(false), shape->transparency(false), shape->paintOrder(), shape->inheritPaintOrder(), context);
57
58 KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
59 bool fillRule = pathShape && pathShape->background() && pathShape->fillRule() == Qt::OddEvenFill? true: false;
60 if (!shape->inheritBackground()) {
61 saveSvgFill(shape->background(), fillRule, shape->outlineRect(), shape->size(), shape->absoluteTransformation(), context);
62 }
63 if (!shape->inheritStroke()) {
64 saveSvgStroke(shape->stroke(), context);
65 }
66
67 saveSvgEffects(shape, context);
68 saveSvgClipping(shape, context);
69 saveSvgMasking(shape, context);
70 saveSvgMarkers(shape, context);
71}
72
73void SvgStyleWriter::saveSvgBasicStyle(const bool isVisible, const qreal transparency, const QVector<KoShape::PaintOrder> paintOrder, bool inheritPaintorder, SvgSavingContext &context, bool textShape)
74{
75 if (!isVisible) {
76 context.shapeWriter().addAttribute("display", "none");
77 } else if (transparency > 0.0) {
78 context.shapeWriter().addAttribute("opacity", 1.0 - transparency);
79 }
80 if (!paintOrder.isEmpty() && !inheritPaintorder) {
81
82 const bool notDefault = paintOrder != KoShape::defaultPaintOrder();
83
84 if ((!textShape && notDefault) || textShape) {
85 QStringList order;
86 Q_FOREACH(const KoShape::PaintOrder p, paintOrder) {
87 if (p == KoShape::Fill) {
88 order.append("fill");
89 } else if (p == KoShape::Stroke) {
90 order.append("stroke");
91 } else if (p == KoShape::Markers) {
92 order.append("markers");
93 }
94 }
95 context.shapeWriter().addAttribute("paint-order", order.join(" "));
96 }
97 }
98
99}
100
101void SvgStyleWriter::saveSvgFill(QSharedPointer<KoShapeBackground> background, const bool fillRuleEvenOdd, const QRectF outlineRect, const QSizeF size, const QTransform absoluteTransform, SvgSavingContext &context)
102{
103 if (! background) {
104 context.shapeWriter().addAttribute("fill", "none");
105 }
106
107 QBrush fill(Qt::NoBrush);
108 QSharedPointer<KoColorBackground> cbg = qSharedPointerDynamicCast<KoColorBackground>(background);
109 if (cbg) {
110 context.shapeWriter().addAttribute("fill", cbg->color().name());
111 if (cbg->color().alphaF() < 1.0)
112 context.shapeWriter().addAttribute("fill-opacity", cbg->color().alphaF());
113 }
114 QSharedPointer<KoGradientBackground> gbg = qSharedPointerDynamicCast<KoGradientBackground>(background);
115 if (gbg) {
116 QString gradientId = saveSvgGradient(gbg->gradient(), gbg->transform(), context);
117 context.shapeWriter().addAttribute("fill", "url(#" + gradientId + ")");
118 }
119 QSharedPointer<KoMeshGradientBackground> mgbg = qSharedPointerDynamicCast<KoMeshGradientBackground>(background);
120 if (mgbg) {
121 QString gradientId = saveSvgMeshGradient(mgbg->gradient(), mgbg->transform(), context);
122 context.shapeWriter().addAttribute("fill", "url(#" + gradientId + ")");
123 }
124 QSharedPointer<KoPatternBackground> pbg = qSharedPointerDynamicCast<KoPatternBackground>(background);
125 if (pbg) {
126 const QString patternId = saveSvgPattern(pbg, size, absoluteTransform, context);
127 context.shapeWriter().addAttribute("fill", "url(#" + patternId + ")");
128 }
129 QSharedPointer<KoVectorPatternBackground> vpbg = qSharedPointerDynamicCast<KoVectorPatternBackground>(background);
130 if (vpbg) {
131 const QString patternId = saveSvgVectorPattern(vpbg, outlineRect, context);
132 context.shapeWriter().addAttribute("fill", "url(#" + patternId + ")");
133 }
134
135 // non-zero is default, so only write fillrule if evenodd is set
136 if (fillRuleEvenOdd)
137 context.shapeWriter().addAttribute("fill-rule", "evenodd");
138}
139
141{
142 const QSharedPointer<KoShapeStroke> lineBorder = qSharedPointerDynamicCast<KoShapeStroke>(stroke);
143
144 if (! lineBorder)
145 return;
146
147 QString strokeStr("none");
148 if (lineBorder->lineBrush().gradient()) {
149 QString gradientId = saveSvgGradient(lineBorder->lineBrush().gradient(), lineBorder->lineBrush().transform(), context);
150 strokeStr = "url(#" + gradientId + ")";
151 } else {
152 if (lineBorder->color().isValid()) {
153 strokeStr = lineBorder->color().name();
154 }
155 if (lineBorder->color().alphaF() < 1.0) {
156 context.shapeWriter().addAttribute("stroke-opacity", lineBorder->color().alphaF());
157 }
158 }
159 if (!strokeStr.isEmpty())
160 context.shapeWriter().addAttribute("stroke", strokeStr);
161
162 context.shapeWriter().addAttribute("stroke-width", SvgUtil::toUserSpace(lineBorder->lineWidth()));
163
164 if (lineBorder->capStyle() == Qt::FlatCap)
165 context.shapeWriter().addAttribute("stroke-linecap", "butt");
166 else if (lineBorder->capStyle() == Qt::RoundCap)
167 context.shapeWriter().addAttribute("stroke-linecap", "round");
168 else if (lineBorder->capStyle() == Qt::SquareCap)
169 context.shapeWriter().addAttribute("stroke-linecap", "square");
170
171 if (lineBorder->joinStyle() == Qt::MiterJoin) {
172 context.shapeWriter().addAttribute("stroke-linejoin", "miter");
173 context.shapeWriter().addAttribute("stroke-miterlimit", lineBorder->miterLimit());
174 } else if (lineBorder->joinStyle() == Qt::RoundJoin)
175 context.shapeWriter().addAttribute("stroke-linejoin", "round");
176 else if (lineBorder->joinStyle() == Qt::BevelJoin)
177 context.shapeWriter().addAttribute("stroke-linejoin", "bevel");
178
179 // dash
180 if (lineBorder->lineStyle() > Qt::SolidLine) {
181 qreal dashFactor = lineBorder->lineWidth();
182
183 if (lineBorder->dashOffset() != 0)
184 context.shapeWriter().addAttribute("stroke-dashoffset", dashFactor * lineBorder->dashOffset());
185
186 QString dashStr;
187 const QVector<qreal> dashes = lineBorder->lineDashes();
188 int dashCount = dashes.size();
189 for (int i = 0; i < dashCount; ++i) {
190 if (i > 0)
191 dashStr += ",";
192 dashStr += QString("%1").arg(KisDomUtils::toString(dashes[i] * dashFactor));
193 }
194 context.shapeWriter().addAttribute("stroke-dasharray", dashStr);
195 }
196}
197
199{
200 KoFilterEffectStack * filterStack = shape->filterEffectStack();
201 if (!filterStack)
202 return;
203
204 QList<KoFilterEffect*> filterEffects = filterStack->filterEffects();
205 if (!filterEffects.count())
206 return;
207
208 const QString uid = context.createUID("filter");
209
210 filterStack->save(context.styleWriter(), uid);
211
212 context.shapeWriter().addAttribute("filter", "url(#" + uid + ")");
213}
214
215void embedShapes(const QList<KoShape*> &shapes, KoXmlWriter &outWriter)
216{
217 QBuffer buffer;
218 buffer.open(QIODevice::WriteOnly);
219 {
220 SvgWriter shapesWriter(shapes);
221 shapesWriter.saveDetached(buffer);
222 }
223 buffer.close();
224 outWriter.addCompleteElement(&buffer);
225}
226
228{
229 QList<KoShape *> shapes;
230 KoShape* clonedShape = shape->cloneShape();
231 if (!clonedShape) {
232 return QString();
233 }
234 const QString uid = context.createUID("path");
235 clonedShape->setName(uid);
236 shapes.append(clonedShape);
237 embedShapes(shapes, context.styleWriter());
238 return uid;
239}
240
242{
243 const QString title = shape->additionalAttribute("title");
244 if (!title.trimmed().isEmpty()) {
245 context.shapeWriter().startElement("title");
246 context.shapeWriter().addTextNode(title);
247 context.shapeWriter().endElement();
248 }
249 const QString desc = shape->additionalAttribute("desc");
250 if (!desc.trimmed().isEmpty()) {
251 context.shapeWriter().startElement("desc");
252 context.shapeWriter().addTextNode(desc);
253 context.shapeWriter().endElement();
254 }
255}
256
258{
259 KoClipPath *clipPath = shape->clipPath();
260 if (!clipPath)
261 return;
262
263 const QString uid = context.createUID("clippath");
264
265 context.styleWriter().startElement("clipPath");
266 context.styleWriter().addAttribute("id", uid);
267 context.styleWriter().addAttribute("clipPathUnits", KoFlake::coordinateToString(clipPath->coordinates()));
268
269 embedShapes(clipPath->clipShapes(), context.styleWriter());
270
271 context.styleWriter().endElement(); // clipPath
272
273 context.shapeWriter().addAttribute("clip-path", "url(#" + uid + ")");
274 if (clipPath->clipRule() != Qt::WindingFill)
275 context.shapeWriter().addAttribute("clip-rule", "evenodd");
276}
277
279{
280 KoClipMask*clipMask = shape->clipMask();
281 if (!clipMask)
282 return;
283
284 const QString uid = context.createUID("clipmask");
285
286 context.styleWriter().startElement("mask");
287 context.styleWriter().addAttribute("id", uid);
288 context.styleWriter().addAttribute("maskUnits", KoFlake::coordinateToString(clipMask->coordinates()));
289 context.styleWriter().addAttribute("maskContentUnits", KoFlake::coordinateToString(clipMask->contentCoordinates()));
290
291 const QRectF rect = clipMask->maskRect();
292
293 context.styleWriter().addAttribute("x", rect.x());
294 context.styleWriter().addAttribute("y", rect.y());
295 context.styleWriter().addAttribute("width", rect.width());
296 context.styleWriter().addAttribute("height", rect.height());
297
298 embedShapes(clipMask->shapes(), context.styleWriter());
299
300 context.styleWriter().endElement(); // clipMask
301
302 context.shapeWriter().addAttribute("mask", "url(#" + uid + ")");
303}
304
305namespace {
306void writeMarkerStyle(KoXmlWriter &styleWriter, const KoMarker *marker, const QString &assignedId) {
307
308 styleWriter.startElement("marker");
309 styleWriter.addAttribute("id", assignedId);
310 styleWriter.addAttribute("markerUnits", KoMarker::coordinateSystemToString(marker->coordinateSystem()));
311
312 const QPointF refPoint = marker->referencePoint();
313 styleWriter.addAttribute("refX", refPoint.x());
314 styleWriter.addAttribute("refY", refPoint.y());
315
316 const QSizeF refSize = marker->referenceSize();
317 styleWriter.addAttribute("markerWidth", refSize.width());
318 styleWriter.addAttribute("markerHeight", refSize.height());
319
320
321 if (marker->hasAutoOrientation()) {
322 styleWriter.addAttribute("orient", "auto");
323 } else {
324 // no suffix means 'degrees'
325 styleWriter.addAttribute("orient", kisRadiansToDegrees(marker->explicitOrientation()));
326 }
327
328 embedShapes(marker->shapes(), styleWriter);
329
330 styleWriter.endElement(); // marker
331}
332
333void tryEmbedMarker(const KoPathShape *pathShape,
334 const QString &markerTag,
335 KoFlake::MarkerPosition markerPosition,
336 SvgSavingContext &context)
337{
338 KoMarker *marker = pathShape->marker(markerPosition);
339
340 if (marker) {
341 const QString uid = context.createUID("lineMarker");
342 writeMarkerStyle(context.styleWriter(), marker, uid);
343 context.shapeWriter().addAttribute(markerTag.toLatin1().data(), "url(#" + uid + ")");
344 }
345}
346
347}
348
350{
351 KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
352 if (!pathShape || !pathShape->hasMarkers()) return;
353
354
355 tryEmbedMarker(pathShape, "marker-start", KoFlake::StartMarker, context);
356 tryEmbedMarker(pathShape, "marker-mid", KoFlake::MidMarker, context);
357 tryEmbedMarker(pathShape, "marker-end", KoFlake::EndMarker, context);
358
359 if (pathShape->autoFillMarkers()) {
360 context.shapeWriter().addAttribute("krita:marker-fill-method", "auto");
361 }
362}
363
364void SvgStyleWriter::saveSvgColorStops(const QGradientStops &colorStops, SvgSavingContext &context)
365{
366 Q_FOREACH (const QGradientStop &stop, colorStops) {
367 context.styleWriter().startElement("stop");
368 context.styleWriter().addAttribute("stop-color", stop.second.name());
369 context.styleWriter().addAttribute("offset", stop.first);
370 context.styleWriter().addAttribute("stop-opacity", stop.second.alphaF());
371 context.styleWriter().endElement();
372 }
373}
374
375inline QString convertGradientMode(QGradient::CoordinateMode mode) {
376 KIS_ASSERT_RECOVER_NOOP(mode != QGradient::StretchToDeviceMode);
377
378 return
379 mode == QGradient::ObjectBoundingMode ?
380 "objectBoundingBox" :
381 "userSpaceOnUse";
382
383}
384
385QString SvgStyleWriter::saveSvgGradient(const QGradient *gradient, const QTransform &gradientTransform, SvgSavingContext &context)
386{
387 if (! gradient)
388 return QString();
389
390 const QString spreadMethod[3] = {
391 QString("pad"),
392 QString("reflect"),
393 QString("repeat")
394 };
395
396 const QString uid = context.createUID("gradient");
397
398 if (gradient->type() == QGradient::LinearGradient) {
399 const QLinearGradient * g = static_cast<const QLinearGradient*>(gradient);
400 context.styleWriter().startElement("linearGradient");
401 context.styleWriter().addAttribute("id", uid);
402 SvgUtil::writeTransformAttributeLazy("gradientTransform", gradientTransform, context.styleWriter());
403 context.styleWriter().addAttribute("gradientUnits", convertGradientMode(g->coordinateMode()));
404 context.styleWriter().addAttribute("x1", g->start().x());
405 context.styleWriter().addAttribute("y1", g->start().y());
406 context.styleWriter().addAttribute("x2", g->finalStop().x());
407 context.styleWriter().addAttribute("y2", g->finalStop().y());
408 context.styleWriter().addAttribute("spreadMethod", spreadMethod[g->spread()]);
409 // color stops
410 saveSvgColorStops(gradient->stops(), context);
411 context.styleWriter().endElement();
412 } else if (gradient->type() == QGradient::RadialGradient) {
413 const QRadialGradient * g = static_cast<const QRadialGradient*>(gradient);
414 context.styleWriter().startElement("radialGradient");
415 context.styleWriter().addAttribute("id", uid);
416 SvgUtil::writeTransformAttributeLazy("gradientTransform", gradientTransform, context.styleWriter());
417 context.styleWriter().addAttribute("gradientUnits", convertGradientMode(g->coordinateMode()));
418 context.styleWriter().addAttribute("cx", g->center().x());
419 context.styleWriter().addAttribute("cy", g->center().y());
420 context.styleWriter().addAttribute("fx", g->focalPoint().x());
421 context.styleWriter().addAttribute("fy", g->focalPoint().y());
422 context.styleWriter().addAttribute("r", g->radius());
423 context.styleWriter().addAttribute("spreadMethod", spreadMethod[g->spread()]);
424 // color stops
425 saveSvgColorStops(gradient->stops(), context);
426 context.styleWriter().endElement();
427 } else if (gradient->type() == QGradient::ConicalGradient) {
428 //const QConicalGradient * g = static_cast<const QConicalGradient*>( gradient );
429 // fake conical grad as radial.
430 // fugly but better than data loss.
431 /*
432 printIndentation( m_defs, m_indent2 );
433 *m_defs << "<radialGradient id=\"" << uid << "\" ";
434 *m_defs << "gradientUnits=\"userSpaceOnUse\" ";
435 *m_defs << "cx=\"" << g->center().x() << "\" ";
436 *m_defs << "cy=\"" << g->center().y() << "\" ";
437 *m_defs << "fx=\"" << grad.focalPoint().x() << "\" ";
438 *m_defs << "fy=\"" << grad.focalPoint().y() << "\" ";
439 double r = sqrt( pow( grad.vector().x() - grad.origin().x(), 2 ) + pow( grad.vector().y() - grad.origin().y(), 2 ) );
440 *m_defs << "r=\"" << QString().setNum( r ) << "\" ";
441 *m_defs << spreadMethod[g->spread()];
442 *m_defs << ">" << Qt::endl;
443
444 // color stops
445 getColorStops( gradient->stops() );
446
447 printIndentation( m_defs, m_indent2 );
448 *m_defs << "</radialGradient>" << Qt::endl;
449 *m_body << "url(#" << uid << ")";
450 */
451 }
452
453 return uid;
454}
455
457 const QTransform& transform,
458 SvgSavingContext &context)
459{
460 if (!gradient || !gradient->isValid())
461 return QString();
462
463 const QString uid = context.createUID("meshgradient");
464 context.styleWriter().startElement("meshgradient");
465 context.styleWriter().addAttribute("id", uid);
466
467 if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) {
468 context.styleWriter().addAttribute("gradientUnits", "objectBoundingBox");
469 } else {
470 context.styleWriter().addAttribute("gradientUnits", "userSpaceOnUse");
471 }
472
473 SvgUtil::writeTransformAttributeLazy("transform", transform, context.styleWriter());
474
475 SvgMeshArray *mesharray = gradient->getMeshArray().data();
476 QPointF start = mesharray->getPatch(0, 0)->getStop(SvgMeshPatch::Top).point;
477
478 context.styleWriter().addAttribute("x", start.x());
479 context.styleWriter().addAttribute("y", start.y());
480
481 if (gradient->type() == SvgMeshGradient::BILINEAR) {
482 context.styleWriter().addAttribute("type", "bilinear");
483 } else {
484 context.styleWriter().addAttribute("type", "bicubic");
485 }
486
487 for (int row = 0; row < mesharray->numRows(); ++row) {
488
489 const QString uid = context.createUID("meshrow");
490 context.styleWriter().startElement("meshrow");
491 context.styleWriter().addAttribute("id", uid);
492
493 for (int col = 0; col < mesharray->numColumns(); ++col) {
494
495 const QString uid = context.createUID("meshpatch");
496 context.styleWriter().startElement("meshpatch");
497 context.styleWriter().addAttribute("id", uid);
498
499 SvgMeshPatch *patch = mesharray->getPatch(row, col);
500
501 for (int s = 0; s < 4; ++s) {
502 SvgMeshPatch::Type type = static_cast<SvgMeshPatch::Type> (s);
503
504 // only first row and first col have Top and Left stop, respectively
505 if ((row != 0 && s == SvgMeshPatch::Top) ||
506 (col != 0 && s == SvgMeshPatch::Left)) {
507 continue;
508 }
509
510 context.styleWriter().startElement("stop");
511
512 std::array<QPointF, 4> segment = patch->getSegment(type);
513
514 QString pathstr;
515 QTextStream stream(&pathstr);
517
518 stream.setRealNumberPrecision(10);
519 // TODO: other path type?
520 stream << "C "
521 << segment[1].x() << "," << segment[1].y() << " "
522 << segment[2].x() << "," << segment[2].y() << " "
523 << segment[3].x() << "," << segment[3].y(); // I don't see any harm, inkscape does this too
524
525 context.styleWriter().addAttribute("path", pathstr);
526
527 // don't add color/opacity if stop is in first row and stop == Top (or)
528 // don't add color/opacity if stop is not in first row and stop == Right
529 if ((row != 0 || col == 0 || s != SvgMeshPatch::Top) &&
530 (row == 0 || s != SvgMeshPatch::Right)) {
531
532 SvgMeshStop stop = patch->getStop(type);
533 context.styleWriter().addAttribute("stop-color", stop.color.name());
534 context.styleWriter().addAttribute("stop-opacity", stop.color.alphaF());
535 }
536
537 context.styleWriter().endElement(); // stop
538 }
539
540 context.styleWriter().endElement(); // meshpatch
541 }
542 context.styleWriter().endElement(); // meshrow
543 }
544 context.styleWriter().endElement(); // meshgradient
545
546 return uid;
547}
548
549QString SvgStyleWriter::saveSvgPattern(QSharedPointer<KoPatternBackground> pattern, const QSizeF shapeSize, const QTransform absoluteTransform, SvgSavingContext &context)
550{
551 const QString uid = context.createUID("pattern");
552
553 const QSizeF patternSize = pattern->patternDisplaySize();
554 const QSize imageSize = pattern->pattern().size();
555
556 // calculate offset in point
557 QPointF offset = pattern->referencePointOffset();
558 offset.rx() = 0.01 * offset.x() * patternSize.width();
559 offset.ry() = 0.01 * offset.y() * patternSize.height();
560
561 // now take the reference point into account
562 switch (pattern->referencePoint()) {
564 break;
566 offset += QPointF(0.5 * shapeSize.width(), 0.0);
567 break;
569 offset += QPointF(shapeSize.width(), 0.0);
570 break;
572 offset += QPointF(0.0, 0.5 * shapeSize.height());
573 break;
575 offset += QPointF(0.5 * shapeSize.width(), 0.5 * shapeSize.height());
576 break;
578 offset += QPointF(shapeSize.width(), 0.5 * shapeSize.height());
579 break;
581 offset += QPointF(0.0, shapeSize.height());
582 break;
584 offset += QPointF(0.5 * shapeSize.width(), shapeSize.height());
585 break;
587 offset += QPointF(shapeSize.width(), shapeSize.height());
588 break;
589 }
590
591 offset = absoluteTransform.map(offset);
592
593 context.styleWriter().startElement("pattern");
594 context.styleWriter().addAttribute("id", uid);
595 context.styleWriter().addAttribute("x", SvgUtil::toUserSpace(offset.x()));
596 context.styleWriter().addAttribute("y", SvgUtil::toUserSpace(offset.y()));
597
598 if (pattern->repeat() == KoPatternBackground::Stretched) {
599 context.styleWriter().addAttribute("width", "100%");
600 context.styleWriter().addAttribute("height", "100%");
601 context.styleWriter().addAttribute("patternUnits", "objectBoundingBox");
602 } else {
603 context.styleWriter().addAttribute("width", SvgUtil::toUserSpace(patternSize.width()));
604 context.styleWriter().addAttribute("height", SvgUtil::toUserSpace(patternSize.height()));
605 context.styleWriter().addAttribute("patternUnits", "userSpaceOnUse");
606 }
607
608 context.styleWriter().addAttribute("viewBox", QString("0 0 %1 %2").arg(KisDomUtils::toString(imageSize.width())).arg(KisDomUtils::toString(imageSize.height())));
609 //*m_defs << " patternContentUnits=\"userSpaceOnUse\"";
610
611 context.styleWriter().startElement("image");
612 context.styleWriter().addAttribute("x", "0");
613 context.styleWriter().addAttribute("y", "0");
614 context.styleWriter().addAttribute("width", QString("%1px").arg(KisDomUtils::toString(imageSize.width())));
615 context.styleWriter().addAttribute("height", QString("%1px").arg(KisDomUtils::toString(imageSize.height())));
616
617 QBuffer buffer;
618 buffer.open(QIODevice::WriteOnly);
619 if (pattern->pattern().save(&buffer, "PNG")) {
620 const QString mimeType = KisMimeDatabase::mimeTypeForSuffix("*.png");
621 context.styleWriter().addAttribute("xlink:href", "data:"+ mimeType + ";base64," + buffer.data().toBase64());
622 }
623
624 context.styleWriter().endElement(); // image
625 context.styleWriter().endElement(); // pattern
626
627 return uid;
628}
629
631{
632 const QString uid = context.createUID("pattern");
633
634 context.styleWriter().startElement("pattern");
635 context.styleWriter().addAttribute("id", uid);
636
637 context.styleWriter().addAttribute("patternUnits", KoFlake::coordinateToString(pattern->referenceCoordinates()));
638 context.styleWriter().addAttribute("patternContentUnits", KoFlake::coordinateToString(pattern->contentCoordinates()));
639
640 const QRectF rect = pattern->referenceRect();
641
642 context.styleWriter().addAttribute("x", rect.x());
643 context.styleWriter().addAttribute("y", rect.y());
644 context.styleWriter().addAttribute("width", rect.width());
645 context.styleWriter().addAttribute("height", rect.height());
646
647 SvgUtil::writeTransformAttributeLazy("patternTransform", pattern->patternTransform(), context.styleWriter());
648
649 if (pattern->contentCoordinates() == KoFlake::ObjectBoundingBox) {
650 // TODO: move this normalization into the KoVectorPatternBackground itself
651
652 QList<KoShape*> shapes = pattern->shapes();
653 QList<KoShape*> clonedShapes;
654
655 const QTransform relativeToShape = KisAlgebra2D::mapToRect(outlineRect);
656 const QTransform shapeToRelative = relativeToShape.inverted();
657
658 Q_FOREACH (KoShape *shape, shapes) {
659 KoShape *clone = shape->cloneShape();
660 clone->applyAbsoluteTransformation(shapeToRelative);
661 clonedShapes.append(clone);
662 }
663
664 embedShapes(clonedShapes, context.styleWriter());
665 qDeleteAll(clonedShapes);
666
667 } else {
668 QList<KoShape*> shapes = pattern->shapes();
669 embedShapes(shapes, context.styleWriter());
670 }
671
672 context.styleWriter().endElement(); // pattern
673
674 return uid;
675}
const Params2D p
void embedShapes(const QList< KoShape * > &shapes, KoXmlWriter &outWriter)
QString convertGradientMode(QGradient::CoordinateMode mode)
static QString mimeTypeForSuffix(const QString &suffix)
Find the mimetype for a given extension. The extension may have the form "*.xxx" or "xxx".
Clip path used to clip shapes.
KoFlake::CoordinateSystem coordinates
QList< KoShape * > clipShapes() const
Qt::FillRule clipRule
This class manages a stack of filter effects.
void save(KoXmlWriter &writer, const QString &filterId)
QList< KoFilterEffect * > filterEffects
qreal explicitOrientation
Definition KoMarker.cpp:78
bool hasAutoOrientation
Definition KoMarker.cpp:77
MarkerCoordinateSystem coordinateSystem
Definition KoMarker.cpp:73
static QString coordinateSystemToString(MarkerCoordinateSystem value)
Definition KoMarker.cpp:166
QPointF referencePoint
Definition KoMarker.cpp:74
QList< KoShape * > shapes
Definition KoMarker.cpp:80
QSizeF referenceSize
Definition KoMarker.cpp:75
The position of a path point within a path shape.
Definition KoPathShape.h:63
bool autoFillMarkers() const
KoMarker * marker(KoFlake::MarkerPosition pos) const
Qt::FillRule fillRule() const
Returns the fill rule for the path object.
bool hasMarkers() const
virtual QSizeF size() const
Get the size of the shape in pt.
Definition KoShape.cpp:820
void setName(const QString &name)
Definition KoShape.cpp:1155
virtual QRectF outlineRect() const
Definition KoShape.cpp:637
virtual QVector< PaintOrder > paintOrder() const
paintOrder
Definition KoShape.cpp:773
static QVector< PaintOrder > defaultPaintOrder()
default paint order as per SVG specification
Definition KoShape.cpp:784
virtual KoShapeStrokeModelSP stroke() const
Definition KoShape.cpp:1067
void applyAbsoluteTransformation(const QTransform &matrix)
Definition KoShape.cpp:400
KoClipPath * clipPath() const
Returns the currently set clip path or 0 if there is no clip path set.
Definition KoShape.cpp:1128
bool inheritStroke() const
inheritStroke shows if the shape inherits the stroke from its parent
Definition KoShape.cpp:1098
bool inheritBackground() const
inheritBackground shows if the shape inherits background from its parent
Definition KoShape.cpp:949
QTransform absoluteTransformation() const
Definition KoShape.cpp:382
KoClipMask * clipMask() const
Returns the currently set clip mask or 0 if there is no clip mask set.
Definition KoShape.cpp:1140
virtual KoShape * cloneShape() const
creates a deep copy of the shape or shape's subtree
Definition KoShape.cpp:200
virtual QSharedPointer< KoShapeBackground > background() const
Definition KoShape.cpp:926
QString additionalAttribute(const QString &name) const
Definition KoShape.cpp:1279
KoFilterEffectStack * filterEffectStack() const
Definition KoShape.cpp:1294
bool inheritPaintOrder() const
inheritPaintOrder
Definition KoShape.cpp:795
@ Stroke
Definition KoShape.h:147
@ Markers
Definition KoShape.h:148
bool isVisible(bool recursive=true) const
Definition KoShape.cpp:979
qreal transparency(bool recursive=false) const
Definition KoShape.cpp:730
void addCompleteElement(QIODevice *dev)
void startElement(const char *tagName, bool indentInside=true)
void endElement()
void addAttribute(const char *attrName, const QString &value)
Definition KoXmlWriter.h:61
int numRows() const
int numColumns() const
SvgMeshPatch * getPatch(const int row, const int col) const
SvgMeshGradient::Shading type() const
bool isValid() const
const QScopedPointer< SvgMeshArray > & getMeshArray() const
KoFlake::CoordinateSystem gradientUnits() const
Type
Position of stop in the patch.
SvgMeshStop getStop(Type type) const
returns the starting point of the stop
std::array< QPointF, 4 > getSegment(Type type) const
Get a segment of the path in the meshpatch.
Context for saving svg files.
QString createUID(const QString &base)
Create a unique id from the specified base text.
QScopedPointer< KoXmlWriter > shapeWriter
QScopedPointer< KoXmlWriter > styleWriter
static void saveSvgMarkers(KoShape *shape, SvgSavingContext &context)
Saves markers of the path shape if present.
static void saveSvgFill(QSharedPointer< KoShapeBackground > background, const bool fillRuleEvenOdd, const QRectF outlineRect, const QSizeF size, const QTransform absoluteTransform, SvgSavingContext &context)
Saves fill style of specified shape.
static void saveSvgClipping(KoShape *shape, SvgSavingContext &context)
Saves clipping of specified shape.
static QString embedShape(const KoShape *shape, SvgSavingContext &context)
static void saveSvgColorStops(const QGradientStops &colorStops, SvgSavingContext &context)
Saves gradient color stops.
static void saveSvgBasicStyle(const bool isVisible, const qreal transparency, const QVector< KoShape::PaintOrder > paintOrder, bool inheritPaintorder, SvgSavingContext &context, bool textShape=false)
Saves only stroke, fill and transparency of the shape.
static QString saveSvgGradient(const QGradient *gradient, const QTransform &gradientTransform, SvgSavingContext &context)
Saves gradient.
static void saveSvgStyle(KoShape *shape, SvgSavingContext &context)
Saves the style of the specified shape.
static QString saveSvgMeshGradient(SvgMeshGradient *gradient, const QTransform &transform, SvgSavingContext &context)
static void saveMetadata(const KoShape *shape, SvgSavingContext &context)
static QString saveSvgPattern(QSharedPointer< KoPatternBackground > pattern, const QSizeF shapeSize, const QTransform absoluteTransform, SvgSavingContext &context)
Saves pattern.
static void saveSvgMasking(KoShape *shape, SvgSavingContext &context)
Saves masking of specified shape.
static void saveSvgEffects(KoShape *shape, SvgSavingContext &context)
Saves effects of specified shape.
static QString saveSvgVectorPattern(QSharedPointer< KoVectorPatternBackground > pattern, const QRectF outlineRect, SvgSavingContext &context)
static void saveSvgStroke(KoShapeStrokeModelSP, SvgSavingContext &context)
Saves stroke style of specified shape.
static double toUserSpace(double value)
Definition SvgUtil.cpp:34
static void writeTransformAttributeLazy(const QString &name, const QTransform &transform, KoXmlWriter &shapeWriter)
Writes a transform as an attribute name iff the transform is not empty.
Definition SvgUtil.cpp:124
Implements exporting shapes to SVG.
Definition SvgWriter.h:33
bool saveDetached(QIODevice &outputDevice)
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
T kisRadiansToDegrees(T radians)
Definition kis_global.h:181
QTransform mapToRect(const QRectF &rect)
QString toString(const QString &value)
void setUtf8OnStream(QTextStream &stream)
QString coordinateToString(CoordinateSystem value)
MarkerPosition
Definition KoFlake.h:41
@ EndMarker
Definition KoFlake.h:44
@ StartMarker
Definition KoFlake.h:42
@ MidMarker
Definition KoFlake.h:43
QList< KoShape * > shapes
KoFlake::CoordinateSystem coordinates
KoFlake::CoordinateSystem contentCoordinates
QRectF maskRect
QPointF point