/* Copyright (C) 1997 and 1998, Scott Kempf.  All rights reserved.

Ghostscript is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY.  No author or distributor accepts responsibility
to anyone for the consequences of using it or for whether it serves any
particular purpose or works at all, unless he says so in writing.  Refer
to the Ghostscript General Public License for full details.

Everyone is granted permission to copy, modify and redistribute
Ghostscript, but only under the conditions described in the Ghostscript
General Public License.  A copy of this license is supposed to have been
given to you along with Ghostscript so you can know your rights and
responsibilities.  It should be in a file named COPYLEFT.  Among other
things, the copyright notice and this notice must be preserved on all
copies.  */

/* gdev820c.c */
/* HP820C printer driver for Ghostscript */
/* Version 0.1.3 - beta */
/* $Id: gdev820c.c,v 1.1 2002/08/03 03:08:26 tillkamppeter Exp $ */

/* This driver is designed to work with the HP820Cse and the HP820Cxi.
   It was written without much information from the manufacturer, so
   there are a number of problems.  Although I have no reason to believe
   it, this driver could destroy your printer.  Use it at your own risk.
   The HP1000Cse and HP1000Cxi are reported to be similar printers
   and this driver may work for them too.  Only 8.5"x11" paper is supported.

   The 820C is a "dumb" printer.  All rastering must be done in the
   computer's CPU.  This works well with ghostscript.  The 820C is not
   a PCL printer like nearly all other HP printers.  HP calls the 820C
   a PPA printer.  This printer is unique enough that the driver shares
   no code with other HP printer drivers.

   This driver is still fairly untested.  On my printer it works well
   for printing pages of text in 300 or 600 dpi.  My progress is slow,
   but I expect there will be more releases.  I can't say when, since
   I only spend a few hours on the weekends working on it.

   If you have any bug, comments, patches, or suggestions, feel free to
   e-mail me at scottk@ig.utexas.edu.  Since this is my work address and
   not a work project, I will not be able to spend much time answering
   e-mail.

   For the newest version, check out:
       ftp://ftp.ig.utexas.edu/outgoing/scottk/820C

   Things yet to be done (in order of likelihood):
       simplifying and cleaning of code
           remove hardcoded constants where resonable
       skip ends of lines if blank
       color printing
       bidirectional monitoring/status
       1000C support (no testers, yet)
       720C/722C support
*/

#define DEBUG 1
#include "gdevprn.h"

private dev_proc_open_device(djet820c_open);

gx_device_procs prn_djet820c_procs =
  prn_procs(djet820c_open, gdev_prn_output_page, gdev_prn_close);

#define A4_SIDE_MARGIN		0.13	/* .1338 = (21/2.54-8)/2 */
#define A4_BOTTOM_MARGIN	0.50
#define A4_TOP_MARGIN		0.07

#define LETTER_SIDE_MARGIN	0.25
#define LETTER_BOTTOM_MARGIN	0.50
#define LETTER_TOP_MARGIN	0.07

#define LETTER_PAPER	0
#define A4_PAPER	1

static int paper_type;

/* The device descriptor */
private dev_proc_print_page(djet820c_print_page);
gx_device_printer far_data gs_djet820c_device =
  prn_device(prn_djet820c_procs, "djet820c",
	DEFAULT_WIDTH_10THS,		/* width_10ths, 8.5" */
	DEFAULT_HEIGHT_10THS,		/* height_10ths, 11" */
	600,				/* x_dpi */
	600,				/* y_dpi */
	LETTER_SIDE_MARGIN, LETTER_BOTTOM_MARGIN,
	LETTER_SIDE_MARGIN, LETTER_TOP_MARGIN,
	1, djet820c_print_page);

private int djet820c_open(gx_device *pdev)
{      
        float letter[4] = {LETTER_SIDE_MARGIN, LETTER_BOTTOM_MARGIN,
		    LETTER_SIDE_MARGIN, LETTER_TOP_MARGIN};
        float a4[4] = {A4_SIDE_MARGIN, A4_BOTTOM_MARGIN,
		    A4_SIDE_MARGIN, A4_TOP_MARGIN};

        /* Set margins for type of paper used. */
        if (pdev->width / pdev->x_pixels_per_inch <= 8.4) {
            paper_type = A4_PAPER;
            gx_device_set_margins(pdev, a4, true);
	} else {
            paper_type = LETTER_PAPER;
            gx_device_set_margins(pdev, letter, true);
	}

        return gdev_prn_open(pdev);
}

/* Always end a swipe with two empty bytes. */
#define END_BUFF 2


/* ---------- Binary Sequence Section ---------- */

/* Bytes three and four, the bytes after 0x24 0x01, are clearly length. */
/* The bytes after 0x07 0x00, are also length. */
/* What the rest means, only God and HP knows. */

/* This appears to clear memory (the dead beef stuff). */
private byte clear[] = {
    0x24, 0x01, 0x00, 0x10, 0x00, 0x23, 0x00, 0x01, 0x07, 0x00, 0x00, 0x08,
    0x00, 0x00, 0x01, 0xf4, 0x01, 0x00, 0x00, 0x00,

    0x24, 0x00, 0x00, 0x04, 0xde, 0xad, 0xbe, 0xef,

    0x24, 0x01, 0x00, 0x10, 0x00, 0x65, 0x00, 0x02, 0x07, 0x00, 0x00, 0x08,
    0xde, 0xad, 0xbe, 0xef, 0x02, 0x00, 0x00, 0x00
};

/* Start a new page on "regular" paper. */
private byte newpage_reg[] = {
    /* ? */
    0x24, 0x01, 0x00, 0x18, 0x00, 0x15, 0x00, 0x01, 0x05, 0x00, 0x00, 0x10,
    0x28, 0x2d, 0x00, 0x41, 0x29, 0x2e, 0x00, 0x42, 0x29, 0x2e, 0x00, 0x42,
    0x29, 0x2e, 0x00, 0x42,

    /* HANDLE_MEDIA: Load */
    0x24, 0x01, 0x00, 0x0c, 0x00, 0x13, 0x00, 0x01, 0x07, 0x00, 0x00, 0x04,
    0x01, 0x01, 0x09, 0x60
};

/* Start a new page on transparency or glossy paper. */
private byte newpage_trans[] = {
    /* ? */
    0x24, 0x01, 0x00, 0x18, 0x00, 0x15, 0x00, 0x01, 0x05, 0x00, 0x00, 0x10,
    0x28, 0x2d, 0x00, 0x37, 0x38, 0x3d, 0x00, 0x47, 0x38, 0x3d, 0x00, 0x47,
    0x38, 0x3d, 0x00, 0x47,

    /* HANDLE_MEDIA: Load */
    0x24, 0x01, 0x00, 0x0c, 0x00, 0x13, 0x00, 0x01, 0x07, 0x00, 0x00, 0x04,
    0x01, 0x01, 0x09, 0x60
};

/* Preface for setup of black and white head. */
/* 0x01 = 0001 = ...K? */
private byte bw_head[] = {
    /* PRINT_SWEEP */
    0x24, 0x01, 0x00, 0x58, 0x00, 0x12, 0x00, 0x01, 0x07, 0x00, 0x00, 0x50,
    0x00, 0x01, 0x02, 0x01, 0x00, 0x00
};

/* Preface for setup of color head. */
/* 0x0e = 1110 = CYM.? */
private byte color_head[] = {
    /* PRINT_SWEEP */
    0x24, 0x01, 0x00, 0x98, 0x00, 0x12, 0x00, 0x01, 0x07, 0x00, 0x00, 0x90,
    0x00, 0x01, 0x02, 0x0e, 0x00, 0x00
};

/* Eject the page. */
private byte page_out[] = {
    /* HANDLE_MEDIA: Eject */
    0x24, 0x01, 0x00, 0x0c, 0x00, 0x13, 0x00, 0x01, 0x07, 0x00, 0x00, 0x04,
    0x02, 0x01, 0x09, 0x60
};

/* ---------- ---------- */

/* Function compress:
	Compress a single byte width piece.
    Input:
	address of first byte to compress
	offset between bytes to compress
	number of bytes to compress
	address at which to write compressed bytes
    Returns:
	address of first byte after output
	(i.e. where to write next byte)
*/
private byte *compress(byte *in, int step, int len, byte *out)
{
    int i = 0;
    while (i < len) {
	/* Find the size of duplicate values */
	int dup_len = 0;
	while ((i + dup_len < len)
		&& (in[step * (i + dup_len)] == in[step * i])) {
	    dup_len++;
	}
	/* See if we have enough zeros to be worth compressing. */
	/* I figure one is enough. */
	if ((dup_len >= 1) && (in[step * i] == 0)) {
	    /* Output run of zeros. */
	    if (dup_len >= 128) {
		/* Max is 128 */
		*out++ = 0x00;
		i += 128;
	    } else {
		*out++ = dup_len;
		i += dup_len;
	    }
	/* See if we have enough non-zeros to be worth compressing. */
	/* Here two should be enough. */
	} else if (dup_len >= 2) {
	    /* Output run of duplicates. */
	    if (dup_len >= 64) {
		/* Max is 64 */
		*out++ = 0x80;
		*out++ = in[step * i];
		i += 64;
	    } else {
		*out++ = dup_len + 0x80;
		*out++ = in[step * i];
		i += dup_len;
	    }
	} else {
	    /* Look for two zeros, or three duplicates to end literal run. */
	    /* Note this is one more than the number to start a run. */
	    int lit_len = -1;
	    int add_more = 1;
	    while (add_more) {
		lit_len++;
		if (i + lit_len == len) add_more = 0;
		/* Always add more if we are near the very end. */
		if (i + lit_len < len - 3) {
		    byte a = in[step * (i + lit_len + 0)];
		    byte b = in[step * (i + lit_len + 1)];
		    byte c = in[step * (i + lit_len + 2)];
		    /* See if there are enough zeros */
		    if ((a == b) && (b == 0)) add_more = 0;
		    /* See if there are enough duplicates */
		    if ((a == b) && (b == c)) add_more = 0;
		}
	    }
	    /* Output run of literals. */
	    if (lit_len >= 64) {
		/* Max is 64 */
		int j;
		*out++ = 0xc0;
		for (j = i; j < i + 64; j++) {
		    *out++ = in[step * j];
		}
		i += 64;
	    } else {
		int j;
		*out++ = lit_len + 0xc0;
		for (j = i; j < i + lit_len; j++) {
		    *out++ = in[step * j];
		}
		i += lit_len;
	    }
	}
    }
    return out;
}

/* Write a two byte big endian value. */
/* Some values are signed. */
void
put_int2(int val, FILE *prn_stream)
{
    fputc((val >> 8) & 0xff, prn_stream);
    fputc((val & 0xff), prn_stream);
}

/* Write a four byte big endian value. */
/* Some values are signed. */
void
put_int4(int val, FILE *prn_stream)
{
    fputc((val >> 24) & 0xff, prn_stream);
    fputc((val >> 16) & 0xff, prn_stream);
    fputc((val >> 8) & 0xff, prn_stream);
    fputc(val & 0xff, prn_stream);
}

/* This sections defines some parameters specific to the
   print heads at different print modes. No color definitions
   have been written, but room has been saved for them.
*/
/* Currently four black and white modes are supported:
    300x300 - like HP's econo fast, but unidirectional
    300x600 - uses as little ink as 300x300, but provides more resolution
    600x300 - not worth using, but it was no extra work to provide
    600x600 - somewhat like HP's normal print mode
*/
#define SKIP4 {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}
struct st_mode {
    int paper;
    int pens, xdpi, ydpi;
    int min_poff, max_poff;
    struct {
	int swipe_offset;
	int pixel_offset;
	int flag;
    } pen[6];
#define MODE_CNT 8
} modes[MODE_CNT] = {
/*   paper type   pen xdpi ydpi min_p   max_p  s  p    f     s  p       f */
    {LETTER_PAPER, 2, 300, 300, 450-98, 450, {{0, 450, 14}, {6, 450-98, 0}, SKIP4}},
    {LETTER_PAPER, 2, 300, 600, 450-98, 450, {{0, 450, 14}, {6, 450-98, 0}, SKIP4}},
    {LETTER_PAPER, 2, 600, 300, 450-98, 450, {{0, 450, 6}, {12, 450-98, 0}, SKIP4}},
    {LETTER_PAPER, 2, 600, 600, 450-98, 450, {{0, 450, 6}, {12, 450-98, 0}, SKIP4}},

    {A4_PAPER,     2, 300, 300, 340-98, 340, {{0, 340, 14}, {6, 340-98, 0}, SKIP4}},
    {A4_PAPER,     2, 300, 600, 340-98, 340, {{0, 340, 14}, {6, 340-98, 0}, SKIP4}},
    {A4_PAPER,     2, 600, 300, 340-98, 340, {{0, 340, 6}, {12, 340-98, 0}, SKIP4}},
    {A4_PAPER,     2, 600, 600, 340-98, 340, {{0, 340, 6}, {12, 340-98, 0}, SKIP4}}
};

/* Figure out what print mode we are using. */
private int
get_mode(gx_device_printer *pdev)
{
    int mode = -1;
    int i;

    for (i = 0; i < MODE_CNT; i++) {
	if ((pdev->x_pixels_per_inch == modes[i].xdpi) &&
	    (pdev->y_pixels_per_inch == modes[i].ydpi) &&
	    (paper_type == modes[i].paper)) mode = i;
    }
    if (mode == -1) {
	return_error(gs_error_rangecheck);
    }

    return mode;
}

/* Build the image of the data printed by one sweep of the print head.
   Since we don't know the length until the compression has been done,
   we fill it in at the end.  A NULL is returned if the data cannot be
   compressed into 64K.  Data from the two (or six) print jets sets is
   interlaced making this code very strange.
*/
byte *make_sweep(byte *in, int width, int height, int line_size,
		 struct st_mode *mode, byte *out, int *total_len)
{
    byte *ptr = out + 4;   /* Skip heading. */
    int i, pen;
    int max_off = -10000;

    for (i = 0; i < mode->pens; i++) {
	if (mode->pen[i].swipe_offset > max_off) {
	    max_off = mode->pen[i].swipe_offset;
	}
    }

    for (i = 0; i < width + max_off; i++) {
	for (pen = 0; pen < mode->pens; pen++) {
	    if ((i >= mode->pen[pen].swipe_offset) &&
		    (i <  width + mode->pen[pen].swipe_offset)) {
		if (mode->ydpi == 300) {
		    /* 300 dpi vertical is really just done by duplication. */
		    ptr = compress(in + i - mode->pen[pen].swipe_offset,
				    line_size, height, ptr);
		} else {
		    ptr = compress(in + i - mode->pen[pen].swipe_offset
				    + pen * line_size,
				    line_size * mode->pens, height, ptr);
		}
		if (ptr - (out + 4) > 0x10000) {
		    return NULL;
		}
	    }
	}
    }

    *total_len = ptr - (out + 4);

    /* Fill in heading. */
    out[0] = 0x24;
    out[1] = 0x00;
    out[2] = *total_len / 256;
    out[3] = *total_len % 256;

    return ptr;
}

/* Calculate the data to be sent to the printer for a sweep across the page. */
/* Here is were the 64K buffer limit is handled.  If the sweep doesn't fit,
   just try a smaller one.  A less rapid back off could improve print speed
   at the cost of CPU use.  Here we try 300, 150, and 64.  I can't see how
   32 could ever be used, but just in case it is tried.
*/
private byte *calc_sweep(byte *in, int width, int *height,
	        int line_size, struct st_mode *mode, byte *out, int *len)
{
    byte *end = NULL;
    while (end == NULL) {
	end = make_sweep(in, width, *height / 2, line_size, mode, out, len);
	if (end == NULL) {
	    if (*height > 150) *height = 150;
	    else if (*height > 64) *height = 64;
	    else if (*height > 32) {
	        /* Unreachable? */
	        gs_note_error(gs_error_limitcheck);
	        *height = 32;
	    } else return NULL;
	}
    }

    return end;
}

/* These behave something like global variables. */
struct state {
    int mode;
    int print_loc, head_loc, nozzle_off;
    int width_units, width_bytes, height, len;
    byte *data, *data_end;
} curr, next;

/* The origin is not at the top of the page.
   We need to shift the image up by this amount. */
#define V_SHIFT 181

private void
print_sweep_A(FILE *prn_stream)
{
    /* Write out the data computed last loop */
    fwrite(curr.data, 1, curr.data_end - curr.data, prn_stream);
    fwrite(bw_head, 1, sizeof(bw_head), prn_stream);
    /* B/W limited */
    put_int2(curr.len, prn_stream);

    put_int4(0, prn_stream);

    /* 0 is plain paper, a big endian value of 120000 would
       be use for transparencies or glossy paper. */
    put_int4(0, prn_stream);

    put_int4(curr.head_loc, prn_stream);
    put_int2(18000, prn_stream);
    put_int2(0+modes[curr.mode].min_poff, prn_stream);
    put_int2(curr.width_units+modes[curr.mode].max_poff, prn_stream);
    /* Get this from modes! */
    switch (modes[curr.mode].xdpi) {
	case 600:
	    put_int2(12000, prn_stream);	/* print mode */
	    break;
	case 300:
	    put_int2(15000, prn_stream);	/* print mode */
	    break;
    }
    put_int2(2400, prn_stream);
    fputc(0x01, prn_stream);
    fputc(0x00, prn_stream);
}

private void
print_sweep_B(FILE *prn_stream)
{
    int pen;

    /* Inform printer about next sweep. */
    /* Anything at all is ok if there is no next sweep. */
    putc(0x01, prn_stream); /* Inks = K */
    put_int4(next.head_loc, prn_stream);
    put_int2(0+modes[next.mode].min_poff, prn_stream);
    put_int2(curr.width_units+modes[next.mode].max_poff, prn_stream);
    switch (modes[next.mode].xdpi) {
	case 600:
	    put_int2(12000, prn_stream); /* next print mode */
	    break;
	case 300:
	    put_int2(15000, prn_stream); /* next print mode */
	    break;
    }
    put_int2(2400, prn_stream);

    fputc(0x08, prn_stream);
    fputc(0x02, prn_stream);  /* B/W has two pens. */

    for (pen = 0; pen < modes[curr.mode].pens; pen++) {
	put_int2(modes[curr.mode].xdpi, prn_stream);	/* dpi */
	put_int2(curr.height / 2, prn_stream); /* Height */
	put_int2(curr.nozzle_off + 1, prn_stream);
	putc(0x00, prn_stream);
	putc(0x01, prn_stream);
	put_int2(curr.height / 2, prn_stream);
	put_int2(0 + modes[curr.mode].pen[pen].pixel_offset, prn_stream);
	put_int2(curr.width_units + modes[curr.mode].pen[pen].pixel_offset, prn_stream);
	putc(modes[curr.mode].pen[pen].flag, prn_stream);
	putc(0x00, prn_stream);
    }
}

private int
cut_next_swath(gx_device_printer *pdev)
{
    int lines;
    static byte *in;
    int line_size = gx_device_raster((gx_device *)pdev, 0);
    int end_loc = ((float)pdev->height/pdev->y_pixels_per_inch -
	    dev_t_margin(pdev) - dev_b_margin(pdev)) * 600 + -V_SHIFT;

    if (!in) {
	/* 300 lines is the largest amount that can be printed. */
	/* i.e. the print head is 1/2" long. */
	/* B/W limited */
	in = (byte *)gs_malloc(line_size * 300, 1, "djet820c_print_page(in)");
	if (in == NULL) {
	    return_error(gs_error_VMerror);
	}
    }

    /* Skip blank lines.  We can only skip 4 / 600 inches sets */
    {
	int i, done = 0;
	while (!done && (next.print_loc < end_loc)) {
	    lines = gdev_prn_copy_scan_lines(pdev,
		    (next.print_loc + V_SHIFT)
		     * modes[next.mode].ydpi / 600, in,
		    line_size * 4
		    * modes[next.mode].ydpi / 600);
	    /* UGLY, fix */
	    for (i = 0; i < line_size * 4 * modes[next.mode].ydpi / 600; i++) {
		if (in[i] != 0) {
		    done = 1;
		    i = 100000000;
		}
	    }
	    if (!done) next.print_loc = next.print_loc + 4;
	}
    }

    /* Will we loop again? */
    if (next.print_loc >= end_loc) {
	/* No more */
	gs_free((char *)in, line_size * 300, 1, "djet820c_print_page(in)");
	return 0;
    }

    /* Compute next printout. */
    next.height = 300;  /* B/W limited */
    next.head_loc = curr.head_loc;
    if (next.height > end_loc - next.print_loc) {
	next.height = end_loc - next.print_loc;
    }
    lines = gdev_prn_copy_scan_lines(pdev,
	    (next.print_loc + V_SHIFT)
	     * modes[next.mode].ydpi / 600, in,
	    line_size * next.height
	    * modes[next.mode].ydpi / 600);
    if (lines != next.height * modes[next.mode].ydpi / 600) {
	/* height = lines; ? */
	fprintf(stderr, "No ogla oxen.\n");
	return 0;
    }
    /* Zero out the end. */
    {
	int i,j;
	for (i=0; i<lines; i++) {
	    for (j=1; j<=END_BUFF; j++) {
		in[i*line_size + next.width_bytes - j] = 0;
	    }
	}
    }
    next.data_end = calc_sweep(in, next.width_bytes, &next.height,
	line_size, &modes[next.mode], next.data, &next.len);
    if (!next.data_end)
    	return_error(gs_error_Fatal);
    next.nozzle_off = next.print_loc - next.head_loc;
    if (next.nozzle_off + next.height > 300) {
	next.head_loc = next.print_loc;
	/* Is this next line really needed? */
	/* It doesn't cost anything even if it is silly. */
	/* It keeps the printhead from going too far. */
	if (next.head_loc > end_loc - 300) {
	    next.head_loc = end_loc - 300;
	}
	next.nozzle_off = next.print_loc - next.head_loc;
    }
    return 1;
}

/* Send the page to the printer. */
/* This function is ugly. */
/* If you are trying to understand it, remember that each time
   though the loop information is computed for the next loop.
   Because of this, the first and last times through the loop
   must be different.
*/
private int
djet820c_print_page(gx_device_printer *pdev, FILE *prn_stream)
{
	next.mode = curr.mode = get_mode(pdev);

	/* We will need a buffer large enough to handle an entire swipe. */
	/* Data could be output as it is created, but if the swipe is then
	   discovered to overrun the printer's buffer, we would have to
	   cancel it and resend a half swipe.  It is much better to figure
	   out first if we need to half swipe.
       */
	next.data = curr.data
	    = (byte *)gs_malloc((0x2000+40)*8, 1, "djet820c_print_page(out)");
	if (next.data == NULL) {
	    return_error(gs_error_VMerror);
	}

	/* Initialize the printer. */
	/* Clear out any left over stuff. */
	fwrite(clear, 1, sizeof(clear), prn_stream);

	/* Initialize a new page */
	/* note: newpage_trans is for transparencies and glossy paper. */
	fwrite(newpage_reg, 1, sizeof(newpage_reg), prn_stream);

	{
	    int not_done = 1;

	    /* The width of the page in bytes. */
	    /* Currently these are fixed, but they will vary in the end. */
	    curr.width_units = 8*600 + 8*END_BUFF * 600/modes[curr.mode].xdpi;
	    curr.width_bytes = 8*modes[curr.mode].xdpi/8 + END_BUFF;
	    next.width_units = 8*600 + 8*END_BUFF * 600/modes[curr.mode].xdpi;
	    next.width_bytes = 8*modes[curr.mode].xdpi/8 + END_BUFF;

	    /* Start the printing at the top and start the print head there. */
	    curr.print_loc = -V_SHIFT;
	    curr.head_loc = -V_SHIFT;
	    next.print_loc = curr.print_loc;
	    next.head_loc = curr.head_loc;

	    not_done = cut_next_swath(pdev);
	    memcpy(&curr, &next, sizeof(curr));

	    /* Keep printing until it's done. */
	    while (not_done) {
		print_sweep_A(prn_stream);

		/* Compute where to print next. */
		next.print_loc = curr.print_loc + curr.height;

		not_done = cut_next_swath(pdev);
		if (not_done < 0) {
		    return not_done;
		} else if (not_done) {
		    /* More to print (i.e. next swath cut) */
		    /* Is this 1 or 2? */
		    fputc(0x01, prn_stream);
		} else {
		    /* No more to print. */
		    fputc(0x00, prn_stream);
		}

		print_sweep_B(prn_stream);

		memcpy(&curr, &next, sizeof(curr));
	    }
	}

	/* Eject the page */
	fwrite(page_out, 1, sizeof(page_out), prn_stream);

	if (curr.data) {
	    gs_free((char *)curr.data, (0x2000+40)*8, 1,
		"djet820c_print_page(curr.data)");
	}
	return 0;
}
