diff --git a/README.md b/README.md index f76e19f..d7e3369 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,14 @@ This is a work in progress. ####ALL: Golang + //Gcc + zlib & libpng (bitmap) ####For Mac OS X: Xcode Command Line Tools + + brew install libpng + brew install homebrew/dupes/zlib ####For Windows: MinGW or other GCC ####For everything else: diff --git a/README_zh.md b/README_zh.md index 7f0fc50..1c224c8 100644 --- a/README_zh.md +++ b/README_zh.md @@ -23,9 +23,14 @@ RobotGo 支持 Mac, Windows, and Linux(X11). ####ALL: Golang + //Gcc + zlib & libpng (bitmap) ####For Mac OS X: Xcode Command Line Tools + + brew install libpng + brew install homebrew/dupes/zlib ####For Windows: MinGW or other GCC ####For everything else(Linux等其他系统): diff --git a/base/MMBitmap.h b/base/MMBitmap.h index 91fea63..bcdec77 100644 --- a/base/MMBitmap.h +++ b/base/MMBitmap.h @@ -7,6 +7,12 @@ #include #include +// #if defined(_MSC_VER) +// #include "ms_stdint.h" +// #else +// #include +// #endif + #ifdef __cplusplus extern "C" { @@ -24,6 +30,7 @@ struct _MMBitmap { typedef struct _MMBitmap MMBitmap; typedef MMBitmap *MMBitmapRef; +MMBitmapRef bitmap; /* Creates new MMBitmap with the given values. * Follows the Create Rule (caller is responsible for destroy()'ing object). */ diff --git a/base/MMPointArray.h b/base/MMPointArray.h new file mode 100644 index 0000000..447fc72 --- /dev/null +++ b/base/MMPointArray.h @@ -0,0 +1,33 @@ +#pragma once +#ifndef MMARRAY_H +#define MMARRAY_H + +#include "types.h" + +struct _MMPointArray { + MMPoint *array; /* Pointer to actual data. */ + size_t count; /* Number of elements in array. */ + size_t _allocedCount; /* Private; do not use outside of MMPointArray.c. */ +}; + +typedef struct _MMPointArray MMPointArray; +typedef MMPointArray *MMPointArrayRef; + +/* Creates array of an initial size (the maximum size is still limitless). + * This follows the "Create" Rule; i.e., responsibility for "destroying" the + * array is given to the caller. */ +MMPointArrayRef createMMPointArray(size_t initialCount); + +/* Frees memory occupied by |pointArray|. Does not accept NULL. */ +void destroyMMPointArray(MMPointArrayRef pointArray); + +/* Appends a point to an array, increasing the internal size if necessary. */ +void MMPointArrayAppendPoint(MMPointArrayRef pointArray, MMPoint point); + +/* Retrieve point from array. */ +#define MMPointArrayGetItem(a, i) ((a)->array)[i] + +/* Set point in array. */ +#define MMPointArraySetItem(a, i, item) ((a)->array[i] = item) + +#endif /* MMARRAY_H */ diff --git a/base/MMPointArray_init.h b/base/MMPointArray_init.h new file mode 100644 index 0000000..f77f3fc --- /dev/null +++ b/base/MMPointArray_init.h @@ -0,0 +1,41 @@ +#include "MMPointArray.h" +#include + +MMPointArrayRef createMMPointArray(size_t initialCount) +{ + MMPointArrayRef pointArray = calloc(1, sizeof(MMPointArray)); + + if (initialCount == 0) initialCount = 1; + + pointArray->_allocedCount = initialCount; + pointArray->array = malloc(pointArray->_allocedCount * sizeof(MMPoint)); + if (pointArray->array == NULL) return NULL; + + return pointArray; +} + +void destroyMMPointArray(MMPointArrayRef pointArray) +{ + if (pointArray->array != NULL) { + free(pointArray->array); + pointArray->array = NULL; + } + + free(pointArray); +} + +void MMPointArrayAppendPoint(MMPointArrayRef pointArray, MMPoint point) +{ + const size_t newCount = ++(pointArray->count); + if (pointArray->_allocedCount < newCount) { + do { + /* Double size each time to avoid calls to realloc(). */ + pointArray->_allocedCount <<= 1; + } while (pointArray->_allocedCount < newCount); + pointArray->array = realloc(pointArray->array, + sizeof(point) * + pointArray->_allocedCount); + } + + pointArray->array[pointArray->count - 1] = point; +} diff --git a/base/UTHashTable.h b/base/UTHashTable.h new file mode 100644 index 0000000..54cfbe8 --- /dev/null +++ b/base/UTHashTable.h @@ -0,0 +1,83 @@ +#pragma once +#ifndef UTHASHTABLE_H +#define UTHASHTABLE_H + +#include +#include "uthash.h" + +/* All node structs must begin with this (note that there is NO semicolon). */ +#define UTHashNode_HEAD UT_hash_handle hh; + +/* This file contains convenience macros and a standard struct for working with + * uthash hash tables. + * + * The main purpose of this is for convenience of creating/freeing nodes. */ +struct _UTHashTable { + void *uttable; /* The uthash table -- must start out as NULL. */ + void *nodes; /* Contiguous array of nodes. */ + size_t allocedNodeCount; /* Node count currently allocated for. */ + size_t nodeCount; /* Current node count. */ + size_t nodeSize; /* Size of each node. */ +}; + +typedef struct _UTHashTable UTHashTable; + +/* Initiates a hash table to the default values. |table| should point to an + * already allocated UTHashTable struct. + * + * If the |initialCount| argument in initHashTable is given, |nodes| is + * allocated immediately to the maximum size and new nodes are simply slices of + * that array. This can save calls to malloc if many nodes are to be added, and + * the a reasonable maximum number is known ahead of time. + * + * If the node count goes over this maximum, or if |initialCount| is 0, the + * array is dynamically reallocated to fit the size. + */ +void initHashTable(UTHashTable *table, size_t initialCount, size_t nodeSize); + +/* Frees memory occupied by a UTHashTable's members. + * + * Note that this does NOT free memory for the UTHashTable pointed to by + * |table| itself; if that was allocated on the heap, you must free() it + * yourself after calling this. */ +void destroyHashTable(UTHashTable *table); + +/* Returns memory allocated for a new node. Responsibility for freeing this is + * up to the destroyHashTable() macro; this should NOT be freed by the caller. + * + * This is intended to be used with a HASH_ADD() macro, e.g.: + * {% + * struct myNode *uttable = utHashTable->uttable; + * struct myNode *node = getNewNode(utHashTable); + * node->key = 42; + * node->value = someValue; + * HASH_ADD_INT(uttable, key, node); + * utHashTable->uttable = uttable; + * %} + * + * Or, use the UTHASHTABLE_ADD_INT or UTHASHTABLE_ADD_STR macros + * for convenience (they are exactly equivalent): + * {% + * struct myNode *node = getNewNode(utHashTable); + * node->key = 42; + * node->value = someValue; + * UTHASHTABLE_ADD_INT(utHashTable, key, node, struct myNode); + * %} + */ +void *getNewNode(UTHashTable *table); + +#define UTHASHTABLE_ADD_INT(tablePtr, keyName, node, nodeType) \ +do { \ + nodeType *uttable = (tablePtr)->uttable; \ + HASH_ADD_INT(uttable, keyName, node); \ + (tablePtr)->uttable = uttable; \ +} while (0) + +#define UTHASHTABLE_ADD_STR(tablePtr, keyName, node, nodeType) \ +do { \ + nodeType *uttable = (tablePtr)->uttable; \ + HASH_ADD_STR(uttable, keyName, node); \ + (tablePtr)->uttable = uttable; \ +} while (0) + +#endif /* MMHASHTABLE_H */ diff --git a/base/UTHashTable_init.h b/base/UTHashTable_init.h new file mode 100644 index 0000000..c0c482a --- /dev/null +++ b/base/UTHashTable_init.h @@ -0,0 +1,56 @@ +#include "UTHashTable.h" +#include +#include + +/* Base struct class (all nodes must contain at least the elements in + * this struct). */ +struct _UTHashNode { + UTHashNode_HEAD +}; + +typedef struct _UTHashNode UTHashNode; + +void initHashTable(UTHashTable *table, size_t initialCount, size_t nodeSize) +{ + assert(table != NULL); + assert(nodeSize >= sizeof(UTHashNode)); + + table->uttable = NULL; /* Must be set to NULL for uthash. */ + table->allocedNodeCount = (initialCount == 0) ? 1 : initialCount; + table->nodeCount = 0; + table->nodeSize = nodeSize; + table->nodes = calloc(table->nodeSize, nodeSize * table->allocedNodeCount); +} + +void destroyHashTable(UTHashTable *table) +{ + UTHashNode *uttable = table->uttable; + UTHashNode *node; + + /* Let uthash do its magic. */ + while (uttable != NULL) { + node = uttable; /* Grab pointer to first item. */ + HASH_DEL(uttable, node); /* Delete it (table advances to next). */ + } + + /* Only giant malloc'd block containing each node must be freed. */ + if (table->nodes != NULL) free(table->nodes); + table->uttable = table->nodes = NULL; +} + +void *getNewNode(UTHashTable *table) +{ + /* Increment node count, resizing table if necessary. */ + const size_t newNodeCount = ++(table->nodeCount); + if (table->allocedNodeCount < newNodeCount) { + do { + /* Double size each time to avoid calls to realloc(). */ + table->allocedNodeCount <<= 1; + } while (table->allocedNodeCount < newNodeCount); + + table->nodes = realloc(table->nodes, table->nodeSize * + table->allocedNodeCount); + } + + return (char *)table->nodes + (table->nodeSize * (table->nodeCount - 1)); +} diff --git a/base/base64.c b/base/base64.c new file mode 100644 index 0000000..5b384c4 --- /dev/null +++ b/base/base64.c @@ -0,0 +1,111 @@ +#include "base64.h" +#include +#include +#include +#include + +/* Encoding table as described in RFC1113. */ +const static uint8_t b64_encode_table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* Decoding table. */ +const static int8_t b64_decode_table[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00-0F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10-1F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, /* 20-2F */ + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, /* 30-3F */ + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 40-4F */ + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, /* 50-5F */ + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 60-6F */ + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, /* 70-7F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80-8F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90-9F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* A0-AF */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* B0-BF */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* C0-CF */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* D0-DF */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* E0-EF */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 /* F0-FF */ +}; + +uint8_t *base64decode(const uint8_t *src, const size_t buflen, size_t *retlen) +{ + int8_t digit, lastdigit; + size_t i, j; + uint8_t *decoded; + const size_t maxlen = ((buflen + 3) / 4) * 3; + + /* Sanity check */ + assert(src != NULL); + + digit = lastdigit = j = 0; + decoded = malloc(maxlen + 1); + if (decoded == NULL) return NULL; + for (i = 0; i < buflen; ++i) { + if ((digit = b64_decode_table[src[i]]) != -1) { + /* Decode block */ + switch (i % 4) { + case 1: + decoded[j++] = ((lastdigit << 2) | ((digit & 0x30) >> 4)); + break; + case 2: + decoded[j++] = (((lastdigit & 0xF) << 4) | ((digit & 0x3C) >> 2)); + break; + case 3: + decoded[j++] = (((lastdigit & 0x03) << 6) | digit); + break; + } + lastdigit = digit; + } + } + + if (retlen != NULL) *retlen = j; + decoded[j] = '\0'; + return decoded; /* Must be free()'d by caller */ +} + +uint8_t *base64encode(const uint8_t *src, const size_t buflen, size_t *retlen) +{ + size_t i, j; + const size_t maxlen = (((buflen + 3) & ~3)) * 4; + uint8_t *encoded = malloc(maxlen + 1); + if (encoded == NULL) return NULL; + + /* Sanity check */ + assert(src != NULL); + assert(buflen > 0); + + j = 0; + for (i = 0; i < buflen + 1; ++i) { + /* Encode block */ + switch (i % 3) { + case 0: + encoded[j++] = b64_encode_table[src[i] >> 2]; + encoded[j++] = b64_encode_table[((src[i] & 0x03) << 4) | + ((src[i + 1] & 0xF0) >> 4)]; + break; + case 1: + encoded[j++] = b64_encode_table[((src[i] & 0x0F) << 2) | + ((src[i + 1] & 0xC0) >> 6)]; + break; + case 2: + encoded[j++] = b64_encode_table[(src[i] & 0x3F)]; + break; + } + } + + /* Add padding if necessary */ + if ((j % 4) != 0) { + const size_t with_padding = ((j + 3) & ~3); /* Align to 4 bytes */ + do { + encoded[j++] = '='; + } while (j < with_padding); + } + + assert(j <= maxlen); + + if (retlen != NULL) *retlen = j; + encoded[j] = '\0'; + return encoded; /* Must be free()'d by caller */ +} diff --git a/base/base64.h b/base/base64.h new file mode 100644 index 0000000..d12700c --- /dev/null +++ b/base/base64.h @@ -0,0 +1,31 @@ +#pragma once +#ifndef BASE64_H +#define BASE64_H + +#include + +#if defined(_MSC_VER) + #include "ms_stdint.h" +#else + #include +#endif + +/* Decode a base64 encoded string discarding line breaks and noise. + * + * Returns a new string to be free()'d by caller, or NULL on error. + * Returned string is guaranteed to be NUL-terminated. + * + * If |retlen| is not NULL, it is set to the length of the returned string + * (minus the NUL-terminator) on successful return. */ +uint8_t *base64decode(const uint8_t *buf, const size_t buflen, size_t *retlen); + +/* Encode a base64 encoded string without line breaks or noise. + * + * Returns a new string to be free()'d by caller, or NULL on error. + * Returned string is guaranteed to be NUL-terminated with the correct padding. + * + * If |retlen| is not NULL, it is set to the length of the returned string + * (minus the NUL-terminator) on successful return. */ +uint8_t *base64encode(const uint8_t *buf, const size_t buflen, size_t *retlen); + +#endif /* BASE64_H */ diff --git a/base/base64_init.h b/base/base64_init.h new file mode 100644 index 0000000..5b384c4 --- /dev/null +++ b/base/base64_init.h @@ -0,0 +1,111 @@ +#include "base64.h" +#include +#include +#include +#include + +/* Encoding table as described in RFC1113. */ +const static uint8_t b64_encode_table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* Decoding table. */ +const static int8_t b64_decode_table[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00-0F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10-1F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, /* 20-2F */ + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, /* 30-3F */ + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 40-4F */ + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, /* 50-5F */ + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 60-6F */ + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, /* 70-7F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80-8F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90-9F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* A0-AF */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* B0-BF */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* C0-CF */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* D0-DF */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* E0-EF */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 /* F0-FF */ +}; + +uint8_t *base64decode(const uint8_t *src, const size_t buflen, size_t *retlen) +{ + int8_t digit, lastdigit; + size_t i, j; + uint8_t *decoded; + const size_t maxlen = ((buflen + 3) / 4) * 3; + + /* Sanity check */ + assert(src != NULL); + + digit = lastdigit = j = 0; + decoded = malloc(maxlen + 1); + if (decoded == NULL) return NULL; + for (i = 0; i < buflen; ++i) { + if ((digit = b64_decode_table[src[i]]) != -1) { + /* Decode block */ + switch (i % 4) { + case 1: + decoded[j++] = ((lastdigit << 2) | ((digit & 0x30) >> 4)); + break; + case 2: + decoded[j++] = (((lastdigit & 0xF) << 4) | ((digit & 0x3C) >> 2)); + break; + case 3: + decoded[j++] = (((lastdigit & 0x03) << 6) | digit); + break; + } + lastdigit = digit; + } + } + + if (retlen != NULL) *retlen = j; + decoded[j] = '\0'; + return decoded; /* Must be free()'d by caller */ +} + +uint8_t *base64encode(const uint8_t *src, const size_t buflen, size_t *retlen) +{ + size_t i, j; + const size_t maxlen = (((buflen + 3) & ~3)) * 4; + uint8_t *encoded = malloc(maxlen + 1); + if (encoded == NULL) return NULL; + + /* Sanity check */ + assert(src != NULL); + assert(buflen > 0); + + j = 0; + for (i = 0; i < buflen + 1; ++i) { + /* Encode block */ + switch (i % 3) { + case 0: + encoded[j++] = b64_encode_table[src[i] >> 2]; + encoded[j++] = b64_encode_table[((src[i] & 0x03) << 4) | + ((src[i + 1] & 0xF0) >> 4)]; + break; + case 1: + encoded[j++] = b64_encode_table[((src[i] & 0x0F) << 2) | + ((src[i + 1] & 0xC0) >> 6)]; + break; + case 2: + encoded[j++] = b64_encode_table[(src[i] & 0x3F)]; + break; + } + } + + /* Add padding if necessary */ + if ((j % 4) != 0) { + const size_t with_padding = ((j + 3) & ~3); /* Align to 4 bytes */ + do { + encoded[j++] = '='; + } while (j < with_padding); + } + + assert(j <= maxlen); + + if (retlen != NULL) *retlen = j; + encoded[j] = '\0'; + return encoded; /* Must be free()'d by caller */ +} diff --git a/base/color_find.h b/base/color_find.h new file mode 100644 index 0000000..3c732f6 --- /dev/null +++ b/base/color_find.h @@ -0,0 +1,49 @@ +#pragma once +#ifndef COLOR_FIND_H +#define COLOR_FIND_H + +#include "MMBitmap.h" +#include "MMPointArray.h" + +/* Convenience wrapper around findColorInRect(), where |rect| is the bounds of + * the image. */ +#define findColorInImage(image, color, pointPtr, tolerance) \ + findColorInRect(image, color, pointPtr, MMBitmapGetBounds(image), tolerance) + +/* Attempt to find a pixel with the given color in |image| inside |rect|. + * Returns 0 on success, non-zero on failure. If the color was found and + * |point| is not NULL, it will be initialized to the (x, y) coordinates the + * RGB color. + * + * |tolerance| should be in the range 0.0f - 1.0f, denoting how closely the + * colors need to match, with 0 being exact and 1 being any. */ +int findColorInRect(MMBitmapRef image, MMRGBHex color, MMPoint *point, + MMRect rect, float tolerance); + +/* Convenience wrapper around findAllRGBInRect(), where |rect| is the bounds of + * the image. */ +#define findAllColorInImage(image, color, tolerance) \ + findAllColorInRect(image, color, MMBitmapGetBounds(image), tolerance) + +/* Returns MMPointArray of all pixels of given color in |image| inside of + * |rect|. Note that an array is returned regardless of whether the color was + * found; check array->count to see if it actually was. + * + * Responsibility for freeing the MMPointArray with destroyMMPointArray() is + * given to the caller. + * + * |tolerance| should be in the range 0.0f - 1.0f, denoting how closely the + * colors need to match, with 0 being exact and 1 being any. */ +MMPointArrayRef findAllColorInRect(MMBitmapRef image, MMRGBHex color, + MMRect rect, float tolerance); + +/* Convenience wrapper around countOfColorsInRect, where |rect| is the bounds + * of the image. */ +#define countOfColorsInImage(image, color, tolerance) \ + countOfColorsInRect(image, color, MMBitmapGetBounds(image), tolerance) + +/* Returns the count of the given color in |rect| inside of |image|. */ +size_t countOfColorsInRect(MMBitmapRef image, MMRGBHex color, MMRect rect, + float tolerance); + +#endif /* COLOR_FIND_H */ diff --git a/base/color_find_init.h b/base/color_find_init.h new file mode 100644 index 0000000..9953d82 --- /dev/null +++ b/base/color_find_init.h @@ -0,0 +1,58 @@ +#include "color_find.h" +// #include "../screen/screen_init.h" +#include + +/* Abstracted, general function to avoid repeated code. */ +static int findColorInRectAt(MMBitmapRef image, MMRGBHex color, MMPoint *point, + MMRect rect, float tolerance, MMPoint startPoint) +{ + MMPoint scan = startPoint; + if (!MMBitmapRectInBounds(image, rect)) return -1; + + for (; scan.y < rect.size.height; ++scan.y) { + for (; scan.x < rect.size.width; ++scan.x) { + MMRGBHex found = MMRGBHexAtPoint(image, scan.x, scan.y); + if (MMRGBHexSimilarToColor(color, found, tolerance)) { + if (point != NULL) *point = scan; + return 0; + } + } + scan.x = rect.origin.x; + } + + return -1; +} + +int findColorInRect(MMBitmapRef image, MMRGBHex color, + MMPoint *point, MMRect rect, float tolerance) +{ + return findColorInRectAt(image, color, point, rect, tolerance, rect.origin); +} + +MMPointArrayRef findAllColorInRect(MMBitmapRef image, MMRGBHex color, + MMRect rect, float tolerance) +{ + MMPointArrayRef pointArray = createMMPointArray(0); + MMPoint point = MMPointZero; + + while (findColorInRectAt(image, color, &point, rect, tolerance, point) == 0) { + MMPointArrayAppendPoint(pointArray, point); + ITER_NEXT_POINT(point, rect.size.width, rect.origin.x); + } + + return pointArray; +} + +size_t countOfColorsInRect(MMBitmapRef image, MMRGBHex color, MMRect rect, + float tolerance) +{ + size_t count = 0; + MMPoint point = MMPointZero; + + while (findColorInRectAt(image, color, &point, rect, tolerance, point) == 0) { + ITER_NEXT_POINT(point, rect.size.width, rect.origin.x); + ++count; + } + + return count; +} diff --git a/base/io_init.h b/base/io_init.h new file mode 100644 index 0000000..32de05f --- /dev/null +++ b/base/io_init.h @@ -0,0 +1,79 @@ +#include "io.h" +// #include "os.h" +// #include "bmp_io_init.h" +#include "png_io_init.h" +#include /* For fputs() */ +#include /* For strcmp() */ +#include /* For tolower() */ + +const char *getExtension(const char *fname, size_t len) +{ + if (fname == NULL || len <= 0) return NULL; + + while (--len > 0 && fname[len] != '.' && fname[len] != '\0') + ; + + return fname + len + 1; +} + +MMImageType imageTypeFromExtension(const char *extension) +{ + char ext[4]; + const size_t maxlen = sizeof(ext) / sizeof(ext[0]); + size_t i; + + for (i = 0; extension[i] != '\0'; ++i) { + if (i >= maxlen) return kInvalidImageType; + ext[i] = tolower(extension[i]); + } + ext[i] = '\0'; + + if (strcmp(ext, "png") == 0) { + return kPNGImageType; + } else if (strcmp(ext, "bmp") == 0) { + return kBMPImageType; + } else { + return kInvalidImageType; + } +} + +MMBitmapRef newMMBitmapFromFile(const char *path, + MMImageType type, + MMIOError *err) +{ + switch (type) { + case kBMPImageType: + return newMMBitmapFromBMP(path, err); + case kPNGImageType: + return newMMBitmapFromPNG(path, err); + default: + if (err != NULL) *err = kMMIOUnsupportedTypeError; + return NULL; + } +} + +int saveMMBitmapToFile(MMBitmapRef bitmap, + const char *path, + MMImageType type) +{ + switch (type) { + case kBMPImageType: + return saveMMBitmapAsBMP(bitmap, path); + case kPNGImageType: + return saveMMBitmapAsPNG(bitmap, path); + default: + return -1; + } +} + +const char *MMIOErrorString(MMImageType type, MMIOError error) +{ + switch (type) { + case kBMPImageType: + return MMBMPReadErrorString(error); + case kPNGImageType: + return MMPNGReadErrorString(error); + default: + return "Unsupported image type"; + } +} diff --git a/base/pasteboard.h b/base/pasteboard.h new file mode 100644 index 0000000..f8cba6d --- /dev/null +++ b/base/pasteboard.h @@ -0,0 +1,28 @@ +#pragma once +#ifndef PASTEBOARD_H +#define PASTEBOARD_H + +#include "MMBitmap.h" +#include "io.h" + +enum _MMBitmapPasteError { + kMMPasteNoError = 0, + kMMPasteGenericError, + kMMPasteOpenError, + kMMPasteClearError, + kMMPasteDataError, + kMMPastePasteError, + kMMPasteUnsupportedError +}; + +typedef MMIOError MMPasteError; + +/* Copies |bitmap| to the pasteboard as a PNG. + * Returns 0 on success, non-zero on error. */ +MMPasteError copyMMBitmapToPasteboard(MMBitmapRef bitmap); + +/* Returns description of given MMPasteError. + * Returned string is constant and hence should not be freed. */ +const char *MMPasteErrorString(MMPasteError error); + +#endif /* PASTEBOARD_H */ diff --git a/base/pasteboard_init.h b/base/pasteboard_init.h new file mode 100644 index 0000000..c829c1d --- /dev/null +++ b/base/pasteboard_init.h @@ -0,0 +1,106 @@ +#include "pasteboard.h" +#include "os.h" + +#if defined(IS_MACOSX) + #include "png_io.h" + #include +#elif defined(IS_WINDOWS) + #include "bmp_io.h" +#endif + +MMPasteError copyMMBitmapToPasteboard(MMBitmapRef bitmap) +{ +#if defined(IS_MACOSX) + PasteboardRef clipboard; + + size_t len; + uint8_t *pngbuf; + CFDataRef data; + OSStatus err; + + if (PasteboardCreate(kPasteboardClipboard, &clipboard) != noErr) { + return kMMPasteOpenError; + } + + if (PasteboardClear(clipboard) != noErr) { + CFRelease(clipboard); + return kMMPasteClearError; + } + + pngbuf = createPNGData(bitmap, &len); + if (pngbuf == NULL) { + CFRelease(clipboard); + return kMMPasteDataError; + } + + data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, pngbuf, len, + kCFAllocatorNull); + if (data == NULL) { + CFRelease(clipboard); + free(pngbuf); + return kMMPasteDataError; + } + + err = PasteboardPutItemFlavor(clipboard, bitmap, kUTTypePNG, data, 0); + CFRelease(data); + CFRelease(clipboard); + free(pngbuf); + return (err == noErr) ? kMMPasteNoError : kMMPastePasteError; +#elif defined(IS_WINDOWS) + MMPasteError ret = kMMPasteNoError; + uint8_t *bmpData; + size_t len; + HGLOBAL handle; + + if (!OpenClipboard(NULL)) return kMMPasteOpenError; + if (!EmptyClipboard()) return kMMPasteClearError; + + bmpData = createBitmapData(bitmap, &len); + if (bmpData == NULL) return kMMPasteDataError; + + /* CF_DIB does not include the BITMAPFILEHEADER struct (and displays a + * cryptic error if it is included). */ + len -= sizeof(BITMAPFILEHEADER); + + /* SetClipboardData() needs a "handle", not just a buffer, so we have to + * allocate one with GlobalAlloc(). */ + if ((handle = GlobalAlloc(GMEM_MOVEABLE, len)) == NULL) { + CloseClipboard(); + free(bmpData); + return kMMPasteDataError; + } + + memcpy(GlobalLock(handle), bmpData + sizeof(BITMAPFILEHEADER), len); + GlobalUnlock(handle); + free(bmpData); + + if (SetClipboardData(CF_DIB, handle) == NULL) { + ret = kMMPastePasteError; + } + + CloseClipboard(); + GlobalFree(handle); + return ret; +#elif defined(USE_X11) + /* TODO (X11's clipboard is _weird_.) */ + return kMMPasteUnsupportedError; +#endif +} + +const char *MMPasteErrorString(MMPasteError err) +{ + switch (err) { + case kMMPasteOpenError: + return "Could not open pasteboard"; + case kMMPasteClearError: + return "Could not clear pasteboard"; + case kMMPasteDataError: + return "Could not create image data from bitmap"; + case kMMPastePasteError: + return "Could not paste data"; + case kMMPasteUnsupportedError: + return "Unsupported platform"; + default: + return NULL; + } +} diff --git a/base/png_io.h b/base/png_io.h new file mode 100644 index 0000000..bcdab2e --- /dev/null +++ b/base/png_io.h @@ -0,0 +1,37 @@ +#pragma once +#ifndef PNG_IO_H +#define PNG_IO_H + +// #include "MMBitmap_init.h" +// #include "io_init.h" + +enum _PNGReadError { + kPNGGenericError = 0, + kPNGReadError, + kPNGAccessError, + kPNGInvalidHeaderError +}; + +typedef MMIOError MMPNGReadError; + +/* Returns description of given MMPNGReadError. + * Returned string is constant and hence should not be freed. */ +const char *MMPNGReadErrorString(MMIOError error); + +/* Attempts to read PNG file at path; returns new MMBitmap on success, or + * NULL on error. If |error| is non-NULL, it will be set to the error code + * on return. + * Responsibility for destroy()'ing returned MMBitmap is left up to caller. */ +MMBitmapRef newMMBitmapFromPNG(const char *path, MMPNGReadError *error); + +/* Attempts to write PNG at path; returns 0 on success, -1 on error. */ +int saveMMBitmapAsPNG(MMBitmapRef bitmap, const char *path); + +/* Returns a buffer containing the raw PNG file data, ready to be saved to a + * file. |len| will be set to the number of bytes allocated in the returned + * buffer (it cannot be NULL). + * + * Responsibility for free()'ing data is left up to the caller. */ +uint8_t *createPNGData(MMBitmapRef bitmap, size_t *len); + +#endif /* PNG_IO_H */ diff --git a/base/png_io_init.h b/base/png_io_init.h new file mode 100644 index 0000000..cbd132c --- /dev/null +++ b/base/png_io_init.h @@ -0,0 +1,339 @@ +#include "png_io.h" +#include "os.h" +// #include "libpng/png.c" +#include +#include /* fopen() */ +#include /* malloc/realloc */ +#include + +#if defined(_MSC_VER) + #include "ms_stdint.h" + #include "ms_stdbool.h" +#else + #include + #include +#endif + +const char *MMPNGReadErrorString(MMIOError error) +{ + switch (error) { + case kPNGAccessError: + return "Could not open file"; + case kPNGReadError: + return "Could not read file"; + case kPNGInvalidHeaderError: + return "Not a PNG file"; + default: + return NULL; + } +} + +MMBitmapRef newMMBitmapFromPNG(const char *path, MMPNGReadError *err) +{ + FILE *fp; + uint8_t header[8]; + png_struct *png_ptr = NULL; + png_info *info_ptr = NULL; + png_byte bit_depth, color_type; + uint8_t *row, *bitmapData; + uint8_t bytesPerPixel; + png_uint_32 width, height, y; + uint32_t bytewidth; + + if ((fp = fopen(path, "rb")) == NULL) { + if (err != NULL) *err = kPNGAccessError; + return NULL; + } + + /* Initialize error code to generic value. */ + if (err != NULL) *err = kPNGGenericError; + + /* Validate the PNG. */ + if (fread(header, 1, sizeof header, fp) == 0) { + if (err != NULL) *err = kPNGReadError; + goto bail; + } else if (png_sig_cmp(header, 0, sizeof(header)) != 0) { + if (err != NULL) *err = kPNGInvalidHeaderError; + goto bail; + } + + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (png_ptr == NULL) goto bail; + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) goto bail; + + /* Set up error handling. */ + if (setjmp(png_jmpbuf(png_ptr))) { + goto bail; + } + + png_init_io(png_ptr, fp); + + /* Skip past the header. */ + png_set_sig_bytes(png_ptr, sizeof header); + + png_read_info(png_ptr, info_ptr); + + /* Convert different image types to common type to be read. */ + bit_depth = png_get_bit_depth(png_ptr, info_ptr); + color_type = png_get_color_type(png_ptr, info_ptr); + + /* Convert color palettes to RGB. */ + if (color_type == PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(png_ptr); + } + + /* Convert PNG to bit depth of 8. */ + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { + png_set_expand_gray_1_2_4_to_8(png_ptr); + } else if (bit_depth == 16) { + png_set_strip_16(png_ptr); + } + + /* Convert transparency chunk to alpha channel. */ + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(png_ptr); + } + + /* Convert gray images to RGB. */ + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_gray_to_rgb(png_ptr); + } + + /* Ignore alpha for now. */ + if (color_type & PNG_COLOR_MASK_ALPHA) { + png_set_strip_alpha(png_ptr); + } + + /* Get image attributes. */ + width = png_get_image_width(png_ptr, info_ptr); + height = png_get_image_height(png_ptr, info_ptr); + bytesPerPixel = 3; /* All images decompress to this size. */ + bytewidth = ADD_PADDING(width * bytesPerPixel); /* Align width. */ + + /* Decompress the PNG row by row. */ + bitmapData = calloc(1, bytewidth * height); + row = png_malloc(png_ptr, png_get_rowbytes(png_ptr, info_ptr)); + if (bitmapData == NULL || row == NULL) goto bail; + for (y = 0; y < height; ++y) { + png_uint_32 x; + const uint32_t rowOffset = y * bytewidth; + uint8_t *rowptr = row; + png_read_row(png_ptr, (png_byte *)row, NULL); + + for (x = 0; x < width; ++x) { + const uint32_t colOffset = x * bytesPerPixel; + MMRGBColor *color = (MMRGBColor *)(bitmapData + rowOffset + colOffset); + color->red = *rowptr++; + color->green = *rowptr++; + color->blue = *rowptr++; + } + } + free(row); + + /* Finish reading. */ + png_read_end(png_ptr, NULL); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(fp); + + return createMMBitmap(bitmapData, width, height, + bytewidth, bytesPerPixel * 8, bytesPerPixel); + +bail: + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(fp); + return NULL; +} + +struct _PNGWriteInfo { + png_struct *png_ptr; + png_info *info_ptr; + png_byte **row_pointers; + size_t row_count; + bool free_row_pointers; +}; + +typedef struct _PNGWriteInfo PNGWriteInfo; +typedef PNGWriteInfo *PNGWriteInfoRef; + +/* Returns pointer to PNGWriteInfo struct containing data ready to be used with + * functions such as png_write_png(). + * + * It is the caller's responsibility to destroy() the returned structure with + * destroyPNGWriteInfo(). */ +static PNGWriteInfoRef createPNGWriteInfo(MMBitmapRef bitmap) +{ + PNGWriteInfoRef info = malloc(sizeof(PNGWriteInfo)); + png_uint_32 y; + + if (info == NULL) return NULL; + info->png_ptr = NULL; + info->info_ptr = NULL; + info->row_pointers = NULL; + + assert(bitmap != NULL); + + /* Initialize the write struct. */ + info->png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + if (info->png_ptr == NULL) goto bail; + + /* Set up error handling. */ + if (setjmp(png_jmpbuf(info->png_ptr))) { + png_destroy_write_struct(&(info->png_ptr), &(info->info_ptr)); + goto bail; + } + + /* Initialize the info struct. */ + info->info_ptr = png_create_info_struct(info->png_ptr); + if (info->info_ptr == NULL) { + png_destroy_write_struct(&(info->png_ptr), NULL); + goto bail; + } + + /* Set image attributes. */ + png_set_IHDR(info->png_ptr, + info->info_ptr, + (png_uint_32)bitmap->width, + (png_uint_32)bitmap->height, + 8, + PNG_COLOR_TYPE_RGB, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + info->row_count = bitmap->height; + info->row_pointers = png_malloc(info->png_ptr, + sizeof(png_byte *) * info->row_count); + + if (bitmap->bytesPerPixel == 3) { + /* No alpha channel; image data can be copied directly. */ + for (y = 0; y < info->row_count; ++y) { + info->row_pointers[y] = bitmap->imageBuffer + (bitmap->bytewidth * y); + } + info->free_row_pointers = false; + + /* Convert BGR to RGB if necessary. */ + if (MMRGB_IS_BGR) { + png_set_bgr(info->png_ptr); + } + } else { + /* Ignore alpha channel; copy image data row by row. */ + const size_t bytesPerPixel = 3; + const size_t bytewidth = ADD_PADDING(bitmap->width * bytesPerPixel); + + for (y = 0; y < info->row_count; ++y) { + png_uint_32 x; + png_byte *row_ptr = png_malloc(info->png_ptr, bytewidth); + info->row_pointers[y] = row_ptr; + for (x = 0; x < bitmap->width; ++x) { + MMRGBColor *color = MMRGBColorRefAtPoint(bitmap, x, y); + row_ptr[0] = color->red; + row_ptr[1] = color->green; + row_ptr[2] = color->blue; + + row_ptr += bytesPerPixel; + } + } + info->free_row_pointers = true; + } + + png_set_rows(info->png_ptr, info->info_ptr, info->row_pointers); + return info; + +bail: + if (info != NULL) free(info); + return NULL; +} + +/* Free memory in use by |info|. */ +static void destroyPNGWriteInfo(PNGWriteInfoRef info) +{ + assert(info != NULL); + if (info->row_pointers != NULL) { + if (info->free_row_pointers) { + size_t y; + for (y = 0; y < info->row_count; ++y) { + free(info->row_pointers[y]); + } + } + png_free(info->png_ptr, info->row_pointers); + } + + png_destroy_write_struct(&(info->png_ptr), &(info->info_ptr)); + free(info); +} + +int saveMMBitmapAsPNG(MMBitmapRef bitmap, const char *path) +{ + FILE *fp = fopen(path, "wb"); + PNGWriteInfoRef info; + if (fp == NULL) return -1; + + if ((info = createPNGWriteInfo(bitmap)) == NULL) { + fclose(fp); + return -1; + } + + png_init_io(info->png_ptr, fp); + png_write_png(info->png_ptr, info->info_ptr, PNG_TRANSFORM_IDENTITY, NULL); + fclose(fp); + + destroyPNGWriteInfo(info); + return 0; +} + +/* Structure to store PNG image bytes. */ +struct io_data +{ + uint8_t *buffer; /* Pointer to raw file data. */ + size_t size; /* Number of bytes actually written to buffer. */ + size_t allocedSize; /* Number of bytes allocated for buffer. */ +}; + +/* Called each time libpng attempts to write data in createPNGData(). */ +void png_append_data(png_struct *png_ptr, + png_byte *new_data, + png_size_t length) +{ + struct io_data *data = png_get_io_ptr(png_ptr); + data->size += length; + + /* Allocate or grow buffer. */ + if (data->buffer == NULL) { + data->allocedSize = data->size; + data->buffer = png_malloc(png_ptr, data->allocedSize); + assert(data->buffer != NULL); + } else if (data->allocedSize < data->size) { + do { + /* Double size each time to avoid calls to realloc. */ + data->allocedSize <<= 1; + } while (data->allocedSize < data->size); + + data->buffer = realloc(data->buffer, data->allocedSize); + } + + /* Copy new bytes to end of buffer. */ + memcpy(data->buffer + data->size - length, new_data, length); +} + +uint8_t *createPNGData(MMBitmapRef bitmap, size_t *len) +{ + PNGWriteInfoRef info = NULL; + struct io_data data = {NULL, 0, 0}; + + assert(bitmap != NULL); + assert(len != NULL); + + if ((info = createPNGWriteInfo(bitmap)) == NULL) return NULL; + + png_set_write_fn(info->png_ptr, &data, &png_append_data, NULL); + png_write_png(info->png_ptr, info->info_ptr, PNG_TRANSFORM_IDENTITY, NULL); + + destroyPNGWriteInfo(info); + + *len = data.size; + return data.buffer; +} diff --git a/base/snprintf.h b/base/snprintf.h new file mode 100644 index 0000000..60046e5 --- /dev/null +++ b/base/snprintf.h @@ -0,0 +1,46 @@ +#ifndef _PORTABLE_SNPRINTF_H_ +#define _PORTABLE_SNPRINTF_H_ + +#define PORTABLE_SNPRINTF_VERSION_MAJOR 2 +#define PORTABLE_SNPRINTF_VERSION_MINOR 2 + +#include "os.h" +#if defined(IS_MACOSX) + #define HAVE_SNPRINTF +#else + #define HAVE_SNPRINTF + #define PREFER_PORTABLE_SNPRINTF +#endif + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef HAVE_SNPRINTF +#include +#else +extern int snprintf(char *, size_t, const char *, /*args*/ ...); +extern int vsnprintf(char *, size_t, const char *, va_list); +#endif + +#if defined(HAVE_SNPRINTF) && defined(PREFER_PORTABLE_SNPRINTF) +extern int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...); +extern int portable_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap); +#define snprintf portable_snprintf +#define vsnprintf portable_vsnprintf +#endif + +extern int asprintf (char **ptr, const char *fmt, /*args*/ ...); +extern int vasprintf (char **ptr, const char *fmt, va_list ap); +extern int asnprintf (char **ptr, size_t str_m, const char *fmt, /*args*/ ...); +extern int vasnprintf(char **ptr, size_t str_m, const char *fmt, va_list ap); + +#endif + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/base/snprintf_init.h b/base/snprintf_init.h new file mode 100644 index 0000000..22ae367 --- /dev/null +++ b/base/snprintf_init.h @@ -0,0 +1,1019 @@ +/* + * snprintf.c - a portable implementation of snprintf + * + * AUTHOR + * Mark Martinec , April 1999. + * + * Copyright 1999, Mark Martinec. All rights reserved. + * + * TERMS AND CONDITIONS + * This program is free software; you can redistribute it and/or modify + * it under the terms of the "Frontier Artistic License" which comes + * with this Kit. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Frontier Artistic License for more details. + * + * You should have received a copy of the Frontier Artistic License + * with this Kit in the file named LICENSE.txt . + * If not, I'll be glad to provide one. + * + * FEATURES + * - careful adherence to specs regarding flags, field width and precision; + * - good performance for large string handling (large format, large + * argument or large paddings). Performance is similar to system's sprintf + * and in several cases significantly better (make sure you compile with + * optimizations turned on, tell the compiler the code is strict ANSI + * if necessary to give it more freedom for optimizations); + * - return value semantics per ISO/IEC 9899:1999 ("ISO C99"); + * - written in standard ISO/ANSI C - requires an ANSI C compiler. + * + * SUPPORTED CONVERSION SPECIFIERS AND DATA TYPES + * + * This snprintf only supports the following conversion specifiers: + * s, c, d, u, o, x, X, p (and synonyms: i, D, U, O - see below) + * with flags: '-', '+', ' ', '0' and '#'. + * An asterisk is supported for field width as well as precision. + * + * Length modifiers 'h' (short int), 'l' (long int), + * and 'll' (long long int) are supported. + * NOTE: + * If macro SNPRINTF_LONGLONG_SUPPORT is not defined (default) the + * length modifier 'll' is recognized but treated the same as 'l', + * which may cause argument value truncation! Defining + * SNPRINTF_LONGLONG_SUPPORT requires that your system's sprintf also + * handles length modifier 'll'. long long int is a language extension + * which may not be portable. + * + * Conversion of numeric data (conversion specifiers d, u, o, x, X, p) + * with length modifiers (none or h, l, ll) is left to the system routine + * sprintf, but all handling of flags, field width and precision as well as + * c and s conversions is done very carefully by this portable routine. + * If a string precision (truncation) is specified (e.g. %.8s) it is + * guaranteed the string beyond the specified precision will not be referenced. + * + * Length modifiers h, l and ll are ignored for c and s conversions (data + * types wint_t and wchar_t are not supported). + * + * The following common synonyms for conversion characters are supported: + * - i is a synonym for d + * - D is a synonym for ld, explicit length modifiers are ignored + * - U is a synonym for lu, explicit length modifiers are ignored + * - O is a synonym for lo, explicit length modifiers are ignored + * The D, O and U conversion characters are nonstandard, they are supported + * for backward compatibility only, and should not be used for new code. + * + * The following is specifically NOT supported: + * - flag ' (thousands' grouping character) is recognized but ignored + * - numeric conversion specifiers: f, e, E, g, G and synonym F, + * as well as the new a and A conversion specifiers + * - length modifier 'L' (long double) and 'q' (quad - use 'll' instead) + * - wide character/string conversions: lc, ls, and nonstandard + * synonyms C and S + * - writeback of converted string length: conversion character n + * - the n$ specification for direct reference to n-th argument + * - locales + * + * It is permitted for str_m to be zero, and it is permitted to specify NULL + * pointer for resulting string argument if str_m is zero (as per ISO C99). + * + * The return value is the number of characters which would be generated + * for the given input, excluding the trailing null. If this value + * is greater or equal to str_m, not all characters from the result + * have been stored in str, output bytes beyond the (str_m-1) -th character + * are discarded. If str_m is greater than zero it is guaranteed + * the resulting string will be null-terminated. + * + * NOTE that this matches the ISO C99, OpenBSD, and GNU C library 2.1, + * but is different from some older and vendor implementations, + * and is also different from XPG, XSH5, SUSv2 specifications. + * For historical discussion on changes in the semantics and standards + * of snprintf see printf(3) man page in the Linux programmers manual. + * + * Routines asprintf and vasprintf return a pointer (in the ptr argument) + * to a buffer sufficiently large to hold the resulting string. This pointer + * should be passed to free(3) to release the allocated storage when it is + * no longer needed. If sufficient space cannot be allocated, these functions + * will return -1 and set ptr to be a NULL pointer. These two routines are a + * GNU C library extensions (glibc). + * + * Routines asnprintf and vasnprintf are similar to asprintf and vasprintf, + * yet, like snprintf and vsnprintf counterparts, will write at most str_m-1 + * characters into the allocated output string, the last character in the + * allocated buffer then gets the terminating null. If the formatted string + * length (the return value) is greater than or equal to the str_m argument, + * the resulting string was truncated and some of the formatted characters + * were discarded. These routines present a handy way to limit the amount + * of allocated memory to some sane value. + * + * AVAILABILITY + * http://www.ijs.si/software/snprintf/ + * + * REVISION HISTORY + * 1999-04 V0.9 Mark Martinec + * - initial version, some modifications after comparing printf + * man pages for Digital Unix 4.0, Solaris 2.6 and HPUX 10, + * and checking how Perl handles sprintf (differently!); + * 1999-04-09 V1.0 Mark Martinec + * - added main test program, fixed remaining inconsistencies, + * added optional (long long int) support; + * 1999-04-12 V1.1 Mark Martinec + * - support the 'p' conversion (pointer to void); + * - if a string precision is specified + * make sure the string beyond the specified precision + * will not be referenced (e.g. by strlen); + * 1999-04-13 V1.2 Mark Martinec + * - support synonyms %D=%ld, %U=%lu, %O=%lo; + * - speed up the case of long format string with few conversions; + * 1999-06-30 V1.3 Mark Martinec + * - fixed runaway loop (eventually crashing when str_l wraps + * beyond 2^31) while copying format string without + * conversion specifiers to a buffer that is too short + * (thanks to Edwin Young for + * spotting the problem); + * - added macros PORTABLE_SNPRINTF_VERSION_(MAJOR|MINOR) + * to snprintf.h + * 2000-02-14 V2.0 (never released) Mark Martinec + * - relaxed license terms: The Artistic License now applies. + * You may still apply the GNU GENERAL PUBLIC LICENSE + * as was distributed with previous versions, if you prefer; + * - changed REVISION HISTORY dates to use ISO 8601 date format; + * - added vsnprintf (patch also independently proposed by + * Caolan McNamara 2000-05-04, and Keith M Willenson 2000-06-01) + * 2000-06-27 V2.1 Mark Martinec + * - removed POSIX check for str_m<1; value 0 for str_m is + * allowed by ISO C99 (and GNU C library 2.1) - (pointed out + * on 2000-05-04 by Caolan McNamara, caolan@ csn dot ul dot ie). + * Besides relaxed license this change in standards adherence + * is the main reason to bump up the major version number; + * - added nonstandard routines asnprintf, vasnprintf, asprintf, + * vasprintf that dynamically allocate storage for the + * resulting string; these routines are not compiled by default, + * see comments where NEED_V?ASN?PRINTF macros are defined; + * - autoconf contributed by Caolan McNamara + * 2000-10-06 V2.2 Mark Martinec + * - BUG FIX: the %c conversion used a temporary variable + * that was no longer in scope when referenced, + * possibly causing incorrect resulting character; + * - BUG FIX: make precision and minimal field width unsigned + * to handle huge values (2^31 <= n < 2^32) correctly; + * also be more careful in the use of signed/unsigned/size_t + * internal variables - probably more careful than many + * vendor implementations, but there may still be a case + * where huge values of str_m, precision or minimal field + * could cause incorrect behaviour; + * - use separate variables for signed/unsigned arguments, + * and for short/int, long, and long long argument lengths + * to avoid possible incompatibilities on certain + * computer architectures. Also use separate variable + * arg_sign to hold sign of a numeric argument, + * to make code more transparent; + * - some fiddling with zero padding and "0x" to make it + * Linux compatible; + * - systematically use macros fast_memcpy and fast_memset + * instead of case-by-case hand optimization; determine some + * breakeven string lengths for different architectures; + * - terminology change: 'format' -> 'conversion specifier', + * 'C9x' -> 'ISO/IEC 9899:1999 ("ISO C99")', + * 'alternative form' -> 'alternate form', + * 'data type modifier' -> 'length modifier'; + * - several comments rephrased and new ones added; + * - make compiler not complain about 'credits' defined but + * not used; + */ + + +/* Define HAVE_SNPRINTF if your system already has snprintf and vsnprintf. + * + * If HAVE_SNPRINTF is defined this module will not produce code for + * snprintf and vsnprintf, unless PREFER_PORTABLE_SNPRINTF is defined as well, + * causing this portable version of snprintf to be called portable_snprintf + * (and portable_vsnprintf). + */ +/* #define HAVE_SNPRINTF */ + +/* Define PREFER_PORTABLE_SNPRINTF if your system does have snprintf and + * vsnprintf but you would prefer to use the portable routine(s) instead. + * In this case the portable routine is declared as portable_snprintf + * (and portable_vsnprintf) and a macro 'snprintf' (and 'vsnprintf') + * is defined to expand to 'portable_v?snprintf' - see file snprintf.h . + * Defining this macro is only useful if HAVE_SNPRINTF is also defined, + * but does does no harm if defined nevertheless. + */ +/* #define PREFER_PORTABLE_SNPRINTF */ + +/* Define SNPRINTF_LONGLONG_SUPPORT if you want to support + * data type (long long int) and length modifier 'll' (e.g. %lld). + * If undefined, 'll' is recognized but treated as a single 'l'. + * + * If the system's sprintf does not handle 'll' + * the SNPRINTF_LONGLONG_SUPPORT must not be defined! + * + * This is off by default as (long long int) is a language extension. + */ +/* #define SNPRINTF_LONGLONG_SUPPORT */ + +/* Define NEED_SNPRINTF_ONLY if you only need snprintf, and not vsnprintf. + * If NEED_SNPRINTF_ONLY is defined, the snprintf will be defined directly, + * otherwise both snprintf and vsnprintf routines will be defined + * and snprintf will be a simple wrapper around vsnprintf, at the expense + * of an extra procedure call. + */ +/* #define NEED_SNPRINTF_ONLY */ + +/* Define NEED_V?ASN?PRINTF macros if you need library extension + * routines asprintf, vasprintf, asnprintf, vasnprintf respectively, + * and your system library does not provide them. They are all small + * wrapper routines around portable_vsnprintf. Defining any of the four + * NEED_V?ASN?PRINTF macros automatically turns off NEED_SNPRINTF_ONLY + * and turns on PREFER_PORTABLE_SNPRINTF. + * + * Watch for name conflicts with the system library if these routines + * are already present there. + * + * NOTE: vasprintf and vasnprintf routines need va_copy() from stdarg.h, as + * specified by C99, to be able to traverse the same list of arguments twice. + * I don't know of any other standard and portable way of achieving the same. + * With some versions of gcc you may use __va_copy(). You might even get away + * with "ap2 = ap", in this case you must not call va_end(ap2) ! + * #define va_copy(ap2,ap) ap2 = ap + */ +/* #define NEED_ASPRINTF */ +/* #define NEED_ASNPRINTF */ +/* #define NEED_VASPRINTF */ +/* #define NEED_VASNPRINTF */ + + +/* Define the following macros if desired: + * SOLARIS_COMPATIBLE, SOLARIS_BUG_COMPATIBLE, + * HPUX_COMPATIBLE, HPUX_BUG_COMPATIBLE, LINUX_COMPATIBLE, + * DIGITAL_UNIX_COMPATIBLE, DIGITAL_UNIX_BUG_COMPATIBLE, + * PERL_COMPATIBLE, PERL_BUG_COMPATIBLE, + * + * - For portable applications it is best not to rely on peculiarities + * of a given implementation so it may be best not to define any + * of the macros that select compatibility and to avoid features + * that vary among the systems. + * + * - Selecting compatibility with more than one operating system + * is not strictly forbidden but is not recommended. + * + * - 'x'_BUG_COMPATIBLE implies 'x'_COMPATIBLE . + * + * - 'x'_COMPATIBLE refers to (and enables) a behaviour that is + * documented in a sprintf man page on a given operating system + * and actually adhered to by the system's sprintf (but not on + * most other operating systems). It may also refer to and enable + * a behaviour that is declared 'undefined' or 'implementation specific' + * in the man page but a given implementation behaves predictably + * in a certain way. + * + * - 'x'_BUG_COMPATIBLE refers to (and enables) a behaviour of system's sprintf + * that contradicts the sprintf man page on the same operating system. + * + * - I do not claim that the 'x'_COMPATIBLE and 'x'_BUG_COMPATIBLE + * conditionals take into account all idiosyncrasies of a particular + * implementation, there may be other incompatibilities. + */ + + + +/* ============================================= */ +/* NO USER SERVICABLE PARTS FOLLOWING THIS POINT */ +/* ============================================= */ + +#define PORTABLE_SNPRINTF_VERSION_MAJOR 2 +#define PORTABLE_SNPRINTF_VERSION_MINOR 2 + +#if defined(NEED_ASPRINTF) || defined(NEED_ASNPRINTF) || defined(NEED_VASPRINTF) || defined(NEED_VASNPRINTF) +# if defined(NEED_SNPRINTF_ONLY) +# undef NEED_SNPRINTF_ONLY +# endif +# if !defined(PREFER_PORTABLE_SNPRINTF) +# define PREFER_PORTABLE_SNPRINTF +# endif +#endif + +#if defined(SOLARIS_BUG_COMPATIBLE) && !defined(SOLARIS_COMPATIBLE) +#define SOLARIS_COMPATIBLE +#endif + +#if defined(HPUX_BUG_COMPATIBLE) && !defined(HPUX_COMPATIBLE) +#define HPUX_COMPATIBLE +#endif + +#if defined(DIGITAL_UNIX_BUG_COMPATIBLE) && !defined(DIGITAL_UNIX_COMPATIBLE) +#define DIGITAL_UNIX_COMPATIBLE +#endif + +#if defined(PERL_BUG_COMPATIBLE) && !defined(PERL_COMPATIBLE) +#define PERL_COMPATIBLE +#endif + +#if defined(LINUX_BUG_COMPATIBLE) && !defined(LINUX_COMPATIBLE) +#define LINUX_COMPATIBLE +#endif + +#include "snprintf.h" +#include +#include +#include +#include +#include +#include +#include + +#ifdef isdigit +#undef isdigit +#endif +#define isdigit(c) ((c) >= '0' && (c) <= '9') + +/* For copying strings longer or equal to 'breakeven_point' + * it is more efficient to call memcpy() than to do it inline. + * The value depends mostly on the processor architecture, + * but also on the compiler and its optimization capabilities. + * The value is not critical, some small value greater than zero + * will be just fine if you don't care to squeeze every drop + * of performance out of the code. + * + * Small values favor memcpy, large values favor inline code. + */ +#if defined(__alpha__) || defined(__alpha) +# define breakeven_point 2 /* AXP (DEC Alpha) - gcc or cc or egcs */ +#endif +#if defined(__i386__) || defined(__i386) +# define breakeven_point 12 /* Intel Pentium/Linux - gcc 2.96 */ +#endif +#if defined(__hppa) +# define breakeven_point 10 /* HP-PA - gcc */ +#endif +#if defined(__sparc__) || defined(__sparc) +# define breakeven_point 33 /* Sun Sparc 5 - gcc 2.8.1 */ +#endif + +/* some other values of possible interest: */ +/* #define breakeven_point 8 */ /* VAX 4000 - vaxc */ +/* #define breakeven_point 19 */ /* VAX 4000 - gcc 2.7.0 */ + +#ifndef breakeven_point +# define breakeven_point 6 /* some reasonable one-size-fits-all value */ +#endif + +#define fast_memcpy(d,s,n) \ + { register size_t nn = (size_t)(n); \ + if (nn >= breakeven_point) memcpy((d), (s), nn); \ + else if (nn > 0) { /* proc call overhead is worth only for large strings*/\ + register char *dd; register const char *ss; \ + for (ss=(s), dd=(d); nn>0; nn--) *dd++ = *ss++; } } + +#define fast_memset(d,c,n) \ + { register size_t nn = (size_t)(n); \ + if (nn >= breakeven_point) memset((d), (int)(c), nn); \ + else if (nn > 0) { /* proc call overhead is worth only for large strings*/\ + register char *dd; register const int cc=(int)(c); \ + for (dd=(d); nn>0; nn--) *dd++ = cc; } } + +/* prototypes */ + +#if defined(NEED_ASPRINTF) +int asprintf (char **ptr, const char *fmt, /*args*/ ...); +#endif +#if defined(NEED_VASPRINTF) +int vasprintf (char **ptr, const char *fmt, va_list ap); +#endif +#if defined(NEED_ASNPRINTF) +int asnprintf (char **ptr, size_t str_m, const char *fmt, /*args*/ ...); +#endif +#if defined(NEED_VASNPRINTF) +int vasnprintf (char **ptr, size_t str_m, const char *fmt, va_list ap); +#endif + +#if defined(HAVE_SNPRINTF) +/* declare our portable snprintf routine under name portable_snprintf */ +/* declare our portable vsnprintf routine under name portable_vsnprintf */ +#else +/* declare our portable routines under names snprintf and vsnprintf */ +#define portable_snprintf snprintf +#if !defined(NEED_SNPRINTF_ONLY) +#define portable_vsnprintf vsnprintf +#endif +#endif + +#if !defined(HAVE_SNPRINTF) || defined(PREFER_PORTABLE_SNPRINTF) +int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...); +#if !defined(NEED_SNPRINTF_ONLY) +int portable_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap); +#endif +#endif + +/* declarations */ + +#if defined(NEED_ASPRINTF) +int asprintf(char **ptr, const char *fmt, /*args*/ ...) { + va_list ap; + size_t str_m; + int str_l; + + *ptr = NULL; + va_start(ap, fmt); /* measure the required size */ + str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap); + va_end(ap); + assert(str_l >= 0); /* possible integer overflow if str_m > INT_MAX */ + *ptr = (char *) malloc(str_m = (size_t)str_l + 1); + if (*ptr == NULL) { errno = ENOMEM; str_l = -1; } + else { + int str_l2; + va_start(ap, fmt); + str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap); + va_end(ap); + assert(str_l2 == str_l); + } + return str_l; +} +#endif + +#if defined(NEED_VASPRINTF) +int vasprintf(char **ptr, const char *fmt, va_list ap) { + size_t str_m; + int str_l; + + *ptr = NULL; + { va_list ap2; + va_copy(ap2, ap); /* don't consume the original ap, we'll need it again */ + str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap2);/*get required size*/ + va_end(ap2); + } + assert(str_l >= 0); /* possible integer overflow if str_m > INT_MAX */ + *ptr = (char *) malloc(str_m = (size_t)str_l + 1); + if (*ptr == NULL) { errno = ENOMEM; str_l = -1; } + else { + int str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap); + assert(str_l2 == str_l); + } + return str_l; +} +#endif + +#if defined(NEED_ASNPRINTF) +int asnprintf (char **ptr, size_t str_m, const char *fmt, /*args*/ ...) { + va_list ap; + int str_l; + + *ptr = NULL; + va_start(ap, fmt); /* measure the required size */ + str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap); + va_end(ap); + assert(str_l >= 0); /* possible integer overflow if str_m > INT_MAX */ + if ((size_t)str_l + 1 < str_m) str_m = (size_t)str_l + 1; /* truncate */ + /* if str_m is 0, no buffer is allocated, just set *ptr to NULL */ + if (str_m == 0) { /* not interested in resulting string, just return size */ + } else { + *ptr = (char *) malloc(str_m); + if (*ptr == NULL) { errno = ENOMEM; str_l = -1; } + else { + int str_l2; + va_start(ap, fmt); + str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap); + va_end(ap); + assert(str_l2 == str_l); + } + } + return str_l; +} +#endif + +#if defined(NEED_VASNPRINTF) +int vasnprintf (char **ptr, size_t str_m, const char *fmt, va_list ap) { + int str_l; + + *ptr = NULL; + { va_list ap2; + va_copy(ap2, ap); /* don't consume the original ap, we'll need it again */ + str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap2);/*get required size*/ + va_end(ap2); + } + assert(str_l >= 0); /* possible integer overflow if str_m > INT_MAX */ + if ((size_t)str_l + 1 < str_m) str_m = (size_t)str_l + 1; /* truncate */ + /* if str_m is 0, no buffer is allocated, just set *ptr to NULL */ + if (str_m == 0) { /* not interested in resulting string, just return size */ + } else { + *ptr = (char *) malloc(str_m); + if (*ptr == NULL) { errno = ENOMEM; str_l = -1; } + else { + int str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap); + assert(str_l2 == str_l); + } + } + return str_l; +} +#endif + +/* + * If the system does have snprintf and the portable routine is not + * specifically required, this module produces no code for snprintf/vsnprintf. + */ +#if !defined(HAVE_SNPRINTF) || defined(PREFER_PORTABLE_SNPRINTF) + +#if !defined(NEED_SNPRINTF_ONLY) +int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...) { + va_list ap; + int str_l; + + va_start(ap, fmt); + str_l = portable_vsnprintf(str, str_m, fmt, ap); + va_end(ap); + return str_l; +} +#endif + +#if defined(NEED_SNPRINTF_ONLY) +int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...) { +#else +int portable_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap) { +#endif + +#if defined(NEED_SNPRINTF_ONLY) + va_list ap; +#endif + size_t str_l = 0; + const char *p = fmt; + +/* In contrast with POSIX, the ISO C99 now says + * that str can be NULL and str_m can be 0. + * This is more useful than the old: if (str_m < 1) return -1; */ + +#if defined(NEED_SNPRINTF_ONLY) + va_start(ap, fmt); +#endif + if (!p) p = ""; + while (*p) { + if (*p != '%') { + /* if (str_l < str_m) str[str_l++] = *p++; -- this would be sufficient */ + /* but the following code achieves better performance for cases + * where format string is long and contains few conversions */ + const char *q = strchr(p+1,'%'); + size_t n = !q ? strlen(p) : (q-p); + if (str_l < str_m) { + size_t avail = str_m-str_l; + fast_memcpy(str+str_l, p, (n>avail?avail:n)); + } + p += n; str_l += n; + } else { + const char *starting_p; + size_t min_field_width = 0, precision = 0; + int zero_padding = 0, precision_specified = 0, justify_left = 0; + int alternate_form = 0, force_sign = 0; + int space_for_positive = 1; /* If both the ' ' and '+' flags appear, + the ' ' flag should be ignored. */ + char length_modifier = '\0'; /* allowed values: \0, h, l, L */ + char tmp[32];/* temporary buffer for simple numeric->string conversion */ + + const char *str_arg; /* string address in case of string argument */ + size_t str_arg_l; /* natural field width of arg without padding + and sign */ + unsigned char uchar_arg; + /* unsigned char argument value - only defined for c conversion. + N.B. standard explicitly states the char argument for + the c conversion is unsigned */ + + size_t number_of_zeros_to_pad = 0; + /* number of zeros to be inserted for numeric conversions + as required by the precision or minimal field width */ + + size_t zero_padding_insertion_ind = 0; + /* index into tmp where zero padding is to be inserted */ + + char fmt_spec = '\0'; + /* current conversion specifier character */ + + starting_p = p; p++; /* skip '%' */ + /* parse flags */ + while (*p == '0' || *p == '-' || *p == '+' || + *p == ' ' || *p == '#' || *p == '\'') { + switch (*p) { + case '0': zero_padding = 1; break; + case '-': justify_left = 1; break; + case '+': force_sign = 1; space_for_positive = 0; break; + case ' ': force_sign = 1; + /* If both the ' ' and '+' flags appear, the ' ' flag should be ignored */ +#ifdef PERL_COMPATIBLE + /* ... but in Perl the last of ' ' and '+' applies */ + space_for_positive = 1; +#endif + break; + case '#': alternate_form = 1; break; + case '\'': break; + } + p++; + } + /* If the '0' and '-' flags both appear, the '0' flag should be ignored. */ + + /* parse field width */ + if (*p == '*') { + int j; + p++; j = va_arg(ap, int); + if (j >= 0) min_field_width = j; + else { min_field_width = -j; justify_left = 1; } + } else if (isdigit((int)(*p))) { + /* size_t could be wider than unsigned int; + make sure we treat argument like common implementations do */ + unsigned int uj = *p++ - '0'; + while (isdigit((int)(*p))) uj = 10*uj + (unsigned int)(*p++ - '0'); + min_field_width = uj; + } + /* parse precision */ + if (*p == '.') { + p++; precision_specified = 1; + if (*p == '*') { + int j = va_arg(ap, int); + p++; + if (j >= 0) precision = j; + else { + precision_specified = 0; precision = 0; + /* NOTE: + * Solaris 2.6 man page claims that in this case the precision + * should be set to 0. Digital Unix 4.0, HPUX 10 and BSD man page + * claim that this case should be treated as unspecified precision, + * which is what we do here. + */ + } + } else if (isdigit((int)(*p))) { + /* size_t could be wider than unsigned int; + make sure we treat argument like common implementations do */ + unsigned int uj = *p++ - '0'; + while (isdigit((int)(*p))) uj = 10*uj + (unsigned int)(*p++ - '0'); + precision = uj; + } + } + /* parse 'h', 'l' and 'll' length modifiers */ + if (*p == 'h' || *p == 'l') { + length_modifier = *p; p++; + if (length_modifier == 'l' && *p == 'l') { /* double l = long long */ +#ifdef SNPRINTF_LONGLONG_SUPPORT + length_modifier = '2'; /* double l encoded as '2' */ +#else + length_modifier = 'l'; /* treat it as a single 'l' */ +#endif + p++; + } + } + fmt_spec = *p; + /* common synonyms: */ + switch (fmt_spec) { + case 'i': fmt_spec = 'd'; break; + case 'D': fmt_spec = 'd'; length_modifier = 'l'; break; + case 'U': fmt_spec = 'u'; length_modifier = 'l'; break; + case 'O': fmt_spec = 'o'; length_modifier = 'l'; break; + default: break; + } + /* get parameter value, do initial processing */ + switch (fmt_spec) { + case '%': /* % behaves similar to 's' regarding flags and field widths */ + case 'c': /* c behaves similar to 's' regarding flags and field widths */ + case 's': + length_modifier = '\0'; /* wint_t and wchar_t not supported */ + /* the result of zero padding flag with non-numeric conversion specifier*/ + /* is undefined. Solaris and HPUX 10 does zero padding in this case, */ + /* Digital Unix and Linux does not. */ +#if !defined(SOLARIS_COMPATIBLE) && !defined(HPUX_COMPATIBLE) + zero_padding = 0; /* turn zero padding off for string conversions */ +#endif + str_arg_l = 1; + switch (fmt_spec) { + case '%': + str_arg = p; break; + case 'c': { + int j = va_arg(ap, int); + uchar_arg = (unsigned char) j; /* standard demands unsigned char */ + str_arg = (const char *) &uchar_arg; + break; + } + case 's': + str_arg = va_arg(ap, const char *); + if (!str_arg) str_arg_l = 0; + /* make sure not to address string beyond the specified precision !!! */ + else if (!precision_specified) str_arg_l = strlen(str_arg); + /* truncate string if necessary as requested by precision */ + else if (precision == 0) str_arg_l = 0; + else { + /* memchr on HP does not like n > 2^31 !!! */ + const char *q = memchr(str_arg, '\0', + precision <= 0x7fffffff ? precision : 0x7fffffff); + str_arg_l = !q ? precision : (q-str_arg); + } + break; + default: break; + } + break; + case 'd': case 'u': case 'o': case 'x': case 'X': case 'p': { + /* NOTE: the u, o, x, X and p conversion specifiers imply + the value is unsigned; d implies a signed value */ + + int arg_sign = 0; + /* 0 if numeric argument is zero (or if pointer is NULL for 'p'), + +1 if greater than zero (or nonzero for unsigned arguments), + -1 if negative (unsigned argument is never negative) */ + + int int_arg = 0; unsigned int uint_arg = 0; + /* only defined for length modifier h, or for no length modifiers */ + + long int long_arg = 0; unsigned long int ulong_arg = 0; + /* only defined for length modifier l */ + + void *ptr_arg = NULL; + /* pointer argument value -only defined for p conversion */ + +#ifdef SNPRINTF_LONGLONG_SUPPORT + long long int long_long_arg = 0; + unsigned long long int ulong_long_arg = 0; + /* only defined for length modifier ll */ +#endif + if (fmt_spec == 'p') { + /* HPUX 10: An l, h, ll or L before any other conversion character + * (other than d, i, u, o, x, or X) is ignored. + * Digital Unix: + * not specified, but seems to behave as HPUX does. + * Solaris: If an h, l, or L appears before any other conversion + * specifier (other than d, i, u, o, x, or X), the behavior + * is undefined. (Actually %hp converts only 16-bits of address + * and %llp treats address as 64-bit data which is incompatible + * with (void *) argument on a 32-bit system). + */ +#ifdef SOLARIS_COMPATIBLE +# ifdef SOLARIS_BUG_COMPATIBLE + /* keep length modifiers even if it represents 'll' */ +# else + if (length_modifier == '2') length_modifier = '\0'; +# endif +#else + length_modifier = '\0'; +#endif + ptr_arg = va_arg(ap, void *); + if (ptr_arg != NULL) arg_sign = 1; + } else if (fmt_spec == 'd') { /* signed */ + switch (length_modifier) { + case '\0': + case 'h': + /* It is non-portable to specify a second argument of char or short + * to va_arg, because arguments seen by the called function + * are not char or short. C converts char and short arguments + * to int before passing them to a function. + */ + int_arg = va_arg(ap, int); + if (int_arg > 0) arg_sign = 1; + else if (int_arg < 0) arg_sign = -1; + break; + case 'l': + long_arg = va_arg(ap, long int); + if (long_arg > 0) arg_sign = 1; + else if (long_arg < 0) arg_sign = -1; + break; +#ifdef SNPRINTF_LONGLONG_SUPPORT + case '2': + long_long_arg = va_arg(ap, long long int); + if (long_long_arg > 0) arg_sign = 1; + else if (long_long_arg < 0) arg_sign = -1; + break; +#endif + } + } else { /* unsigned */ + switch (length_modifier) { + case '\0': + case 'h': + uint_arg = va_arg(ap, unsigned int); + if (uint_arg) arg_sign = 1; + break; + case 'l': + ulong_arg = va_arg(ap, unsigned long int); + if (ulong_arg) arg_sign = 1; + break; +#ifdef SNPRINTF_LONGLONG_SUPPORT + case '2': + ulong_long_arg = va_arg(ap, unsigned long long int); + if (ulong_long_arg) arg_sign = 1; + break; +#endif + } + } + str_arg = tmp; str_arg_l = 0; + /* NOTE: + * For d, i, u, o, x, and X conversions, if precision is specified, + * the '0' flag should be ignored. This is so with Solaris 2.6, + * Digital UNIX 4.0, HPUX 10, Linux, FreeBSD, NetBSD; but not with Perl. + */ +#ifndef PERL_COMPATIBLE + if (precision_specified) zero_padding = 0; +#endif + if (fmt_spec == 'd') { + if (force_sign && arg_sign >= 0) + tmp[str_arg_l++] = space_for_positive ? ' ' : '+'; + /* leave negative numbers for sprintf to handle, + to avoid handling tricky cases like (short int)(-32768) */ +#ifdef LINUX_COMPATIBLE + } else if (fmt_spec == 'p' && force_sign && arg_sign > 0) { + tmp[str_arg_l++] = space_for_positive ? ' ' : '+'; +#endif + } else if (alternate_form) { + if (arg_sign != 0 && (fmt_spec == 'x' || fmt_spec == 'X') ) + { tmp[str_arg_l++] = '0'; tmp[str_arg_l++] = fmt_spec; } + /* alternate form should have no effect for p conversion, but ... */ +#ifdef HPUX_COMPATIBLE + else if (fmt_spec == 'p' + /* HPUX 10: for an alternate form of p conversion, + * a nonzero result is prefixed by 0x. */ +#ifndef HPUX_BUG_COMPATIBLE + /* Actually it uses 0x prefix even for a zero value. */ + && arg_sign != 0 +#endif + ) { tmp[str_arg_l++] = '0'; tmp[str_arg_l++] = 'x'; } +#endif + } + zero_padding_insertion_ind = str_arg_l; + if (!precision_specified) precision = 1; /* default precision is 1 */ + if (precision == 0 && arg_sign == 0 +#if defined(HPUX_BUG_COMPATIBLE) || defined(LINUX_COMPATIBLE) + && fmt_spec != 'p' + /* HPUX 10 man page claims: With conversion character p the result of + * converting a zero value with a precision of zero is a null string. + * Actually HP returns all zeroes, and Linux returns "(nil)". */ +#endif + ) { + /* converted to null string */ + /* When zero value is formatted with an explicit precision 0, + the resulting formatted string is empty (d, i, u, o, x, X, p). */ + } else { + char f[5]; int f_l = 0; + f[f_l++] = '%'; /* construct a simple format string for sprintf */ + if (!length_modifier) { } + else if (length_modifier=='2') { f[f_l++] = 'l'; f[f_l++] = 'l'; } + else f[f_l++] = length_modifier; + f[f_l++] = fmt_spec; f[f_l++] = '\0'; + if (fmt_spec == 'p') str_arg_l += sprintf(tmp+str_arg_l, f, ptr_arg); + else if (fmt_spec == 'd') { /* signed */ + switch (length_modifier) { + case '\0': + case 'h': str_arg_l+=sprintf(tmp+str_arg_l, f, int_arg); break; + case 'l': str_arg_l+=sprintf(tmp+str_arg_l, f, long_arg); break; +#ifdef SNPRINTF_LONGLONG_SUPPORT + case '2': str_arg_l+=sprintf(tmp+str_arg_l,f,long_long_arg); break; +#endif + } + } else { /* unsigned */ + switch (length_modifier) { + case '\0': + case 'h': str_arg_l+=sprintf(tmp+str_arg_l, f, uint_arg); break; + case 'l': str_arg_l+=sprintf(tmp+str_arg_l, f, ulong_arg); break; +#ifdef SNPRINTF_LONGLONG_SUPPORT + case '2': str_arg_l+=sprintf(tmp+str_arg_l,f,ulong_long_arg);break; +#endif + } + } + /* include the optional minus sign and possible "0x" + in the region before the zero padding insertion point */ + if (zero_padding_insertion_ind < str_arg_l && + tmp[zero_padding_insertion_ind] == '-') { + zero_padding_insertion_ind++; + } + if (zero_padding_insertion_ind+1 < str_arg_l && + tmp[zero_padding_insertion_ind] == '0' && + (tmp[zero_padding_insertion_ind+1] == 'x' || + tmp[zero_padding_insertion_ind+1] == 'X') ) { + zero_padding_insertion_ind += 2; + } + } + { size_t num_of_digits = str_arg_l - zero_padding_insertion_ind; + if (alternate_form && fmt_spec == 'o' +#ifdef HPUX_COMPATIBLE /* ("%#.o",0) -> "" */ + && (str_arg_l > 0) +#endif +#ifdef DIGITAL_UNIX_BUG_COMPATIBLE /* ("%#o",0) -> "00" */ +#else + /* unless zero is already the first character */ + && !(zero_padding_insertion_ind < str_arg_l + && tmp[zero_padding_insertion_ind] == '0') +#endif + ) { /* assure leading zero for alternate-form octal numbers */ + if (!precision_specified || precision < num_of_digits+1) { + /* precision is increased to force the first character to be zero, + except if a zero value is formatted with an explicit precision + of zero */ + precision = num_of_digits+1; precision_specified = 1; + } + } + /* zero padding to specified precision? */ + if (num_of_digits < precision) + number_of_zeros_to_pad = precision - num_of_digits; + } + /* zero padding to specified minimal field width? */ + if (!justify_left && zero_padding) { + int n = min_field_width - (str_arg_l+number_of_zeros_to_pad); + if (n > 0) number_of_zeros_to_pad += n; + } + break; + } + default: /* unrecognized conversion specifier, keep format string as-is*/ + zero_padding = 0; /* turn zero padding off for non-numeric convers. */ +#ifndef DIGITAL_UNIX_COMPATIBLE + justify_left = 1; min_field_width = 0; /* reset flags */ +#endif +#if defined(PERL_COMPATIBLE) || defined(LINUX_COMPATIBLE) + /* keep the entire format string unchanged */ + str_arg = starting_p; str_arg_l = p - starting_p; + /* well, not exactly so for Linux, which does something inbetween, + * and I don't feel an urge to imitate it: "%+++++hy" -> "%+y" */ +#else + /* discard the unrecognized conversion, just keep * + * the unrecognized conversion character */ + str_arg = p; str_arg_l = 0; +#endif + if (*p) str_arg_l++; /* include invalid conversion specifier unchanged + if not at end-of-string */ + break; + } + if (*p) p++; /* step over the just processed conversion specifier */ + /* insert padding to the left as requested by min_field_width; + this does not include the zero padding in case of numerical conversions*/ + if (!justify_left) { /* left padding with blank or zero */ + int n = min_field_width - (str_arg_l+number_of_zeros_to_pad); + if (n > 0) { + if (str_l < str_m) { + size_t avail = str_m-str_l; + fast_memset(str+str_l, (zero_padding?'0':' '), (n>avail?avail:n)); + } + str_l += n; + } + } + /* zero padding as requested by the precision or by the minimal field width + * for numeric conversions required? */ + if (number_of_zeros_to_pad <= 0) { + /* will not copy first part of numeric right now, * + * force it to be copied later in its entirety */ + zero_padding_insertion_ind = 0; + } else { + /* insert first part of numerics (sign or '0x') before zero padding */ + int n = zero_padding_insertion_ind; + if (n > 0) { + if (str_l < str_m) { + size_t avail = str_m-str_l; + fast_memcpy(str+str_l, str_arg, (n>avail?avail:n)); + } + str_l += n; + } + /* insert zero padding as requested by the precision or min field width */ + n = number_of_zeros_to_pad; + if (n > 0) { + if (str_l < str_m) { + size_t avail = str_m-str_l; + fast_memset(str+str_l, '0', (n>avail?avail:n)); + } + str_l += n; + } + } + /* insert formatted string + * (or as-is conversion specifier for unknown conversions) */ + { int n = str_arg_l - zero_padding_insertion_ind; + if (n > 0) { + if (str_l < str_m) { + size_t avail = str_m-str_l; + fast_memcpy(str+str_l, str_arg+zero_padding_insertion_ind, + (n>avail?avail:n)); + } + str_l += n; + } + } + /* insert right padding */ + if (justify_left) { /* right blank padding to the field width */ + int n = min_field_width - (str_arg_l+number_of_zeros_to_pad); + if (n > 0) { + if (str_l < str_m) { + size_t avail = str_m-str_l; + fast_memset(str+str_l, ' ', (n>avail?avail:n)); + } + str_l += n; + } + } + } + } +#if defined(NEED_SNPRINTF_ONLY) + va_end(ap); +#endif + if (str_m > 0) { /* make sure the string is null-terminated + even at the expense of overwriting the last character + (shouldn't happen, but just in case) */ + str[str_l <= str_m-1 ? str_l : str_m-1] = '\0'; + } + /* Return the number of characters formatted (excluding trailing null + * character), that is, the number of characters that would have been + * written to the buffer if it were large enough. + * + * The value of str_l should be returned, but str_l is of unsigned type + * size_t, and snprintf is int, possibly leading to an undetected + * integer overflow, resulting in a negative return value, which is illegal. + * Both XSH5 and ISO C99 (at least the draft) are silent on this issue. + * Should errno be set to EOVERFLOW and EOF returned in this case??? + */ + return (int) str_l; +} +#endif diff --git a/base/str_io.h b/base/str_io.h new file mode 100644 index 0000000..0a6f8ed --- /dev/null +++ b/base/str_io.h @@ -0,0 +1,50 @@ +#pragma once +#ifndef STR_IO_H +#define STR_IO_H + +#include "MMBitmap.h" +#include "io.h" +#include + + +enum _MMBMPStringError { + kMMBMPStringGenericError = 0, + kMMBMPStringInvalidHeaderError, + kMMBMPStringDecodeError, + kMMBMPStringDecompressError, + kMMBMPStringSizeError, /* Size does not match header. */ + MMMBMPStringEncodeError, + kMMBMPStringCompressError +}; + +typedef MMIOError MMBMPStringError; + +/* Creates a 24-bit bitmap from a compressed, printable string. + * + * String should be in the format: "b[width],[height],[data]", + * where [width] and [height] are the image width & height, and [data] + * is the raw image data run through zlib_compress() and base64_encode(). + * + * Returns NULL on error; follows the Create Rule (that is, the caller is + * responsible for destroy'()ing object). + * If |error| is non-NULL, it will be set to the error code on return. + */ +MMBitmapRef createMMBitmapFromString(const uint8_t *buffer, size_t buflen, + MMBMPStringError *error); + +/* Inverse of createMMBitmapFromString(). + * + * Creates string in the format: "b[width],[height],[data]", where [width] and + * [height] are the image width & height, and [data] is the raw image data run + * through zlib_compress() and base64_encode(). + * + * Returns NULL on error, or new string on success (to be free'()d by caller). + * If |error| is non-NULL, it will be set to the error code on return. + */ +uint8_t *createStringFromMMBitmap(MMBitmapRef bitmap, MMBMPStringError *error); + +/* Returns description of given error code. + * Returned string is constant and hence should not be freed. */ +const char *MMBitmapStringErrorString(MMBMPStringError err); + +#endif /* STR_IO_H */ diff --git a/base/str_io_init.h b/base/str_io_init.h new file mode 100644 index 0000000..8099c19 --- /dev/null +++ b/base/str_io_init.h @@ -0,0 +1,209 @@ +#include "str_io.h" +#include "zlib_util_init.h" +#include "base64_init.h" +#include "snprintf_init.h" /* snprintf() */ +#include /* fputs() */ +#include /* isdigit() */ +#include /* atoi() */ +#include /* strlen() */ +#include + +#if defined(_MSC_VER) + #include "ms_stdbool.h" +#else + #include +#endif + +#define STR_BITS_PER_PIXEL 24 +#define STR_BYTES_PER_PIXEL ((STR_BITS_PER_PIXEL) / 8) + +#define MAX_DIMENSION_LEN 5 /* Maximum length for [width] or [height] + * in string. */ + +const char *MMBitmapStringErrorString(MMBMPStringError err) +{ + switch (err) { + case kMMBMPStringInvalidHeaderError: + return "Invalid header for string"; + case kMMBMPStringDecodeError: + return "Error decoding string"; + case kMMBMPStringDecompressError: + return "Error decompressing string"; + case kMMBMPStringSizeError: + return "String not of expected size"; + case MMMBMPStringEncodeError: + return "Error encoding string"; + case kMMBMPStringCompressError: + return "Error compressing string"; + default: + return NULL; + } +} + +/* Parses beginning of string in the form of "[width],[height],*". + * + * If successful, |width| and |height| are set to the appropropriate values, + * |len| is set to the length of [width] + the length of [height] + 2, + * and true is returned; otherwise, false is returned. + */ +static bool getSizeFromString(const uint8_t *buf, size_t buflen, + size_t *width, size_t *height, + size_t *len); + +MMBitmapRef createMMBitmapFromString(const uint8_t *buffer, size_t buflen, + MMBMPStringError *err) +{ + uint8_t *decoded, *decompressed; + size_t width, height; + size_t len, bytewidth; + + if (*buffer++ != 'b' || !getSizeFromString(buffer, --buflen, + &width, &height, &len)) { + if (err != NULL) *err = kMMBMPStringInvalidHeaderError; + return NULL; + } + buffer += len; + buflen -= len; + + decoded = base64decode(buffer, buflen, NULL); + if (decoded == NULL) { + if (err != NULL) *err = kMMBMPStringDecodeError; + return NULL; + } + + decompressed = zlib_decompress(decoded, &len); + free(decoded); + + if (decompressed == NULL) { + if (err != NULL) *err = kMMBMPStringDecompressError; + return NULL; + } + + bytewidth = width * STR_BYTES_PER_PIXEL; /* Note that bytewidth is NOT + * aligned to a padding. */ + if (height * bytewidth != len) { + if (err != NULL) *err = kMMBMPStringSizeError; + return NULL; + } + + return createMMBitmap(decompressed, width, height, + bytewidth, STR_BITS_PER_PIXEL, STR_BYTES_PER_PIXEL); +} + +/* Returns bitmap data suitable for encoding to a string; that is, 24-bit BGR + * bitmap with no padding and 3 bytes per pixel. + * + * Caller is responsible for free()'ing returned buffer. */ +static uint8_t *createRawBitmapData(MMBitmapRef bitmap); + +uint8_t *createStringFromMMBitmap(MMBitmapRef bitmap, MMBMPStringError *err) +{ + uint8_t *raw, *compressed; + uint8_t *ret, *encoded; + size_t len, retlen; + + assert(bitmap != NULL); + + raw = createRawBitmapData(bitmap); + if (raw == NULL) { + if (err != NULL) *err = kMMBMPStringGenericError; + return NULL; + } + + compressed = zlib_compress(raw, + bitmap->width * bitmap->height * + STR_BYTES_PER_PIXEL, + 9, &len); + free(raw); + if (compressed == NULL) { + if (err != NULL) *err = kMMBMPStringCompressError; + return NULL; + } + + encoded = base64encode(compressed, len - 1, &retlen); + free(compressed); + if (encoded == NULL) { + if (err != NULL) *err = MMMBMPStringEncodeError; + return NULL; + } + + retlen += 3 + (MAX_DIMENSION_LEN * 2); + ret = calloc(sizeof(char), (retlen + 1)); + snprintf((char *)ret, retlen, "b%lu,%lu,%s", (unsigned long)bitmap->width, + (unsigned long)bitmap->height, + encoded); + ret[retlen] = '\0'; + free(encoded); + return ret; +} + +static uint32_t parseDimension(const uint8_t *buf, size_t buflen, + size_t *numlen); + +static bool getSizeFromString(const uint8_t *buf, size_t buflen, + size_t *width, size_t *height, + size_t *len) +{ + size_t numlen; + assert(buf != NULL); + assert(width != NULL); + assert(height != NULL); + + if ((*width = parseDimension(buf, buflen, &numlen)) == 0) { + return false; + } + *len = numlen + 1; + + if ((*height = parseDimension(buf + *len, buflen, &numlen)) == 0) { + return false; + } + *len += numlen + 1; + + return true; +} + +/* Parses one dimension from string as described in getSizeFromString(). + * Returns dimension on success, or 0 on error. */ +static uint32_t parseDimension(const uint8_t *buf, size_t buflen, + size_t *numlen) +{ + char num[MAX_DIMENSION_LEN + 1]; + size_t i; + // ssize_t len; + // size_t len; + uint8_t * len; + + assert(buf != NULL); + assert(len != NULL); + for (i = 0; i < buflen && buf[i] != ',' && buf[i] != '\0'; ++i) { + if (!isdigit(buf[i]) || i > MAX_DIMENSION_LEN) return 0; + num[i] = buf[i]; + } + num[i] = '\0'; + *numlen = i; + + return (uint32_t)atoi(num); +} + +static uint8_t *createRawBitmapData(MMBitmapRef bitmap) +{ + uint8_t *raw = calloc(STR_BYTES_PER_PIXEL, bitmap->width * bitmap->height); + size_t y; + + for (y = 0; y < bitmap->height; ++y) { + /* No padding is added to string bitmaps. */ + const size_t rowOffset = y * bitmap->width * STR_BYTES_PER_PIXEL; + size_t x; + for (x = 0; x < bitmap->width; ++x) { + /* Copy in BGR format. */ + const size_t colOffset = x * STR_BYTES_PER_PIXEL; + uint8_t *dest = raw + rowOffset + colOffset; + MMRGBColor *srcColor = MMRGBColorRefAtPoint(bitmap, x, y); + dest[0] = srcColor->blue; + dest[1] = srcColor->green; + dest[2] = srcColor->red; + } + } + + return raw; +} diff --git a/base/zlib_util.h b/base/zlib_util.h new file mode 100644 index 0000000..b0cee02 --- /dev/null +++ b/base/zlib_util.h @@ -0,0 +1,32 @@ +#pragma once +#ifndef ZLIB_UTIL_H +#define ZLIB_UTIL_H + +#include + +#if defined(_MSC_VER) + #include "ms_stdint.h" +#else + #include +#endif + +/* Attempts to decompress given deflated NUL-terminated buffer. + * + * If successful and |len| is not NULL, |len| will be set to the number of + * bytes in the returned buffer. + * Returns new string to be free()'d by caller, or NULL on error. */ +uint8_t *zlib_decompress(const uint8_t *buf, size_t *len); + +/* Attempt to compress given buffer. + * + * The compression level is passed directly to zlib: it must between 0 and 9, + * where 1 gives best speed, 9 gives best compression, and 0 gives no + * compression at all. + * + * If successful and |len| is not NULL, |len| will be set to the number of + * bytes in the returned buffer. + * Returns new string to be free()'d by caller, or NULL on error. */ +uint8_t *zlib_compress(const uint8_t *buf, const size_t buflen, int level, + size_t *len); + +#endif /* ZLIB_UTIL_H */ diff --git a/base/zlib_util_init.h b/base/zlib_util_init.h new file mode 100644 index 0000000..bde529b --- /dev/null +++ b/base/zlib_util_init.h @@ -0,0 +1,99 @@ +#include "zlib_util.h" +#include +#include /* fprintf() */ +#include /* malloc() */ +#include + +#define ZLIB_CHUNK (16 * 1024) + +uint8_t *zlib_decompress(const uint8_t *buf, size_t *len) +{ + size_t output_size = ZLIB_CHUNK; + uint8_t *output = malloc(output_size); + int err; + z_stream zst; + + /* Sanity check */ + if (output == NULL) return NULL; + assert(buf != NULL); + + /* Set inflate state */ + zst.zalloc = Z_NULL; + zst.zfree = Z_NULL; + zst.opaque = Z_NULL; + zst.next_out = (Byte *)output; + zst.next_in = (Byte *)buf; + zst.avail_out = ZLIB_CHUNK; + + if (inflateInit(&zst) != Z_OK) goto error; + + /* Decompress input buffer */ + do { + if ((err = inflate(&zst, Z_NO_FLUSH)) == Z_OK) { /* Need more memory */ + zst.avail_out = (uInt)output_size; + + /* Double size each time to avoid calls to realloc() */ + output_size <<= 1; + output = realloc(output, output_size + 1); + if (output == NULL) return NULL; + + zst.next_out = (Byte *)(output + zst.avail_out); + } else if (err != Z_STREAM_END) { /* Error decompressing */ + if (zst.msg != NULL) { + fprintf(stderr, "Could not decompress data: %s\n", zst.msg); + } + inflateEnd(&zst); + goto error; + } + } while (err != Z_STREAM_END); + + if (len != NULL) *len = zst.total_out; + if (inflateEnd(&zst) != Z_OK) goto error; + return output; /* To be free()'d by caller */ + +error: + if (output != NULL) free(output); + return NULL; +} + +uint8_t *zlib_compress(const uint8_t *buf, const size_t buflen, int level, + size_t *len) +{ + z_stream zst; + uint8_t *output = NULL; + + /* Sanity check */ + assert(buf != NULL); + assert(len != NULL); + assert(level <= 9 && level >= 0); + + zst.avail_out = (uInt)((buflen + (buflen / 10)) + 12); + output = malloc(zst.avail_out); + if (output == NULL) return NULL; + + /* Set deflate state */ + zst.zalloc = Z_NULL; + zst.zfree = Z_NULL; + zst.next_out = (Byte *)output; + zst.next_in = (Byte *)buf; + zst.avail_in = (uInt)buflen; + + if (deflateInit(&zst, level) != Z_OK) goto error; + + /* Compress input buffer */ + if (deflate(&zst, Z_FINISH) != Z_STREAM_END) { + if (zst.msg != NULL) { + fprintf(stderr, "Could not compress data: %s\n", zst.msg); + } + deflateEnd(&zst); + goto error; + } + + if (len != NULL) *len = zst.total_out; + if (deflateEnd(&zst) != Z_OK) goto error; + return output; /* To be free()'d by caller */ + +error: + if (output != NULL) free(output); + return NULL; +} diff --git a/event/goEvent.h b/event/goEvent.h new file mode 100644 index 0000000..e69de29