/* 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-1307.

*/

/*$Id: gdevcfax.c,v 1.3.6.1.2.1 2003/01/17 00:49:00 giles Exp $ */
/* SFF format writer for CAPI fax devices */
#include "gdevprn.h"
#include "strimpl.h"
#include "scfx.h"
#include "gdevfax.h"
 
/* The device descriptor */
private dev_proc_print_page(cfax_print_page);
private dev_proc_close_device(cfax_prn_close);

/* Define procedures for cfax. For sff multipage documents  */
/* a special close procedure is required because sff needs  */
/* an additional "end of document" signature after the last */ 
/* "end page" signature */
private const gx_device_procs gdev_cfax_std_procs =
    prn_params_procs(gdev_prn_open, gdev_prn_output_page, cfax_prn_close,
		     gdev_fax_get_params, gdev_fax_put_params);

const gx_device_fax gs_cfax_device = {
    FAX_DEVICE_BODY(gx_device_fax, gdev_cfax_std_procs, "cfax", cfax_print_page)
};

/* ---------------- SFF output ----------------- */

private void
cfax_byte(uint c, FILE * file)
{
    fputc(c & 0xff, file);
}

private void
cfax_word(ushort c, FILE * file)
{
    cfax_byte(c & 0xff, file);
    cfax_byte(c >> 8, file);
}

private void
cfax_dword(ulong c, FILE * file)
{
    cfax_byte(c & 0xff, file);
    cfax_byte(c >> 8, file);
    cfax_byte(c >> 16, file);
    cfax_byte(c >> 24, file);
}

private void
cfax_doc_hdr(FILE * file)
{
    cfax_byte('S', file);
    cfax_byte('f', file);
    cfax_byte('f', file);
    cfax_byte('f', file);
    cfax_byte(1, file);
    cfax_byte(0, file);
    cfax_word(0, file);
    cfax_word(0, file);
    cfax_word(20, file);
    cfax_dword(0, file);
    cfax_dword(0, file);
}

private void
cfax_page_hdr(gx_device_printer * pdev, FILE * file)
{
    cfax_byte(254, file);
    cfax_byte(16, file);
    cfax_byte((pdev->y_pixels_per_inch < 100 ? 0 : 1), file);
    cfax_byte(0, file);
    cfax_byte(0, file);
    cfax_byte(0, file);
    cfax_word(pdev->width, file);
    cfax_word(pdev->height, file);
    cfax_dword(0, file);
    cfax_dword(0, file);
}

private void
cfax_doc_end(FILE * file)
{
    cfax_byte(254, file);
    cfax_byte(0, file);
}

/* Send the page to the printer. */
private int
cfax_stream_print_page_width(gx_device_printer * pdev, FILE * prn_stream,
			     const stream_template * temp, stream_state * ss, 
               		     int width)
{
    gs_memory_t *mem = pdev->memory;
    int code = 0;
    stream_cursor_read r;
    stream_cursor_write w;
    int in_size = gdev_prn_raster((gx_device *) pdev);
    /*
     * Because of the width adjustment for fax systems, width may
     * be different from (either greater than or less than) pdev->width.
     * Allocate a large enough buffer to account for this.
     */
    int col_size = (width * pdev->color_info.depth + 7) >> 3;
    int max_size = max(in_size, col_size);
    int lnum, nbytes, i;
    byte *in;
    byte *out;
    /* If the file is 'nul', don't even do the writes. */
    bool nul = !strcmp(pdev->fname, "nul");

    /* Initialize the common part of the encoder state. */
    ss->template = temp;
    ss->memory = mem;

    /* Allocate the buffers. */
    in = gs_alloc_bytes(mem, temp->min_in_size + max_size + 1, 
    	"cfax_stream_print_page(in)");

#define OUT_SIZE 1000
    out = gs_alloc_bytes(mem, OUT_SIZE, "cfax_stream_print_page(out)");
    if (in == 0 || out == 0) {
	code = gs_note_error(gs_error_VMerror);
	goto done;
    }

    /* Process the image */ 
    for (lnum = 0; lnum < pdev->height; lnum++) {
	/* Initialize read and write pointer each time, because they're getting modified */
	r.ptr = in - 1;
    	r.limit = in + col_size;
    	w.ptr = out - 1;
    	w.limit = w.ptr + OUT_SIZE;
	/* Decoder must encode line for line, so init it for each line */
	code = (*temp->init) (ss);
	if (code < 0)
	    return_error(gs_error_limitcheck);
	/* Now, get the bits and encode them */
	gdev_prn_copy_scan_lines(pdev, lnum, in, in_size);
	if (col_size > in_size) {
	    memset(in + in_size , 0, col_size - in_size);
        }
	code = (*temp->process) (ss, &r, &w, 1 /* always last line */);
	nbytes = w.ptr - out + 1;
	if (!nul) {
	    if (nbytes > 0) {
		if (nbytes < 217) {
		    cfax_byte(nbytes, prn_stream);
		    for (i = 0; i < nbytes; i++)
			  cfax_byte(out[i], prn_stream);
		} else {
		    cfax_byte(0, prn_stream);
		    cfax_word(nbytes, prn_stream);
		    for (i = 0; i < nbytes; i++)
			  cfax_byte(out[i], prn_stream);
		}
	    } else {
		cfax_byte(218, prn_stream);
	    }
	} 
	if (temp->release != 0)
	    (*temp->release) (ss);
    }
#undef OUT_SIZE

  done:
    gs_free_object(mem, out, "cfax_stream_print_page(out)");
    gs_free_object(mem, in, "cfax_stream_print_page(in)");
    return code;
}

/* Begin a capi fax page. */
private int
cfax_begin_page(gx_device_printer * pdev, FILE * fp, int width)
{
    /* Patch the width to reflect fax page width adjustment. */
    int save_width = pdev->width;

    pdev->width = width;
    if (gdev_prn_file_is_new(pdev)) {
	cfax_doc_hdr(fp);
    }
    cfax_page_hdr(pdev, fp);

    pdev->width = save_width;
    return 0;
}

/* Print an capi fax (sff-encoded) page. */
private int
cfax_print_page(gx_device_printer * pdev, FILE * prn_stream)
{
    stream_CFE_state state;
    int code;

    gdev_fax_init_fax_state(&state, (gx_device_fax *)pdev);
    state.EndOfLine = false;
    state.EndOfBlock = false;
    state.EncodedByteAlign = true;
    state.FirstBitLowOrder = true;
    state.K = 0;

    cfax_begin_page(pdev, prn_stream, state.Columns);
    code = cfax_stream_print_page_width(pdev, prn_stream, 
      &s_CFE_template, (stream_state *) &state, state.Columns);
    return code;
}

/* Close an capi fax (sff-encoded) document. */
private int
cfax_prn_close(gx_device * pdev)
{
    gx_device_printer * const ppdev = (gx_device_printer *)pdev;

    if (ppdev->file != NULL) {
      cfax_doc_end(ppdev->file);
    }
    return gdev_prn_close(pdev);
}
