/* Copyright (C) 2000 artofcode LLC.  All rights reserved.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 U.S.A.

*/

/*$Id: gdevatx.c,v 1.6.8.1 2003/01/17 00:49:00 giles Exp $ */
/* Practical Automation ATX-23, -24, and -38 driver */
#include "math_.h"
#include "gdevprn.h"

/*
 * All of the ATX printers have an unprintable margin of 0.125" at the top
 * and bottom of the page.  They also have unprintable left/right margins:
 *	ATX-23	0.25"
 *	ATX-24	0.193"
 *	ATS-38	0.25"
 * The code below assumes that coordinates refer only to the *printable*
 * part of each page.  This is wrong and must eventually be changed.
 */

/* Define the printer commands. */
#define ATX_SET_PAGE_LENGTH "\033f"  /* + 2-byte length */
#define ATX_VERTICAL_TAB "\033L"  /* + 2-byte count */
#define ATX_UNCOMPRESSED_DATA "\033d"  /* + 2-byte count */
#define ATX_COMPRESSED_DATA "\033x"  /* + 1-byte word count */
#define ATX_END_PAGE "\033e"

/* The device descriptors */
private dev_proc_print_page(atx23_print_page);
private dev_proc_print_page(atx24_print_page);
private dev_proc_print_page(atx38_print_page);

#define ATX_DEVICE(dname, w10, h10, dpi, lrm, btm, print_page)\
  prn_device_margins(prn_std_procs, dname, w10, h10, dpi, dpi, 0, 0,\
		     lrm, btm, lrm, btm, 1, print_page)

const gx_device_printer gs_atx23_device = /* real width = 576 pixels */
ATX_DEVICE("atx23", 28 /* 2.84" */, 35 /* (minimum) */,
	   203, 0.25, 0.125, atx23_print_page);

const gx_device_printer gs_atx24_device = /* real width = 832 pixels */
ATX_DEVICE("atx24", 41 /* 4.1" */, 35 /* (minimum) */,
	   203, 0.193, 0.125, atx24_print_page);

const gx_device_printer gs_atx38_device = /* real width = 2400 pixels */
ATX_DEVICE("atx38", 80 /* 8.0" */, 35 /* (minimum) */,
	   300, 0.25, 0.125, atx38_print_page);

/* Output a printer command with a 2-byte, little-endian numeric argument. */
private void
fput_atx_command(FILE *f, const char *str, int value)
{
    fputs(str, f);
    fputc((byte)value, f);
    fputc((byte)(value >> 8), f);
}

/*
 * Attempt to compress a scan line of data.  in_size and out_size are even.
 * Return -1 if the compressed data would exceed out_size, otherwise the
 * size of the compressed data (always even).
 */
#define MIN_IN_SIZE_TO_COMPRESS 50
#define MAX_COMPRESSED_SEGMENT_PAIRS 127
#define MAX_UNCOMPRESSED_SEGMENT_PAIRS 255
#define COMPRESSED_SEGMENT_COMMAND 0x80	/* + # of repeated pairs */
#define UNCOMPRESSED_SEGMENT_COMMAND 0x7f /* followed by # of pairs */
private int
atx_compress(const byte *in_buf, int in_size, byte *out_buf, int out_size)
{
    const byte *const in_end = in_buf + in_size;
    byte *const out_end = out_buf + out_size;
    const byte *in = in_buf;
    byte *out = out_buf;
    byte *out_command;
    int pair_count;

    if (in_size < MIN_IN_SIZE_TO_COMPRESS)
	return -1;			/* not worth compressing */

    /* Start a new segment. */
 New_Segment:
    if (in == in_end)		/* end of input data */
	return out - out_buf;
    if (out == out_end)		/* output buffer full */
	return -1;
    out_command = out;
    out += 2;
    if (in[1] == in[0]) {		/* start compressed segment */
	/* out[-2] will be compressed segment command */
	out[-1] = in[0];
	pair_count = 1;
	goto Scan_Compressed_Pair;
    } else {			/* start uncompressed segment */
	out[-2] = UNCOMPRESSED_SEGMENT_COMMAND;
	/* out[-1] will be pair count */
	pair_count = 0;
	goto Scan_Uncompressed_Pair;
    }

    /* Scan compressed data. */
 Scan_Compressed:
    if (pair_count == MAX_COMPRESSED_SEGMENT_PAIRS ||
	in == in_end || in[0] != in[-1] || in[1] != in[0]
	) {			/* end the segment */
	out_command[0] = COMPRESSED_SEGMENT_COMMAND + pair_count;
	goto New_Segment;
    }
    ++pair_count;
 Scan_Compressed_Pair:
    in += 2;
    goto Scan_Compressed;

    /* Scan uncompressed data. */
 Scan_Uncompressed:
    if (pair_count == MAX_UNCOMPRESSED_SEGMENT_PAIRS ||
	in == in_end || in[1] == in[0]
	) {			/* end the segment */
	out_command[1] = pair_count;
	goto New_Segment;
    }
 Scan_Uncompressed_Pair:
    if (out == out_end)		/* output buffer full */
	return -1;
    out[0] = in[0], out[1] = in[1];
    in += 2;
    out += 2;
    ++pair_count;
    goto Scan_Uncompressed;
  
}

/* Send the page to the printer. */
private int
atx_print_page(gx_device_printer *pdev, FILE *f, int max_width_bytes)
{
    /*
     * The page length command uses 16 bits to represent the length in
     * units of 0.01", so the maximum representable page length is 
     * 655.35", including the unprintable top and bottom margins.
     * Compute the maximum height of the printable area in pixels.
     */
    float top_bottom_skip = (pdev->HWMargins[1] + pdev->HWMargins[3]) / 72.0;
    int max_height = (int)(pdev->HWResolution[1] * 655 - top_bottom_skip);
    int height = min(pdev->height, max_height);
    int page_length_100ths =
	(int)ceil((height / pdev->HWResolution[1] + top_bottom_skip) * 100);
    gs_memory_t *mem = pdev->memory;
    int raster = gx_device_raster((gx_device *)pdev, true);
    byte *buf;
    /*
     * ATX_COMPRESSED_DATA only takes a 1-byte (word) count.
     * Thus no compressed scan line can take more than 510 bytes.
     */
    int compressed_raster = min(raster / 2, 510); /* require 50% compression */
    byte *compressed;
    int blank_lines, lnum;
    int code = 0;

    /* Enforce a minimum 3" page length. */
    if (page_length_100ths < 300)
	page_length_100ths = 300;
    buf = gs_alloc_bytes(mem, raster, "atx_print_page(buf)");
    compressed = gs_alloc_bytes(mem, compressed_raster,
				"atx_print_page(compressed)");
    if (buf == 0 || compressed == 0) {
	code = gs_note_error(gs_error_VMerror);
	goto done;
    }
    fput_atx_command(f, ATX_SET_PAGE_LENGTH, page_length_100ths);
    for (blank_lines = 0, lnum = 0; lnum < height; ++lnum) {
	byte *row;
	byte *end;
	int count;

	gdev_prn_get_bits(pdev, lnum, buf, &row);
	/* Find the end of the non-blank data. */
	for (end = row + raster; end > row && end[-1] == 0 && end[-2] == 0; )
	    end -= 2;
	if (end == row) {		/* blank line */
	    ++blank_lines;
	    continue;
	}
	if (blank_lines) {		/* skip vertically */
	    fput_atx_command(f, ATX_VERTICAL_TAB, blank_lines + 1);
	    blank_lines = 0;
	}
	/* Truncate the line to the maximum printable width. */
	if (end - row > max_width_bytes)
	    end = row + max_width_bytes;
	count = atx_compress(row, end - row, compressed, compressed_raster);
	if (count >= 0) {		/* compressed line */
	    /*
	     * Note that since compressed_raster can't exceed 510, count
	     * can't exceed 510 either.
	     */
	    fputs(ATX_COMPRESSED_DATA, f);
	    fputc(count / 2, f);
	    fwrite(compressed, 1, count, f);
	} else {			/* uncompressed line */
	    int num_bytes = end - row;

	    fput_atx_command(f, ATX_UNCOMPRESSED_DATA, num_bytes);
	    fwrite(row, 1, num_bytes, f);
	}
    }

#if 0	/**************** MAY NOT BE NEEDED ****************/
    /* Enforce the minimum page length, and skip any final blank lines. */
    {
	int paper_length = (int)(pdev->HWResolution[1] * 3 + 0.5);
	int printed_length = height - blank_lines;

	if (height > paper_length)
	    paper_length = height;
	if (printed_length < paper_length)
	    fput_atx_command(f, ATX_VERTICAL_TAB,
			     paper_length - printed_length + 1);
    }
#endif

    /* End the page. */
    fputs(ATX_END_PAGE, f);

 done:
    gs_free_object(mem, compressed, "atx_print_page(compressed)");
    gs_free_object(mem, buf, "atx_print_page(buf)");
    return code;
}

/* Print pages with specified maximum pixel widths. */
private int
atx23_print_page(gx_device_printer *pdev, FILE *f)
{
    return atx_print_page(pdev, f, 576 / 8);
}
private int
atx24_print_page(gx_device_printer *pdev, FILE *f)
{
    return atx_print_page(pdev, f, 832 / 8);
}
private int
atx38_print_page(gx_device_printer *pdev, FILE *f)
{
    return atx_print_page(pdev, f, 2400 / 8);
}
