/* xbmtopbm.c - read an X bitmap file and produce a PBM image
**
** Copyright (C) 1988 by Jef Poskanzer.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/


#include <assert.h>
#include <string.h>

#include "pm_c_util.h"
#include "mallocvar.h"
#include "nstring.h"
#include "pbm.h"
#include "bitreverse.h"



#define MAX_LINE 500

static unsigned int hexTable[256];
    /* Hexadecimal ASCII translation table.  Constant */

static void
initHexTable(void) {

    unsigned int i;

    for (i = 0; i < 256; ++i)
        hexTable[i] = 256;

    hexTable['0'] =  0;
    hexTable['1'] =  1;
    hexTable['2'] =  2;
    hexTable['3'] =  3;
    hexTable['4'] =  4;
    hexTable['5'] =  5;
    hexTable['6'] =  6;
    hexTable['7'] =  7;
    hexTable['8'] =  8;
    hexTable['9'] =  9;
    hexTable['A'] = 10;
    hexTable['B'] = 11;
    hexTable['C'] = 12;
    hexTable['D'] = 13;
    hexTable['E'] = 14;
    hexTable['F'] = 15;
    hexTable['a'] = 10;
    hexTable['b'] = 11;
    hexTable['c'] = 12;
    hexTable['d'] = 13;
    hexTable['e'] = 14;
    hexTable['f'] = 15;
}



static void
parseWidthHeightLine(const char *   const line,
                     bool *         const gotWidthP,
                     unsigned int * const widthP,
                     bool *         const gotHeightP,
                     unsigned int * const heightP) {

    int rc;
    char nameAndType[MAX_LINE];
    unsigned int value;

    rc = sscanf(line, "#define %s %u", nameAndType, &value);
    if (rc == 2) {
        const char * underscorePos = strrchr(nameAndType, '_');
        const char * type;
        if (underscorePos)
            type = underscorePos + 1;
        else
            type = nameAndType;
        if (streq(type, "width")) {
            *gotWidthP = TRUE;
            *widthP = value;
        } else if (streq(type, "height")) {
            *gotHeightP = TRUE;
            *heightP = value;
        }
    }
}



static void
parseDeclaration(const char * const line,
                 bool *       const isDeclarationP,
                 bool *       const version10P) {
/*----------------------------------------------------------------------------
   Parse the XBM file line 'line' as the first line of the data structure
   declaration, i.e. the one that looks like this:

      static unsigned char myImage = {

   Return as *isDeclarationP whether the line actually is such a line,
   and if so, return as nameAndType what the variable name ('myImage'
   in the example) is and as *version10P whether it's of the type used
   by X10 as opposed to X11.
-----------------------------------------------------------------------------*/
    char nameAndType[MAX_LINE];
    int rc;
        
    rc = sscanf(line, "static short %s = {", nameAndType);
    if (rc == 1) {
        *version10P     = TRUE;
        *isDeclarationP = TRUE;
    } else {
        int rc;
        rc = sscanf(line, "static char %s = {", nameAndType);
        if (rc == 1) {
            *version10P     = FALSE;
            *isDeclarationP = TRUE;
        } else {
            int rc;
            rc = sscanf(line, "static unsigned char %s = {", nameAndType);
            if (rc == 1) {
                *version10P     = FALSE;
                *isDeclarationP = TRUE;
            } else
                *isDeclarationP = FALSE;
        }
    }
}



static void
getXbmHeader(FILE *         const ifP,
             unsigned int * const widthP,
             unsigned int * const heightP,
             bool *         const version10P) {

    bool foundDeclaration;
        /* In scanning through the bitmap file, we have found the first
           line of the C declaration of the array (the "static char ..."
           or whatever line)
        */
    bool gotWidth, gotHeight;
        /* We found the line in the bitmap file that gives the width
           or height, respectively, of the image (and have set
           *widthP or *heightP to the value in it).
        */

    bool eof;
        /* We've encountered end of file while searching file */

    gotWidth = FALSE;
    gotHeight = FALSE;
    foundDeclaration = FALSE;    /* Haven't found it yet; haven't even looked*/
    eof = FALSE;                 /* Haven't encountered end of file yet */

    while (!foundDeclaration && !eof) {
        char * rc;
        char line[MAX_LINE];

        rc = fgets(line, MAX_LINE, ifP);
        if (rc == NULL)
            eof = TRUE;
        else {
            if (strlen(line) == MAX_LINE - 1)
                pm_error("A line in the input file is %u characters long.  "
                         "%u is the maximum we can handle",
                         (unsigned)strlen(line), MAX_LINE-1);

            parseWidthHeightLine(line, &gotWidth, widthP, &gotHeight, heightP);

            parseDeclaration(line, &foundDeclaration, version10P);
        }
    }

    if (!foundDeclaration) 
        pm_error("Unable to find a line in the file containing the start "
                 "of C array declaration (\"static char\" or whatever)");

    if (!gotWidth)
        pm_error("Unable to find the #define statement that gives the "
                 "width of the image, before the data structure "
                 "declaration.");
    if (!gotHeight)
        pm_error("Unable to find the #define statement that gives the "
                 "height of the image, before the data structure "
                 "declaration.");
}



static void
getHexByte(FILE *         const ifP,
           unsigned int * const valueP) {

    int c1, c2;
    unsigned int value;

    c1 = getc(ifP);
    c2 = getc(ifP);
    if (c1 == EOF || c2 == EOF)
        pm_error("EOF / read error");

    assert(c1 >= 0); assert(c1 < 256);
    assert(c2 >= 0); assert(c2 < 256);
    
    value = (hexTable[c1] << 4) + hexTable[c2];
    if (value >= 256)
        pm_error("Invalid XBM input.  What should be a two digit "
                 "hexadecimal cipher is instead '%c%c'", c1, c2);

    *valueP = value;
}


                     
static void
readX10Raster(FILE *          const ifP,
              unsigned int    const rasterLength,
              unsigned char * const data,
              unsigned int    const bytesPerLine,
              bool            const mustPad) {

    unsigned int bytesDone;
    unsigned char * p;

    for (bytesDone = 0, p = &data[0];
         bytesDone < rasterLength;
         bytesDone += 2) {

        unsigned int value1;
        unsigned int value2;

        while (getc(ifP) != 'x') {}  /* Read up through the 'x' in 0x1234 */

        getHexByte(ifP, &value1);  /* Read first two hex digits */
        getHexByte(ifP, &value2);  /* Read last two hex digits */

        *p++ = value2;

        if (!mustPad || ((bytesDone + 2) % bytesPerLine))
            *p++ = value1;
    }
}



static void
readX11Raster(FILE * const ifP,
              unsigned int const rasterLength,
              unsigned char * data) {

    unsigned int i;

    for (i = 0; i < rasterLength; ++i) {
        unsigned int value;
        int c;

        /* Read up through the 'x' in 0x12 */
        while ((c = getc(ifP))) {
            if (c == EOF)
                pm_error("EOF where 0x expected");
            else if (toupper(c) == 'X')
                break;
        }

        getHexByte(ifP, &value);  /* Read the two hex digits */

        assert(value < 256);

        data[i] = value;
    }
}



static void
readBitmapFile(FILE *           const ifP,
               unsigned int *   const widthP,
               unsigned int *   const heightP,
               unsigned char ** const dataP) {

    bool version10;
    unsigned int rasterLength;
    unsigned int width, height;
    unsigned char * data;

    unsigned int bytesPerLine;
    bool mustPad;

    getXbmHeader(ifP, &width, &height, &version10);

    *widthP = width;
    *heightP = height;

    mustPad = (width % 16 >= 1 && width % 16 <= 8 && version10);

    bytesPerLine = (width + 7) / 8 + (mustPad ? 1 : 0);
    
    rasterLength = bytesPerLine * height;

    MALLOCARRAY(data, rasterLength);
    if (data == NULL)
        pm_error("Unable to allocate memory for the %u-byte raster",
                 rasterLength);

    if (version10)
        readX10Raster(ifP, rasterLength, data, bytesPerLine, mustPad);
    else
        readX11Raster(ifP, rasterLength, data);

    *dataP = data;
}



int
main(int    argc,
     char * argv[]) {

    FILE * ifP;
    bit * bitrow;
    unsigned int rows, cols;
    unsigned int row;
    unsigned char * data;
    const char * inputFileName;
    unsigned char * p;
        /* Cursor in raster data data[] */
    
    initHexTable();

    pbm_init(&argc, argv);

    if (argc-1 > 1)
        pm_error("The only possible argument is the input file name.  "
                 "You specified %u arguments", argc-1);
    
    if (argc-1 > 0)
        inputFileName = argv[1];
    else
        inputFileName = "-";

    ifP = pm_openr(inputFileName);

    readBitmapFile(ifP, &cols, &rows, &data);

    pm_close(ifP);

    pbm_writepbminit(stdout, cols, rows, 0);
    bitrow = pbm_allocrow_packed(cols);

    p = &data[0];  /* Start at beginning of raster */

    for (row = 0; row < rows; ++row) {
        unsigned int const bytesPerRow = pbm_packed_bytes(cols);
        unsigned int i;
        
        for (i = 0; i < bytesPerRow; ++i)
            bitrow[i] = bitreverse[*p++];
            
        if (cols % 8 > 0) {
            bitrow[bytesPerRow-1] >>= 8 - cols % 8;
            bitrow[bytesPerRow-1] <<= 8 - cols % 8;
        }
            
        pbm_writepbmrow_packed(stdout, bitrow, cols, 0);
    }

    pbm_freerow(bitrow);
    free(data);
    pm_close(stdout);

    return 0;
}

/*  CHANGE HISTORY:

  99.09.08 bryanh    Recognize "static unsigned char" declaration.

  06.10 (afu)
   Changed bitrow from plain to raw, write function from pbm_writepbmrow()
   to pbm_writepbmrow_packed().
   Retired bitwise transformation functions.

*/

