/**
 * File: XBM IO
 *
 * Read and write XBM images.
 */

/* $Id$ */

#include "gd.h"
#include "gd_errors.h"
#include "gd_intern.h"
#include "gdhelpers.h"
#include <ctype.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "php.h"
#define MAX_XBM_LINE_SIZE 255

/*
  Function: gdImageCreateFromXbm

        <gdImageCreateFromXbm> is called to load images from X bitmap
        format files. Invoke <gdImageCreateFromXbm> with an already opened
        pointer to a file containing the desired
        image. <gdImageCreateFromXbm> returns a <gdImagePtr> to the new
        image, or NULL if unable to load the image (most often because the
        file is corrupt or does not contain an X bitmap format
        image). <gdImageCreateFromXbm> does not close the file.

        You can inspect the sx and sy members of the image to determine
        its size. The image must eventually be destroyed using
        <gdImageDestroy>.

        X11 X bitmaps (which define a char[]) as well as X10 X bitmaps (which define
        a short[]) are supported.

  Parameters:

        fd - The input FILE pointer

  Returns:

        A pointer to the new image or NULL if an error occurred.

  Example:
        (start code)

        gdImagePtr im;
        FILE *in;
        in = fopen("myxbm.xbm", "rb");
        im = gdImageCreateFromXbm(in);
        fclose(in);
        // ... Use the image ...
        gdImageDestroy(im);

        (end code)
*/
BGD_DECLARE(gdImagePtr) gdImageCreateFromXbm(FILE *fd)
{
    char fline[MAX_XBM_LINE_SIZE];
    char iname[MAX_XBM_LINE_SIZE];
    char *type;
    int value;
    unsigned int width = 0, height = 0;
    int fail = 0;
    int max_bit = 0;

    gdImagePtr im;
    int bytes = 0, i;
    int bit, x = 0, y = 0;
    int ch;
    char h[8];
    unsigned int b;

    rewind(fd);
    while (fgets(fline, MAX_XBM_LINE_SIZE, fd)) {
        fline[MAX_XBM_LINE_SIZE - 1] = '\0';
        if (strlen(fline) == MAX_XBM_LINE_SIZE - 1) {
            return 0;
        }
        if (sscanf(fline, "#define %s %d", iname, &value) == 2) {
            if (!(type = strrchr(iname, '_'))) {
                type = iname;
            } else {
                type++;
            }

            if (!strcmp("width", type)) {
                width = (unsigned int)value;
            }
            if (!strcmp("height", type)) {
                height = (unsigned int)value;
            }
        } else {
            if (sscanf(fline, "static unsigned char %s = {", iname) == 1 ||
                sscanf(fline, "static char %s = {", iname) == 1) {
                max_bit = 128;
            } else if (sscanf(fline, "static unsigned short %s = {", iname) == 1 ||
                       sscanf(fline, "static short %s = {", iname) == 1) {
                max_bit = 32768;
            }
            if (max_bit) {
                bytes = (width + 7) / 8 * height;
                if (!bytes) {
                    return 0;
                }
                if (!(type = strrchr(iname, '_'))) {
                    type = iname;
                } else {
                    type++;
                }
                if (!strcmp("bits[]", type)) {
                    break;
                }
            }
        }
    }
    if (!bytes || !max_bit) {
        return 0;
    }

    if (!(im = gdImageCreate(width, height))) {
        return 0;
    }
    gdImageColorAllocate(im, 255, 255, 255);
    gdImageColorAllocate(im, 0, 0, 0);
    h[2] = '\0';
    h[4] = '\0';
    for (i = 0; i < bytes; i++) {
        while (1) {
            if ((ch = getc(fd)) == EOF) {
                fail = 1;
                break;
            }
            if (ch == 'x') {
                break;
            }
        }
        if (fail) {
            break;
        }
        /* Get hex value */
        if ((ch = getc(fd)) == EOF) {
            break;
        }
        h[0] = ch;
        if ((ch = getc(fd)) == EOF) {
            break;
        }
        h[1] = ch;
        if (max_bit == 32768) {
            if ((ch = getc(fd)) == EOF) {
                break;
            }
            h[2] = ch;
            if ((ch = getc(fd)) == EOF) {
                break;
            }
            h[3] = ch;
        }
        if (sscanf(h, "%x", &b) != 1) {
            gd_error("Invalid XBM");
            gdImageDestroy(im);
            return 0;
        }
        for (bit = 1; bit <= max_bit; bit = bit << 1) {
            gdImageSetPixel(im, x++, y, (b & bit) ? 1 : 0);
            if (x == im->sx) {
                x = 0;
                y++;
                if (y == im->sy) {
                    return im;
                }
                break;
            }
        }
    }

    gd_error("EOF before image was complete");
    gdImageDestroy(im);
    return 0;
}

/* {{{ gdCtxPrintf */
static void gdCtxPrintf(gdIOCtx *out, const char *format, ...)
{
    char *buf;
    int len;
    va_list args;

    va_start(args, format);
    len = vspprintf(&buf, 0, format, args);
    va_end(args);
    out->putBuf(out, buf, len);
    gdFree(buf);
}
/* }}} */

/* The compiler will optimize strlen(constant) to a constant number. */
#define gdCtxPuts(out, s) out->putBuf(out, s, strlen(s))

/**
 * Function: gdImageXbmCtx
 *
 *  Writes an image to an IO context in X11 bitmap format.
 *
 * Parameters:
 *
 *  image     - The <gdImagePtr> to write.
 *  file_name - The prefix of the XBM's identifiers. Illegal characters are
 *              automatically stripped.
 *  gd        - Which color to use as forground color. All pixels with another
 *              color are unset.
 *  out       - The <gdIOCtx> to write the image file to.
 *
 */
BGD_DECLARE(void)
gdImageXbmCtx(gdImagePtr image, char *file_name, int fg, gdIOCtx *out)
{
    int x, y, c, b, sx, sy, p;
    char *name, *f;
    size_t i, l;

    name = file_name;
    if ((f = strrchr(name, '/')) != NULL)
        name = f + 1;
    if ((f = strrchr(name, '\\')) != NULL)
        name = f + 1;
    name = estrdup(name);
    if ((f = strrchr(name, '.')) != NULL && !gd_strcasecmp(f, ".XBM"))
        *f = '\0';
    if ((l = strlen(name)) == 0) {
        gdFree(name);
        name = estrdup("image");
    } else {
        for (i = 0; i < l; i++) {
            char c = name[i];
            // Explicitly check for valid ASCII ranges and the underscore
            if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') ||
                  (c == '_'))) {

                // Anything else (spaces, punctuation, accents, UTF-8 bytes)
                // becomes '_'
                name[i] = '_';
            }
        }
    }

    /* Since "name" comes from the user, run it through a direct puts.
     * Trying to printf it into a local buffer means we'd need a large
     * or dynamic buffer to hold it all. */

    /* #define <name>_width 1234 */
    gdCtxPuts(out, "#define ");
    gdCtxPuts(out, name);
    gdCtxPuts(out, "_width ");
    gdCtxPrintf(out, "%d\n", gdImageSX(image));

    /* #define <name>_height 1234 */
    gdCtxPuts(out, "#define ");
    gdCtxPuts(out, name);
    gdCtxPuts(out, "_height ");
    gdCtxPrintf(out, "%d\n", gdImageSY(image));

    /* static unsigned char <name>_bits[] = {\n */
    gdCtxPuts(out, "static unsigned char ");
    gdCtxPuts(out, name);
    gdCtxPuts(out, "_bits[] = {\n  ");

    gdFree(name);

    b = 1;
    p = 0;
    c = 0;
    sx = gdImageSX(image);
    sy = gdImageSY(image);
    for (y = 0; y < sy; y++) {
        for (x = 0; x < sx; x++) {
            if (gdImageGetPixel(image, x, y) == fg) {
                c |= b;
            }
            if ((b == 128) || (x == sx - 1)) {
                b = 1;
                if (p) {
                    gdCtxPuts(out, ", ");
                    if (!(p % 12)) {
                        gdCtxPuts(out, "\n  ");
                        p = 12;
                    }
                }
                p++;
                gdCtxPrintf(out, "0x%02X", c);
                c = 0;
            } else {
                b <<= 1;
            }
        }
    }
    gdCtxPuts(out, "};\n");
}
