/* Copyright (C) 1994, 1995 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: gdevtifs.c,v 1.4.2.1.2.1 2003/01/17 00:49:01 giles Exp $ */
/* TIFF-writing substructure */
#include "stdio_.h"
#include "time_.h"
#include "gstypes.h"
#include "gscdefs.h"
#include "gdevprn.h"
#include "gdevtifs.h"

/*
 * Define the standard contents of a TIFF directory.
 * Clients may add more items, also sorted in increasing tag order.
 */
typedef struct TIFF_std_directory_entries_s {
    TIFF_dir_entry SubFileType;
    TIFF_dir_entry ImageWidth;
    TIFF_dir_entry ImageLength;
    TIFF_dir_entry StripOffsets;
    TIFF_dir_entry Orientation;
    TIFF_dir_entry RowsPerStrip;
    TIFF_dir_entry StripByteCounts;
    TIFF_dir_entry XResolution;
    TIFF_dir_entry YResolution;
    TIFF_dir_entry PlanarConfig;
    TIFF_dir_entry ResolutionUnit;
    TIFF_dir_entry PageNumber;
    TIFF_dir_entry Software;
    TIFF_dir_entry DateTime;
} TIFF_std_directory_entries;

/* Define values that follow the directory entries. */
typedef struct TIFF_std_directory_values_s {
    TIFF_ulong diroff;		/* offset to next directory */
    TIFF_ulong xresValue[2];	/* XResolution indirect value */
    TIFF_ulong yresValue[2];	/* YResolution indirect value */
#define maxSoftware 40
    char softwareValue[maxSoftware];	/* Software indirect value */
    char dateTimeValue[20];	/* DateTime indirect value */
} TIFF_std_directory_values;
private const TIFF_std_directory_entries std_entries_initial =
{
    {TIFFTAG_SubFileType, TIFF_LONG, 1, SubFileType_page},
    {TIFFTAG_ImageWidth, TIFF_LONG, 1},
    {TIFFTAG_ImageLength, TIFF_LONG, 1},
    {TIFFTAG_StripOffsets, TIFF_LONG, 1},
    {TIFFTAG_Orientation, TIFF_SHORT, 1, Orientation_top_left},
    {TIFFTAG_RowsPerStrip, TIFF_LONG, 1},
    {TIFFTAG_StripByteCounts, TIFF_LONG, 1},
    {TIFFTAG_XResolution, TIFF_RATIONAL | TIFF_INDIRECT, 1,
     offset_of(TIFF_std_directory_values, xresValue[0])},
    {TIFFTAG_YResolution, TIFF_RATIONAL | TIFF_INDIRECT, 1,
     offset_of(TIFF_std_directory_values, yresValue[0])},
    {TIFFTAG_PlanarConfig, TIFF_SHORT, 1, PlanarConfig_contig},
    {TIFFTAG_ResolutionUnit, TIFF_SHORT, 1, ResolutionUnit_inch},
    {TIFFTAG_PageNumber, TIFF_SHORT, 2},
    {TIFFTAG_Software, TIFF_ASCII | TIFF_INDIRECT, 0,
     offset_of(TIFF_std_directory_values, softwareValue[0])},
    {TIFFTAG_DateTime, TIFF_ASCII | TIFF_INDIRECT, 20,
     offset_of(TIFF_std_directory_values, dateTimeValue[0])}
};
private const TIFF_std_directory_values std_values_initial =
{
    0,
    {0, 1},
    {0, 1},
    {0},
    {0, 0}
};

/* Fix up tag values on big-endian machines if necessary. */
#if arch_is_big_endian
private void
tiff_fixup_tag(TIFF_dir_entry * dp)
{
    switch (dp->type) {
	case TIFF_SHORT:
	case TIFF_SSHORT:
	    /* We may have two shorts packed into a TIFF_ulong. */
	    dp->value = (dp->value << 16) + (dp->value >> 16);
	    break;
	case TIFF_BYTE:
	case TIFF_SBYTE:
	    dp->value <<= 24;
	    break;
    }
}
#else
#  define tiff_fixup_tag(dp) DO_NOTHING
#endif

/* Begin a TIFF page. */
int
gdev_tiff_begin_page(gx_device_printer * pdev, gdev_tiff_state * tifs,
		     FILE * fp,
		     const TIFF_dir_entry * entries, int entry_count,
		     const byte * values, int value_size, long max_strip_size)
{
    gs_memory_t *mem = pdev->memory;
    TIFF_std_directory_entries std_entries;
    const TIFF_dir_entry *pse;
    const TIFF_dir_entry *pce;
    TIFF_dir_entry entry;
#define std_entry_count\
  (sizeof(TIFF_std_directory_entries) / sizeof(TIFF_dir_entry))
    int nse, nce, ntags;
    TIFF_std_directory_values std_values;

#define std_value_size sizeof(TIFF_std_directory_values)

    tifs->mem = mem;
    if (gdev_prn_file_is_new(pdev)) {
	/* This is a new file; write the TIFF header. */
	static const TIFF_header hdr = {
#if arch_is_big_endian
	    TIFF_magic_big_endian,
#else
	    TIFF_magic_little_endian,
#endif
	    TIFF_version_value,
	    sizeof(TIFF_header)
	};

	fwrite((const char *)&hdr, sizeof(hdr), 1, fp);
	tifs->prev_dir = 0;
    } else {			/* Patch pointer to this directory from previous. */
	TIFF_ulong offset = (TIFF_ulong) tifs->dir_off;

	fseek(fp, tifs->prev_dir, SEEK_SET);
	fwrite((char *)&offset, sizeof(offset), 1, fp);
	fseek(fp, tifs->dir_off, SEEK_SET);
    }

    /* We're going to shuffle the two tag lists together. */
    /* Both lists are sorted; entries in the client list */
    /* replace entries with the same tag in the standard list. */
    for (ntags = 0, pse = (const TIFF_dir_entry *)&std_entries_initial,
	 nse = std_entry_count, pce = entries, nce = entry_count;
	 nse && nce; ++ntags
	) {
	if (pse->tag < pce->tag)
	    ++pse, --nse;
	else if (pce->tag < pse->tag)
	    ++pce, --nce;
	else
	    ++pse, --nse, ++pce, --nce;
    }
    ntags += nse + nce;
    tifs->ntags = ntags;

    /* Write count of tags in directory. */
    {
	TIFF_short dircount = ntags;

	fwrite((char *)&dircount, sizeof(dircount), 1, fp);
    }
    tifs->dir_off = ftell(fp);

    /* Fill in standard directory tags. */
    std_entries = std_entries_initial;
    std_values = std_values_initial;
    std_entries.ImageWidth.value = pdev->width;
    std_entries.ImageLength.value = pdev->height;
    if (max_strip_size == 0) {
	tifs->strip_count = 1;
	tifs->rows = pdev->height;
	std_entries.RowsPerStrip.value = pdev->height;
    } else {
	int max_strip_rows =
	    max_strip_size / gdev_mem_bytes_per_scan_line((gx_device *)pdev);
        int rps = max(1, max_strip_rows);

	tifs->strip_count = (pdev->height + rps - 1) / rps;
	tifs->rows = rps;
	std_entries.RowsPerStrip.value = rps;
	std_entries.StripOffsets.count = tifs->strip_count;
	std_entries.StripByteCounts.count = tifs->strip_count;
    }
    tifs->StripOffsets = (TIFF_ulong *)gs_alloc_bytes(mem,
    		    (tifs->strip_count)*2*sizeof(TIFF_ulong),
		    "gdev_tiff_begin_page(StripOffsets)");
    tifs->StripByteCounts = &(tifs->StripOffsets[tifs->strip_count]);
    if (tifs->StripOffsets == 0)
	return_error(gs_error_VMerror);
    std_entries.PageNumber.value = (TIFF_ulong) pdev->PageCount;
    std_values.xresValue[0] = pdev->x_pixels_per_inch;
    std_values.yresValue[0] = pdev->y_pixels_per_inch;
    {
	char revs[10];

	strncpy(std_values.softwareValue, gs_product, maxSoftware);
	std_values.softwareValue[maxSoftware - 1] = 0;
	sprintf(revs, " %1.2f", gs_revision / 100.0);
	strncat(std_values.softwareValue, revs,
		maxSoftware - strlen(std_values.softwareValue) - 1);
	std_entries.Software.count =
	    strlen(std_values.softwareValue) + 1;
    }
    {
	struct tm tms;
	time_t t;

	time(&t);
	tms = *localtime(&t);
	sprintf(std_values.dateTimeValue,
		"%04d:%02d:%02d %02d:%02d:%02d",
		tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
		tms.tm_hour, tms.tm_min, tms.tm_sec);
    }

    /* Write the merged directory. */
    for (pse = (const TIFF_dir_entry *)&std_entries,
	 nse = std_entry_count, pce = entries, nce = entry_count;
	 nse || nce;
	) {
	bool std;

	if (nce == 0 || (nse != 0 && pse->tag < pce->tag))
	    std = true, entry = *pse++, --nse;
	else if (nse == 0 || (nce != 0 && pce->tag < pse->tag))
	    std = false, entry = *pce++, --nce;
	else
	    std = false, ++pse, --nse, entry = *pce++, --nce;
	if (entry.tag == TIFFTAG_StripOffsets)  {
	    if (tifs->strip_count > 1) {
		tifs->offset_StripOffsets = tifs->dir_off +
		    (ntags * sizeof(TIFF_dir_entry)) + std_value_size + value_size;
		entry.value = tifs->offset_StripOffsets;
	    } else {
		tifs->offset_StripOffsets = ftell(fp) +
		    offset_of(TIFF_dir_entry, value);
	    }
	}
	if (entry.tag == TIFFTAG_StripByteCounts) {
	    if (tifs->strip_count > 1) {
	        tifs->offset_StripByteCounts = tifs->dir_off +
		    (ntags * sizeof(TIFF_dir_entry)) + std_value_size + value_size +
		    (sizeof(TIFF_ulong) * tifs->strip_count);
	        entry.value = tifs->offset_StripByteCounts;
	    } else {
		tifs->offset_StripByteCounts = ftell(fp) +
		    offset_of(TIFF_dir_entry, value);
	    }
	}
	tiff_fixup_tag(&entry);	/* don't fix up indirects */
	if (entry.type & TIFF_INDIRECT) {
	    /* Fix up the offset for an indirect value. */
	    entry.type -= TIFF_INDIRECT;
	    entry.value +=
		tifs->dir_off + ntags * sizeof(TIFF_dir_entry) +
		(std ? 0 : std_value_size);
	}
	fwrite((char *)&entry, sizeof(entry), 1, fp);
    }

    /* Write the indirect values. */
    fwrite((const char *)&std_values, sizeof(std_values), 1, fp);
    fwrite((const char *)values, value_size, 1, fp);
    /* Write placeholders for the strip offsets. */
    fwrite(tifs->StripOffsets, sizeof(TIFF_ulong), 2 * tifs->strip_count, fp);
    tifs->strip_index = 0;
    tifs->StripOffsets[0] = ftell(fp);
    return 0;
}

/* End a TIFF strip. */
/* Record the size of the current strip, update the	*/
/* start of the next strip  and bump the strip_index	*/
int
gdev_tiff_end_strip(gdev_tiff_state * tifs, FILE * fp)
{
    TIFF_ulong strip_size;
    TIFF_ulong next_strip_start;
    int pad = 0;

    next_strip_start = (TIFF_ulong)ftell(fp);
    strip_size = next_strip_start - tifs->StripOffsets[tifs->strip_index];
    if (next_strip_start & 1) {
        next_strip_start++;	/* WORD alignment */
	fwrite(&pad, 1, 1, fp);
    }
    tifs->StripByteCounts[tifs->strip_index++] = strip_size;
    if (tifs->strip_index < tifs->strip_count)
	tifs->StripOffsets[tifs->strip_index] = next_strip_start;
    return 0;
}

/* End a TIFF page. */
int
gdev_tiff_end_page(gdev_tiff_state * tifs, FILE * fp)
{
    gs_memory_t *mem = tifs->mem;
    long dir_off = tifs->dir_off;
    int tags_size = tifs->ntags * sizeof(TIFF_dir_entry);

    tifs->prev_dir =
	dir_off + tags_size + offset_of(TIFF_std_directory_values, diroff);
    tifs->dir_off = ftell(fp);
    /* Patch strip byte counts and offsets values. */
    /* The offset in the file was determined at begin_page and may be indirect */
    fseek(fp, tifs->offset_StripOffsets, SEEK_SET);
    fwrite(tifs->StripOffsets, sizeof(TIFF_ulong), tifs->strip_count, fp);
    fseek(fp, tifs->offset_StripByteCounts, SEEK_SET);
    fwrite(tifs->StripByteCounts, sizeof(TIFF_ulong), tifs->strip_count, fp);
    gs_free_object(mem, tifs->StripOffsets, "gdev_tiff_begin_page(StripOffsets)");
    return 0;
}
