File indexing completed on 2024-05-26 04:26:25

0001 /* This file is part of the KDE project
0002  * SPDX-FileCopyrightText: 2011 Jan Hambrecht <jaham@gmx.net>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "SvgSavingContext.h"
0008 #include "SvgUtil.h"
0009 
0010 #include <KoXmlWriter.h>
0011 #include <KoShape.h>
0012 #include <KoShapeGroup.h>
0013 #include <KoShapeLayer.h>
0014 
0015 #include <QTemporaryFile>
0016 
0017 #include <QImage>
0018 #include <QTransform>
0019 #include <QBuffer>
0020 #include <QHash>
0021 #include <QFile>
0022 #include <QFileInfo>
0023 #include <KisMimeDatabase.h>
0024 
0025 class Q_DECL_HIDDEN SvgSavingContext::Private
0026 {
0027 public:
0028     Private(QIODevice *_mainDevice, QIODevice *_styleDevice)
0029         : mainDevice(_mainDevice)
0030         , styleDevice(_styleDevice)
0031         , styleWriter(0)
0032         , shapeWriter(0)
0033         , saveInlineImages(true)
0034     {
0035         styleWriter.reset(new KoXmlWriter(&styleBuffer, 1));
0036         styleWriter->startElement("defs");
0037         shapeWriter.reset(new KoXmlWriter(&shapeBuffer, 1));
0038 
0039         const qreal scaleToUserSpace = SvgUtil::toUserSpace(1.0);
0040         userSpaceMatrix.scale(scaleToUserSpace, scaleToUserSpace);
0041     }
0042 
0043     ~Private()
0044     {
0045     }
0046 
0047     QIODevice *mainDevice;
0048     QIODevice *styleDevice;
0049     QBuffer styleBuffer;
0050     QBuffer shapeBuffer;
0051     QScopedPointer<KoXmlWriter> styleWriter;
0052     QScopedPointer<KoXmlWriter> shapeWriter;
0053 
0054     QHash<QString, int> uniqueNames;
0055     QHash<const KoShape*, QString> shapeIds;
0056     QTransform userSpaceMatrix;
0057     bool saveInlineImages;
0058     bool strippedTextMode = false;
0059 };
0060 
0061 SvgSavingContext::SvgSavingContext(QIODevice &outputDevice, bool saveInlineImages)
0062     : d(new Private(&outputDevice, 0))
0063 {
0064     d->saveInlineImages = saveInlineImages;
0065 }
0066 
0067 SvgSavingContext::SvgSavingContext(QIODevice &shapesDevice, QIODevice &styleDevice, bool saveInlineImages)
0068     : d(new Private(&shapesDevice, &styleDevice))
0069 {
0070     d->saveInlineImages = saveInlineImages;
0071 }
0072 
0073 SvgSavingContext::~SvgSavingContext()
0074 {
0075     d->styleWriter->endElement();
0076 
0077     if (d->styleDevice) {
0078         d->styleDevice->write(d->styleBuffer.data());
0079     } else {
0080         d->mainDevice->write(d->styleBuffer.data());
0081         d->mainDevice->write("\n");
0082     }
0083 
0084     d->mainDevice->write(d->shapeBuffer.data());
0085 
0086     delete d;
0087 }
0088 
0089 KoXmlWriter &SvgSavingContext::styleWriter()
0090 {
0091     return *d->styleWriter;
0092 }
0093 
0094 KoXmlWriter &SvgSavingContext::shapeWriter()
0095 {
0096     return *d->shapeWriter;
0097 }
0098 
0099 QString SvgSavingContext::createUID(const QString &base)
0100 {
0101     QString idBase = base.isEmpty() ? "defitem" : base;
0102     int counter = d->uniqueNames.value(idBase);
0103     QString res;
0104     do {
0105         res = idBase + QString::number(counter);
0106         counter++;
0107     } while (d->uniqueNames.contains(res));
0108 
0109     d->uniqueNames.insert(idBase, counter);
0110     d->uniqueNames.insert(res, 1);
0111     return res;
0112 }
0113 
0114 QString SvgSavingContext::getID(const KoShape *obj)
0115 {
0116     QString id;
0117     // do we have already an id for this object ?
0118     if (d->shapeIds.contains(obj)) {
0119         // use existing id
0120         id = d->shapeIds[obj];
0121     } else {
0122         // initialize from object name
0123         id = obj->name();
0124         // if object name is not empty and was not used already
0125         // we can use it as is
0126         if (!id.isEmpty() && !d->uniqueNames.contains(id)) {
0127             // add to unique names so it does not get reused
0128             d->uniqueNames.insert(id, 1);
0129         } else {
0130             if (id.isEmpty()) {
0131                 // differentiate a little between shape types
0132                 if (dynamic_cast<const KoShapeGroup*>(obj))
0133                     id = "group";
0134                 else if (dynamic_cast<const KoShapeLayer*>(obj))
0135                     id = "layer";
0136                 else
0137                     id = "shape";
0138             }
0139             // create a completely new id based on object name
0140             // or a generic name
0141             id = createUID(id);
0142         }
0143         // record id for this shape
0144         d->shapeIds.insert(obj, id);
0145     }
0146     return id;
0147 }
0148 
0149 QTransform SvgSavingContext::userSpaceTransform() const
0150 {
0151     return d->userSpaceMatrix;
0152 }
0153 
0154 bool SvgSavingContext::isSavingInlineImages() const
0155 {
0156     return d->saveInlineImages;
0157 }
0158 
0159 QString SvgSavingContext::createFileName(const QString &extension)
0160 {
0161     QFile *file = qobject_cast<QFile*>(d->mainDevice);
0162     if (!file)
0163         return QString();
0164 
0165     QFileInfo fi(file->fileName());
0166     QString path = fi.absolutePath();
0167     QString dstBaseFilename = fi.completeBaseName();
0168 
0169     // create a filename for the image file at the destination directory
0170     QString fname = dstBaseFilename + '_' + createUID("file");
0171 
0172     // check if file exists already
0173     int i = 0;
0174     QString counter;
0175     // change filename as long as the filename already exists
0176     while (QFile(path + fname + counter + extension).exists()) {
0177         counter = QString("_%1").arg(++i);
0178     }
0179 
0180     return fname + counter + extension;
0181 }
0182 
0183 QString SvgSavingContext::saveImage(const QImage &image)
0184 {
0185     if (isSavingInlineImages()) {
0186         QBuffer buffer;
0187         buffer.open(QIODevice::WriteOnly);
0188         if (image.save(&buffer, "PNG")) {
0189             const QString header("data:image/x-png;base64,");
0190             return header + buffer.data().toBase64();
0191         }
0192     } else {
0193         // write to a temp file first
0194         QTemporaryFile imgFile;
0195         if (image.save(&imgFile, "PNG")) {
0196             QString dstFilename = createFileName(".png");
0197             if (QFile::copy(imgFile.fileName(), dstFilename)) {
0198                 return dstFilename;
0199             }
0200             else {
0201                 QFile f(imgFile.fileName());
0202                 f.remove();
0203             }
0204         }
0205     }
0206 
0207     return QString();
0208 }
0209 
0210 void SvgSavingContext::setStrippedTextMode(bool value)
0211 {
0212     d->strippedTextMode = value;
0213 }
0214 
0215 bool SvgSavingContext::strippedTextMode() const
0216 {
0217     return d->strippedTextMode;
0218 }