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 }