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