1 /*
2  *  Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "kis_asl_xml_writer.h"
20 
21 #include <QDomDocument>
22 #include <QColor>
23 #include <QPointF>
24 #include <QUuid>
25 #include <QBuffer>
26 
27 #include <resources/KoPattern.h>
28 #include <resources/KoSegmentGradient.h>
29 #include <resources/KoStopGradient.h>
30 
31 #include <cfloat>
32 
33 #include "kis_dom_utils.h"
34 #include "kis_asl_writer_utils.h"
35 
36 struct KisAslXmlWriter::Private
37 {
38     QDomDocument document;
39     QDomElement currentElement;
40 };
41 
42 
KisAslXmlWriter()43 KisAslXmlWriter::KisAslXmlWriter()
44     : m_d(new Private)
45 {
46     QDomElement el = m_d->document.createElement("asl");
47     m_d->document.appendChild(el);
48     m_d->currentElement = el;
49 }
50 
~KisAslXmlWriter()51 KisAslXmlWriter::~KisAslXmlWriter()
52 {
53 }
54 
document() const55 QDomDocument KisAslXmlWriter::document() const
56 {
57     if (m_d->document.documentElement() != m_d->currentElement) {
58         warnKrita << "KisAslXmlWriter::document(): unbalanced enter/leave descriptor/array";
59     }
60 
61     return m_d->document;
62 }
63 
enterDescriptor(const QString & key,const QString & name,const QString & classId)64 void KisAslXmlWriter::enterDescriptor(const QString &key, const QString &name, const QString &classId)
65 {
66     QDomElement el = m_d->document.createElement("node");
67 
68     if (!key.isEmpty()) {
69         el.setAttribute("key", key);
70     }
71 
72     el.setAttribute("type", "Descriptor");
73     el.setAttribute("name", name);
74     el.setAttribute("classId", classId);
75 
76     m_d->currentElement.appendChild(el);
77     m_d->currentElement = el;
78 }
79 
leaveDescriptor()80 void KisAslXmlWriter::leaveDescriptor()
81 {
82     if (!m_d->currentElement.parentNode().toElement().isNull()) {
83         m_d->currentElement = m_d->currentElement.parentNode().toElement();
84     } else {
85         warnKrita << "KisAslXmlWriter::leaveDescriptor(): unbalanced enter/leave descriptor";
86     }
87 }
88 
enterList(const QString & key)89 void KisAslXmlWriter::enterList(const QString &key)
90 {
91     QDomElement el = m_d->document.createElement("node");
92 
93     if (!key.isEmpty()) {
94         el.setAttribute("key", key);
95     }
96 
97     el.setAttribute("type", "List");
98 
99     m_d->currentElement.appendChild(el);
100     m_d->currentElement = el;
101 }
102 
leaveList()103 void KisAslXmlWriter::leaveList()
104 {
105     if (!m_d->currentElement.parentNode().toElement().isNull()) {
106         m_d->currentElement = m_d->currentElement.parentNode().toElement();
107     } else {
108         warnKrita << "KisAslXmlWriter::leaveList(): unbalanced enter/leave list";
109     }
110 }
111 
writeDouble(const QString & key,double value)112 void KisAslXmlWriter::writeDouble(const QString &key, double value)
113 {
114     QDomElement el = m_d->document.createElement("node");
115 
116     if (!key.isEmpty()) {
117         el.setAttribute("key", key);
118     }
119 
120     el.setAttribute("type", "Double");
121     el.setAttribute("value", KisDomUtils::toString(value));
122 
123     m_d->currentElement.appendChild(el);
124 }
125 
writeInteger(const QString & key,int value)126 void KisAslXmlWriter::writeInteger(const QString &key, int value)
127 {
128     QDomElement el = m_d->document.createElement("node");
129 
130     if (!key.isEmpty()) {
131         el.setAttribute("key", key);
132     }
133 
134     el.setAttribute("type", "Integer");
135     el.setAttribute("value", KisDomUtils::toString(value));
136 
137     m_d->currentElement.appendChild(el);
138 }
139 
writeEnum(const QString & key,const QString & typeId,const QString & value)140 void KisAslXmlWriter::writeEnum(const QString &key, const QString &typeId, const QString &value)
141 {
142     QDomElement el = m_d->document.createElement("node");
143 
144     if (!key.isEmpty()) {
145         el.setAttribute("key", key);
146     }
147 
148     el.setAttribute("type", "Enum");
149     el.setAttribute("typeId", typeId);
150     el.setAttribute("value", value);
151 
152     m_d->currentElement.appendChild(el);
153 }
154 
writeUnitFloat(const QString & key,const QString & unit,double value)155 void KisAslXmlWriter::writeUnitFloat(const QString &key, const QString &unit, double value)
156 {
157     QDomElement el = m_d->document.createElement("node");
158 
159     if (!key.isEmpty()) {
160         el.setAttribute("key", key);
161     }
162 
163     el.setAttribute("type", "UnitFloat");
164     el.setAttribute("unit", unit);
165     el.setAttribute("value", KisDomUtils::toString(value));
166 
167     m_d->currentElement.appendChild(el);
168 }
169 
writeText(const QString & key,const QString & value)170 void KisAslXmlWriter::writeText(const QString &key, const QString &value)
171 {
172     QDomElement el = m_d->document.createElement("node");
173 
174     if (!key.isEmpty()) {
175         el.setAttribute("key", key);
176     }
177 
178     el.setAttribute("type", "Text");
179     el.setAttribute("value", value);
180 
181     m_d->currentElement.appendChild(el);
182 }
183 
writeBoolean(const QString & key,bool value)184 void KisAslXmlWriter::writeBoolean(const QString &key, bool value)
185 {
186     QDomElement el = m_d->document.createElement("node");
187 
188     if (!key.isEmpty()) {
189         el.setAttribute("key", key);
190     }
191 
192     el.setAttribute("type", "Boolean");
193     el.setAttribute("value", KisDomUtils::toString(value));
194 
195     m_d->currentElement.appendChild(el);
196 }
197 
writeColor(const QString & key,const QColor & value)198 void KisAslXmlWriter::writeColor(const QString &key, const QColor &value)
199 {
200     enterDescriptor(key, "", "RGBC");
201 
202     writeDouble("Rd  ", value.red());
203     writeDouble("Grn ", value.green());
204     writeDouble("Bl  ", value.blue());
205 
206     leaveDescriptor();
207 }
208 
writePoint(const QString & key,const QPointF & value)209 void KisAslXmlWriter::writePoint(const QString &key, const QPointF &value)
210 {
211     enterDescriptor(key, "", "CrPt");
212 
213     writeDouble("Hrzn", value.x());
214     writeDouble("Vrtc", value.y());
215 
216     leaveDescriptor();
217 }
218 
writePhasePoint(const QString & key,const QPointF & value)219 void KisAslXmlWriter::writePhasePoint(const QString &key, const QPointF &value)
220 {
221     enterDescriptor(key, "", "Pnt ");
222 
223     writeDouble("Hrzn", value.x());
224     writeDouble("Vrtc", value.y());
225 
226     leaveDescriptor();
227 }
228 
writeOffsetPoint(const QString & key,const QPointF & value)229 void KisAslXmlWriter::writeOffsetPoint(const QString &key, const QPointF &value)
230 {
231     enterDescriptor(key, "", "Pnt ");
232 
233     writeUnitFloat("Hrzn", "#Prc", value.x());
234     writeUnitFloat("Vrtc", "#Prc", value.y());
235 
236     leaveDescriptor();
237 }
238 
writeCurve(const QString & key,const QString & name,const QVector<QPointF> & points)239 void KisAslXmlWriter::writeCurve(const QString &key, const QString &name, const QVector<QPointF> &points)
240 {
241     enterDescriptor(key, "", "ShpC");
242 
243     writeText("Nm  ", name);
244 
245     enterList("Crv ");
246 
247     Q_FOREACH (const QPointF &pt, points) {
248         writePoint("", pt);
249     }
250 
251     leaveList();
252     leaveDescriptor();
253 }
254 
writePattern(const QString & key,const KoPattern * pattern)255 QString KisAslXmlWriter::writePattern(const QString &key, const KoPattern *pattern)
256 {
257     enterDescriptor(key, "", "KisPattern");
258 
259     writeText("Nm  ", pattern->name());
260 
261     QString uuid = KisAslWriterUtils::getPatternUuidLazy(pattern);
262     writeText("Idnt", uuid);
263 
264     // Write pattern data
265 
266     QBuffer buffer;
267     buffer.open(QIODevice::WriteOnly);
268     pattern->savePatToDevice(&buffer);
269 
270     QDomCDATASection dataSection = m_d->document.createCDATASection(qCompress(buffer.buffer()).toBase64());
271 
272     QDomElement dataElement = m_d->document.createElement("node");
273     dataElement.setAttribute("type", "KisPatternData");
274     dataElement.setAttribute("key", "Data");
275     dataElement.appendChild(dataSection);
276 
277     m_d->currentElement.appendChild(dataElement);
278 
279     leaveDescriptor();
280 
281     return uuid;
282 }
283 
writePatternRef(const QString & key,const KoPattern * pattern,const QString & uuid)284 void KisAslXmlWriter::writePatternRef(const QString &key, const KoPattern *pattern, const QString &uuid)
285 {
286     enterDescriptor(key, "", "Ptrn");
287 
288     writeText("Nm  ", pattern->name());
289     writeText("Idnt", uuid);
290 
291     leaveDescriptor();
292 }
293 
writeGradientImpl(const QString & key,const QString & name,QVector<QColor> colors,QVector<qreal> transparencies,QVector<qreal> positions,QVector<QString> types,QVector<qreal> middleOffsets)294 void KisAslXmlWriter::writeGradientImpl(const QString &key,
295                                         const QString &name,
296                                         QVector<QColor> colors,
297                                         QVector<qreal> transparencies,
298                                         QVector<qreal> positions,
299                                         QVector<QString> types,
300                                         QVector<qreal> middleOffsets)
301 {
302     enterDescriptor(key, "Gradient", "Grdn");
303 
304     writeText("Nm  ", name);
305     writeEnum("GrdF", "GrdF", "CstS");
306     writeDouble("Intr", 4096);
307 
308     enterList("Clrs");
309 
310     for (int i = 0; i < colors.size(); i++) {
311         enterDescriptor("", "", "Clrt");
312 
313         writeColor("Clr ", colors[i]);
314         writeEnum("Type", "Clry", types[i]);
315         writeInteger("Lctn", positions[i] * 4096.0);
316         writeInteger("Mdpn", middleOffsets[i] * 100.0);
317 
318         leaveDescriptor();
319     };
320 
321     leaveList();
322 
323     enterList("Trns");
324 
325     for (int i = 0; i < colors.size(); i++) {
326         enterDescriptor("", "", "TrnS");
327         writeUnitFloat("Opct", "#Prc", transparencies[i] * 100.0);
328         writeInteger("Lctn", positions[i] * 4096.0);
329         writeInteger("Mdpn", middleOffsets[i] * 100.0);
330         leaveDescriptor();
331     };
332 
333     leaveList();
334 
335     leaveDescriptor();
336 }
337 
getSegmentEndpointTypeString(KoGradientSegmentEndpointType segtype)338 QString KisAslXmlWriter::getSegmentEndpointTypeString(KoGradientSegmentEndpointType segtype) {
339     switch (segtype) {
340     case COLOR_ENDPOINT:
341         return "UsrS";
342         break;
343     case FOREGROUND_ENDPOINT:
344     case FOREGROUND_TRANSPARENT_ENDPOINT:
345         return "FrgC";
346         break;
347     case BACKGROUND_ENDPOINT:
348     case BACKGROUND_TRANSPARENT_ENDPOINT:
349         return "BckC";
350         break;
351     default:
352         return "UsrS";
353     }
354 }
355 
writeSegmentGradient(const QString & key,const KoSegmentGradient * gradient)356 void KisAslXmlWriter::writeSegmentGradient(const QString &key, const KoSegmentGradient *gradient)
357 {
358     const QList<KoGradientSegment *>&segments = gradient->segments();
359     KIS_SAFE_ASSERT_RECOVER_RETURN(!segments.isEmpty());
360 
361     QVector<QColor> colors;
362     QVector<qreal> transparencies;
363     QVector<qreal> positions;
364     QVector<QString> types;
365     QVector<qreal> middleOffsets;
366 
367     Q_FOREACH (const KoGradientSegment *seg, segments) {
368         const qreal start = seg->startOffset();
369         const qreal end = seg->endOffset();
370         const qreal mid = (end - start) > DBL_EPSILON ? (seg->middleOffset() - start) / (end - start) : 0.5;
371 
372         QColor color = seg->startColor().toQColor();
373         qreal transparency = color.alphaF();
374         color.setAlphaF(1.0);
375 
376         QString type = getSegmentEndpointTypeString(seg->startType());
377 
378         colors << color;
379         transparencies << transparency;
380         positions << start;
381         types << type;
382         middleOffsets << mid;
383     }
384 
385     // last segment
386 
387     if (!segments.isEmpty()) {
388         const KoGradientSegment *lastSeg = segments.last();
389 
390         QColor color = lastSeg->endColor().toQColor();
391         qreal transparency = color.alphaF();
392         color.setAlphaF(1.0);
393         QString type = getSegmentEndpointTypeString(lastSeg->endType());
394 
395         colors << color;
396         transparencies << transparency;
397         positions << lastSeg->endOffset();
398         types << type;
399         middleOffsets << 0.5;
400     }
401 
402     writeGradientImpl(key, gradient->name(), colors, transparencies, positions, types, middleOffsets);
403 }
404 
writeStopGradient(const QString & key,const KoStopGradient * gradient)405 void KisAslXmlWriter::writeStopGradient(const QString &key, const KoStopGradient *gradient)
406 {
407     QVector<QColor> colors;
408     QVector<qreal> transparencies;
409     QVector<qreal> positions;
410     QVector<QString> types;
411     QVector<qreal> middleOffsets;
412 
413     Q_FOREACH (const KoGradientStop &stop, gradient->stops()) {
414         QColor color = stop.color.toQColor();
415         qreal transparency = color.alphaF();
416         color.setAlphaF(1.0);
417 
418         QString type;
419         switch (stop.type) {
420         case COLORSTOP:
421             type = "UsrS";
422             break;
423         case FOREGROUNDSTOP:
424             type = "FrgC";
425             break;
426         case BACKGROUNDSTOP:
427             type = "BckC";
428             break;
429         }
430 
431         colors << color;
432         transparencies << transparency;
433         positions << stop.position;
434         types << type;
435         middleOffsets << 0.5;
436     }
437 
438     writeGradientImpl(key, gradient->name(), colors, transparencies, positions, types, middleOffsets);
439 }
440