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 <KoColorBackground.h>
33#include <KoPatternBackground.h>
35#include <KoShapeStroke.h>
36#include <KoClipPath.h>
37#include <KoClipMask.h>
38#include <KoMarker.h>
39#include <KoXmlWriter.h>
40
41#include <QBuffer>
42#include <QGradient>
43#include <QLinearGradient>
44#include <QRadialGradient>
45#include <KisMimeDatabase.h>
46#include "kis_dom_utils.h"
47#include "kis_algebra_2d.h"
48#include <SvgWriter.h>
50
51
53{
54 saveSvgBasicStyle(shape->isVisible(false), shape->transparency(false), shape->paintOrder(), shape->inheritPaintOrder(), context);
55
56 KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
57 bool fillRule = pathShape && pathShape->background() && pathShape->fillRule() == Qt::OddEvenFill? true: false;
58 if (!shape->inheritBackground()) {
59 saveSvgFill(shape->background(), fillRule, shape->outlineRect(), shape->size(), shape->absoluteTransformation(), context);
60 }
61 if (!shape->inheritStroke()) {
62 saveSvgStroke(shape->stroke(), context);
63 }
64
65 saveSvgClipping(shape, context);
66 saveSvgMasking(shape, context);
67 saveSvgMarkers(shape, context);
68}
69
70void SvgStyleWriter::saveSvgBasicStyle(const bool isVisible, const qreal transparency, const QVector<KoShape::PaintOrder> paintOrder, bool inheritPaintorder, SvgSavingContext &context, bool textShape)
71{
72 if (!isVisible) {
73 context.shapeWriter().addAttribute("display", "none");
74 } else if (transparency > 0.0) {
75 context.shapeWriter().addAttribute("opacity", 1.0 - transparency);
76 }
77 if (!paintOrder.isEmpty() && !inheritPaintorder) {
78
79 const bool notDefault = paintOrder != KoShape::defaultPaintOrder();
80
81 if ((!textShape && notDefault) || textShape) {
82 QStringList order;
83 Q_FOREACH(const KoShape::PaintOrder p, paintOrder) {
84 if (p == KoShape::Fill) {
85 order.append("fill");
86 } else if (p == KoShape::Stroke) {
87 order.append("stroke");
88 } else if (p == KoShape::Markers) {
89 order.append("markers");
90 }
91 }
92 context.shapeWriter().addAttribute("paint-order", order.join(" "));
93 }
94 }
95
96}
97
98void SvgStyleWriter::saveSvgFill(QSharedPointer<KoShapeBackground> background, const bool fillRuleEvenOdd, const QRectF outlineRect, const QSizeF size, const QTransform absoluteTransform, SvgSavingContext &context)
99{
100 if (! background) {
101 context.shapeWriter().addAttribute("fill", "none");
102 }
103
104 QBrush fill(Qt::NoBrush);
105 QSharedPointer<KoColorBackground> cbg = qSharedPointerDynamicCast<KoColorBackground>(background);
106 if (cbg) {
107 context.shapeWriter().addAttribute("fill", cbg->color().name());
108 if (cbg->color().alphaF() < 1.0)
109 context.shapeWriter().addAttribute("fill-opacity", cbg->color().alphaF());
110 }
111 QSharedPointer<KoGradientBackground> gbg = qSharedPointerDynamicCast<KoGradientBackground>(background);
112 if (gbg) {
113 QString gradientId = saveSvgGradient(gbg->gradient(), gbg->transform(), context);
114 context.shapeWriter().addAttribute("fill", "url(#" + gradientId + ")");
115 }
116 QSharedPointer<KoMeshGradientBackground> mgbg = qSharedPointerDynamicCast<KoMeshGradientBackground>(background);
117 if (mgbg) {
118 QString gradientId = saveSvgMeshGradient(mgbg->gradient(), mgbg->transform(), context);
119 context.shapeWriter().addAttribute("fill", "url(#" + gradientId + ")");
120 }
121 QSharedPointer<KoPatternBackground> pbg = qSharedPointerDynamicCast<KoPatternBackground>(background);
122 if (pbg) {
123 const QString patternId = saveSvgPattern(pbg, size, absoluteTransform, context);
124 context.shapeWriter().addAttribute("fill", "url(#" + patternId + ")");
125 }
126 QSharedPointer<KoVectorPatternBackground> vpbg = qSharedPointerDynamicCast<KoVectorPatternBackground>(background);
127 if (vpbg) {
128 const QString patternId = saveSvgVectorPattern(vpbg, outlineRect, context);
129 context.shapeWriter().addAttribute("fill", "url(#" + patternId + ")");
130 }
131
132 // non-zero is default, so only write fillrule if evenodd is set
133 if (fillRuleEvenOdd)
134 context.shapeWriter().addAttribute("fill-rule", "evenodd");
135}
136
138{
139 const QSharedPointer<KoShapeStroke> lineBorder = qSharedPointerDynamicCast<KoShapeStroke>(stroke);
140
141 if (! lineBorder)
142 return;
143
144 QString strokeStr("none");
145 if (lineBorder->lineBrush().gradient()) {
146 QString gradientId = saveSvgGradient(lineBorder->lineBrush().gradient(), lineBorder->lineBrush().transform(), context);
147 strokeStr = "url(#" + gradientId + ")";
148 } else {
149 if (lineBorder->color().isValid()) {
150 strokeStr = lineBorder->color().name();
151 }
152 if (lineBorder->color().alphaF() < 1.0) {
153 context.shapeWriter().addAttribute("stroke-opacity", lineBorder->color().alphaF());
154 }
155 }
156 if (!strokeStr.isEmpty())
157 context.shapeWriter().addAttribute("stroke", strokeStr);
158
159 context.shapeWriter().addAttribute("stroke-width", SvgUtil::toUserSpace(lineBorder->lineWidth()));
160
161 if (lineBorder->capStyle() == Qt::FlatCap)
162 context.shapeWriter().addAttribute("stroke-linecap", "butt");
163 else if (lineBorder->capStyle() == Qt::RoundCap)
164 context.shapeWriter().addAttribute("stroke-linecap", "round");
165 else if (lineBorder->capStyle() == Qt::SquareCap)
166 context.shapeWriter().addAttribute("stroke-linecap", "square");
167
168 if (lineBorder->joinStyle() == Qt::MiterJoin) {
169 context.shapeWriter().addAttribute("stroke-linejoin", "miter");
170 context.shapeWriter().addAttribute("stroke-miterlimit", lineBorder->miterLimit());
171 } else if (lineBorder->joinStyle() == Qt::RoundJoin)
172 context.shapeWriter().addAttribute("stroke-linejoin", "round");
173 else if (lineBorder->joinStyle() == Qt::BevelJoin)
174 context.shapeWriter().addAttribute("stroke-linejoin", "bevel");
175
176 // dash
177 if (lineBorder->lineStyle() > Qt::SolidLine) {
178 qreal dashFactor = lineBorder->lineWidth();
179
180 if (lineBorder->dashOffset() != 0)
181 context.shapeWriter().addAttribute("stroke-dashoffset", dashFactor * lineBorder->dashOffset());
182
183 QString dashStr;
184 const QVector<qreal> dashes = lineBorder->lineDashes();
185 int dashCount = dashes.size();
186 for (int i = 0; i < dashCount; ++i) {
187 if (i > 0)
188 dashStr += ",";
189 dashStr += QString("%1").arg(KisDomUtils::toString(dashes[i] * dashFactor));
190 }
191 context.shapeWriter().addAttribute("stroke-dasharray", dashStr);
192 }
193}
194
195void embedShapes(const QList<KoShape*> &shapes, KoXmlWriter &outWriter)
196{
197 QBuffer buffer;
198 buffer.open(QIODevice::WriteOnly);
199 {
200 SvgWriter shapesWriter(shapes);
201 shapesWriter.saveDetached(buffer);
202 }
203 buffer.close();
204 outWriter.addCompleteElement(&buffer);
205}
206
208{
209 QList<KoShape *> shapes;
210 KoShape* clonedShape = shape->cloneShape();
211 if (!clonedShape) {
212 return QString();
213 }
214 const QString uid = context.createUID("path");
215 clonedShape->setName(uid);
216 shapes.append(clonedShape);
217 embedShapes(shapes, context.styleWriter());
218 return uid;
219}
220
222{
223 const QString title = shape->additionalAttribute("title");
224 if (!title.trimmed().isEmpty()) {
225 context.shapeWriter().startElement("title");
226 context.shapeWriter().addTextNode(title);
227 context.shapeWriter().endElement();
228 }
229 const QString desc = shape->additionalAttribute("desc");
230 if (!desc.trimmed().isEmpty()) {
231 context.shapeWriter().startElement("desc");
232 context.shapeWriter().addTextNode(desc);
233 context.shapeWriter().endElement();
234 }
235}
236
238{
239 KoClipPath *clipPath = shape->clipPath();
240 if (!clipPath)
241 return;
242
243 const QString uid = context.createUID("clippath");
244
245 context.styleWriter().startElement("clipPath");
246 context.styleWriter().addAttribute("id", uid);
247 context.styleWriter().addAttribute("clipPathUnits", KoFlake::coordinateToString(clipPath->coordinates()));
248
249 embedShapes(clipPath->clipShapes(), context.styleWriter());
250
251 context.styleWriter().endElement(); // clipPath
252
253 context.shapeWriter().addAttribute("clip-path", "url(#" + uid + ")");
254 if (clipPath->clipRule() != Qt::WindingFill)
255 context.shapeWriter().addAttribute("clip-rule", "evenodd");
256}
257
259{
260 KoClipMask*clipMask = shape->clipMask();
261 if (!clipMask)
262 return;
263
264 const QString uid = context.createUID("clipmask");
265
266 context.styleWriter().startElement("mask");
267 context.styleWriter().addAttribute("id", uid);
268 context.styleWriter().addAttribute("maskUnits", KoFlake::coordinateToString(clipMask->coordinates()));
269 context.styleWriter().addAttribute("maskContentUnits", KoFlake::coordinateToString(clipMask->contentCoordinates()));
270
271 const QRectF rect = clipMask->maskRect();
272
273 context.styleWriter().addAttribute("x", rect.x());
274 context.styleWriter().addAttribute("y", rect.y());
275 context.styleWriter().addAttribute("width", rect.width());
276 context.styleWriter().addAttribute("height", rect.height());
277
278 embedShapes(clipMask->shapes(), context.styleWriter());
279
280 context.styleWriter().endElement(); // clipMask
281
282 context.shapeWriter().addAttribute("mask", "url(#" + uid + ")");
283}
284
285namespace {
286void writeMarkerStyle(KoXmlWriter &styleWriter, const KoMarker *marker, const QString &assignedId) {
287
288 styleWriter.startElement("marker");
289 styleWriter.addAttribute("id", assignedId);
290 styleWriter.addAttribute("markerUnits", KoMarker::coordinateSystemToString(marker->coordinateSystem()));
291
292 const QPointF refPoint = marker->referencePoint();
293 styleWriter.addAttribute("refX", refPoint.x());
294 styleWriter.addAttribute("refY", refPoint.y());
295
296 const QSizeF refSize = marker->referenceSize();
297 styleWriter.addAttribute("markerWidth", refSize.width());
298 styleWriter.addAttribute("markerHeight", refSize.height());
299
300
301 if (marker->hasAutoOrientation()) {
302 styleWriter.addAttribute("orient", "auto");
303 } else {
304 // no suffix means 'degrees'
305 styleWriter.addAttribute("orient", kisRadiansToDegrees(marker->explicitOrientation()));
306 }
307
308 embedShapes(marker->shapes(), styleWriter);
309
310 styleWriter.endElement(); // marker
311}
312
313void tryEmbedMarker(const KoPathShape *pathShape,
314 const QString &markerTag,
315 KoFlake::MarkerPosition markerPosition,
316 SvgSavingContext &context)
317{
318 KoMarker *marker = pathShape->marker(markerPosition);
319
320 if (marker) {
321 const QString uid = context.createUID("lineMarker");
322 writeMarkerStyle(context.styleWriter(), marker, uid);
323 context.shapeWriter().addAttribute(markerTag.toLatin1().data(), "url(#" + uid + ")");
324 }
325}
326
327}
328
330{
331 KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
332 if (!pathShape || !pathShape->hasMarkers()) return;
333
334
335 tryEmbedMarker(pathShape, "marker-start", KoFlake::StartMarker, context);
336 tryEmbedMarker(pathShape, "marker-mid", KoFlake::MidMarker, context);
337 tryEmbedMarker(pathShape, "marker-end", KoFlake::EndMarker, context);
338
339 if (pathShape->autoFillMarkers()) {
340 context.shapeWriter().addAttribute("krita:marker-fill-method", "auto");
341 }
342}
343
344void SvgStyleWriter::saveSvgColorStops(const QGradientStops &colorStops, SvgSavingContext &context)
345{
346 Q_FOREACH (const QGradientStop &stop, colorStops) {
347 context.styleWriter().startElement("stop");
348 context.styleWriter().addAttribute("stop-color", stop.second.name());
349 context.styleWriter().addAttribute("offset", stop.first);
350 context.styleWriter().addAttribute("stop-opacity", stop.second.alphaF());
351 context.styleWriter().endElement();
352 }
353}
354
355inline QString convertGradientMode(QGradient::CoordinateMode mode) {
356 KIS_ASSERT_RECOVER_NOOP(mode != QGradient::StretchToDeviceMode);
357
358 return
359 mode == QGradient::ObjectBoundingMode ?
360 "objectBoundingBox" :
361 "userSpaceOnUse";
362
363}
364
365QString SvgStyleWriter::saveSvgGradient(const QGradient *gradient, const QTransform &gradientTransform, SvgSavingContext &context)
366{
367 if (! gradient)
368 return QString();
369
370 const QString spreadMethod[3] = {
371 QString("pad"),
372 QString("reflect"),
373 QString("repeat")
374 };
375
376 const QString uid = context.createUID("gradient");
377
378 if (gradient->type() == QGradient::LinearGradient) {
379 const QLinearGradient * g = static_cast<const QLinearGradient*>(gradient);
380 context.styleWriter().startElement("linearGradient");
381 context.styleWriter().addAttribute("id", uid);
382 SvgUtil::writeTransformAttributeLazy("gradientTransform", gradientTransform, context.styleWriter());
383 context.styleWriter().addAttribute("gradientUnits", convertGradientMode(g->coordinateMode()));
384 context.styleWriter().addAttribute("x1", g->start().x());
385 context.styleWriter().addAttribute("y1", g->start().y());
386 context.styleWriter().addAttribute("x2", g->finalStop().x());
387 context.styleWriter().addAttribute("y2", g->finalStop().y());
388 context.styleWriter().addAttribute("spreadMethod", spreadMethod[g->spread()]);
389 // color stops
390 saveSvgColorStops(gradient->stops(), context);
391 context.styleWriter().endElement();
392 } else if (gradient->type() == QGradient::RadialGradient) {
393 const QRadialGradient * g = static_cast<const QRadialGradient*>(gradient);
394 context.styleWriter().startElement("radialGradient");
395 context.styleWriter().addAttribute("id", uid);
396 SvgUtil::writeTransformAttributeLazy("gradientTransform", gradientTransform, context.styleWriter());
397 context.styleWriter().addAttribute("gradientUnits", convertGradientMode(g->coordinateMode()));
398 context.styleWriter().addAttribute("cx", g->center().x());
399 context.styleWriter().addAttribute("cy", g->center().y());
400 context.styleWriter().addAttribute("fx", g->focalPoint().x());
401 context.styleWriter().addAttribute("fy", g->focalPoint().y());
402 context.styleWriter().addAttribute("r", g->radius());
403 context.styleWriter().addAttribute("spreadMethod", spreadMethod[g->spread()]);
404 // color stops
405 saveSvgColorStops(gradient->stops(), context);
406 context.styleWriter().endElement();
407 } else if (gradient->type() == QGradient::ConicalGradient) {
408 //const QConicalGradient * g = static_cast<const QConicalGradient*>( gradient );
409 // fake conical grad as radial.
410 // fugly but better than data loss.
411 /*
412 printIndentation( m_defs, m_indent2 );
413 *m_defs << "<radialGradient id=\"" << uid << "\" ";
414 *m_defs << "gradientUnits=\"userSpaceOnUse\" ";
415 *m_defs << "cx=\"" << g->center().x() << "\" ";
416 *m_defs << "cy=\"" << g->center().y() << "\" ";
417 *m_defs << "fx=\"" << grad.focalPoint().x() << "\" ";
418 *m_defs << "fy=\"" << grad.focalPoint().y() << "\" ";
419 double r = sqrt( pow( grad.vector().x() - grad.origin().x(), 2 ) + pow( grad.vector().y() - grad.origin().y(), 2 ) );
420 *m_defs << "r=\"" << QString().setNum( r ) << "\" ";
421 *m_defs << spreadMethod[g->spread()];
422 *m_defs << ">" << Qt::endl;
423
424 // color stops
425 getColorStops( gradient->stops() );
426
427 printIndentation( m_defs, m_indent2 );
428 *m_defs << "</radialGradient>" << Qt::endl;
429 *m_body << "url(#" << uid << ")";
430 */
431 }
432
433 return uid;
434}
435
437 const QTransform& transform,
438 SvgSavingContext &context)
439{
440 if (!gradient || !gradient->isValid())
441 return QString();
442
443 const QString uid = context.createUID("meshgradient");
444 context.styleWriter().startElement("meshgradient");
445 context.styleWriter().addAttribute("id", uid);
446
447 if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) {
448 context.styleWriter().addAttribute("gradientUnits", "objectBoundingBox");
449 } else {
450 context.styleWriter().addAttribute("gradientUnits", "userSpaceOnUse");
451 }
452
453 SvgUtil::writeTransformAttributeLazy("transform", transform, context.styleWriter());
454
455 SvgMeshArray *mesharray = gradient->getMeshArray().data();
456 QPointF start = mesharray->getPatch(0, 0)->getStop(SvgMeshPatch::Top).point;
457
458 context.styleWriter().addAttribute("x", start.x());
459 context.styleWriter().addAttribute("y", start.y());
460
461 if (gradient->type() == SvgMeshGradient::BILINEAR) {
462 context.styleWriter().addAttribute("type", "bilinear");
463 } else {
464 context.styleWriter().addAttribute("type", "bicubic");
465 }
466
467 for (int row = 0; row < mesharray->numRows(); ++row) {
468
469 const QString uid = context.createUID("meshrow");
470 context.styleWriter().startElement("meshrow");
471 context.styleWriter().addAttribute("id", uid);
472
473 for (int col = 0; col < mesharray->numColumns(); ++col) {
474
475 const QString uid = context.createUID("meshpatch");
476 context.styleWriter().startElement("meshpatch");
477 context.styleWriter().addAttribute("id", uid);
478
479 SvgMeshPatch *patch = mesharray->getPatch(row, col);
480
481 for (int s = 0; s < 4; ++s) {
482 SvgMeshPatch::Type type = static_cast<SvgMeshPatch::Type> (s);
483
484 // only first row and first col have Top and Left stop, respectively
485 if ((row != 0 && s == SvgMeshPatch::Top) ||
486 (col != 0 && s == SvgMeshPatch::Left)) {
487 continue;
488 }
489
490 context.styleWriter().startElement("stop");
491
492 std::array<QPointF, 4> segment = patch->getSegment(type);
493
494 QString pathstr;
495 QTextStream stream(&pathstr);
497
498 stream.setRealNumberPrecision(10);
499 // TODO: other path type?
500 stream << "C "
501 << segment[1].x() << "," << segment[1].y() << " "
502 << segment[2].x() << "," << segment[2].y() << " "
503 << segment[3].x() << "," << segment[3].y(); // I don't see any harm, inkscape does this too
504
505 context.styleWriter().addAttribute("path", pathstr);
506
507 // don't add color/opacity if stop is in first row and stop == Top (or)
508 // don't add color/opacity if stop is not in first row and stop == Right
509 if ((row != 0 || col == 0 || s != SvgMeshPatch::Top) &&
510 (row == 0 || s != SvgMeshPatch::Right)) {
511
512 SvgMeshStop stop = patch->getStop(type);
513 context.styleWriter().addAttribute("stop-color", stop.color.name());
514 context.styleWriter().addAttribute("stop-opacity", stop.color.alphaF());
515 }
516
517 context.styleWriter().endElement(); // stop
518 }
519
520 context.styleWriter().endElement(); // meshpatch
521 }
522 context.styleWriter().endElement(); // meshrow
523 }
524 context.styleWriter().endElement(); // meshgradient
525
526 return uid;
527}
528
529QString SvgStyleWriter::saveSvgPattern(QSharedPointer<KoPatternBackground> pattern, const QSizeF shapeSize, const QTransform absoluteTransform, SvgSavingContext &context)
530{
531 const QString uid = context.createUID("pattern");
532
533 const QSizeF patternSize = pattern->patternDisplaySize();
534 const QSize imageSize = pattern->pattern().size();
535
536 // calculate offset in point
537 QPointF offset = pattern->referencePointOffset();
538 offset.rx() = 0.01 * offset.x() * patternSize.width();
539 offset.ry() = 0.01 * offset.y() * patternSize.height();
540
541 // now take the reference point into account
542 switch (pattern->referencePoint()) {
544 break;
546 offset += QPointF(0.5 * shapeSize.width(), 0.0);
547 break;
549 offset += QPointF(shapeSize.width(), 0.0);
550 break;
552 offset += QPointF(0.0, 0.5 * shapeSize.height());
553 break;
555 offset += QPointF(0.5 * shapeSize.width(), 0.5 * shapeSize.height());
556 break;
558 offset += QPointF(shapeSize.width(), 0.5 * shapeSize.height());
559 break;
561 offset += QPointF(0.0, shapeSize.height());
562 break;
564 offset += QPointF(0.5 * shapeSize.width(), shapeSize.height());
565 break;
567 offset += QPointF(shapeSize.width(), shapeSize.height());
568 break;
569 }
570
571 offset = absoluteTransform.map(offset);
572
573 context.styleWriter().startElement("pattern");
574 context.styleWriter().addAttribute("id", uid);
575 context.styleWriter().addAttribute("x", SvgUtil::toUserSpace(offset.x()));
576 context.styleWriter().addAttribute("y", SvgUtil::toUserSpace(offset.y()));
577
578 if (pattern->repeat() == KoPatternBackground::Stretched) {
579 context.styleWriter().addAttribute("width", "100%");
580 context.styleWriter().addAttribute("height", "100%");
581 context.styleWriter().addAttribute("patternUnits", "objectBoundingBox");
582 } else {
583 context.styleWriter().addAttribute("width", SvgUtil::toUserSpace(patternSize.width()));
584 context.styleWriter().addAttribute("height", SvgUtil::toUserSpace(patternSize.height()));
585 context.styleWriter().addAttribute("patternUnits", "userSpaceOnUse");
586 }
587
588 context.styleWriter().addAttribute("viewBox", QString("0 0 %1 %2").arg(KisDomUtils::toString(imageSize.width())).arg(KisDomUtils::toString(imageSize.height())));
589 //*m_defs << " patternContentUnits=\"userSpaceOnUse\"";
590
591 context.styleWriter().startElement("image");
592 context.styleWriter().addAttribute("x", "0");
593 context.styleWriter().addAttribute("y", "0");
594 context.styleWriter().addAttribute("width", QString("%1px").arg(KisDomUtils::toString(imageSize.width())));
595 context.styleWriter().addAttribute("height", QString("%1px").arg(KisDomUtils::toString(imageSize.height())));
596
597 QBuffer buffer;
598 buffer.open(QIODevice::WriteOnly);
599 if (pattern->pattern().save(&buffer, "PNG")) {
600 const QString mimeType = KisMimeDatabase::mimeTypeForSuffix("*.png");
601 context.styleWriter().addAttribute("xlink:href", "data:"+ mimeType + ";base64," + buffer.data().toBase64());
602 }
603
604 context.styleWriter().endElement(); // image
605 context.styleWriter().endElement(); // pattern
606
607 return uid;
608}
609
611{
612 const QString uid = context.createUID("pattern");
613
614 context.styleWriter().startElement("pattern");
615 context.styleWriter().addAttribute("id", uid);
616
617 context.styleWriter().addAttribute("patternUnits", KoFlake::coordinateToString(pattern->referenceCoordinates()));
618 context.styleWriter().addAttribute("patternContentUnits", KoFlake::coordinateToString(pattern->contentCoordinates()));
619
620 const QRectF rect = pattern->referenceRect();
621
622 context.styleWriter().addAttribute("x", rect.x());
623 context.styleWriter().addAttribute("y", rect.y());
624 context.styleWriter().addAttribute("width", rect.width());
625 context.styleWriter().addAttribute("height", rect.height());
626
627 SvgUtil::writeTransformAttributeLazy("patternTransform", pattern->patternTransform(), context.styleWriter());
628
629 if (pattern->contentCoordinates() == KoFlake::ObjectBoundingBox) {
630 // TODO: move this normalization into the KoVectorPatternBackground itself
631
632 QList<KoShape*> shapes = pattern->shapes();
633 QList<KoShape*> clonedShapes;
634
635 const QTransform relativeToShape = KisAlgebra2D::mapToRect(outlineRect);
636 const QTransform shapeToRelative = relativeToShape.inverted();
637
638 Q_FOREACH (KoShape *shape, shapes) {
639 KoShape *clone = shape->cloneShape();
640 clone->applyAbsoluteTransformation(shapeToRelative);
641 clonedShapes.append(clone);
642 }
643
644 embedShapes(clonedShapes, context.styleWriter());
645 qDeleteAll(clonedShapes);
646
647 } else {
648 QList<KoShape*> shapes = pattern->shapes();
649 embedShapes(shapes, context.styleWriter());
650 }
651
652 context.styleWriter().endElement(); // pattern
653
654 return uid;
655}
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
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:740
void setName(const QString &name)
Definition KoShape.cpp:960
virtual QRectF outlineRect() const
Definition KoShape.cpp:566
virtual QVector< PaintOrder > paintOrder() const
paintOrder
Definition KoShape.cpp:693
static QVector< PaintOrder > defaultPaintOrder()
default paint order as per SVG specification
Definition KoShape.cpp:704
virtual KoShapeStrokeModelSP stroke() const
Definition KoShape.cpp:890
void applyAbsoluteTransformation(const QTransform &matrix)
Definition KoShape.cpp:353
KoClipPath * clipPath() const
Returns the currently set clip path or 0 if there is no clip path set.
Definition KoShape.cpp:933
bool inheritStroke() const
inheritStroke shows if the shape inherits the stroke from its parent
Definition KoShape.cpp:921
bool inheritBackground() const
inheritBackground shows if the shape inherits background from its parent
Definition KoShape.cpp:782
QTransform absoluteTransformation() const
Definition KoShape.cpp:335
KoClipMask * clipMask() const
Returns the currently set clip mask or 0 if there is no clip mask set.
Definition KoShape.cpp:945
virtual KoShape * cloneShape() const
creates a deep copy of the shape or shape's subtree
Definition KoShape.cpp:172
virtual QSharedPointer< KoShapeBackground > background() const
Definition KoShape.cpp:759
QString additionalAttribute(const QString &name) const
Definition KoShape.cpp:1092
bool inheritPaintOrder() const
inheritPaintOrder
Definition KoShape.cpp:715
@ Stroke
Definition KoShape.h:117
@ Markers
Definition KoShape.h:118
bool isVisible(bool recursive=true) const
Definition KoShape.cpp:802
qreal transparency(bool recursive=false) const
Definition KoShape.cpp:650
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 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