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