From 80191b41352ad20493fb62e8f3683d69133d0d24 Mon Sep 17 00:00:00 2001 From: P. J. McDermott Date: Thu, 14 Feb 2013 18:03:09 -0500 Subject: Initial commit. --- (limited to 'src/tmx.c') diff --git a/src/tmx.c b/src/tmx.c new file mode 100644 index 0000000..0e401b3 --- /dev/null +++ b/src/tmx.c @@ -0,0 +1,370 @@ +#include +#include +#include +#include +#include +#include "logging.h" +#include "base64.h" +#include "compression.h" +#include "map.h" +#include "image.h" + +struct tmx { + char *dirname; + struct map *map; + enum { + TMX_PARSING_NONE = 0, + TMX_PARSING_MAP, + TMX_PARSING_TILESET, + TMX_PARSING_IMAGE, + TMX_PARSING_LAYER, + TMX_PARSING_DATA + } parsing; + struct tileset *cur_tileset; + struct layer *cur_layer; + enum { + TMX_DATA_ENC_NONE = 0, + TMX_DATA_ENC_BASE64, + TMX_DATA_ENC_CSV + } data_encoding; + enum { + TMX_DATA_COMP_NONE = 0, + TMX_DATA_COMP_GZIP, + TMX_DATA_COMP_ZLIB + } data_compression; + char *layer_data; +}; + +#define foreach_tmx_attr(attr) \ + for (; (attr)[0] != NULL; (attr) += 2) + +#define tmx_get_int_attr(attr, name, p) \ + do { \ + if (strcmp((attr)[0], (name)) == 0) { \ + if (sscanf((attr)[1], "%d", (p)) != 1) { \ + err(1, "Invalid \"%s\" attribute", (name)); \ + } \ + } \ + } while (0); + +#define tmx_get_string_attr(attr, name, p) \ + do { \ + if (strcmp((attr)[0], (name)) == 0) { \ + (p) = strdup((attr)[1]); \ + } \ + } while (0); + +static void +tmx_start_map(struct tmx *cur_tmx, const char **attr) +{ + if (cur_tmx->map != NULL) { + err(1, "Found multiple maps in TMX file"); + } + + cur_tmx->map = malloc(sizeof(*cur_tmx->map)); + if (cur_tmx->map == NULL) { + err(1, "Failed to allocate map"); + } + memset(cur_tmx->map, 0, sizeof(*cur_tmx->map)); + + foreach_tmx_attr (attr) { + tmx_get_int_attr(attr, "width", &cur_tmx->map->width); + tmx_get_int_attr(attr, "height", &cur_tmx->map->height); + tmx_get_int_attr(attr, "tilewidth", &cur_tmx->map->tilewidth); + tmx_get_int_attr(attr, "tileheight", &cur_tmx->map->tileheight); + } +} + +static void +tmx_start_tileset(struct tmx *cur_tmx, const char **attr) +{ + struct tileset *new_tileset; + + if (cur_tmx->map == NULL) { + err(1, "Malformed TMX file"); + } + + new_tileset = malloc(sizeof(*new_tileset)); + if (new_tileset == NULL) { + err(1, "Failed to allocate tileset"); + } + memset(new_tileset, 0, sizeof(*new_tileset)); + + if (cur_tmx->cur_tileset == NULL) { + cur_tmx->map->tilesets = new_tileset; + } else { + cur_tmx->cur_tileset->next = new_tileset; + } + cur_tmx->cur_tileset = new_tileset; + + foreach_tmx_attr (attr) { + tmx_get_int_attr(attr, "firstgid", + &cur_tmx->cur_tileset->firstgid); + tmx_get_int_attr(attr, "tilewidth", + &cur_tmx->cur_tileset->tilewidth); + tmx_get_int_attr(attr, "tileheight", + &cur_tmx->cur_tileset->tileheight); + } + + /* TODO: Parse tilesets referenced in "source" attributes. */ +} + +static void +tmx_start_image(struct tmx *cur_tmx, const char **attr) +{ + char *source; + char *path; + + if (cur_tmx->cur_tileset == NULL) { + err(1, "Malformed TMX file"); + } + + foreach_tmx_attr (attr) { + tmx_get_string_attr(attr, "source", source); + } + + /* TODO: Move to end tag handler and check for "loadimage" property. */ + path = malloc(strlen(cur_tmx->dirname) + strlen(source) + 2); + if (path == NULL) { + err(1, "Failed to allocate resource path string"); + } + sprintf(path, "%s/%s", cur_tmx->dirname, source); + cur_tmx->cur_tileset->image = load_png(path); + free(path); + + debug(" Found tileset with firstgid %d and source \"%s\"", + cur_tmx->cur_tileset->firstgid, source); + + free(source); +} + +static void +tmx_start_layer(struct tmx *cur_tmx, const char **attr) +{ + struct layer *new_layer; + + if (cur_tmx->map == NULL) { + err(1, "Malformed TMX file"); + } + + new_layer = malloc(sizeof(*new_layer)); + if (new_layer == NULL) { + err(1, "Failed to allocate layer"); + } + memset(new_layer, 0, sizeof(*new_layer)); + + if (cur_tmx->cur_layer == NULL) { + cur_tmx->map->layers = new_layer; + } else { + cur_tmx->cur_layer->next = new_layer; + } + cur_tmx->cur_layer = new_layer; + + foreach_tmx_attr (attr) { + tmx_get_string_attr(attr, "name", cur_tmx->cur_layer->name); + } + + debug(" Found layer with name \"%s\"", cur_tmx->cur_layer->name); +} + +static void +tmx_start_data(struct tmx *cur_tmx, const char **attr) +{ + if (cur_tmx->cur_layer == NULL) { + err(1, "Malformed TMX file"); + } + + foreach_tmx_attr (attr) { + if (strcmp(attr[0], "encoding") == 0) { + if (strcmp(attr[1], "base64") == 0) { + cur_tmx->data_encoding = TMX_DATA_ENC_BASE64; + } else if (strcmp(attr[1], "csv") == 0) { + cur_tmx->data_encoding = TMX_DATA_ENC_CSV; + } else { + err(1, "Unsupported data encoding in TMX file"); + } + } else if (strcmp(attr[0], "compression") == 0) { + if (strcmp(attr[1], "gzip") == 0) { + cur_tmx->data_compression = TMX_DATA_COMP_GZIP; + } else if (strcmp(attr[1], "zlib") == 0) { + cur_tmx->data_compression = TMX_DATA_COMP_ZLIB; + } else { + err(1, "Unsupported compression in TMX file"); + } + } + } +} + +static void XMLCALL +tmx_start(void *data, const char *el, const char **attr) +{ + struct tmx *cur_tmx; + + cur_tmx = (struct tmx *) data; + + if (strcmp(el, "map") == 0) { + tmx_start_map(cur_tmx, attr); + cur_tmx->parsing = TMX_PARSING_MAP; + } else if (strcmp(el, "tileset") == 0) { + tmx_start_tileset(cur_tmx, attr); + cur_tmx->parsing = TMX_PARSING_TILESET; + } else if (strcmp(el, "image") == 0) { + tmx_start_image(cur_tmx, attr); + cur_tmx->parsing = TMX_PARSING_IMAGE; + } else if (strcmp(el, "layer") == 0) { + tmx_start_layer(cur_tmx, attr); + cur_tmx->parsing = TMX_PARSING_LAYER; + } else if (strcmp(el, "data") == 0) { + tmx_start_data(cur_tmx, attr); + cur_tmx->parsing = TMX_PARSING_DATA; + } + + /* TODO: Handle objectgroup, object, properties, and property tags. */ +} + +static void +tmx_end_data(struct tmx *cur_tmx) +{ + size_t decoded_len; + size_t decomp_len; + char *decoded_buf; + char *decomp_buf; + + decoded_len = strlen(cur_tmx->layer_data); + + decomp_len = 4 * cur_tmx->map->width * cur_tmx->map->height; + debug(" Expected map data size: %d", decomp_len); + + decoded_buf = malloc(decoded_len + 1); + if (decoded_buf == NULL) { + err(1, "Failed to allocate map layer data buffer"); + } + + if (cur_tmx->data_encoding == TMX_DATA_ENC_BASE64) { + debug(" Decoding base 64 layer data..."); + base64_decode(cur_tmx->layer_data, + decoded_buf, decoded_len + 1); + } + + if (cur_tmx->data_compression == TMX_DATA_COMP_NONE) { + debug(" Layer data already decompressed"); + decomp_buf = decoded_buf; + } else if (cur_tmx->data_compression == TMX_DATA_COMP_ZLIB) { + debug(" Decompressing layer data with zlib..."); + decomp_buf = malloc(decomp_len); + if (decomp_buf == NULL) { + err(1, "Failed to allocate map layer data buffer"); + } + decompress(decoded_buf, decoded_len, decomp_buf, decomp_len); + free(decoded_buf); + } else if (cur_tmx->data_compression == TMX_DATA_COMP_GZIP) { + debug(" Decompressing layer data with gzip..."); + decomp_buf = malloc(decomp_len); + if (decomp_buf == NULL) { + err(1, "Failed to allocate map layer data buffer"); + } + decompress(decoded_buf, decoded_len, decomp_buf, decomp_len); + free(decoded_buf); + } + + free(cur_tmx->layer_data); + + cur_tmx->cur_layer->tiles = (Uint32 *) decomp_buf; +} + +static void XMLCALL +tmx_end(void *data, const char *el) +{ + struct tmx *cur_tmx; + + cur_tmx = (struct tmx *) data; + + if (strcmp(el, "data") == 0) { + tmx_end_data(cur_tmx); + } + + cur_tmx->parsing = TMX_PARSING_NONE; +} + +static void XMLCALL +tmx_data(void *data, const char *s, int len) +{ + struct tmx *cur_tmx; + char *s_z; + char *s_z_trimmed, *s_z_trimmed_end; + + cur_tmx = (struct tmx *) data; + + if (cur_tmx->parsing != TMX_PARSING_DATA) { + return; + } + + s_z = s_z_trimmed = strndup(s, len); + + while(isspace(*s_z_trimmed)) { + ++s_z_trimmed; + --len; + } + if (*s_z_trimmed == '\0') { + return; + } + s_z_trimmed_end = s_z_trimmed + len - 1; + while (s_z_trimmed_end > s_z_trimmed && isspace(*s_z_trimmed_end)) { + --s_z_trimmed_end; + } + *(s_z_trimmed_end + 1) = '\0'; + + cur_tmx->layer_data = strdup(s_z_trimmed); + + free(s_z); +} + +struct map * +tmx_load(const char *path) +{ + XML_Parser p; + struct tmx cur_tmx; + char *path_dup; + FILE *tmx_fp; + void *tmx_buf; + size_t len; + + debug("Parsing TMX file \"%s\"...", path); + + p = XML_ParserCreate(NULL); + if (p == NULL) { + err(1, "Failed to create TMX parser"); + } + + XML_SetElementHandler(p, tmx_start, tmx_end); + XML_SetCharacterDataHandler(p, tmx_data); + + memset(&cur_tmx, 0, sizeof(cur_tmx)); + path_dup = strdup(path); + cur_tmx.dirname = dirname(path_dup); + XML_SetUserData(p, &cur_tmx); + + tmx_fp = fopen(path, "rb"); + if (tmx_fp == NULL) { + err(1, "Failed to open TMX file"); + } + + tmx_buf = XML_GetBuffer(p, 8192); + if (tmx_buf == NULL) { + err(1, "Failed to create TMX parse buffer"); + } + + while (!feof(tmx_fp)) { + len = fread(tmx_buf, 1, 8192, tmx_fp); + if (XML_ParseBuffer(p, len, feof(tmx_fp)) == XML_STATUS_ERROR) { + err(1, "Failed to parse TMX file (%s)", + XML_ErrorString(XML_GetErrorCode(p))); + } + } + + fclose(tmx_fp); + + free(path_dup); + + return cur_tmx.map; +} -- cgit v0.9.1