//////////////////////////////////////////////////////////
// pcl2bmp.cpp
//////////////////////////////////////////////////////////

#include <ctype.h>
#include <fstream.h>
#include <iostream.h>
#include <math.h>
#ifdef __WATCOMC__
#include <mem.h>
#else
#include <memory.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "bmpio.h"
#include "pcl2bmp.h"
#include "rasterop.h"
#include "softfont.h"
#include "util.h"

static int px, py;
static int rasterx, rastery;
static int patternf;
static int patternx, patterny;
static int patternw, patternh;
static FONTNODE *curfont[2];    // primary & secondary
static int fontshift;
static int fontid;
static int charcode;
static unsigned char bits[3300][300];
static int dirtybits;
static BMPINFO bmpinfo;
static int debugtrace, debugerror;

int pcl2bmp( char *NamePcl, char *NameBmp );
int Execute( COMMAND& command, ifstream& ifPcl );

/////////////////////////////////////////////////////////////////
// main
/////////////////////////////////////////////////////////////////

main( int argc, char *argv[] )
{
        bmpinfo.bits    = (unsigned char *)bits;
        bmpinfo.width   = 2400;
        bmpinfo.height  = 3300;
        bmpinfo.colours = 2;
        bmpinfo.palette = NULL;
        bmpinfo.cropx   = bmpinfo.cropy  = 0;
        bmpinfo.scalex  = bmpinfo.scaley = 1;

        for( int i=1; i<argc; i++ )
        {
                if( argv[i][0] != '-' )
                        break;

                if( !stricmp(argv[i], "-c") )
                        bmpinfo.cropx = bmpinfo.cropy = 1;

                else if( !stricmp(argv[i], "-cx") )
                        bmpinfo.cropx = 1;

                else if( !stricmp(argv[i], "-cy") )
                        bmpinfo.cropy = 1;

                else if( !strnicmp(argv[i], "-sx", 3) )
                        bmpinfo.scalex = atoi( argv[i] + 3 );

                else if( !strnicmp(argv[i], "-sy", 3) )
                        bmpinfo.scaley = atoi( argv[i] + 3 );

                else if( !strnicmp(argv[i], "-s", 2) )
                        bmpinfo.scalex = bmpinfo.scaley = atoi( argv[i] + 2 );

                else if( !stricmp(argv[i], "-trace") )
                        debugtrace = 1;

                else if( !stricmp(argv[i], "-debug") )
                        debugerror = 1;

                else
                        cerr << "bmp2pcl: unknown option " << argv[i] << endl;
        }

        if( bmpinfo.scalex < 1 )
                bmpinfo.scalex = 1;
        if( bmpinfo.scaley < 1 )
                bmpinfo.scaley = 1;

    if( argc - i < 2 )
    {
                cerr << endl;
        cerr << "pcl2bmp: convert HP LaserJet print file to bitmap" << endl << endl;

        cerr << "  usage: pcl2bmp {options} infile.prn outfile" << endl;
                cerr << "         the output file(s) will be named outfile1.bmp, outfile2.bmp, ..." << endl << endl;

                cerr << "options: -c      crop top, bottom, left, and right edges" << endl;
                cerr << "         -cx     crop left and right edges" << endl;
                cerr << "         -cy     crop top and bottom edges" << endl;
                cerr << "         -s#     shrink width and height by factor of '#'" << endl;
                cerr << "         -sx#    shrink width by factor of '#'" << endl;
                cerr << "         -sy#    shrink height by factor of '#'" << endl << endl;
                cerr << "         -trace  output trace log to stdout" << endl;
                cerr << "         -debug  output error log to stderr" << endl;
        return 1;
    }

    pcl2bmp( argv[i], argv[i+1] );
    return 0;
}

int pcl2bmp( char *NamePcl, char *NameBmp )
{
    ifstream ifPcl( NamePcl, ios::in | ios::binary | ios::nocreate );

        ifPcl.setf( ios::binary, ios::binary | ios::skipws );

        int PageNo = 1;
        char PageNameBmp[1024];
    static COMMAND command0;
    COMMAND command;
    int phase = 0;
    int ch;

        ch = ifPcl.get();
    while( ch != EOF )
    {
        switch( phase )
        {
        case 0: // search for ESC/control sequence
            if( ch == 0x1B )
            { // ESC
                command = command0;
                command.control = ch;
                phase++;
            }
            else if( ch < ' ' || ch == 0x7f )
            { // other control character
                command.control = ch;
                                switch( command.control )
                                {
                                case 0x0C: // Form-Feed
                                        if( dirtybits )
                                        {
                                                sprintf( PageNameBmp, "%s%d.bmp", NameBmp, PageNo );
                                                bmp_save( PageNameBmp, &bmpinfo );

                                                memset( bits, 0, sizeof(bits) );
                                                dirtybits = 0;
                                        }
                                        PageNo++;
                                        if( debugtrace )
                                cout << endl << "Ff ";
                                        break;

                                case 0x0E: // SO: select secondary font
                                        fontshift = 1;
                                        if( debugtrace )
                                cout << endl << "So ";
                                        break;

                                case 0x0F: // SI: select primary font
                                        fontshift = 0;
                                        if( debugtrace )
                                cout << endl << "Si ";
                                        break;

                                default:
                                        if( debugerror )
                                cerr << endl << hex << "unk: 0x" << ch << " " << dec;
                                        break;
                                }
            }
            else
                        {
                                px += (font_drawchar( bits, px, py, curfont[fontshift], ch ) + 2) / 4;
                                dirtybits = 1;
                                if( debugtrace )
                        cout << char(ch);
                        }
            break;

        case 1:
                        if( ch >= '!' && ch <= '/' )
                        {
                command.group1 = ch;
                phase++;
                        }
                        else if( ch >= '0' && ch <= '~' )
                        { // two character escape sequence
                                command.term = ch;
                Execute( command, ifPcl );
                                phase = 0;
                        }
                        else
                        {
                                if( debugtrace )
                        cout << endl << "unk: Ec " << hex << "0x" << ch << dec << " " << endl;
                                phase = 0;
                        }
            break;

        case 2:
            phase++;
            command.sign = 0;
            command.value = 0.0;

                        if( ch >= '`' && ch <= '~' )
                        {
                command.group2 = ch;
                        }
                        else
                        {
                                command.group2 = 0;
                                continue;
                        }
            break;

        case 3:
            if( ch=='+' || ch=='-' )
            {
                command.sign = ch;
                                ch = ifPcl.get();
            }
            while( ch != EOF && isdigit(ch) )
            {
                command.value = command.value * 10 + (ch - '0');
                                ch = ifPcl.get();
            }
            if( ch=='.' )
            {
                                double frac = 0.1;
                    while( (ch = ifPcl.get()) != EOF && isdigit(ch) )
                    {
                        command.value += (ch - '0') * frac;
                                        frac /= 10;
                    }
            }
                        if( command.sign == '-' )
                                command.value = -command.value;
                        phase++;
                        continue;

        case 4:
            command.term = ch;
            Execute( command, ifPcl );
            if( ch >= '`' && ch <= '~' )
            {
                phase = 3;
                command.sign = 0;
                command.value = 0;
            }
            else
                phase = 0;
            break;
        }
                ch = ifPcl.get();
    } // while

        if( dirtybits )
        {
                sprintf( PageNameBmp, "%s%d.bmp", NameBmp, PageNo );
                bmp_save( PageNameBmp, &bmpinfo );
        }

    return 0;
}

int Execute( COMMAND& command, ifstream& ifPcl )
{
        command.error = 1;              // unrecognized command
    if( command.control == 0x1B )
    {
                switch( command.group1 )
                {
                case 0:
                        switch( command.term )
                        {
                        case 'E': // reset
                                command.error = 2;
                                break;
                        }
                        break;

                case ')':
                case '(':
                        switch( command.group2 )
                        {
                        case 's':
                                switch( command.term )
                                {
                                case 'W':
                                        if( command.group1 == ')' )
                                        { // font descriptor and data
                                                int cdata = int(command.value);
                                                char *pdata = new char[cdata];
                                                if( pdata )
                                                {
                                                        ifPcl.read( pdata, cdata );
                                                        font_new( fontid, pdata, cdata );
                                                        delete pdata;
                                                }
                                        }
                                        else
                                        { // char descriptor and data
                                                int cdata = int(command.value);
                                                char *pdata = new char[cdata];
                                                if( pdata )
                                                {
                                                        ifPcl.read( pdata, cdata );
                                                        font_newchar( fontid, charcode, pdata, cdata );
                                                        delete pdata;
                                                }
                                        }
                                        command.error = 0;
                                        break;
                                }
                                break;

                        case 0:
                                if( toupper(command.term) == 'X' )
                                {
                                        curfont[command.group1==')'] = font_find( int(command.value) );
                                        command.error = 0;
                                }
                                break;
                        }
                        break;

                case '*':
                        switch( command.group2 )
                        {
                        case 'b': // Raster Graphics
                                switch( toupper(command.term) )
                                {
                                case 'M': // set compression method
                                        // 0=unencoded
                                        // 1=run-length encoding
                                        // 2=TIFF revision 4.0
                                        // 3=delta row
                                        // 5=adaptive compression
                                        command.error = 2;      // ignored command
                                        break;

                                case 'W': // transfer raster data
                                    {
                                                int i = rasterx/8, j = rasterx % 8;
                                                int n = int(command.value);
                                                int ch;

                                                while( n-- && (ch = ifPcl.get()) != EOF )
                                                {
                                                        if( ch )
                                                        {
                                                                dirtybits = 1;
                                                                bits[rastery][i++] |= ch >> j;
                                                                bits[rastery][i] |= ch << (8-j);
                                                        }
                                                        else
                                                                i++;
                                                }
                                                rastery++;
                                                py++;
                                    }
                                        command.error = 0;      // processed command
                                        break;

                                case 'Y': // y offset
                                        command.error = 2;      // ignored command
                                        break;
                                }
                                break;

                        case 'c': // Rectangular Area Fill Graphics
                                switch( toupper(command.term) )
                                {
                                case 'A': // rectangle width (dots)
                                        patternw = int(command.value);
                                        command.error = 0;
                                        break;
                                case 'B': // rectangle height (dots)
                                        patternh = int(command.value);
                                        command.error = 0;
                                        break;
                                case 'H': // rectangle width (decipoints)
                                        patternw = int( (long(command.value * 300) + 719) / 720 );
                                        command.error = 0;
                                        break;
                                case 'V': // rectangle height (decipoints)
                                        patternh = int( (long(command.value * 300) + 719) / 720 );
                                        command.error = 0;
                                        break;

                                case 'G': // area fill ID (pattern ID)
                                        // shading: 1-2, 2-10, 11-20, 21-35, 36-55, 56-80, 81-99, 100
                                        // cross-hatch: 1 -, 2 |, 3 /, 4 \, 5 #, 6 X
                                        // user-defined: # of pattern (0-32767)
                                        command.error = 2;      // ignored
                                        break;

                                case 'P': // fill rectangular area
                                        switch( int(command.value) )
                                        {
                                        case 0: // solid
                                                if( patternw > 0 && patternh > 0 )
                                                {
                                                        bit_set( bits, px, py, px + patternw - 1, py + patternh - 1 );
                                                        dirtybits = 1;
                                                }
                                                command.error = 0;
                                                break;
                                        case 1: // solid white
                                                command.error = 2;      // ignored
                                                break;
                                        case 2: // shading
                                                command.error = 2;      // ignored
                                                break;
                                        case 3: // cross-hatch pattern
                                                command.error = 2;      // ignored
                                                break;
                                        case 4: // user-defined pattern
                                                command.error = 2;      // ignored
                                                break;
                                        case 5: // current pattern
                                                command.error = 2;      // ignored
                                                break;
                                        }
                                        break;

                                case 'Q': // pattern control
                                        // 0=delete all paterns
                                        // 1=delete all temporary patterns
                                        // 2=delete pattern (last pattern ID specified)
                                        // 3=reserved
                                        // 4=make pattern temporary (last pattern ID specified)
                                        // 5=make pattern permanent (last pattern ID specified)
                                        command.error = 2;      // ignored
                                        break;

                                case 'W': // user-defined pattern
                                        // followed by # of pattern bytes
                                        ifPcl.ignore( int(command.value) );
                                        command.error = 2;      // ignored
                                        break;

                                case 'D': // font ID
                                        fontid = int(command.value);
                                        command.error = 0;
                                        break;

                                case 'E': // char code
                                        charcode = int(command.value);
                                        command.error = 0;
                                        break;

                                case 'F': // soft font control
                                        command.error = 2; // ignored
                                        break;
                                }
                                break;

                        case 'p': // Cursor Positioning/Rectangular Area Fill Graphics
                                switch( toupper(command.term) )
                                {
                                case 'R': // set pattern reference point
                                        // 0=rotate patterns with print direction
                                        // 1=keep patterns fixed
                                        patternf = int(command.value);
                                        patternx = px;
                                        patterny = py;
                                        command.error = 2;      // ignored
                                        break;

                                case 'X': // horizontal (dots)
                                        if( command.sign )
                                                px += int(command.value);
                                        else
                                                px = int(command.value);
                                        command.error = 0;
                                        break;
                                case 'Y': // vertical (dots)
                                        if( command.sign )
                                                py += int(command.value);
                                        else
                                                py = int(command.value);
                                        command.error = 0;
                                        break;
                                }
                                break;

                        case 'r': // Raster Graphics
                                switch( toupper(command.term) )
                                {
                                case 'A': // start
                                        // 0=at x-position 0
                                        // 1=at current x-position
                                        if( int(command.value) )
                                                rasterx = px;
                                        else
                                                rastery = 0;
                                        rastery = py;
                                        command.error = 0;
                                        break;
                                case 'B': // end: any LaserJet
                                case 'C': // end: LJ IIISi and IIIP
                                        command.error = 0;
                                        break;
                                case 'F': // presentation mode
                                        // 0=current print direction
                                        // 3=along width of physical page
                                        command.error = 2;      // ignored
                                        break;
                                case 'S': // raster width
                                        command.error = 2;      // ignored
                                        break;
                                case 'T': // raster height
                                        command.error = 2;      // ignored
                                        break;
                                }
                                break;

                        case 't': // Raster Graphics
                                if( toupper(command.term) == 'R' )
                                { // Resolution: values 75, 100, 150, 300
                                        if( int(command.value) == 300 )
                                                command.error = 0;
                                        else
                                                command.error = 2;      // ignored
                                }
                                break;
                        }
                        break;
                }
        }

    if( command.control == 0x1B )
    {
                ostream *pcout;

                switch( command.error )
                {
                case 0:
                        pcout = &cout;
                        if( debugtrace )
                                *pcout << endl;
                        break;
                case 1:
                        pcout = &cerr;
                        if( debugerror )
                                *pcout << endl << "unk: ";
                        break;
                case 2:
                        pcout = &cerr;
                        if( debugerror )
                                *pcout << endl << "nop: ";
                        break;
                default:
                        pcout = &cerr;
                        if( debugerror )
                                *pcout << endl << "int: ";
                        break;
                }

                if( pcout == &cout && debugtrace ||
                    pcout == &cerr && debugerror )
                {
                        if( command.group1 )
                        {
                                *pcout << "Ec " << command.group1;
                                if( command.group2 )
                                *pcout << command.group2;
                        *pcout << command.term << " ";
                        if( command.sign )
                                *pcout << command.sign;
                        *pcout << fabs(command.value) << " ";
                        }
                        else
                        {
                                *pcout << "Ec " << command.term << " ";
                        }
                }
    }
    return 0;
}