/* $Header: /cvsroot/osrs/libtiff/tools/fax2ps.c,v 1.4 2000/03/28 18:22:59 warmerda Exp $" */

/*
 * Copyright (c) 1991-1997 Sam Leffler
 * Copyright (c) 1991-1997 Silicon Graphics, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Sam Leffler and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Sam Leffler and Silicon Graphics.
 *
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
 *
 * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THIS SOFTWARE.
 */
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#if defined(VMS)
#include <unixio.h>
#elif defined(_WINDOWS) || defined(__OS2__)
#include <io.h>
#define off_t   toff_t
#else
#include <unistd.h>
#endif

#include "tiffio.h"

float   defxres = 204.;         /* default x resolution (pixels/inch) */
float   defyres = 98.;          /* default y resolution (lines/inch) */
const float basePageWidth = 8.5;
const float basePageHeight = 11.0;
const float half = 0.5;
const float points = 72.0;
float   pageWidth = 8.5;        /* image page width (inches) */
float   pageHeight = 11.0;      /* image page length (inches) */
int     scaleToPage = 0;        /* if true, scale raster to page dimensions */
int     totalPages = 0;         /* total # pages printed */
int     row;                    /* current output row */
int     maxline = 512;          /* max output line of PostScript */

/*
 * Turn a bit-mapped scanline into the appropriate sequence
 * of PostScript characters to be rendered.
 *
 * Original version written by Bret D. Whissel,
 * Florida State University Meteorology Department
 * March 13-15, 1995.
 */
static void
printruns(unsigned char* buf, uint32* runs, uint32* erun, uint32 lastx)
{
    static struct {
        char white, black;
        short width;
    } WBarr[] = {
        { 'd', 'n', 512 }, { 'e', 'o', 256 }, { 'f', 'p', 128 },
        { 'g', 'q',  64 }, { 'h', 'r',  32 }, { 'i', 's',  16 },
        { 'j', 't',   8 }, { 'k', 'u',   4 }, { 'l', 'v',   2 },
        { 'm', 'w',   1 }
    };
    static char* svalue =
        " !\"#$&'*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abc";
    int colormode = 1;          /* 0 for white, 1 for black */
    int runlength = 0;
    int n = maxline;
    int x = 0;
    int l;

    (void) buf;
    printf("%d m(", row++);
    while (runs < erun) {
        if (!runlength) {
            colormode ^= 1;
            runlength = *runs++;
            if (x+runlength > lastx)
                runlength = runs[-1] = lastx-x;
            x += runlength;
            if (!colormode && runs == erun)
                break;          /* don't bother printing the final white run */
        }
        /*
         * If a runlength is greater than 6 pixels, then spit out
         * black or white characters until the runlength drops to
         * 6 or less.  Once a runlength is <= 6, then combine black
         * and white runlengths until a 6-pixel pattern is obtained.
         * Then write out the special character.  Six-pixel patterns
         * were selected since 64 patterns is the largest power of
         * two less than the 92 "easily printable" PostScript
         * characters (i.e., no escape codes or octal chars).
         */
        l = 0;
        while (runlength > 6) { /* Run is greater than six... */
            if (runlength >= WBarr[l].width) {
                if (n == 0) {
                    putchar('\n');
                    n = maxline;
                }
                putchar(colormode ? WBarr[l].black : WBarr[l].white), n--;
                runlength -= WBarr[l].width;
            } else
                l++;
        }
        while (runlength > 0 && runlength <= 6) {
            int bitsleft = 6;
            int t = 0;
            while (bitsleft) {
                if (runlength <= bitsleft) {
                    if (colormode)
                        t |= ((1 << runlength)-1) << (bitsleft-runlength);
                    bitsleft -= runlength;
                    runlength = 0;
                    if (bitsleft) {
                        if (runs >= erun)
                            break;
                        colormode ^= 1;
                        runlength = *runs++;
                        if (x+runlength > lastx)
                            runlength = runs[-1] = lastx-x;
                        x += runlength;
                    }
                } else {                /* runlength exceeds bits left */
                    if (colormode)
                        t |= ((1 << bitsleft)-1);
                    runlength -= bitsleft;
                    bitsleft = 0;
                }
            }
            if (n == 0) {
                putchar('\n');
                n = maxline;
            }
            putchar(svalue[t]), n--;
        }
    }
    printf(")s\n");
}

void
printTIF(TIFF* tif, int pageNumber)
{
    uint32 w, h;
    uint16 unit;
    float xres, yres;
    tstrip_t s, ns;

    TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h);
    TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &w);
    if (!TIFFGetField(tif, TIFFTAG_XRESOLUTION, &xres)) {
        TIFFWarning(TIFFFileName(tif),
            "No x-resolution, assuming %g dpi", defxres);
        xres = defxres;
    }
    if (!TIFFGetField(tif, TIFFTAG_YRESOLUTION, &yres)) {
        TIFFWarning(TIFFFileName(tif),
            "No y-resolution, assuming %g lpi", defyres);
        yres = defyres;                                 /* XXX */
    }
    if (TIFFGetField(tif, TIFFTAG_RESOLUTIONUNIT, &unit) &&
      unit == RESUNIT_CENTIMETER) {
        xres *= 25.4;
        yres *= 25.4;
    }

    printf("%%%%Page: \"%d\" %d\n", pageNumber, pageNumber);
    printf("/$pageTop save def gsave\n");
    if (scaleToPage) {
        float yscale = pageHeight / (h/yres);
        float xscale = pageWidth / (w/xres);
        printf("%d %d translate\n",
               (int) (((basePageWidth - pageWidth) * points) * half),
               (int)((yscale*(h/yres)*points) +
               (basePageHeight - pageHeight) * points * half)  );
        printf("%g %g scale\n", (72.*xscale)/xres, -(72.*yscale)/yres);
    } else {
        printf("%d %d translate\n",
               (int) ((basePageWidth - pageWidth) * points * half),
               (int)((72.*h/yres) +
               (basePageHeight - pageHeight) * points * half) );
        printf("%g %g scale\n", 72./xres, -72./yres);
    }
    printf("0 setgray\n");
    TIFFSetField(tif, TIFFTAG_FAXFILLFUNC, printruns);
    ns = TIFFNumberOfStrips(tif);
    row = 0;
    for (s = 0; s < ns; s++)
        (void) TIFFReadEncodedStrip(tif, s, (tdata_t) NULL, (tsize_t) -1);
    printf("p\n");
    printf("grestore $pageTop restore\n");
    totalPages++;
}

#define GetPageNumber(tif) \
TIFFGetField(tif, TIFFTAG_PAGENUMBER, &pn, &ptotal)

int
findPage(TIFF* tif, int pageNumber)
{
    uint16 pn = (uint16) -1;
    uint16 ptotal = (uint16) -1;
    if (GetPageNumber(tif)) {
        while (pn != pageNumber && TIFFReadDirectory(tif) && GetPageNumber(tif))
            ;
        return (pn == pageNumber);
    } else
        return (TIFFSetDirectory(tif, pageNumber-1));
}

void
fax2ps(TIFF* tif, int npages, int* pages, char* filename)
{
    if (npages > 0) {
        uint16 pn, ptotal;
        int i;

        if (!GetPageNumber(tif))
            fprintf(stderr, "%s: No page numbers, counting directories.\n",
                filename);
        for (i = 0; i < npages; i++) {
            if (findPage(tif, pages[i]))
                printTIF(tif, pages[i]);
            else
                fprintf(stderr, "%s: No page number %d\n", filename, pages[i]);
        }
    } else {
        int pageNumber = 1;
        do
            printTIF(tif, pageNumber++);
        while (TIFFReadDirectory(tif));
    }
}

#undef GetPageNumber

/*
 * Create a special PostScript font for printing FAX documents.  By taking
 * advantage of the font-cacheing mechanism, a substantial speed-up in
 * rendering time is realized.
 */
static void
emitFont(FILE* fd)
{
    static const char* fontPrologue[] = {
        "/newfont 10 dict def newfont begin /FontType 3 def /FontMatrix [1",
        "0 0 1 0 0] def /FontBBox [0 0 512 1] def /Encoding 256 array def",
        "0 1 31{Encoding exch /255 put}for 120 1 255{Encoding exch /255",
        "put}for Encoding 37 /255 put Encoding 40 /255 put Encoding 41 /255",
        "put Encoding 92 /255 put /count 0 def /ls{Encoding exch count 3",
        "string cvs cvn put /count count 1 add def}def 32 1 36{ls}for",
        "38 1 39{ls}for 42 1 91{ls}for 93 1 99{ls}for /count 100",
        "def 100 1 119{ls}for /CharDict 5 dict def CharDict begin /white",
        "{dup 255 eq{pop}{1 dict begin 100 sub neg 512 exch bitshift",
        "/cw exch def cw 0 0 0 cw 1 setcachedevice end}ifelse}def /black",
        "{dup 255 eq{pop}{1 dict begin 110 sub neg 512 exch bitshift",
        "/cw exch def cw 0 0 0 cw 1 setcachedevice 0 0 moveto cw 0 rlineto",
        "0 1 rlineto cw neg 0 rlineto closepath fill end}ifelse}def /numbuild",
        "{dup 255 eq{pop}{6 0 0 0 6 1 setcachedevice 0 1 5{0 moveto",
        "dup 32 and 32 eq{1 0 rlineto 0 1 rlineto -1 0 rlineto closepath",
        "fill newpath}if 1 bitshift}for pop}ifelse}def /.notdef {}",
        "def /255 {}def end /BuildChar{exch begin dup 110 ge{Encoding",
        "exch get 3 string cvs cvi CharDict /black get}{dup 100 ge {Encoding",
        "exch get 3 string cvs cvi CharDict /white get}{Encoding exch get",
        "3 string cvs cvi CharDict /numbuild get}ifelse}ifelse exec end",
        "}def end /Bitfont newfont definefont 1 scalefont setfont",
        NULL
    };
    int i;
    for (i = 0; fontPrologue[i] != NULL; i++)
        fprintf(fd, "%s\n", fontPrologue[i]);
}

static int
pcompar(const void* va, const void* vb)
{
    const int* pa = (const int*) va;
    const int* pb = (const int*) vb;
    return (*pa - *pb);
}

static  void usage(int code);

int
main(int argc, char** argv)
{
    extern int optind;
    extern char* optarg;
    int c, pageNumber;
    int* pages = 0, npages = 0;
    int dowarnings = 0;         /* if 1, enable library warnings */
    time_t t;
    TIFF* tif;

    while ((c = getopt(argc, argv, "l:p:x:y:W:H:wS")) != -1)
        switch (c) {
        case 'H':               /* page height */
            pageHeight = atof(optarg);
            break;
        case 'S':               /* scale to page */
            scaleToPage = 1;
            break;
        case 'W':               /* page width */
            pageWidth = atof(optarg);
            break;
        case 'p':               /* print specific page */
            pageNumber = atoi(optarg);
            if (pageNumber < 1) {
                fprintf(stderr, "%s: Invalid page number (must be > 0).\n",
                    optarg);
                usage(-1);
            }
            if (pages)
                pages = (int*) realloc((char*) pages, (npages+1)*sizeof (int));
            else
                pages = (int*) malloc(sizeof (int));
            pages[npages++] = pageNumber;
            break;
        case 'w':
            dowarnings = 1;
            break;
        case 'x':
            defxres = atof(optarg);
            break;
        case 'y':
            defyres = atof(optarg);
            break;
        case 'l':
            maxline = atoi(optarg);
            break;
        case '?':
            usage(-1);
        }
    if (npages > 0)
        qsort(pages, npages, sizeof (int), pcompar);
    if (!dowarnings)
        TIFFSetWarningHandler(0);
    printf("%%!PS-Adobe-3.0\n");
    printf("%%%%Creator: fax2ps\n");
#ifdef notdef
    printf("%%%%Title: %s\n", file);
#endif
    t = time(0);
    printf("%%%%CreationDate: %s", ctime(&t));
    printf("%%%%Origin: 0 0\n");
    printf("%%%%BoundingBox: 0 0 %u %u\n",
        (int)(pageWidth*72), (int)(pageHeight*72));     /* XXX */
    printf("%%%%Pages: (atend)\n");
    printf("%%%%EndComments\n");
    printf("%%%%BeginProlog\n");
    emitFont(stdout);
    printf("/d{bind def}def\n"); /* bind and def proc */
    printf("/m{0 exch moveto}d\n");
    printf("/s{show}d\n");
    printf("/p{showpage}d \n"); /* end page */
    printf("%%%%EndProlog\n");
    if (optind < argc) {
        do {
            tif = TIFFOpen(argv[optind], "r");
            if (tif) {
                fax2ps(tif, npages, pages, argv[optind]);
                TIFFClose(tif);
            } else
                fprintf(stderr, "%s: Can not open, or not a TIFF file.\n",
                    argv[optind]);
        } while (++optind < argc);
    } else {
        int n;
        FILE* fd;
#ifdef __IBMC__
        char *temp, buf[16*1024];
        temp = tmpnam(NULL);
#else
        char temp[1024], buf[16*1024];
        strcpy(temp, "/tmp/fax2psXXXXXX");
        (void) mktemp(temp);
#endif

        fd = fopen(temp, "w");
        if (fd == NULL) {
            fprintf(stderr, "Could not create temp file \"%s\"\n", temp);
            exit(-2);
        }
        while ((n = read(fileno(stdin), buf, sizeof (buf))) > 0)
            write(fileno(fd), buf, n);
        tif = TIFFOpen(temp, "r");
#ifndef VMS
        unlink(temp);
#else
        remove(temp);
#endif
        if (tif) {
            fax2ps(tif, npages, pages, "<stdin>");
            TIFFClose(tif);
        } else
            fprintf(stderr, "%s: Can not open, or not a TIFF file.\n", temp);
        fclose(fd);
    }
    printf("%%%%Trailer\n");
    printf("%%%%Pages: %u\n", totalPages);
    printf("%%%%EOF\n");

    return (0);
}

char* stuff[] = {
"usage: fax2ps [options] [input.tif ...]",
"where options are:",
" -w            suppress warning messages",
" -l chars      set maximum output line length for generated PostScript",
" -p page#      select page to print (can use multiple times)",
" -x xres       set default horizontal resolution of input data (dpi)",
" -y yres       set default vertical resolution of input data (lpi)",
" -S            scale output to page size",
" -W width      set output page width (inches), default is 8.5",
" -H height     set output page height (inchest), default is 11",
NULL
};

static void
usage(int code)
{
        char buf[BUFSIZ];
        int i;

        setbuf(stderr, buf);
        for (i = 0; stuff[i] != NULL; i++)
                fprintf(stderr, "%s\n", stuff[i]);
        exit(code);
}
