/*
 * Copyright (c) 2000, Ferrari electronic AG
 * All right reserved.
 *
 * As long as the above copyright statement and this notice remain
 * unchanged, you can do what ever you want with this file.
 */

/*$Id: gdevbff.c,v 1.1 2002/08/03 03:08:26 tillkamppeter Exp $ */
/* BFF devices */
#include <errno.h>
#include "gdevprn.h"
#include "strimpl.h"
#include "scfx.h"

/* Define the device parameters. */
#define X_DPI 204
#define Y_DPI 196
#define LINE_SIZE ((X_DPI * 101 / 10 + 7) / 8)	/* max bytes per line */


int gdev_bff_open(gx_device *dev);
int gdev_bff_close(gx_device *dev);

#define BFF_LINE_LEN 1728
#define MAX_BFF_LINE_SIZE (4*1024)
#define BFF_WHITE 0
#define BFF_BLACK 1

#define BITSIZEOF(type) (sizeof(type) << 3)
#define BITPOS(pos) (BITSIZEOF(byte)-1-((pos) & (BITSIZEOF(byte)-1)))
#define BUFPOS(pos) ((pos)>>3)
#define LEN2MASK(n) (((n)==sizeof(signed char)*8) ? ((signed char)-1) : (~(((signed char)-1)<<(n))))

private int bff_write_sol(FILE *prn_stream, short l);
private int bff_write_eop(FILE *prn_stream);
private int bff_write_sop(FILE *prn_stream,
			  unsigned long prev, unsigned long next);
private int bff_write_eod(FILE *prn_stream, unsigned long prev);
private long bff_write_run(byte *buf, int run, long pos);
#ifdef BFF_LITTLE_ENDIAN
private byte bff_swap_byte(byte b);
#endif
private int bff_count_equal_bits(byte w, int bitpos, int val);
private int bff_count_run(byte *buffer, int size, int pos, int val);
private int bff_update_pagehead(FILE *prn_stream, long prev,
				long curr_page_pos);
private int bff_write_line(FILE *prn_stream, byte *line, int linesize);
private int  bff_close_page(FILE *prn_stream,
			    long diff_last_page_pos, long curr_page_pos);

/* The device descriptors */

private dev_proc_print_page(bff_print_page);

struct gx_device_bff_s {
    gx_device_common;
    gx_prn_device_common;
    long last_page_pos;
};
typedef struct gx_device_bff_s gx_device_bff;

/* Define procedures that adjust the paper size. */
private const gx_device_procs gdev_bff_procs =
    prn_procs(gdev_bff_open, gdev_prn_output_page, gdev_bff_close);

const gx_device_bff gs_bff_device = {
    prn_device_std_body(gx_device_bff, gdev_bff_procs, "bff",
			DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
			X_DPI, Y_DPI,
			0, 0, 0, 0,	/* margins */
			1, bff_print_page),
    -1                                  /* there is no previous page */ 
};


/* Open the BFF file. */
int
gdev_bff_open(gx_device *dev)
{
    return gdev_prn_open(dev);
}

/* Close the BFF file. */
int
gdev_bff_close(gx_device *dev)
{
    int rc;
    long curr_page_pos, diff_last_page_pos;
    FILE *fp;

    gx_device_bff *const bdev = (gx_device_bff *)dev;

    fp = bdev->file;
    if (fp != NULL) {
	if ((curr_page_pos = ftell(fp)) >= 0) {
	    diff_last_page_pos = bdev->last_page_pos-curr_page_pos;
	} else {
	    if (bdev->last_page_pos < 0)
		diff_last_page_pos = -1; /* will be a SOD */
	    else
		diff_last_page_pos = 0;
	}
    }

    /* write the EOD */
    if ((rc=bff_write_eod(bdev->file, diff_last_page_pos)))
	return rc;

    return gdev_prn_close(dev);
}


/* Print a BFF page. */
private int
bff_print_page(gx_device_printer *pdev, FILE * prn_stream)
{
    int rc;
    int bytes_per_line = gdev_mem_bytes_per_scan_line((gx_device *) pdev);
    char *line;
    int y;
    byte *data;
    long curr_page_pos, diff_last_page_pos;
    gx_device_bff *bdev = (gx_device_bff *)pdev;

    if ((curr_page_pos=ftell(prn_stream)) >= 0) {
	diff_last_page_pos = bdev->last_page_pos-curr_page_pos;
	bdev->last_page_pos = curr_page_pos;
    } else {
	if (bdev->last_page_pos < 0)
	    diff_last_page_pos = -1; /* will be a SOD */
	else
	    diff_last_page_pos = 0;
	
	bdev->last_page_pos = 0;
    }


    /* write the SOP */
    if ((rc = bff_write_sop(prn_stream, diff_last_page_pos, 0)))
	return rc;

    if ((line=gs_malloc(bytes_per_line, 1, "bff_print_page(in)"))) {
	/* write the lines */
	for (y=0; y<pdev->height; y++) {
	    if (gdev_prn_get_bits(pdev, y, line, &data) < 0)
		break;
	    bff_write_line(prn_stream, data, bytes_per_line);
	}
	
	if ((rc = bff_close_page(prn_stream, diff_last_page_pos, curr_page_pos)))
	    return rc;

	gs_free(line, bytes_per_line, 1, "bff_print_page(in)");
    } else {
	return gs_note_error(gs_error_VMerror);
    }

    return 0;
}

/*
 * bff_write_sol
 *
 * write a SOL from the current file position and set
 * the file seek pointer behind the SOL
 *
 * return 0 on success
 */
private int bff_write_sol(FILE *prn_stream, short l)
{
    byte sol[4], *p;

    p = sol;

    *p++ = 0xff;
    *p++ = 0xff;
    *p++ = (l >> 8) & 0xff;
    *p++ = (l >> 0) & 0xff;

    return (fwrite(sol, 1, sizeof(sol), prn_stream)==sizeof(sol))?0:-1;
}

/*
 * bff_write_eop
 *
 * write a EOP from the current file position and set
 * the file seek pointer behind the EOP
 *
 * return 0 on success
 */
private int bff_write_eop(FILE *prn_stream)
{
    return bff_write_sol(prn_stream, -1);
}


/*
 * bff_write_sop
 *
 * write a SOP at the current file position and set
 * the file seek pointer behind the SOP
 *
 * return 0 on success
 */
private int bff_write_sop(FILE *prn_stream,
			 unsigned long prev, unsigned long next)
{
    byte sop[10], *p;

    p = sop;
    *p++ = 0xff;
    *p++ = 0xfe;
    *p++ = (prev >> 24) & 0xff;
    *p++ = (prev >> 16) & 0xff;
    *p++ = (prev >> 8) & 0xff;
    *p++ = (prev >> 0) & 0xff;
    *p++ = (next >> 24) & 0xff;
    *p++ = (next >> 16) & 0xff;
    *p++ = (next >> 8) & 0xff;
    *p++ = (next >> 0) & 0xff;

    return (fwrite(sop, 1, sizeof(sop), prn_stream)==sizeof(sop))?0:-1;
}

/*
 * bff_write_eod
 *
 * write a EOD at the current file position and set
 * the file seek pointer behind the EOD
 *
 * return 0 on success
 */
private int bff_write_eod(FILE *prn_stream, unsigned long prev)
{
    return bff_write_sop(prn_stream, prev, -1);
}


/*
 * bff_write_run
 *
 * write a run to the BFF stream buffer buf at position pos
 *
 * return new position
 */
private long bff_write_run(byte *buf, int run, long pos)
{
    if (run>127) {
	buf[pos++] = 0x80 | (run>>8);
	buf[pos++] = run & 0xff;
    } else {
	buf[pos++] = run & 0x7f;
    }
    return pos;
}

#ifdef BFF_LITTLE_ENDIAN
private byte bff_swap_byte(byte b)
{
    const byte swap_table[] = {
        0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
        0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
	0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
	0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
	0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
	0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
	0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
	0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
	0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
	0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
	0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
	0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
	0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
	0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
	0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
	0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
	0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
	0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
	0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
	0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
	0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
	0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
	0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
	0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
	0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
	0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
	0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
	0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
	0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
	0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
	0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
	0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
    };

    return swap_table[b];
}
#endif

/*
 * bff_count_equal_bits
 *
 * count the equal bits of value “val“ in the given byte “w“ from the
 * bit position “bitpos“
 */
private int bff_count_equal_bits(byte w, int bitpos, int val)
{
    int count;
#ifdef BFF_LITTLE_ENDIAN
    int level;
    byte mask;

    w = bff_swap_byte(w);

    /* Algorithmus sucht nach 0-en -> Wert anpassen */
    w = val ? w : ~w;

    /* vor bitpos alles ausmaskieren */
    w |= LEN2MASK(bitpos);

    for (level = 2, count = 4; level > 0; level >>= 1) {
        mask = LEN2MASK(count);
        if ((w & mask) == mask) {
            count += level;
        } else {
            count -= level;
        }
    }


    mask = LEN2MASK(count);
    if ((w & mask) == mask) {
        if (w==0xff) count++;
    } else {
        count --;
    }

    return count - bitpos;
#else
    int       level;
    byte     mask;

    /* Algorithmus sucht nach 0-en -> Wert anpassen */
    w = val ? w : ~w;

    /* vor bitpos alles ausmaskieren */
    w |= ~LEN2MASK(bitpos+1);

    for (level = 2, count = 3; level > 0; level >>= 1) {
        mask = ~LEN2MASK(count+1);
        if ((w & mask) == mask) {
            count -= level;
        } else {
            count += level;
        }
    }

    mask = ~LEN2MASK(count+1);
    if ((w & mask) == mask) {
        if (w==0xff) count = -1;
    } else {
        count ++;
    }
    return bitpos - count;
#endif
}


/*
 * bff_count_run
 *
 * count equal bits of value 'val' within the buffer 'buffer' from
 * bitposition 'pos'
 *
 * return the number of counted bits
 */
private int bff_count_run(byte *buffer, int size, int pos, int val)
{
    int run;
    byte mask;
    byte *buf=&buffer[pos>>3], *max_buf=&buffer[size];
    int bitpos=BITPOS(pos);
    /*
     * the is done in three steps:
     *    A   -   within the first byte
     *    B   -   following bytes, whose all bits have the value 'val'
     *    C   -   first byte which contains bits with an other value than 'val' 
     */

    if (buf < max_buf) {
	/* step A */
	run = bff_count_equal_bits(*buf, bitpos, val);
	buf++;
	if ((run>bitpos) && (buf<max_buf)) {
	    /* step B */
	    mask = val ? 0xff : 0;
	    for (; buf < max_buf; buf++) {
		if (*buf == mask) {
		    run += BITSIZEOF(byte);
		} else {
		    break;
		}
	    }
        
	    if (buf < max_buf) {
		/* step C */
		run += bff_count_equal_bits(*buf, BITSIZEOF(byte)-1, val);
	    }
	}
    } else {
	run = -1;
    }

    return run;
}



private int bff_update_pagehead(FILE *prn_stream, 
				long prev,
				long curr_page_pos)
{
    int rc=0;
    long pos;
    
    if ((pos=ftell(prn_stream)) >=0) {
	if (!(rc = fseek(prn_stream, curr_page_pos, SEEK_SET))) {
	    if (!(rc=bff_write_sop(prn_stream, prev, pos - curr_page_pos))) {
		rc = fseek(prn_stream, pos, SEEK_SET);
	    }
	} else {
	    /* if the stream is not seekable, it is not an error, but we
	     * cannot update the page header */
	    if (errno == EBADF)
		rc = 0;
	}
    }
    return rc;
}


/* bff_write_line
 * 
 * write a BFF line based on the bitmap 'line'
 *   the length of the bitmap line can be determined from fp->linelenb
 *   0-bits are black, 1-bits are white
 *   update the line counter fp->numlines
 *
 * return 0 on success
 */
private int bff_write_line(FILE *prn_stream, byte *line, int linesize)
{
    int rc;
    int run, color, pel;
    byte buf[MAX_BFF_LINE_SIZE];
    long bufpos=0;

    /* count the runs until end of line */
    color = BFF_WHITE;
    pel = 0;
    do {
	if ((run = bff_count_run(line, linesize, pel, color)) >= 0) {
	    /* limit the line if nessesary */
	    if (run+pel > BFF_LINE_LEN) {
		run = BFF_LINE_LEN - pel;
	    }
	    bufpos = bff_write_run(buf, run, bufpos);
	} else {
	    /* the actual line is shorter than a BFF line
	     * -> extent it by white pixels
	     */
	    if (color == BFF_BLACK) {
		bufpos = bff_write_run(buf, 0, bufpos);
	    }
	    bufpos = bff_write_run(buf, BFF_LINE_LEN-pel, bufpos);
	    break;
	}
	pel += run;
	color ^= 1;
    } while (pel<BFF_LINE_LEN);

    if ((rc=bff_write_sol(prn_stream, bufpos)))
	return rc;
    if (fwrite(buf, 1, bufpos, prn_stream) != bufpos)
	return -1;
	
    return 0;
}



/*
 * bff_close_page
 *
 * finish the actual page
 *   update the page header
 *   set the file seek pointer behind this page
 *
 * return 0 on success
 */
private int  bff_close_page(FILE *prn_stream,
			    long diff_last_page_pos, 
			    long curr_page_pos)
{
    int rc;

    /* write dummy SOL=EOP */
    if ((rc=bff_write_eop(prn_stream)))
	return rc;

    /* set the next pointer in the SOP of the current page */
    if ((rc=bff_update_pagehead(prn_stream, diff_last_page_pos, curr_page_pos)))
	return rc;

    return rc;
}

/* End */
