File indexing completed on 2025-01-26 04:04:59
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 "SvgWriter.h" 0024 0025 #include "SvgUtil.h" 0026 #include "SvgSavingContext.h" 0027 #include "SvgShape.h" 0028 #include "SvgStyleWriter.h" 0029 0030 #include <KoShapeLayer.h> 0031 #include <KoShapeGroup.h> 0032 #include <KoPathShape.h> 0033 #include <KoXmlWriter.h> 0034 #include <KoShapePainter.h> 0035 #include <KoXmlNS.h> 0036 0037 #include <QFile> 0038 #include <QString> 0039 #include <QTextStream> 0040 #include <QBuffer> 0041 #include <QPainter> 0042 #include <QSvgGenerator> 0043 0044 #include <kis_debug.h> 0045 0046 SvgWriter::SvgWriter(const QList<KoShapeLayer*> &layers) 0047 : m_writeInlineImages(true) 0048 { 0049 Q_FOREACH (KoShapeLayer *layer, layers) 0050 m_toplevelShapes.append(layer); 0051 } 0052 0053 SvgWriter::SvgWriter(const QList<KoShape*> &toplevelShapes) 0054 : m_toplevelShapes(toplevelShapes) 0055 , m_writeInlineImages(true) 0056 { 0057 } 0058 0059 SvgWriter::~SvgWriter() 0060 { 0061 0062 } 0063 0064 bool SvgWriter::save(const QString &filename, const QSizeF &pageSize, bool writeInlineImages) 0065 { 0066 QFile fileOut(filename); 0067 if (!fileOut.open(QIODevice::WriteOnly)) 0068 return false; 0069 0070 m_writeInlineImages = writeInlineImages; 0071 0072 const bool success = save(fileOut, pageSize); 0073 0074 m_writeInlineImages = true; 0075 0076 fileOut.close(); 0077 0078 return success; 0079 } 0080 0081 bool SvgWriter::save(QIODevice &outputDevice, const QSizeF &pageSize) 0082 { 0083 if (m_toplevelShapes.isEmpty()) { 0084 return false; 0085 } 0086 0087 QTextStream svgStream(&outputDevice); 0088 svgStream.setCodec("UTF-8"); 0089 0090 // standard header: 0091 svgStream << "<?xml version=\"1.0\" standalone=\"no\"?>" << endl; 0092 svgStream << "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20010904//EN\" "; 0093 svgStream << "\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">" << endl; 0094 0095 // add some PR. one line is more than enough. 0096 svgStream << "<!-- Created using Krita: https://krita.org -->" << endl; 0097 0098 svgStream << "<svg xmlns=\"http://www.w3.org/2000/svg\" \n"; 0099 svgStream << " xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"; 0100 svgStream << QString(" xmlns:krita=\"%1\"\n").arg(KoXmlNS::krita); 0101 svgStream << " xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"\n"; 0102 svgStream << " width=\"" << pageSize.width() << "pt\"\n"; 0103 svgStream << " height=\"" << pageSize.height() << "pt\"\n"; 0104 svgStream << " viewBox=\"0 0 " 0105 << pageSize.width() << " " << pageSize.height() 0106 << "\""; 0107 svgStream << ">" << endl; 0108 0109 if (!m_documentTitle.isNull() && !m_documentTitle.isEmpty()) { 0110 svgStream << "<title>" << m_documentTitle << "</title>" << endl; 0111 } 0112 0113 if (!m_documentDescription.isNull() && !m_documentDescription.isEmpty()) { 0114 svgStream << "<desc>" << m_documentDescription << "</desc>" << endl; 0115 } 0116 0117 { 0118 SvgSavingContext savingContext(outputDevice, m_writeInlineImages); 0119 saveShapes(m_toplevelShapes, savingContext); 0120 } 0121 0122 // end tag: 0123 svgStream << endl << "</svg>" << endl; 0124 0125 return true; 0126 } 0127 0128 bool SvgWriter::saveDetached(QIODevice &outputDevice) 0129 { 0130 if (m_toplevelShapes.isEmpty()) 0131 return false; 0132 0133 SvgSavingContext savingContext(outputDevice, m_writeInlineImages); 0134 saveShapes(m_toplevelShapes, savingContext); 0135 0136 return true; 0137 } 0138 0139 bool SvgWriter::saveDetached(SvgSavingContext &savingContext) 0140 { 0141 if (m_toplevelShapes.isEmpty()) 0142 return false; 0143 0144 saveShapes(m_toplevelShapes, savingContext); 0145 0146 return true; 0147 } 0148 0149 void SvgWriter::saveShapes(const QList<KoShape *> shapes, SvgSavingContext &savingContext) 0150 { 0151 // top level shapes 0152 Q_FOREACH (KoShape *shape, shapes) { 0153 KoShapeLayer *layer = dynamic_cast<KoShapeLayer*>(shape); 0154 if(layer) { 0155 saveLayer(layer, savingContext); 0156 } else { 0157 KoShapeGroup *group = dynamic_cast<KoShapeGroup*>(shape); 0158 if (group) 0159 saveGroup(group, savingContext); 0160 else 0161 saveShape(shape, savingContext); 0162 } 0163 } 0164 } 0165 0166 void SvgWriter::saveLayer(KoShapeLayer *layer, SvgSavingContext &context) 0167 { 0168 context.shapeWriter().startElement("g"); 0169 context.shapeWriter().addAttribute("id", context.getID(layer)); 0170 0171 QList<KoShape*> sortedShapes = layer->shapes(); 0172 std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); 0173 0174 Q_FOREACH (KoShape * shape, sortedShapes) { 0175 KoShapeGroup * group = dynamic_cast<KoShapeGroup*>(shape); 0176 if (group) 0177 saveGroup(group, context); 0178 else 0179 saveShape(shape, context); 0180 } 0181 0182 context.shapeWriter().endElement(); 0183 } 0184 0185 void SvgWriter::saveGroup(KoShapeGroup * group, SvgSavingContext &context) 0186 { 0187 context.shapeWriter().startElement("g"); 0188 context.shapeWriter().addAttribute("id", context.getID(group)); 0189 0190 SvgUtil::writeTransformAttributeLazy("transform", group->transformation(), context.shapeWriter()); 0191 0192 SvgStyleWriter::saveSvgStyle(group, context); 0193 0194 QList<KoShape*> sortedShapes = group->shapes(); 0195 std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); 0196 0197 Q_FOREACH (KoShape * shape, sortedShapes) { 0198 KoShapeGroup * childGroup = dynamic_cast<KoShapeGroup*>(shape); 0199 if (childGroup) 0200 saveGroup(childGroup, context); 0201 else 0202 saveShape(shape, context); 0203 } 0204 0205 context.shapeWriter().endElement(); 0206 } 0207 0208 void SvgWriter::saveShape(KoShape *shape, SvgSavingContext &context) 0209 { 0210 SvgShape *svgShape = dynamic_cast<SvgShape*>(shape); 0211 if (svgShape && svgShape->saveSvg(context)) 0212 return; 0213 0214 KoPathShape * path = dynamic_cast<KoPathShape*>(shape); 0215 if (path) { 0216 savePath(path, context); 0217 } else { 0218 // generic saving of shape via a switch element 0219 saveGeneric(shape, context); 0220 } 0221 } 0222 0223 void SvgWriter::savePath(KoPathShape *path, SvgSavingContext &context) 0224 { 0225 context.shapeWriter().startElement("path"); 0226 context.shapeWriter().addAttribute("id", context.getID(path)); 0227 0228 SvgUtil::writeTransformAttributeLazy("transform", path->transformation(), context.shapeWriter()); 0229 0230 SvgStyleWriter::saveSvgStyle(path, context); 0231 0232 context.shapeWriter().addAttribute("d", path->toString(context.userSpaceTransform())); 0233 context.shapeWriter().addAttribute("sodipodi:nodetypes", path->nodeTypes()); 0234 context.shapeWriter().endElement(); 0235 } 0236 0237 void SvgWriter::saveGeneric(KoShape *shape, SvgSavingContext &context) 0238 { 0239 KIS_SAFE_ASSERT_RECOVER_RETURN(shape); 0240 0241 const QRectF bbox = shape->boundingRect(); 0242 0243 // paint shape to the image 0244 KoShapePainter painter; 0245 painter.setShapes(QList<KoShape*>()<< shape); 0246 0247 // generate svg from shape 0248 QBuffer svgBuffer; 0249 QSvgGenerator svgGenerator; 0250 svgGenerator.setOutputDevice(&svgBuffer); 0251 0252 /** 0253 * HACK ALERT: Qt (and Krita 3.x) has a weird bug, it assumes that all font sizes are 0254 * defined in 96 ppi resolution, even though your the resolution in QSvgGenerator 0255 * is manually set to 72 ppi. So here we do a tricky thing: we set a fake resolution 0256 * to (72 * 72 / 96) = 54 ppi, which guarantees that the text, when painted in 96 ppi, 0257 * will be actually painted in 72 ppi. 0258 * 0259 * BUG: 389802 0260 */ 0261 if (shape->shapeId() == "TextShapeID") { 0262 svgGenerator.setResolution(54); 0263 } 0264 0265 QPainter svgPainter; 0266 svgPainter.begin(&svgGenerator); 0267 painter.paint(svgPainter, SvgUtil::toUserSpace(bbox).toRect(), bbox); 0268 svgPainter.end(); 0269 0270 // remove anything before the start of the svg element from the buffer 0271 int startOfContent = svgBuffer.buffer().indexOf("<svg"); 0272 if(startOfContent>0) { 0273 svgBuffer.buffer().remove(0, startOfContent); 0274 } 0275 0276 // check if painting to svg produced any output 0277 if (svgBuffer.buffer().isEmpty()) { 0278 // prepare a transparent image, make it twice as big as the original size 0279 QImage image(2*bbox.size().toSize(), QImage::Format_ARGB32); 0280 image.fill(0); 0281 painter.paint(image); 0282 0283 context.shapeWriter().startElement("image"); 0284 context.shapeWriter().addAttribute("id", context.getID(shape)); 0285 context.shapeWriter().addAttribute("x", bbox.x()); 0286 context.shapeWriter().addAttribute("y", bbox.y()); 0287 context.shapeWriter().addAttribute("width", bbox.width()); 0288 context.shapeWriter().addAttribute("height", bbox.height()); 0289 context.shapeWriter().addAttribute("xlink:href", context.saveImage(image)); 0290 context.shapeWriter().endElement(); // image 0291 0292 } else { 0293 context.shapeWriter().addCompleteElement(&svgBuffer); 0294 } 0295 0296 // TODO: once we support saving single (flat) odf files 0297 // we can embed these here to have full support for generic shapes 0298 } 0299 0300 void SvgWriter::setDocumentTitle(QString title) 0301 { 0302 m_documentTitle = title; 0303 } 0304 0305 void SvgWriter::setDocumentDescription(QString description) 0306 { 0307 m_documentDescription = description; 0308 } 0309