diff --git a/source/base/test_util/src/ir_presets.cpp b/source/base/test_util/src/ir_presets.cpp index 8931a92bc3..a87513e84b 100644 --- a/source/base/test_util/src/ir_presets.cpp +++ b/source/base/test_util/src/ir_presets.cpp @@ -37,7 +37,7 @@ auto make_complex_ir_object(const ObjectID id, const ObjectType type) -> ir::Obj { auto object = make_ir_object(id, type, Float2 {12, 34}, Float2 {56, 78}); - object.meta = make_complex_ir_metadata(std::format("Complex object {}", id)); + object.meta = make_complex_ir_metadata(std::format("complex_object {}", id)); object.tag = "this-is-a-tag"; return object; @@ -47,7 +47,7 @@ auto make_complex_ir_object_layer(const LayerID id, ObjectID& next_object_id) -> { auto object_layer = make_ir_object_layer(id); - object_layer.meta = make_complex_ir_metadata(std::format("Complex layer {}", id)); + object_layer.meta = make_complex_ir_metadata(std::format("complex_layer {}", id)); object_layer.opacity = 0.50f; object_layer.visible = false; @@ -65,7 +65,7 @@ auto make_complex_ir_tile_layer(const LayerID id, const MatrixExtent& extent) -> { auto tile_layer = make_ir_tile_layer(id, extent); - tile_layer.meta = make_complex_ir_metadata(std::format("Complex layer {}", id)); + tile_layer.meta = make_complex_ir_metadata(std::format("complex_layer {}", id)); tile_layer.opacity = 1.0f; tile_layer.visible = true; @@ -136,7 +136,7 @@ auto make_complex_ir_tile(const TileIndex index, ObjectID& next_object_id) -> ir { auto tile = make_ir_tile(index); - tile.meta = make_complex_ir_metadata(std::format("Complex tile {}", index)); + tile.meta = make_complex_ir_metadata(std::format("complex_tile {}", index)); tile.objects.reserve(3); tile.objects.push_back(make_complex_ir_object(next_object_id++, ObjectType::kPoint)); @@ -156,7 +156,7 @@ auto make_complex_ir_tileset(TileID& next_tile_id, ObjectID& next_object_id) -> { auto tileset_ref = make_ir_tileset_ref(next_tile_id); - tileset_ref.tileset.meta = make_complex_ir_metadata("Complex tileset"); + tileset_ref.tileset.meta = make_complex_ir_metadata("complex_tileset"); tileset_ref.tileset.tiles.reserve(3); tileset_ref.tileset.tiles.push_back(make_complex_ir_tile(TileIndex {0}, next_object_id)); tileset_ref.tileset.tiles.push_back(make_complex_ir_tile(TileIndex {1}, next_object_id)); @@ -171,7 +171,7 @@ auto make_complex_ir_map(const ir::TileFormat& tile_format) -> ir::Map auto map = make_ir_map(extent); - map.meta = make_complex_ir_metadata("Complex map"); + map.meta = make_complex_ir_metadata("complex_map"); map.tile_size = Int2 {40, 30}; map.next_layer_id = 10; map.next_object_id = 100; diff --git a/source/tiled_tmj_format/lib/src/tmj_format_map_parser.cpp b/source/tiled_tmj_format/lib/src/tmj_format_map_parser.cpp index d030d935c0..de4d1e1831 100644 --- a/source/tiled_tmj_format/lib/src/tmj_format_map_parser.cpp +++ b/source/tiled_tmj_format/lib/src/tmj_format_map_parser.cpp @@ -9,6 +9,40 @@ #include "tactile/tiled_tmj_format/tmj_format_tileset_parser.hpp" namespace tactile { +namespace tmj_format_map_parser { + +void deduce_tile_format_from_layer(const nlohmann::json& layer_json, + ir::TileFormat& tile_format) +{ + if (const auto encoding_iter = layer_json.find("encoding"); + encoding_iter != layer_json.end()) { + const auto& encoding = encoding_iter->get_ref(); + + if (encoding == "base64") { + tile_format.encoding = TileEncoding::kBase64; + } + else { + tile_format.encoding = TileEncoding::kPlainText; + } + } + + if (const auto compression_iter = layer_json.find("compression"); + compression_iter != layer_json.end()) { + const auto& compression_name = compression_iter->get_ref(); + + if (compression_name == "zlib") { + tile_format.compression = CompressionFormat::kZlib; + } + else if (compression_name == "zstd") { + tile_format.compression = CompressionFormat::kZstd; + } + else { + tile_format.compression = kNone; + } + } +} + +} // namespace tmj_format_map_parser auto parse_tiled_tmj_map(const IRuntime& runtime, const nlohmann::json& map_json, @@ -98,6 +132,8 @@ auto parse_tiled_tmj_map(const IRuntime& runtime, map.layers.reserve(layers_iter->size()); for (const auto& [_, layer_json] : layers_iter->items()) { + tmj_format_map_parser::deduce_tile_format_from_layer(layer_json, map.tile_format); + if (auto layer = parse_tiled_tmj_layer(runtime, layer_json)) { map.layers.push_back(std::move(*layer)); } diff --git a/source/tiled_tmj_format/lib/src/tmj_format_save_visitor.cpp b/source/tiled_tmj_format/lib/src/tmj_format_save_visitor.cpp index 5ff37db627..4b211bd347 100644 --- a/source/tiled_tmj_format/lib/src/tmj_format_save_visitor.cpp +++ b/source/tiled_tmj_format/lib/src/tmj_format_save_visitor.cpp @@ -164,9 +164,17 @@ auto TmjFormatSaveVisitor::_find_layer_json(nlohmann::json& root_node, auto TmjFormatSaveVisitor::_find_tileset_json(const TileID first_tile_id) -> nlohmann::json* { - for (const auto& [_, tileset_node] : mMapNode.at("tilesets").items()) { - if (tileset_node.at("firstgid") == first_tile_id) { - return &tileset_node; + if (mOptions.use_external_tilesets) { + const auto external_tileset_iter = mExternalTilesetNodes.find(first_tile_id); + if (external_tileset_iter != mExternalTilesetNodes.end()) { + return &external_tileset_iter->second.json; + } + } + else { + for (const auto& [_, tileset_node] : mMapNode.at("tilesets").items()) { + if (tileset_node.at("firstgid") == first_tile_id) { + return &tileset_node; + } } } diff --git a/source/tiled_tmj_format/lib/src/tmj_format_tileset_parser.cpp b/source/tiled_tmj_format/lib/src/tmj_format_tileset_parser.cpp index 8fbfa0e995..e636953e85 100644 --- a/source/tiled_tmj_format/lib/src/tmj_format_tileset_parser.cpp +++ b/source/tiled_tmj_format/lib/src/tmj_format_tileset_parser.cpp @@ -171,7 +171,6 @@ auto parse_tileset(const nlohmann::json& tileset_json) -> SaveFormatParseResult< } tileset.image_path = Path {relative_image_path}; - tileset.is_embedded = !tileset_json.contains("source"); if (const auto tiles_iter = tileset_json.find("tiles"); tiles_iter != tileset_json.end()) { tileset.tiles.reserve(tiles_iter->size()); @@ -218,6 +217,7 @@ auto parse_tiled_tmj_tileset(const nlohmann::json& tileset_json, if (auto tileset = tmj_format_tileset_parser::parse_tileset(external_tileset_json)) { tileset_ref.tileset = std::move(*tileset); + tileset_ref.tileset.is_embedded = false; } else { return propagate_unexpected(tileset); @@ -226,6 +226,7 @@ auto parse_tiled_tmj_tileset(const nlohmann::json& tileset_json, else { if (auto tileset = tmj_format_tileset_parser::parse_tileset(tileset_json)) { tileset_ref.tileset = std::move(*tileset); + tileset_ref.tileset.is_embedded = true; } else { return propagate_unexpected(tileset); diff --git a/source/tiled_tmj_format/test/src/tmj_format_roundtrip_test.cpp b/source/tiled_tmj_format/test/src/tmj_format_roundtrip_test.cpp index 87624fded0..2526c37926 100644 --- a/source/tiled_tmj_format/test/src/tmj_format_roundtrip_test.cpp +++ b/source/tiled_tmj_format/test/src/tmj_format_roundtrip_test.cpp @@ -1,10 +1,13 @@ // Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0) #include // current_path +#include // ostream #include #include +#include "tactile/base/container/maybe.hpp" +#include "tactile/base/container/string.hpp" #include "tactile/base/test_util/document_view_mocks.hpp" #include "tactile/base/test_util/ir.hpp" #include "tactile/base/test_util/ir_eq.hpp" @@ -22,7 +25,37 @@ namespace tactile::test { -class TmjFormatRoundtripTest : public testing::Test +struct TmjRoundtripConfig final +{ + StringView map_filename; + TileEncoding encoding; + Optional compression; + bool use_external_tilesets; +}; + +inline auto operator<<(std::ostream& stream, const TmjRoundtripConfig& config) -> std::ostream& +{ + stream << config.map_filename; + + stream << " + " << (config.encoding == TileEncoding::kBase64 ? "base64" : "plain text") + << " encoding"; + + if (config.compression == CompressionFormat::kZlib) { + stream << " + zlib compression"; + } + else if (config.compression == CompressionFormat::kZstd) { + stream << " + zstd compression"; + } + else { + stream << " + no compression"; + } + + stream << " + " << (config.use_external_tilesets ? "external" : "embedded") << " tilesets"; + + return stream; +} + +class TmjFormatRoundtripTest : public testing::TestWithParam { public: void SetUp() override @@ -65,29 +98,65 @@ class TmjFormatRoundtripTest : public testing::Test TmjFormatPlugin mTmjFormatPlugin {}; }; -TEST_F(TmjFormatRoundtripTest, SaveAndLoadMapWithEmbeddedTilesets) +INSTANTIATE_TEST_SUITE_P(TMJ, + TmjFormatRoundtripTest, + testing::Values( + TmjRoundtripConfig { + .map_filename = "map_with_embedded_tilesets.tmj", + .encoding = TileEncoding::kPlainText, + .compression = kNone, + .use_external_tilesets = false, + }, + TmjRoundtripConfig { + .map_filename = "map_with_external_tilesets.tmj", + .encoding = TileEncoding::kPlainText, + .compression = kNone, + .use_external_tilesets = true, + }, + TmjRoundtripConfig { + .map_filename = "map_with_base64_tiles.tmj", + .encoding = TileEncoding::kBase64, + .compression = kNone, + .use_external_tilesets = false, + }, + TmjRoundtripConfig { + .map_filename = "map_with_base64_zlib_tiles.tmj", + .encoding = TileEncoding::kBase64, + .compression = CompressionFormat::kZlib, + .use_external_tilesets = false, + }, + TmjRoundtripConfig { + .map_filename = "map_with_base64_zstd_tiles.tmj", + .encoding = TileEncoding::kBase64, + .compression = CompressionFormat::kZstd, + .use_external_tilesets = false, + })); + +TEST_P(TmjFormatRoundtripTest, SaveAndLoadMap) { + const auto& config = GetParam(); + const auto* save_format = mRuntime.get_save_format(SaveFormatId::kTiledTmj); ASSERT_NE(save_format, nullptr); auto ir_map = make_complex_ir_map(ir::TileFormat { - .encoding = TileEncoding::kPlainText, - .compression = kNone, + .encoding = config.encoding, + .compression = config.compression, .compression_level = kNone, }); for (auto& ir_tileset_ref : ir_map.tilesets) { - ir_tileset_ref.tileset.is_embedded = true; + ir_tileset_ref.tileset.is_embedded = !config.use_external_tilesets; } const testing::NiceMock map_view {ir_map}; - const auto map_path = std::filesystem::current_path() / "roundtrip.tmj"; + const auto map_path = std::filesystem::current_path() / config.map_filename; EXPECT_CALL(map_view, get_path).WillRepeatedly(testing::Return(&map_path)); const SaveFormatWriteOptions write_options { .base_dir = map_path.parent_path(), - .use_external_tilesets = false, + .use_external_tilesets = config.use_external_tilesets, .use_indentation = true, .fold_tile_layer_data = false, };