/* Copyright (C) 1994, 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: gdevtfax.c,v 1.4.6.1.2.1 2003/01/17 00:49:01 giles Exp $ */
/* TIFF and TIFF/fax devices */
#include "gdevprn.h"
#include "gdevtifs.h"
#include "strimpl.h"
#include "scfx.h"
#include "gdevfax.h"
#include "gdevtfax.h"		/* for gdev_fax_print_page_stripped proto */

/* ---------------- TIFF/fax output ---------------- */

/* The device descriptors */

private dev_proc_get_params(tfax_get_params);
private dev_proc_put_params(tfax_put_params);
private dev_proc_print_page(tiffcrle_print_page);
private dev_proc_print_page(tiffg3_print_page);
private dev_proc_print_page(tiffg32d_print_page);
private dev_proc_print_page(tiffg4_print_page);

struct gx_device_tfax_s {
    gx_device_common;
    gx_prn_device_common;
    gx_fax_device_common;
    long MaxStripSize;		/* 0 = no limit, other is UNCOMPRESSED limit */
    gdev_tiff_state tiff;	/* for TIFF output only */
};
typedef struct gx_device_tfax_s gx_device_tfax;

/* Define procedures that adjust the paper size. */
private const gx_device_procs gdev_tfax_std_procs =
    prn_params_procs(gdev_prn_open, gdev_prn_output_page, gdev_prn_close,
		     tfax_get_params, tfax_put_params);

#define TFAX_DEVICE(dname, print_page)\
{\
    FAX_DEVICE_BODY(gx_device_tfax, gdev_tfax_std_procs, dname, print_page),\
    0				/* unlimited strip size byte count */\
}

const gx_device_tfax gs_tiffcrle_device =
    TFAX_DEVICE("tiffcrle", tiffcrle_print_page);

const gx_device_tfax gs_tiffg3_device =
    TFAX_DEVICE("tiffg3", tiffg3_print_page);

const gx_device_tfax gs_tiffg32d_device =
    TFAX_DEVICE("tiffg32d", tiffg32d_print_page);

const gx_device_tfax gs_tiffg4_device =
    TFAX_DEVICE("tiffg4", tiffg4_print_page);

/* Get/put the MaxStripSize parameter. */
private int
tfax_get_params(gx_device * dev, gs_param_list * plist)
{
    gx_device_tfax *const tfdev = (gx_device_tfax *)dev;
    int code = gdev_fax_get_params(dev, plist);
    int ecode = code;

    if ((code = param_write_long(plist, "MaxStripSize", &tfdev->MaxStripSize)) < 0)
        ecode = code;
    return ecode;
}
private int
tfax_put_params(gx_device * dev, gs_param_list * plist)
{
    gx_device_tfax *const tfdev = (gx_device_tfax *)dev;
    int ecode = 0;
    int code;
    long mss = tfdev->MaxStripSize;
    const char *param_name;

    switch (code = param_read_long(plist, (param_name = "MaxStripSize"), &mss)) {
        case 0:
	    /*
	     * Strip must be large enough to accommodate a raster line.
	     * If the max strip size is too small, we still write a single
	     * line per strip rather than giving an error.
	     */
	    if (mss >= 0)
	        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_fax_put_params(dev, plist);
    if (code < 0)
	return code;

    tfdev->MaxStripSize = mss;
    return code;
}

/* Send the page to the printer. */
/* Print a page with a specified width, which may differ from	*/
/* the width stored in the device. The TIFF file may have	*/
/* multiple strips of height 'rows'.				*/
private int
gdev_stream_print_page_strips(gx_device_printer * pdev, FILE * prn_stream,
			      const stream_template * temp, stream_state * ss,
			      int width, long rows_per_strip)
{
    gx_device_tfax *const tfdev = (gx_device_tfax *)pdev;
    int row, row_next;
    int code = 0;

    for (row = 0; row < pdev->height; row = row_next) {
	row_next = min(row + rows_per_strip, pdev->height);
	code = gdev_fax_print_strip(pdev, prn_stream, temp, ss, width,
				    row, row_next);
	if (code < 0)
	    break;
	gdev_tiff_end_strip(&tfdev->tiff, prn_stream);

    }
    return code;
}

/* Print a page with a specified width, which may differ from the */
/* width stored in the device. */
private int
gdev_stream_print_page_width(gx_device_printer * pdev, FILE * prn_stream,
			     const stream_template * temp, stream_state * ss,
			     int width)
{
    return gdev_stream_print_page_strips(pdev, prn_stream, temp, ss,
					 width, pdev->height);
}

private int
gdev_stream_print_page(gx_device_printer * pdev, FILE * prn_stream,
		       const stream_template * temp, stream_state * ss)
{
    return gdev_stream_print_page_width(pdev, prn_stream, temp, ss,
					pdev->width);
}

/* Print a fax page.  Other fax drivers use this. */
int
gdev_fax_print_page_stripped(gx_device_printer * pdev, FILE * prn_stream,
		    stream_CFE_state * ss, long rows)
{
    return gdev_stream_print_page_strips(pdev, prn_stream, &s_CFE_template,
					 (stream_state *)ss, ss->Columns,
					 rows);
}

/* ---------------- Other TIFF output ---------------- */

#include "slzwx.h"
#include "srlx.h"

/* Device descriptors for TIFF formats other than fax. */
private dev_proc_print_page(tifflzw_print_page);
private dev_proc_print_page(tiffpack_print_page);

const gx_device_tfax gs_tifflzw_device = {
    prn_device_std_body(gx_device_tfax, prn_std_procs, "tifflzw",
			DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
			X_DPI, Y_DPI,
			0, 0, 0, 0,	/* margins */
			1, tifflzw_print_page)
};

const gx_device_tfax gs_tiffpack_device = {
    prn_device_std_body(gx_device_tfax, prn_std_procs, "tiffpack",
			DEFAULT_WIDTH_10THS, DEFAULT_HEIGHT_10THS,
			X_DPI, Y_DPI,
			0, 0, 0, 0,	/* margins */
			1, tiffpack_print_page)
};

/* Define the TIFF directory we use, beyond the standard entries. */
/* NB: this array is sorted by tag number (assumed below) */
typedef struct tiff_mono_directory_s {
    TIFF_dir_entry BitsPerSample;
    TIFF_dir_entry Compression;
    TIFF_dir_entry Photometric;
    TIFF_dir_entry FillOrder;
    TIFF_dir_entry SamplesPerPixel;
    TIFF_dir_entry T4T6Options;
    /* Don't use CleanFaxData. */
    /*  TIFF_dir_entry  CleanFaxData;   */
} tiff_mono_directory;
private const tiff_mono_directory dir_mono_template =
{
    {TIFFTAG_BitsPerSample, TIFF_SHORT, 1, 1},
    {TIFFTAG_Compression, TIFF_SHORT, 1, Compression_CCITT_T4},
    {TIFFTAG_Photometric, TIFF_SHORT, 1, Photometric_min_is_white},
    {TIFFTAG_FillOrder, TIFF_SHORT, 1, FillOrder_LSB2MSB},
    {TIFFTAG_SamplesPerPixel, TIFF_SHORT, 1, 1},
    {TIFFTAG_T4Options, TIFF_LONG, 1, 0},
	/* { TIFFTAG_CleanFaxData,      TIFF_SHORT, 1, CleanFaxData_clean }, */
};

/* Forward references */
private int tfax_begin_page(P4(gx_device_tfax *, FILE *,
			       const tiff_mono_directory *, int));

/* Print a fax-encoded page. */
private int
tifff_print_page(gx_device_printer * dev, FILE * prn_stream,
		 stream_CFE_state * pstate, tiff_mono_directory * pdir)
{
    gx_device_tfax *const tfdev = (gx_device_tfax *)dev;
    int code;

    tfax_begin_page(tfdev, prn_stream, pdir, pstate->Columns);
    pstate->FirstBitLowOrder = true;	/* decoders prefer this */
    code = gdev_fax_print_page_stripped(dev, prn_stream, pstate, tfdev->tiff.rows);
    gdev_tiff_end_page(&tfdev->tiff, prn_stream);
    return code;
}
private int
tiffcrle_print_page(gx_device_printer * dev, FILE * prn_stream)
{
    stream_CFE_state state;
    tiff_mono_directory dir;

    gdev_fax_init_fax_state(&state, (gx_device_fax *)dev);
    state.EndOfLine = false;
    state.EncodedByteAlign = true;
    dir = dir_mono_template;
    dir.Compression.value = Compression_CCITT_RLE;
    dir.T4T6Options.tag = TIFFTAG_T4Options;
    dir.T4T6Options.value = T4Options_fill_bits;
    return tifff_print_page(dev, prn_stream, &state, &dir);
}
private int
tiffg3_print_page(gx_device_printer * dev, FILE * prn_stream)
{
    stream_CFE_state state;
    tiff_mono_directory dir;

    gdev_fax_init_fax_state(&state, (gx_device_fax *)dev);
    state.EndOfLine = true;
    state.EncodedByteAlign = true;
    dir = dir_mono_template;
    dir.Compression.value = Compression_CCITT_T4;
    dir.T4T6Options.tag = TIFFTAG_T4Options;
    dir.T4T6Options.value = T4Options_fill_bits;
    return tifff_print_page(dev, prn_stream, &state, &dir);
}
private int
tiffg32d_print_page(gx_device_printer * dev, FILE * prn_stream)
{
    stream_CFE_state state;
    tiff_mono_directory dir;

    gdev_fax_init_state(&state, (gx_device_fax *)dev);
    state.K = (dev->y_pixels_per_inch < 100 ? 2 : 4);
    state.EndOfLine = true;
    state.EncodedByteAlign = true;
    dir = dir_mono_template;
    dir.Compression.value = Compression_CCITT_T4;
    dir.T4T6Options.tag = TIFFTAG_T4Options;
    dir.T4T6Options.value = T4Options_2D_encoding | T4Options_fill_bits;
    return tifff_print_page(dev, prn_stream, &state, &dir);
}
private int
tiffg4_print_page(gx_device_printer * dev, FILE * prn_stream)
{
    stream_CFE_state state;
    tiff_mono_directory dir;

    gdev_fax_init_state(&state, (gx_device_fax *)dev);
    state.K = -1;
    /*state.EncodedByteAlign = false; *//* no fill_bits option for T6 */
    dir = dir_mono_template;
    dir.Compression.value = Compression_CCITT_T6;
    dir.T4T6Options.tag = TIFFTAG_T6Options;
    return tifff_print_page(dev, prn_stream, &state, &dir);
}

/* Print an LZW page. */
private int
tifflzw_print_page(gx_device_printer * dev, FILE * prn_stream)
{
    gx_device_tfax *const tfdev = (gx_device_tfax *)dev;
    tiff_mono_directory dir;
    stream_LZW_state state;
    int code;

    dir = dir_mono_template;
    dir.Compression.value = Compression_LZW;
    dir.FillOrder.value = FillOrder_MSB2LSB;
    tfax_begin_page(tfdev, prn_stream, &dir, dev->width);
    state.InitialCodeLength = 8;
    state.FirstBitLowOrder = false;
    state.BlockData = false;
    state.EarlyChange = 0;	/****** CHECK THIS ******/
    code = gdev_stream_print_page(dev, prn_stream, &s_LZWE_template,
				  (stream_state *) & state);
    gdev_tiff_end_page(&tfdev->tiff, prn_stream);
    return code;
}

/* Print a PackBits page. */
private int
tiffpack_print_page(gx_device_printer * dev, FILE * prn_stream)
{
    gx_device_tfax *const tfdev = (gx_device_tfax *)dev;
    tiff_mono_directory dir;
    stream_RLE_state state;
    int code;

    dir = dir_mono_template;
    dir.Compression.value = Compression_PackBits;
    dir.FillOrder.value = FillOrder_MSB2LSB;
    tfax_begin_page(tfdev, prn_stream, &dir, dev->width);
    state.EndOfData = false;
    state.record_size = gdev_mem_bytes_per_scan_line((gx_device *) dev);
    code = gdev_stream_print_page(dev, prn_stream, &s_RLE_template,
				  (stream_state *) & state);
    gdev_tiff_end_page(&tfdev->tiff, prn_stream);
    return code;
}

/* Begin a TIFF fax page. */
private int
tfax_begin_page(gx_device_tfax * tfdev, FILE * fp,
		const tiff_mono_directory * pdir, int width)
{
    /* Patch the width to reflect fax page width adjustment. */
    int save_width = tfdev->width;
    int code;

    tfdev->width = width;
    code = gdev_tiff_begin_page((gx_device_printer *) tfdev,
				&tfdev->tiff, fp,
				(const TIFF_dir_entry *)pdir,
				sizeof(*pdir) / sizeof(TIFF_dir_entry),
				NULL, 0, tfdev->MaxStripSize);
    tfdev->width = save_width;
    return code;
}
