/* 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: gdevfax.c,v 1.2.6.1.2.1 2003/01/17 00:49:00 giles Exp $ */
/* Fax devices */
#include "gdevprn.h"
#include "strimpl.h"
#include "scfx.h"
#include "gdevfax.h"

/* The device descriptors */
private dev_proc_print_page(faxg3_print_page);
private dev_proc_print_page(faxg32d_print_page);
private dev_proc_print_page(faxg4_print_page);

/* Define procedures that adjust the paper size. */
const gx_device_procs gdev_fax_std_procs =
    prn_params_procs(gdev_prn_open, gdev_prn_output_page, gdev_prn_close,
		     gdev_fax_get_params, gdev_fax_put_params);

#define FAX_DEVICE(dname, print_page)\
{\
    FAX_DEVICE_BODY(gx_device_fax, gdev_fax_std_procs, dname, print_page)\
}


const gx_device_fax gs_faxg3_device =
    FAX_DEVICE("faxg3", faxg3_print_page);

const gx_device_fax gs_faxg32d_device =
    FAX_DEVICE("faxg32d", faxg32d_print_page);

const gx_device_fax gs_faxg4_device =
    FAX_DEVICE("faxg4", faxg4_print_page);

/* Open the device. */
/* This is no longer needed: we retain it for client backward compatibility. */
int
gdev_fax_open(gx_device * dev)
{
    return gdev_prn_open(dev);
}

/* Get/put the AdjustWidth parameter. */
int
gdev_fax_get_params(gx_device * dev, gs_param_list * plist)
{
    gx_device_fax *const fdev = (gx_device_fax *)dev;
    int code = gdev_prn_get_params(dev, plist);
    int ecode = code;

    if ((code = param_write_int(plist, "AdjustWidth", &fdev->AdjustWidth)) < 0)
        ecode = code;
    return ecode;
}
int
gdev_fax_put_params(gx_device * dev, gs_param_list * plist)
{
    gx_device_fax *const fdev = (gx_device_fax *)dev;
    int ecode = 0;
    int code;
    int aw = fdev->AdjustWidth;
    const char *param_name;

    switch (code = param_read_int(plist, (param_name = "AdjustWidth"), &aw)) {
        case 0:
	    if (aw >= 0 && aw <= 1)
		break;
	    code = gs_error_rangecheck;
	default:
	    ecode = code;
	    param_signal_error(plist, param_name, ecode);
	case 1:
	    break;
    }

    if (ecode < 0)
	return ecode;
    code = gdev_prn_put_params(dev, plist);
    if (code < 0)
	return code;

    fdev->AdjustWidth = aw;
    return code;
}

/* Initialize the stream state with a set of default parameters. */
/* These select the same defaults as the CCITTFaxEncode filter, */
/* except we set BlackIs1 = true. */
private void
gdev_fax_init_state_adjust(stream_CFE_state *ss,
			   const gx_device_fax *fdev,
			   int adjust_width)
{
    s_CFE_template.set_defaults((stream_state *)ss);
    ss->Columns = fdev->width;
    ss->Rows = fdev->height;
    ss->BlackIs1 = true;
    if (adjust_width > 0) {
	/* Adjust the page width to a legal value for fax systems. */
	if (ss->Columns >= 1680 && ss->Columns <= 1736) {
	    /* Adjust width for A4 paper. */
	    ss->Columns = 1728;
	} else if (ss->Columns >= 2000 && ss->Columns <= 2056) {
	    /* Adjust width for B4 paper. */
	    ss->Columns = 2048;
	}
    }
}
void
gdev_fax_init_state(stream_CFE_state *ss, const gx_device_fax *fdev)
{
    gdev_fax_init_state_adjust(ss, fdev, 1);
}
void
gdev_fax_init_fax_state(stream_CFE_state *ss, const gx_device_fax *fdev)
{
    gdev_fax_init_state_adjust(ss, fdev, fdev->AdjustWidth);
}

/*
 * Print one strip with fax compression.  Fax devices call this once per
 * page; TIFF devices call this once per strip.
 */
int
gdev_fax_print_strip(gx_device_printer * pdev, FILE * prn_stream,
		     const stream_template * temp, stream_state * ss,
		     int width, int row_first, int row_end /* last + 1 */)
{
    gs_memory_t *mem = pdev->memory;
    int code;
    stream_cursor_read r;
    stream_cursor_write w;
    int in_size = gdev_mem_bytes_per_scan_line((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;
    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;
    /* Now initialize the encoder. */
    code = temp->init(ss);
    if (code < 0)
	return_error(gs_error_limitcheck);	/* bogus, but as good as any */

    /* Allocate the buffers. */
    in = gs_alloc_bytes(mem, temp->min_in_size + max_size + 1,
			"gdev_stream_print_page(in)");
#define OUT_SIZE 1000
    out = gs_alloc_bytes(mem, OUT_SIZE, "gdev_stream_print_page(out)");
    if (in == 0 || out == 0) {
	code = gs_note_error(gs_error_VMerror);
	goto done;
    }
    /* Set up the processing loop. */
    lnum = 0;
    r.ptr = r.limit = in - 1;
    w.ptr = out - 1;
    w.limit = w.ptr + OUT_SIZE;
#undef OUT_SIZE

    /* Process the image. */
    for (lnum = row_first; ;) {
	int status;

	if_debug7('w', "[w]lnum=%d r=0x%lx,0x%lx,0x%lx w=0x%lx,0x%lx,0x%lx\n", lnum,
		  (ulong)in, (ulong)r.ptr, (ulong)r.limit,
		  (ulong)out, (ulong)w.ptr, (ulong)w.limit);
	status = temp->process(ss, &r, &w, lnum == row_end);
	if_debug7('w', "...%d, r=0x%lx,0x%lx,0x%lx w=0x%lx,0x%lx,0x%lx\n", status,
		  (ulong)in, (ulong)r.ptr, (ulong)r.limit,
		  (ulong)out, (ulong)w.ptr, (ulong)w.limit);
	switch (status) {
	    case 0:		/* need more input data */
		if (lnum == row_end)
		    goto ok;
		{
		    uint left = r.limit - r.ptr;

		    memcpy(in, r.ptr + 1, left);
		    gdev_prn_copy_scan_lines(pdev, lnum++, in + left, in_size);
		    /* Note: we use col_size here, not in_size. */
		    if (col_size > in_size) {
			memset(in + left + in_size, 0, col_size - in_size);
		    }
		    r.limit = in + left + col_size - 1;
		    r.ptr = in - 1;
		}
		break;
	    case 1:		/* need to write output */
		if (!nul)
		    fwrite(out, 1, w.ptr + 1 - out, prn_stream);
		w.ptr = out - 1;
		break;
	}
    }

  ok:
    /* Write out any remaining output. */
    if (!nul)
	fwrite(out, 1, w.ptr + 1 - out, prn_stream);

  done:
    gs_free_object(mem, out, "gdev_stream_print_page(out)");
    gs_free_object(mem, in, "gdev_stream_print_page(in)");
    if (temp->release)
	temp->release(ss);
    return code;
}

/* Print a fax page.  Other fax drivers use this. */
int
gdev_fax_print_page(gx_device_printer * pdev, FILE * prn_stream,
		    stream_CFE_state * ss)
{
    return gdev_fax_print_strip(pdev, prn_stream, &s_CFE_template,
				(stream_state *)ss, ss->Columns,
				0, pdev->height);
}

/* Print a 1-D Group 3 page. */
private int
faxg3_print_page(gx_device_printer * pdev, FILE * prn_stream)
{
    stream_CFE_state state;

    gdev_fax_init_fax_state(&state, (gx_device_fax *)pdev);
    state.EndOfLine = true;
    state.EndOfBlock = false;
    return gdev_fax_print_page(pdev, prn_stream, &state);
}

/* Print a 2-D Group 3 page. */
private int
faxg32d_print_page(gx_device_printer * pdev, FILE * prn_stream)
{
    stream_CFE_state state;

    gdev_fax_init_fax_state(&state, (gx_device_fax *)pdev);
    state.K = (pdev->y_pixels_per_inch < 100 ? 2 : 4);
    state.EndOfLine = true;
    state.EndOfBlock = false;
    return gdev_fax_print_page(pdev, prn_stream, &state);
}

/* Print a Group 4 page. */
private int
faxg4_print_page(gx_device_printer * pdev, FILE * prn_stream)
{
    stream_CFE_state state;

    gdev_fax_init_fax_state(&state, (gx_device_fax *)pdev);
    state.K = -1;
    state.EndOfBlock = false;
    return gdev_fax_print_page(pdev, prn_stream, &state);
}
