1 /*
2  * maptovariantconverter.cpp
3  * Copyright 2011, Porfírio José Pereira Ribeiro <porfirioribeiro@gmail.com>
4  * Copyright 2011-2015, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
5  *
6  * This file is part of Tiled.
7  *
8  * This program is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU General Public License as published by the Free
10  * Software Foundation; either version 2 of the License, or (at your option)
11  * any later version.
12  *
13  * This program is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
16  * more details.
17  *
18  * You should have received a copy of the GNU General Public License along with
19  * this program. If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include "maptovariantconverter.h"
23 
24 #include "grouplayer.h"
25 #include "imagelayer.h"
26 #include "map.h"
27 #include "mapobject.h"
28 #include "objectgroup.h"
29 #include "objecttemplate.h"
30 #include "properties.h"
31 #include "tile.h"
32 #include "tilelayer.h"
33 #include "tileset.h"
34 #include "wangset.h"
35 
36 #include <QCoreApplication>
37 
38 using namespace Tiled;
39 
toVariant(const Map & map,const QDir & mapDir)40 QVariant MapToVariantConverter::toVariant(const Map &map, const QDir &mapDir)
41 {
42     mDir = mapDir;
43     mGidMapper.clear();
44 
45     QVariantMap mapVariant;
46 
47     mapVariant[QStringLiteral("type")] = QLatin1String("map");
48     if (mVersion == 2)
49         mapVariant[QStringLiteral("version")] = QStringLiteral("1.6");
50     else
51         mapVariant[QStringLiteral("version")] = 1.1;
52     mapVariant[QStringLiteral("tiledversion")] = QCoreApplication::applicationVersion();
53     mapVariant[QStringLiteral("orientation")] = orientationToString(map.orientation());
54     mapVariant[QStringLiteral("renderorder")] = renderOrderToString(map.renderOrder());
55     mapVariant[QStringLiteral("width")] = map.width();
56     mapVariant[QStringLiteral("height")] = map.height();
57     mapVariant[QStringLiteral("tilewidth")] = map.tileWidth();
58     mapVariant[QStringLiteral("tileheight")] = map.tileHeight();
59     mapVariant[QStringLiteral("infinite")] = map.infinite();
60     mapVariant[QStringLiteral("nextlayerid")] = map.nextLayerId();
61     mapVariant[QStringLiteral("nextobjectid")] = map.nextObjectId();
62     mapVariant[QStringLiteral("compressionlevel")] = map.compressionLevel();
63 
64     if (map.chunkSize() != QSize(CHUNK_SIZE, CHUNK_SIZE) || !map.exportFileName.isEmpty() || !map.exportFormat.isEmpty()) {
65         QVariantMap editorSettingsVariant;
66 
67         if (map.chunkSize() != QSize(CHUNK_SIZE, CHUNK_SIZE)) {
68             QVariantMap chunkSizeVariant;
69             chunkSizeVariant[QStringLiteral("width")] = map.chunkSize().width();
70             chunkSizeVariant[QStringLiteral("height")] = map.chunkSize().height();
71             editorSettingsVariant[QStringLiteral("chunksize")] = chunkSizeVariant;
72         }
73 
74         if (!map.exportFileName.isEmpty() || !map.exportFormat.isEmpty()) {
75             QVariantMap exportVariant;
76             if (!map.exportFileName.isEmpty())
77                 exportVariant[QStringLiteral("target")] = mDir.relativeFilePath(map.exportFileName);
78             if (!map.exportFormat.isEmpty())
79                 exportVariant[QStringLiteral("format")] = map.exportFormat;
80             editorSettingsVariant[QStringLiteral("export")] = exportVariant;
81         }
82 
83         mapVariant[QStringLiteral("editorsettings")] = editorSettingsVariant;
84     }
85 
86     addProperties(mapVariant, map.properties());
87 
88     if (map.orientation() == Map::Hexagonal) {
89         mapVariant[QStringLiteral("hexsidelength")] = map.hexSideLength();
90     }
91 
92     if (map.orientation() == Map::Hexagonal || map.orientation() == Map::Staggered) {
93         mapVariant[QStringLiteral("staggeraxis")] = staggerAxisToString(map.staggerAxis());
94         mapVariant[QStringLiteral("staggerindex")] = staggerIndexToString(map.staggerIndex());
95     }
96 
97     const QColor bgColor = map.backgroundColor();
98     if (bgColor.isValid())
99         mapVariant[QStringLiteral("backgroundcolor")] = colorToString(bgColor);
100 
101     QVariantList tilesetVariants;
102 
103     unsigned firstGid = 1;
104     for (const SharedTileset &tileset : map.tilesets()) {
105         tilesetVariants << toVariant(*tileset, firstGid);
106         mGidMapper.insert(firstGid, tileset);
107         firstGid += tileset->nextTileId();
108     }
109     mapVariant[QStringLiteral("tilesets")] = tilesetVariants;
110 
111     mapVariant[QStringLiteral("layers")] = toVariant(map.layers(),
112                                                     map.layerDataFormat(),
113                                                     map.compressionLevel(),
114                                                     map.chunkSize());
115 
116     return mapVariant;
117 }
118 
toVariant(const Tileset & tileset,const QDir & directory)119 QVariant MapToVariantConverter::toVariant(const Tileset &tileset,
120                                           const QDir &directory)
121 {
122     mDir = directory;
123     return toVariant(tileset, 0);
124 }
125 
toVariant(const ObjectTemplate & objectTemplate,const QDir & directory)126 QVariant MapToVariantConverter::toVariant(const ObjectTemplate &objectTemplate,
127                                           const QDir &directory)
128 {
129     mDir = directory;
130     QVariantMap objectTemplateVariant;
131 
132     objectTemplateVariant[QStringLiteral("type")] = QLatin1String("template");
133 
134     mGidMapper.clear();
135     if (Tileset *tileset = objectTemplate.object()->cell().tileset()) {
136         unsigned firstGid = 1;
137         mGidMapper.insert(firstGid, tileset->sharedPointer());
138         objectTemplateVariant[QStringLiteral("tileset")] = toVariant(*tileset, firstGid);
139     }
140 
141     objectTemplateVariant[QStringLiteral("object")] = toVariant(*objectTemplate.object());
142 
143     return objectTemplateVariant;
144 }
145 
toVariant(const Tileset & tileset,int firstGid) const146 QVariant MapToVariantConverter::toVariant(const Tileset &tileset,
147                                           int firstGid) const
148 {
149     QVariantMap tilesetVariant;
150 
151     if (firstGid > 0) {
152         tilesetVariant[QStringLiteral("firstgid")] = firstGid;
153 
154         const QString &fileName = tileset.fileName();
155         if (!fileName.isEmpty()) {
156             QString source = mDir.relativeFilePath(fileName);
157             tilesetVariant[QStringLiteral("source")] = source;
158 
159             // Tileset is external, so no need to write any of the stuff below
160             return tilesetVariant;
161         }
162     } else {
163         // Include a 'type' property if we are writing the tileset to its own file
164         tilesetVariant[QStringLiteral("type")] = QLatin1String("tileset");
165 
166         // Include version in external tilesets
167         if (mVersion == 2)
168             tilesetVariant[QStringLiteral("version")] = QStringLiteral("1.6");
169         else
170             tilesetVariant[QStringLiteral("version")] = 1.1;
171         tilesetVariant[QStringLiteral("tiledversion")] = QCoreApplication::applicationVersion();
172     }
173 
174     tilesetVariant[QStringLiteral("name")] = tileset.name();
175     tilesetVariant[QStringLiteral("tilewidth")] = tileset.tileWidth();
176     tilesetVariant[QStringLiteral("tileheight")] = tileset.tileHeight();
177     tilesetVariant[QStringLiteral("spacing")] = tileset.tileSpacing();
178     tilesetVariant[QStringLiteral("margin")] = tileset.margin();
179     tilesetVariant[QStringLiteral("tilecount")] = tileset.tileCount();
180     tilesetVariant[QStringLiteral("columns")] = tileset.columnCount();
181 
182     // Write editor settings when saving external tilesets
183     if (firstGid == 0) {
184         if (!tileset.exportFileName.isEmpty() || !tileset.exportFormat.isEmpty()) {
185             QVariantMap editorSettingsVariant;
186 
187             QVariantMap exportVariant;
188             exportVariant[QStringLiteral("target")] = mDir.relativeFilePath(tileset.exportFileName);
189             exportVariant[QStringLiteral("format")] = tileset.exportFormat;
190             editorSettingsVariant[QStringLiteral("export")] = exportVariant;
191 
192             tilesetVariant[QStringLiteral("editorsettings")] = editorSettingsVariant;
193         }
194     }
195 
196     const QColor &backgroundColor = tileset.backgroundColor();
197     if (backgroundColor.isValid())
198         tilesetVariant[QStringLiteral("backgroundcolor")] = colorToString(backgroundColor);
199 
200     if (tileset.objectAlignment() != Unspecified)
201         tilesetVariant[QStringLiteral("objectalignment")] = alignmentToString(tileset.objectAlignment());
202 
203     addProperties(tilesetVariant, tileset.properties());
204 
205     const QPoint offset = tileset.tileOffset();
206     if (!offset.isNull()) {
207         QVariantMap tileOffset;
208         tileOffset[QStringLiteral("x")] = offset.x();
209         tileOffset[QStringLiteral("y")] = offset.y();
210         tilesetVariant[QStringLiteral("tileoffset")] = tileOffset;
211     }
212 
213     if (tileset.orientation() != Tileset::Orthogonal || tileset.gridSize() != tileset.tileSize()) {
214         QVariantMap grid;
215         grid[QStringLiteral("orientation")] = Tileset::orientationToString(tileset.orientation());
216         grid[QStringLiteral("width")] = tileset.gridSize().width();
217         grid[QStringLiteral("height")] = tileset.gridSize().height();
218         tilesetVariant[QStringLiteral("grid")] = grid;
219     }
220 
221     // Write the image element
222     const QUrl &imageSource = tileset.imageSource();
223     if (!imageSource.isEmpty()) {
224         const QString rel = toFileReference(imageSource, mDir);
225 
226         tilesetVariant[QStringLiteral("image")] = rel;
227 
228         const QColor transColor = tileset.transparentColor();
229         if (transColor.isValid())
230             tilesetVariant[QStringLiteral("transparentcolor")] = transColor.name();
231 
232         tilesetVariant[QStringLiteral("imagewidth")] = tileset.imageWidth();
233         tilesetVariant[QStringLiteral("imageheight")] = tileset.imageHeight();
234     }
235 
236     const auto transformationFlags = tileset.transformationFlags();
237     if (transformationFlags) {
238         tilesetVariant[QStringLiteral("transformations")] = QVariantMap {
239             { QStringLiteral("hflip"), transformationFlags.testFlag(Tileset::AllowFlipHorizontally) },
240             { QStringLiteral("vflip"), transformationFlags.testFlag(Tileset::AllowFlipVertically) },
241             { QStringLiteral("rotate"), transformationFlags.testFlag(Tileset::AllowRotate) },
242             { QStringLiteral("preferuntransformed"), transformationFlags.testFlag(Tileset::PreferUntransformed) },
243         };
244     }
245 
246     // Write the properties, external image, object group and animation for
247     // those tiles that have them.
248 
249     // Used for version 1
250     QVariantMap tilePropertiesVariant;
251     QVariantMap tilePropertyTypesVariant;
252     QVariantMap tilesVariantMap;
253 
254     // Used for version 2
255     QVariantList tilesVariant;
256 
257     const bool includeAllTiles = mVersion != 1 && tileset.anyTileOutOfOrder();
258 
259     for (const Tile *tile : tileset.tiles()) {
260         const Properties properties = tile->properties();
261         QVariantMap tileVariant;
262 
263         if (mVersion == 1) {
264             if (!properties.isEmpty()) {
265                 tilePropertiesVariant[QString::number(tile->id())] = toVariant(properties);
266                 tilePropertyTypesVariant[QString::number(tile->id())] = propertyTypesToVariant(properties);
267             }
268         } else {
269             addProperties(tileVariant, properties);
270         }
271 
272         if (!tile->type().isEmpty())
273             tileVariant[QStringLiteral("type")] = tile->type();
274         if (tile->probability() != 1.0)
275             tileVariant[QStringLiteral("probability")] = tile->probability();
276         if (!tile->imageSource().isEmpty()) {
277             const QString rel = toFileReference(tile->imageSource(), mDir);
278             tileVariant[QStringLiteral("image")] = rel;
279 
280             const QSize tileSize = tile->size();
281             if (!tileSize.isNull()) {
282                 tileVariant[QStringLiteral("imagewidth")] = tileSize.width();
283                 tileVariant[QStringLiteral("imageheight")] = tileSize.height();
284             }
285         }
286         if (tile->objectGroup())
287             tileVariant[QStringLiteral("objectgroup")] = toVariant(*tile->objectGroup());
288         if (tile->isAnimated()) {
289             QVariantList frameVariants;
290             for (const Frame &frame : tile->frames()) {
291                 QVariantMap frameVariant;
292                 frameVariant[QStringLiteral("tileid")] = frame.tileId;
293                 frameVariant[QStringLiteral("duration")] = frame.duration;
294                 frameVariants.append(frameVariant);
295             }
296             tileVariant[QStringLiteral("animation")] = frameVariants;
297         }
298 
299         if (includeAllTiles || !tileVariant.empty()) {
300             if (mVersion == 1) {
301                 tilesVariantMap[QString::number(tile->id())] = tileVariant;
302             } else {
303                 tileVariant[QStringLiteral("id")] = tile->id();
304                 tilesVariant << tileVariant;
305             }
306         }
307     }
308 
309     if (!tilePropertiesVariant.empty()) {
310         tilesetVariant[QStringLiteral("tileproperties")] = tilePropertiesVariant;
311         tilesetVariant[QStringLiteral("tilepropertytypes")] = tilePropertyTypesVariant;
312     }
313 
314     if (!tilesVariantMap.empty())
315         tilesetVariant[QStringLiteral("tiles")] = tilesVariantMap;
316     else if (!tilesVariant.empty())
317         tilesetVariant[QStringLiteral("tiles")] = tilesVariant;
318 
319     // Write the Wang sets
320     if (tileset.wangSetCount() > 0) {
321         QVariantList wangSetVariants;
322 
323         for (const WangSet *wangSet : tileset.wangSets())
324             wangSetVariants.append(toVariant(*wangSet));
325 
326         tilesetVariant[QStringLiteral("wangsets")] = wangSetVariants;
327     }
328 
329     return tilesetVariant;
330 }
331 
toVariant(const Properties & properties) const332 QVariant MapToVariantConverter::toVariant(const Properties &properties) const
333 {
334     QVariantMap variantMap;
335 
336     Properties::const_iterator it = properties.constBegin();
337     Properties::const_iterator it_end = properties.constEnd();
338     for (; it != it_end; ++it) {
339         const QVariant value = toExportValue(it.value(), mDir);
340         variantMap[it.key()] = value;
341     }
342 
343     return variantMap;
344 }
345 
propertyTypesToVariant(const Properties & properties) const346 QVariant MapToVariantConverter::propertyTypesToVariant(const Properties &properties) const
347 {
348     QVariantMap variantMap;
349 
350     Properties::const_iterator it = properties.constBegin();
351     Properties::const_iterator it_end = properties.constEnd();
352     for (; it != it_end; ++it)
353         variantMap[it.key()] = typeToName(it.value().userType());
354 
355     return variantMap;
356 }
357 
358 
toVariant(const WangSet & wangSet) const359 QVariant MapToVariantConverter::toVariant(const WangSet &wangSet) const
360 {
361     QVariantMap wangSetVariant;
362 
363     wangSetVariant[QStringLiteral("name")] = wangSet.name();
364     wangSetVariant[QStringLiteral("type")] = wangSetTypeToString(wangSet.type());
365     wangSetVariant[QStringLiteral("tile")] = wangSet.imageTileId();
366 
367     QVariantList colorVariants;
368     for (int i = 1; i <= wangSet.colorCount(); ++i)
369         colorVariants.append(toVariant(*wangSet.colorAt(i)));
370     wangSetVariant[QStringLiteral("colors")] = colorVariants;
371 
372     QVariantList wangTileVariants;
373     const auto wangTiles = wangSet.sortedWangTiles();
374     for (const WangTile &wangTile : wangTiles) {
375         QVariantMap wangTileVariant;
376 
377         QVariantList wangIdVariant;
378         for (int i = 0; i < WangId::NumIndexes; ++i)
379             wangIdVariant.append(QVariant(wangTile.wangId().indexColor(i)));
380 
381         wangTileVariant[QStringLiteral("wangid")] = wangIdVariant;
382         wangTileVariant[QStringLiteral("tileid")] = wangTile.tileId();
383 
384         wangTileVariants.append(wangTileVariant);
385     }
386     wangSetVariant[QStringLiteral("wangtiles")] = wangTileVariants;
387 
388     addProperties(wangSetVariant, wangSet.properties());
389 
390     return wangSetVariant;
391 }
392 
toVariant(const WangColor & wangColor) const393 QVariant MapToVariantConverter::toVariant(const WangColor &wangColor) const
394 {
395     QVariantMap colorVariant;
396     colorVariant[QStringLiteral("color")] = colorToString(wangColor.color());
397     colorVariant[QStringLiteral("name")] = wangColor.name();
398     colorVariant[QStringLiteral("probability")] = wangColor.probability();
399     colorVariant[QStringLiteral("tile")] = wangColor.imageId();
400     addProperties(colorVariant, wangColor.properties());
401     return colorVariant;
402 }
403 
toVariant(const QList<Layer * > & layers,Map::LayerDataFormat format,int compressionLevel,QSize chunkSize) const404 QVariant MapToVariantConverter::toVariant(const QList<Layer *> &layers,
405                                           Map::LayerDataFormat format,
406                                           int compressionLevel,
407                                           QSize chunkSize) const
408 {
409     QVariantList layerVariants;
410 
411     for (const Layer *layer : layers) {
412         switch (layer->layerType()) {
413         case Layer::TileLayerType:
414             layerVariants << toVariant(*static_cast<const TileLayer*>(layer), format, compressionLevel, chunkSize);
415             break;
416         case Layer::ObjectGroupType:
417             layerVariants << toVariant(*static_cast<const ObjectGroup*>(layer));
418             break;
419         case Layer::ImageLayerType:
420             layerVariants << toVariant(*static_cast<const ImageLayer*>(layer));
421             break;
422         case Layer::GroupLayerType:
423             layerVariants << toVariant(*static_cast<const GroupLayer*>(layer), format, compressionLevel, chunkSize);
424         }
425     }
426 
427     return layerVariants;
428 }
429 
toVariant(const TileLayer & tileLayer,Map::LayerDataFormat format,int compressionLevel,QSize chunkSize) const430 QVariant MapToVariantConverter::toVariant(const TileLayer &tileLayer,
431                                           Map::LayerDataFormat format,
432                                           int compressionLevel,
433                                           QSize chunkSize) const
434 {
435     QVariantMap tileLayerVariant;
436     tileLayerVariant[QStringLiteral("type")] = QLatin1String("tilelayer");
437 
438     if (tileLayer.map()->infinite()) {
439         QRect bounds = tileLayer.localBounds();
440         tileLayerVariant[QStringLiteral("width")] = bounds.width();
441         tileLayerVariant[QStringLiteral("height")] = bounds.height();
442         tileLayerVariant[QStringLiteral("startx")] = bounds.left();
443         tileLayerVariant[QStringLiteral("starty")] = bounds.top();
444     } else {
445         tileLayerVariant[QStringLiteral("width")] = tileLayer.width();
446         tileLayerVariant[QStringLiteral("height")] = tileLayer.height();
447     }
448 
449     addLayerAttributes(tileLayerVariant, tileLayer);
450 
451     switch (format) {
452     case Map::XML:
453     case Map::CSV:
454         break;
455     case Map::Base64:
456     case Map::Base64Zlib:
457     case Map::Base64Gzip:
458     case Map::Base64Zstandard:
459         tileLayerVariant[QStringLiteral("encoding")] = QLatin1String("base64");
460         tileLayerVariant[QStringLiteral("compression")] = compressionToString(format);
461         break;
462     }
463 
464     if (tileLayer.map()->infinite()) {
465         QVariantList chunkVariants;
466 
467         const auto chunks = tileLayer.sortedChunksToWrite(chunkSize);
468         for (const QRect &rect : chunks) {
469             QVariantMap chunkVariant;
470 
471             chunkVariant[QStringLiteral("x")] = rect.x();
472             chunkVariant[QStringLiteral("y")] = rect.y();
473             chunkVariant[QStringLiteral("width")] = rect.width();
474             chunkVariant[QStringLiteral("height")] = rect.height();
475 
476             addTileLayerData(chunkVariant, tileLayer, format, compressionLevel, rect);
477 
478             chunkVariants.append(chunkVariant);
479         }
480 
481         tileLayerVariant[QStringLiteral("chunks")] = chunkVariants;
482     } else {
483         addTileLayerData(tileLayerVariant, tileLayer, format, compressionLevel,
484                          QRect(0, 0, tileLayer.width(), tileLayer.height()));
485     }
486 
487     return tileLayerVariant;
488 }
489 
toVariant(const ObjectGroup & objectGroup) const490 QVariant MapToVariantConverter::toVariant(const ObjectGroup &objectGroup) const
491 {
492     QVariantMap objectGroupVariant;
493     objectGroupVariant[QStringLiteral("type")] = QLatin1String("objectgroup");
494 
495     if (objectGroup.color().isValid())
496         objectGroupVariant[QStringLiteral("color")] = colorToString(objectGroup.color());
497 
498     objectGroupVariant[QStringLiteral("draworder")] = drawOrderToString(objectGroup.drawOrder());
499 
500     addLayerAttributes(objectGroupVariant, objectGroup);
501     QVariantList objectVariants;
502     for (const MapObject *object : objectGroup.objects())
503         objectVariants << toVariant(*object);
504 
505     objectGroupVariant[QStringLiteral("objects")] = objectVariants;
506 
507     return objectGroupVariant;
508 }
509 
toVariant(const MapObject & object) const510 QVariant MapToVariantConverter::toVariant(const MapObject &object) const
511 {
512     QVariantMap objectVariant;
513     const QString &name = object.name();
514     const QString &type = object.type();
515 
516     addProperties(objectVariant, object.properties());
517 
518     if (const ObjectTemplate *objectTemplate = object.objectTemplate()) {
519         QString relativeFileName = mDir.relativeFilePath(objectTemplate->fileName());
520         objectVariant[QStringLiteral("template")] = relativeFileName;
521     }
522 
523     bool notTemplateInstance = !object.isTemplateInstance();
524 
525     int id = object.id();
526     if (id != 0)
527         objectVariant[QStringLiteral("id")] = id;
528 
529     if (notTemplateInstance || object.propertyChanged(MapObject::NameProperty))
530         objectVariant[QStringLiteral("name")] = name;
531 
532     if (notTemplateInstance || object.propertyChanged(MapObject::TypeProperty))
533         objectVariant[QStringLiteral("type")] = type;
534 
535 
536     if (notTemplateInstance || object.propertyChanged(MapObject::CellProperty))
537         if (!object.cell().isEmpty())
538             objectVariant[QStringLiteral("gid")] = mGidMapper.cellToGid(object.cell());
539 
540     if (!object.isTemplateBase()) {
541         objectVariant[QStringLiteral("x")] = object.x();
542         objectVariant[QStringLiteral("y")] = object.y();
543     }
544 
545     if (notTemplateInstance || object.propertyChanged(MapObject::SizeProperty)) {
546         objectVariant[QStringLiteral("width")] = object.width();
547         objectVariant[QStringLiteral("height")] = object.height();
548     }
549 
550     if (notTemplateInstance || object.propertyChanged(MapObject::RotationProperty))
551         objectVariant[QStringLiteral("rotation")] = object.rotation();
552 
553     if (notTemplateInstance || object.propertyChanged(MapObject::VisibleProperty))
554         objectVariant[QStringLiteral("visible")] = object.isVisible();
555 
556     /* Polygons are stored in this format:
557      *
558      *   "polygon/polyline": [
559      *       { "x": 0, "y": 0 },
560      *       { "x": 1, "y": 1 },
561      *       ...
562      *   ]
563      */
564     switch (object.shape()) {
565     case MapObject::Rectangle:
566         break;
567     case MapObject::Polygon:
568     case MapObject::Polyline: {
569         if (notTemplateInstance || object.propertyChanged(MapObject::ShapeProperty)) {
570             QVariantList pointVariants;
571             for (const QPointF &point : object.polygon()) {
572                 QVariantMap pointVariant;
573                 pointVariant[QStringLiteral("x")] = point.x();
574                 pointVariant[QStringLiteral("y")] = point.y();
575                 pointVariants.append(pointVariant);
576             }
577 
578             if (object.shape() == MapObject::Polygon)
579                 objectVariant[QStringLiteral("polygon")] = pointVariants;
580             else
581                 objectVariant[QStringLiteral("polyline")] = pointVariants;
582         }
583         break;
584     }
585     case MapObject::Ellipse:
586         if (notTemplateInstance || object.propertyChanged(MapObject::ShapeProperty))
587             objectVariant[QStringLiteral("ellipse")] = true;
588         break;
589     case MapObject::Text:
590         if (notTemplateInstance || (object.propertyChanged(MapObject::TextProperty) ||
591                                     object.propertyChanged(MapObject::TextFontProperty) ||
592                                     object.propertyChanged(MapObject::TextAlignmentProperty) ||
593                                     object.propertyChanged(MapObject::TextWordWrapProperty) ||
594                                     object.propertyChanged(MapObject::TextColorProperty)))
595             objectVariant[QStringLiteral("text")] = toVariant(object.textData());
596         break;
597     case MapObject::Point:
598         if (notTemplateInstance || object.propertyChanged(MapObject::ShapeProperty))
599             objectVariant[QStringLiteral("point")] = true;
600         break;
601     }
602 
603     return objectVariant;
604 }
605 
toVariant(const TextData & textData) const606 QVariant MapToVariantConverter::toVariant(const TextData &textData) const
607 {
608     QVariantMap textVariant;
609 
610     textVariant[QStringLiteral("text")] = textData.text;
611 
612     if (textData.font.family() != QLatin1String("sans-serif"))
613         textVariant[QStringLiteral("fontfamily")] = textData.font.family();
614     if (textData.font.pixelSize() >= 0 && textData.font.pixelSize() != 16)
615         textVariant[QStringLiteral("pixelsize")] = textData.font.pixelSize();
616     if (textData.wordWrap)
617         textVariant[QStringLiteral("wrap")] = textData.wordWrap;
618     if (textData.color != Qt::black)
619         textVariant[QStringLiteral("color")] = colorToString(textData.color);
620     if (textData.font.bold())
621         textVariant[QStringLiteral("bold")] = textData.font.bold();
622     if (textData.font.italic())
623         textVariant[QStringLiteral("italic")] = textData.font.italic();
624     if (textData.font.underline())
625         textVariant[QStringLiteral("underline")] = textData.font.underline();
626     if (textData.font.strikeOut())
627         textVariant[QStringLiteral("strikeout")] = textData.font.strikeOut();
628     if (!textData.font.kerning())
629         textVariant[QStringLiteral("kerning")] = textData.font.kerning();
630 
631     if (!textData.alignment.testFlag(Qt::AlignLeft)) {
632         if (textData.alignment.testFlag(Qt::AlignHCenter))
633             textVariant[QStringLiteral("halign")] = QLatin1String("center");
634         else if (textData.alignment.testFlag(Qt::AlignRight))
635             textVariant[QStringLiteral("halign")] = QLatin1String("right");
636         else if (textData.alignment.testFlag(Qt::AlignJustify))
637             textVariant[QStringLiteral("halign")] = QLatin1String("justify");
638     }
639 
640     if (!textData.alignment.testFlag(Qt::AlignTop)) {
641         if (textData.alignment.testFlag(Qt::AlignVCenter))
642             textVariant[QStringLiteral("valign")] = QLatin1String("center");
643         else if (textData.alignment.testFlag(Qt::AlignBottom))
644             textVariant[QStringLiteral("valign")] = QLatin1String("bottom");
645     }
646 
647     return textVariant;
648 }
649 
toVariant(const ImageLayer & imageLayer) const650 QVariant MapToVariantConverter::toVariant(const ImageLayer &imageLayer) const
651 {
652     QVariantMap imageLayerVariant;
653     imageLayerVariant[QStringLiteral("type")] = QLatin1String("imagelayer");
654 
655     addLayerAttributes(imageLayerVariant, imageLayer);
656 
657     const QString rel = toFileReference(imageLayer.imageSource(), mDir);
658     imageLayerVariant[QStringLiteral("image")] = rel;
659 
660     const QColor transColor = imageLayer.transparentColor();
661     if (transColor.isValid())
662         imageLayerVariant[QStringLiteral("transparentcolor")] = transColor.name();
663 
664     return imageLayerVariant;
665 }
666 
toVariant(const GroupLayer & groupLayer,Map::LayerDataFormat format,int compressionLevel,QSize chunkSize) const667 QVariant MapToVariantConverter::toVariant(const GroupLayer &groupLayer,
668                                           Map::LayerDataFormat format,
669                                           int compressionLevel,
670                                           QSize chunkSize) const
671 {
672     QVariantMap groupLayerVariant;
673     groupLayerVariant[QStringLiteral("type")] = QLatin1String("group");
674 
675     addLayerAttributes(groupLayerVariant, groupLayer);
676 
677     groupLayerVariant[QStringLiteral("layers")] = toVariant(groupLayer.layers(),
678                                                            format,
679                                                            compressionLevel,
680                                                            chunkSize);
681 
682     return groupLayerVariant;
683 }
684 
addTileLayerData(QVariantMap & variant,const TileLayer & tileLayer,Map::LayerDataFormat format,int compressionLevel,const QRect & bounds) const685 void MapToVariantConverter::addTileLayerData(QVariantMap &variant,
686                                              const TileLayer &tileLayer,
687                                              Map::LayerDataFormat format,
688                                              int compressionLevel,
689                                              const QRect &bounds) const
690 {
691     switch (format) {
692     case Map::XML:
693     case Map::CSV: {
694         QVariantList tileVariants;
695         for (int y = bounds.top(); y <= bounds.bottom(); ++y)
696             for (int x = bounds.left(); x <= bounds.right(); ++x)
697                 tileVariants << mGidMapper.cellToGid(tileLayer.cellAt(x, y));
698 
699         variant[QStringLiteral("data")] = tileVariants;
700         break;
701     }
702     case Map::Base64:
703     case Map::Base64Zlib:
704     case Map::Base64Gzip:
705     case Map::Base64Zstandard:{
706         QByteArray layerData = mGidMapper.encodeLayerData(tileLayer, format, bounds, compressionLevel);
707         variant[QStringLiteral("data")] = layerData;
708         break;
709     }
710     }
711 }
712 
addLayerAttributes(QVariantMap & layerVariant,const Layer & layer) const713 void MapToVariantConverter::addLayerAttributes(QVariantMap &layerVariant,
714                                                const Layer &layer) const
715 {
716     if (layer.id() != 0)
717         layerVariant[QStringLiteral("id")] = layer.id();
718 
719     layerVariant[QStringLiteral("name")] = layer.name();
720     layerVariant[QStringLiteral("x")] = layer.x();
721     layerVariant[QStringLiteral("y")] = layer.y();
722     layerVariant[QStringLiteral("visible")] = layer.isVisible();
723     layerVariant[QStringLiteral("opacity")] = layer.opacity();
724 
725     const QPointF offset = layer.offset();
726     if (!offset.isNull()) {
727         layerVariant[QStringLiteral("offsetx")] = offset.x();
728         layerVariant[QStringLiteral("offsety")] = offset.y();
729     }
730 
731     const QPointF parallaxFactor = layer.parallaxFactor();
732     if (parallaxFactor.x() != 1.0)
733         layerVariant[QStringLiteral("parallaxx")] = parallaxFactor.x();
734     if (parallaxFactor.y() != 1.0)
735         layerVariant[QStringLiteral("parallaxy")] = parallaxFactor.y();
736 
737     if (layer.tintColor().isValid())
738         layerVariant[QStringLiteral("tintcolor")] = colorToString(layer.tintColor());
739 
740     addProperties(layerVariant, layer.properties());
741 }
742 
addProperties(QVariantMap & variantMap,const Properties & properties) const743 void MapToVariantConverter::addProperties(QVariantMap &variantMap,
744                                           const Properties &properties) const
745 {
746     if (properties.isEmpty())
747         return;
748 
749     if (mVersion == 1) {
750         QVariantMap propertiesMap;
751         QVariantMap propertyTypesMap;
752 
753         Properties::const_iterator it = properties.constBegin();
754         Properties::const_iterator it_end = properties.constEnd();
755         for (; it != it_end; ++it) {
756             int type = it.value().userType();
757             const QVariant value = toExportValue(it.value(), mDir);
758 
759             propertiesMap[it.key()] = value;
760             propertyTypesMap[it.key()] = typeToName(type);
761         }
762 
763         variantMap[QStringLiteral("properties")] = propertiesMap;
764         variantMap[QStringLiteral("propertytypes")] = propertyTypesMap;
765     } else {
766         QVariantList propertiesVariantList;
767 
768         Properties::const_iterator it = properties.constBegin();
769         Properties::const_iterator it_end = properties.constEnd();
770         for (; it != it_end; ++it) {
771             int type = it.value().userType();
772             const QVariant value = toExportValue(it.value(), mDir);
773 
774             QVariantMap propertyVariantMap;
775             propertyVariantMap[QStringLiteral("name")] = it.key();
776             propertyVariantMap[QStringLiteral("value")] = value;
777             propertyVariantMap[QStringLiteral("type")] = typeToName(type);
778             propertiesVariantList << propertyVariantMap;
779         }
780 
781         variantMap[QStringLiteral("properties")] = propertiesVariantList;
782     }
783 }
784