1 /*
2  * Copyright (C) 2015-2019, by Laszlo Szeremi under the Boost license.
3  *
4  * Pixel Perfect Engine, tiledata module
5  */
6 module PixelPerfectEngine.map.tiledata;
7 
8 import PixelPerfectEngine.system.etc;
9 
10 /**
11  * Represents TileInfo that can be embedded into TGA and PNG files, also can be stored in other files.
12  */
13 public class TileInfo {
14 	static enum char[4] IDENTIFIER_PNG = "tILE";	///Identifier for PNG ancillary chunk
15 	static enum ushort	IDENTIFIER_TGA = 0xFF00;	///Identifier for TGA developer extension
16 	/**
17 	 * Header used for shared information.
18 	 */
19 	public struct Header {
20 	align(1):
21 		public ubyte	tileWidth;		///Width of each tile
22 		public ubyte	tileHeight;		///Height of each tile
23 	}
24 	/**
25 	 * Index used for individual info for each tile in the file.
26 	 */
27 	align(1) public struct IndexF {
28 	align(1):
29 		public wchar	id;				///Identifier for each tile. 16bit value used for some support for unicode text.
30 		public ubyte	nameLength;		///Length of the namefield.
31 	}
32 	/**
33 	 * Index used for individual info for each tile in memory.
34 	 */
35 	public struct IndexM {
36 		public wchar	id;				///Identifier for each tile. 16bit value used for some support for unicode text.
37 		private string	_name;			///Name with protection from overflow to more than 255 chars
38 		/**
39 		 * Returns the name of the tile.
40 		 */
41 		public @property @safe @nogc pure nothrow string name() {
42 			return _name;
43 		}
44 		/**
45 		 * Sets the name of the tile if val is shorter than 255 characters, then returns the new name.
46 		 * Returns the old name if not.
47 		 */
48 		public @property @safe pure nothrow string name(string val) {
49 			if(val.length <= ubyte.max)
50 				_name = val;
51 			return _name;
52 		}
53 	}
54 	Header header;		///Header that stores all common data.
55 	IndexM[] indexes;	///Index for each tile in the file, even for "unused" tiles.
56 	/**
57 	 * Automatically generates itself from a bytestream.
58 	 */
59 	public this(ubyte[] source, uint totalWidth, uint totalHeight) @safe pure {
60 		header = reinterpretGet!Header(source[0..Header.sizeof]);
61 		source = source[Header.sizeof..$];
62 		indexes.length = (totalWidth / header.tileWidth) * (totalHeight / header.tileHeight);
63 		for(int i; i < indexes.length; i++) {
64 			const IndexF f = reinterpretGet!IndexF(source[0..IndexF.sizeof]);
65 			source = source[IndexF.sizeof..$];
66 			indexes[i].id = f.id;
67 			if(f.nameLength){
68 				indexes[i].name = (reinterpretCast!char(source[0..f.nameLength])).idup;
69 				source = source[f.nameLength..$];
70 			}
71 		}
72 	}
73 	/**
74 	 * Creates a new instance from scratch.
75 	 */
76 	public this(ubyte tileWidth, ubyte tileHeight, uint totalWidth, uint totalHeight) @safe pure {
77 		header = Header(tileWidth, tileHeight);
78 		indexes.length = (totalWidth / header.tileWidth) * (totalHeight / header.tileHeight);
79 	}
80 	/**
81 	 * Serializes itself into a bytestream.
82 	 */
83 	public ubyte[] serialize() @safe pure {
84 		ubyte[] result;
85 		result ~= toStream(header);
86 		for(int i; i < indexes.length; i++) {
87 			string name = indexes[i].name;
88 			IndexF f = IndexF(indexes[i].id, cast(ubyte)name.length);
89 			result ~= toStream(f);
90 			if(name.length)
91 				result ~= reinterpretCast!ubyte(name.dup);
92 		}
93 		return result;
94 	}
95 }