Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_asl_writer.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2015 Dmitry Kazakov <dimula73@gmail.com>
3 * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include "kis_asl_writer.h"
9
10#include <QDomDocument>
11#include <QIODevice>
12
13#include "kis_dom_utils.h"
14
15#include "kis_debug.h"
16#include "psd.h"
17#include "psd_utils.h"
18
21
22namespace Private
23{
24using namespace KisAslWriterUtils;
25
26template<psd_byte_order byteOrder = psd_byte_order::psdBigEndian>
27void parseElement(const QDomElement &el, QIODevice &device, bool forceTypeInfo = false)
28{
29 KIS_ASSERT_RECOVER_RETURN(el.tagName() == "node");
30
31 QString type = el.attribute("type", "<unknown>");
32 QString key = el.attribute("key", "");
33
34 // should be filtered on a higher level
36
37 if (type == "Descriptor") {
38 if (!key.isEmpty()) {
39 writeVarString<byteOrder>(key, device);
40 }
41
42 if (!key.isEmpty() || forceTypeInfo) {
43 writeFixedString<byteOrder>("Objc", device);
44 }
45
46 QString classId = el.attribute("classId", "");
47 QString name = el.attribute("name", "");
48
49 writeUnicodeString<byteOrder>(name, device);
50 writeVarString<byteOrder>(classId, device);
51
52 quint32 numChildren = static_cast<quint32>(el.childNodes().size());
53 SAFE_WRITE_EX(byteOrder, device, numChildren);
54
55 QDomNode child = el.firstChild();
56 while (!child.isNull()) {
57 parseElement<byteOrder>(child.toElement(), device);
58 child = child.nextSibling();
59 }
60
61 } else if (type == "List") {
62 writeVarString<byteOrder>(key, device);
63 writeFixedString<byteOrder>("VlLs", device);
64
65 quint32 numChildren = static_cast<quint32>(el.childNodes().size());
66 SAFE_WRITE_EX(byteOrder, device, numChildren);
67
68 QDomNode child = el.firstChild();
69 while (!child.isNull()) {
70 parseElement<byteOrder>(child.toElement(), device, true);
71 child = child.nextSibling();
72 }
73 } else if (type == "Double") {
74 double v = KisDomUtils::toDouble(el.attribute("value", "0"));
75
76 writeVarString<byteOrder>(key, device);
77 writeFixedString<byteOrder>("doub", device);
78 SAFE_WRITE_EX(byteOrder, device, v);
79
80 } else if (type == "UnitFloat") {
81 double v = KisDomUtils::toDouble(el.attribute("value", "0"));
82 QString unit = el.attribute("unit", "#Pxl");
83
84 if (!key.isEmpty()) {
85 writeVarString<byteOrder>(key, device);
86 }
87 writeFixedString<byteOrder>("UntF", device);
88 writeFixedString<byteOrder>(unit, device);
89 SAFE_WRITE_EX(byteOrder, device, v);
90 } else if (type == "Text") {
91 QString v = el.attribute("value", "");
92 writeVarString<byteOrder>(key, device);
93 writeFixedString<byteOrder>("TEXT", device);
94 writeUnicodeString<byteOrder>(v, device);
95 } else if (type == "Enum") {
96 QString v = el.attribute("value", "");
97 QString typeId = el.attribute("typeId", "DEAD");
98 writeVarString<byteOrder>(key, device);
99 writeFixedString<byteOrder>("enum", device);
100 writeVarString<byteOrder>(typeId, device);
101 writeVarString<byteOrder>(v, device);
102 } else if (type == "Integer") {
103 quint32 v = static_cast<quint32>(KisDomUtils::toInt(el.attribute("value", "0")));
104 writeVarString<byteOrder>(key, device);
105 writeFixedString<byteOrder>("long", device);
106 SAFE_WRITE_EX(byteOrder, device, v);
107 } else if (type == "Boolean") {
108 quint8 v = static_cast<quint8>(KisDomUtils::toInt(el.attribute("value", "0")));
109
110 writeVarString<byteOrder>(key, device);
111 writeFixedString<byteOrder>("bool", device);
112 SAFE_WRITE_EX(byteOrder, device, v);
113 } else if (type == "RawData" && key == "EngineData") {
114 writeVarString<byteOrder>(key, device);
115 writeFixedString<byteOrder>("tdta", device);
116
117 QDomNode dataNode = el.firstChild();
118
119 if (!dataNode.isCDATASection()) {
120 warnKrita << "WARNING: failed to parse RawData XML section!";
121 return;
122 }
123
124 QDomCDATASection dataSection = dataNode.toCDATASection();
125 QByteArray data = dataSection.data().toLatin1();
126 data = QByteArray::fromBase64(data);
127
128 if (data.isEmpty()) {
129 warnKrita << "WARNING: failed to parse RawData XML section!";
130 }
131 quint32 length = data.size();
132 SAFE_WRITE_EX(byteOrder, device, length);
133 device.write(data);
134 } else {
135 warnKrita << "WARNING: XML (ASL) Unknown element type:" << type << ppVar(key);
136 }
137}
138
139int calculateNumStyles(const QDomElement &root)
140{
141 int numStyles = 0;
142 QDomNode child = root.firstChild();
143
144 while (!child.isNull()) {
145 QDomElement el = child.toElement();
146 QString classId = el.attribute("classId", "");
147
148 if (classId == "null") {
149 numStyles++;
150 }
151
152 child = child.nextSibling();
153 }
154
155 return numStyles;
156}
157
158// No need for endianness, Photoshop-specific
159void writeFileImpl(QIODevice &device, const QDomDocument &doc)
160{
161 {
162 quint16 stylesVersion = 2;
163 SAFE_WRITE_EX(psd_byte_order::psdBigEndian, device, stylesVersion);
164 }
165
166 {
167 QString signature("8BSL");
168 if (!device.write(signature.toLatin1().data(), 4)) {
169 throw ASLWriteException("Failed to write ASL signature");
170 }
171 }
172
173 {
174 quint16 patternsVersion = 3;
175 SAFE_WRITE_EX(psd_byte_order::psdBigEndian, device, patternsVersion);
176 }
177
178 {
180
181 KisAslPatternsWriter patternsWriter(doc, device, psd_byte_order::psdBigEndian);
182 patternsWriter.writePatterns();
183 }
184
185 QDomElement root = doc.documentElement();
186 KIS_ASSERT_RECOVER_RETURN(root.tagName() == "asl");
187
188 int numStyles = calculateNumStyles(root);
189 KIS_ASSERT_RECOVER_RETURN(numStyles > 0);
190
191 {
192 const quint32 numStylesTag = static_cast<quint32>(numStyles);
193 SAFE_WRITE_EX(psd_byte_order::psdBigEndian, device, numStylesTag);
194 }
195
196 QDomNode child = root.firstChild();
197
198 for (int styleIndex = 0; styleIndex < numStyles; styleIndex++) {
200
201 KIS_ASSERT_RECOVER_RETURN(!child.isNull());
202
203 {
204 quint32 stylesFormatVersion = 16;
205 SAFE_WRITE_EX(psd_byte_order::psdBigEndian, device, stylesFormatVersion);
206 }
207
208 while (!child.isNull()) {
209 QDomElement el = child.toElement();
210 QString key = el.attribute("key", "");
211
212 if (key != ResourceType::Patterns)
213 break;
214
215 child = child.nextSibling();
216 }
217
218 parseElement(child.toElement(), device);
219 child = child.nextSibling();
220
221 {
222 quint32 stylesFormatVersion = 16;
223 SAFE_WRITE_EX(psd_byte_order::psdBigEndian, device, stylesFormatVersion);
224 }
225
226 parseElement(child.toElement(), device);
227 child = child.nextSibling();
228
229 // ASL files' size should be 4-bytes aligned
230 const qint64 paddingSize = 4 - (device.pos() & 0x3);
231 if (paddingSize != 4) {
232 QByteArray padding(paddingSize, '\0');
233 device.write(padding);
234 }
235 }
236}
237
238template<psd_byte_order byteOrder = psd_byte_order::psdBigEndian>
239void writePsdLfx2SectionImpl(QIODevice &device, const QDomDocument &doc)
240{
241 QDomElement root = doc.documentElement();
242 KIS_ASSERT_RECOVER_RETURN(root.tagName() == "asl");
243
244 int numStyles = calculateNumStyles(root);
245 KIS_ASSERT_RECOVER_RETURN(numStyles == 1);
246
247 {
248 quint32 objectEffectsVersion = 0;
249 SAFE_WRITE_EX(byteOrder, device, objectEffectsVersion);
250 }
251
252 {
253 quint32 descriptorVersion = 16;
254 SAFE_WRITE_EX(byteOrder, device, descriptorVersion);
255 }
256
257 QDomNode child = root.firstChild();
258
259 while (!child.isNull()) {
260 QDomElement el = child.toElement();
261 QString key = el.attribute("key", "");
262
263 if (key != ResourceType::Patterns)
264 break;
265
266 child = child.nextSibling();
267 }
268
269 parseElement<byteOrder>(child.toElement(), device);
270 child = child.nextSibling();
271
272 // ASL files' size should be 4-bytes aligned
273 const qint64 paddingSize = 4 - (device.pos() & 0x3);
274 if (paddingSize != 4) {
275 QByteArray padding(static_cast<int>(paddingSize), '\0');
276 device.write(padding);
277 }
278}
279
280template<psd_byte_order byteOrder = psd_byte_order::psdBigEndian>
281void writeFillLayerSectionImpl(QIODevice &device, const QDomDocument &doc)
282{
283 QDomElement root = doc.documentElement();
284 KIS_ASSERT_RECOVER_RETURN(root.tagName() == "asl");
285
286 {
287 quint32 descriptorVersion = 16;
288 SAFE_WRITE_EX(byteOrder, device, descriptorVersion);
289 }
290
291 QDomNode child = root.firstChild();
292
293 while (!child.isNull()) {
294 QDomElement el = child.toElement();
295 QString key = el.attribute("key", "");
296
297 if (key != ResourceType::Patterns)
298 break;
299
300 child = child.nextSibling();
301 }
302
303 parseElement<byteOrder>(child.toElement(), device);
304 child = child.nextSibling();
305
306 // ASL files' size should be 4-bytes aligned
307 const qint64 paddingSize = 4 - (device.pos() & 0x3);
308 if (paddingSize != 4) {
309 QByteArray padding(static_cast<int>(paddingSize), '\0');
310 device.write(padding);
311 }
312}
313
314template<psd_byte_order byteOrder = psd_byte_order::psdBigEndian>
315void writeTypeToolSectionImpl(QIODevice &device, const QDomDocument &doc, const QDomDocument &warpDoc, const QTransform tf, const QRectF bounds)
316{
317 QDomElement root = doc.documentElement();
318 KIS_ASSERT_RECOVER_RETURN(root.tagName() == "asl");
319
320 {
321 quint16 descriptorVersion = 1;
322 SAFE_WRITE_EX(byteOrder, device, descriptorVersion);
323 }
324
325 {
326 SAFE_WRITE_EX(byteOrder, device, double(tf.m11()));
327 SAFE_WRITE_EX(byteOrder, device, double(tf.m12()));
328 SAFE_WRITE_EX(byteOrder, device, double(tf.m21()));
329 SAFE_WRITE_EX(byteOrder, device, double(tf.m22()));
330 SAFE_WRITE_EX(byteOrder, device, double(tf.dx()));
331 SAFE_WRITE_EX(byteOrder, device, double(tf.dy()));
332 }
333
334 {
335 quint16 textVersion = 50;
336 SAFE_WRITE_EX(byteOrder, device, textVersion);
337 quint32 descriptorVersion = 16;
338 SAFE_WRITE_EX(byteOrder, device, descriptorVersion);
339 }
340
341 QDomNode child = root.firstChild();
342 parseElement<byteOrder>(child.toElement(), device);
343
344 // warp data
345 {
346 quint16 textVersion = 1;
347 SAFE_WRITE_EX(byteOrder, device, textVersion);
348 quint32 descriptorVersion = 16;
349 SAFE_WRITE_EX(byteOrder, device, descriptorVersion);
350 }
351
352 QDomElement warpRoot = warpDoc.documentElement();
353 KIS_ASSERT_RECOVER_RETURN(warpRoot.tagName() == "asl");
354
355 QDomNode warpChild = warpRoot.firstChild();
356 parseElement<byteOrder>(warpChild.toElement(), device);
357
358 {
359 SAFE_WRITE_EX(byteOrder, device, double(bounds.left()));
360 SAFE_WRITE_EX(byteOrder, device, double(bounds.top()));
361 SAFE_WRITE_EX(byteOrder, device, double(bounds.right()));
362 SAFE_WRITE_EX(byteOrder, device, double(bounds.bottom()));
363 }
364
365 // ASL files' size should be 4-bytes aligned
366 const qint64 paddingSize = 4 - (device.pos() & 0x3);
367 if (paddingSize != 4) {
368 QByteArray padding(static_cast<int>(paddingSize), '\0');
369 device.write(padding);
370 }
371}
372
373template<psd_byte_order byteOrder = psd_byte_order::psdBigEndian>
374void writeVectorStrokeDataImpl(QIODevice &device, const QDomDocument &doc)
375{
376 QDomElement root = doc.documentElement();
377 KIS_ASSERT_RECOVER_RETURN(root.tagName() == "asl");
378
379 {
380 quint32 descriptorVersion = 16;
381 SAFE_WRITE_EX(byteOrder, device, descriptorVersion);
382 }
383
384 QDomNode child = root.firstChild();
385 parseElement<byteOrder>(child.toElement(), device);
386
387 // ASL files' size should be 4-bytes aligned
388 const qint64 paddingSize = 4 - (device.pos() & 0x3);
389 if (paddingSize != 4) {
390 QByteArray padding(static_cast<int>(paddingSize), '\0');
391 device.write(padding);
392 }
393}
394
395template<psd_byte_order byteOrder = psd_byte_order::psdBigEndian>
396void writeVectorOriginationDataImpl(QIODevice &device, const QDomDocument &doc)
397{
398 QDomElement root = doc.documentElement();
399 KIS_ASSERT_RECOVER_RETURN(root.tagName() == "asl");
400
401 {
402 quint32 version = 1;
403 SAFE_WRITE_EX(byteOrder, device, version);
404 }
405 {
406 quint32 descriptorVersion = 16;
407 SAFE_WRITE_EX(byteOrder, device, descriptorVersion);
408 }
409
410 QDomNode child = root.firstChild();
411 parseElement<byteOrder>(child.toElement(), device);
412
413 // ASL files' size should be 4-bytes aligned
414 const qint64 paddingSize = 4 - (device.pos() & 0x3);
415 if (paddingSize != 4) {
416 QByteArray padding(static_cast<int>(paddingSize), '\0');
417 device.write(padding);
418 }
419}
420
421} // namespace
422
424 : m_byteOrder(byteOrder)
425{
426}
427
428void KisAslWriter::writeFile(QIODevice &device, const QDomDocument &doc)
429{
430 try {
431 Private::writeFileImpl(device, doc);
432 } catch (Private::ASLWriteException &e) {
433 warnKrita << "WARNING: ASL:" << e.what();
434 }
435}
436
437void KisAslWriter::writeFillLayerSectionEx(QIODevice &device, const QDomDocument &doc)
438{
439 switch (m_byteOrder) {
441 Private::writeFillLayerSectionImpl<psd_byte_order::psdLittleEndian>(device, doc);
442 break;
443 default:
445 break;
446 }
447}
448
449void KisAslWriter::writePsdLfx2SectionEx(QIODevice &device, const QDomDocument &doc)
450{
451 switch (m_byteOrder) {
453 Private::writePsdLfx2SectionImpl<psd_byte_order::psdLittleEndian>(device, doc);
454 break;
455 default:
457 break;
458 }
459}
460
461void KisAslWriter::writeTypeToolObjectSettings(QIODevice &device, const QDomDocument &doc, const QDomDocument &warpDoc, const QTransform tf, const QRectF bounds)
462{
463 switch (m_byteOrder) {
465 Private::writeTypeToolSectionImpl<psd_byte_order::psdLittleEndian>(device, doc, warpDoc, tf, bounds);
466 break;
467 default:
468 Private::writeTypeToolSectionImpl(device, doc, warpDoc, tf, bounds);
469 break;
470 }
471}
472
473void KisAslWriter::writeVectorStrokeDataEx(QIODevice &device, const QDomDocument &doc)
474{
475 switch (m_byteOrder) {
477 Private::writeVectorStrokeDataImpl<psd_byte_order::psdLittleEndian>(device, doc);
478 break;
479 default:
481 break;
482 }
483}
484
485void KisAslWriter::writeVectorOriginationDataEx(QIODevice &device, const QDomDocument &doc)
486{
487 switch (m_byteOrder) {
489 Private::writeVectorOriginationDataImpl<psd_byte_order::psdLittleEndian>(device, doc);
490 break;
491 default:
493 break;
494 }
495}
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
qreal v
void writeFile(QIODevice &device, const QDomDocument &doc)
void writeFillLayerSectionEx(QIODevice &device, const QDomDocument &doc)
psd_byte_order m_byteOrder
void writePsdLfx2SectionEx(QIODevice &device, const QDomDocument &doc)
KisAslWriter(psd_byte_order byteOrder=psd_byte_order::psdBigEndian)
void writeVectorStrokeDataEx(QIODevice &device, const QDomDocument &doc)
void writeVectorOriginationDataEx(QIODevice &device, const QDomDocument &doc)
void writeTypeToolObjectSettings(QIODevice &device, const QDomDocument &doc, const QDomDocument &warpDoc, const QTransform tf, const QRectF bounds)
#define SAFE_WRITE_EX(byteOrder, device, varname)
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
#define bounds(x, a, b)
#define warnKrita
Definition kis_debug.h:87
#define ppVar(var)
Definition kis_debug.h:155
double toDouble(const QString &str, bool *ok=nullptr)
int toInt(const QString &str, bool *ok=nullptr)
void writeVectorStrokeDataImpl(QIODevice &device, const QDomDocument &doc)
void writeTypeToolSectionImpl(QIODevice &device, const QDomDocument &doc, const QDomDocument &warpDoc, const QTransform tf, const QRectF bounds)
void writeFillLayerSectionImpl(QIODevice &device, const QDomDocument &doc)
void writeFileImpl(QIODevice &device, const QDomDocument &doc)
int calculateNumStyles(const QDomElement &root)
void parseElement(const QDomElement &el, QIODevice &device, bool forceTypeInfo=false)
void writeVectorOriginationDataImpl(QIODevice &device, const QDomDocument &doc)
void writePsdLfx2SectionImpl(QIODevice &device, const QDomDocument &doc)
const QString Patterns
psd_byte_order
Definition psd.h:33