/* HTML-PSformat.c -  Module for NCSA's Mosaic software
 *
 * Purpose:	to parse Hypertext widget contents into appropriate PostScript
 *
 * Author:	Ameet A. Raval & Frans van Hoesel & Andrew Ford
 *		(aar@gfdl.gov & hoesel@chem.rug.nl).
 *		send bugreports to hoesel@chem.rug.nl
 *
 * Institution: for Ameet A. Raval:
 *			Geophysical Fluid Dynamics Laboratory,
 *			National Oceanic and Atmospheric Administration,
 *			U.S. Department of Commerce
 *			P.O. Box 308
 *			Princeton, NJ 08542
 *		for Frans van Hoesel:
 *			Xtreme graphics software
 *			Herepoortenmolendrift 36
 *			9711 DH  Groningen
 *			The Netherlands
 *		also:
 *			Andrew Ford
 *			Independent Software Consultant
 *			30 Upper Cheltenham Place,
 *			Montpelier, Bristol, BS6 5HR, GB
 *			E-mail: andrew@icarus.demon.co.uk
 *
 * Date:		1 aug 1993
 * Modification:	8 nov 1993
 *				o added support for bold/italics courier
 *				o removed unused or no longer needed stuff
 *				o fixed the font alignment problem
 *		 	23 nov 1993
 *				o added support for horizontal ruler
 *				o on request of Ameet, added a specific
 *					line about whome to send bugreports to
 *			15 jun 1994
 *				o add headers, footers and footnotes to convey
 *				  title, page number, url of document, date (of
 *				  printing) and urls of links.
 *				o use A4 or US Letter size paper (currently hard coded)
 *
 *			9 may 1995 (Andrew Ford)
 *				o general overhaul
 *
 * Copyright:   This work is the product of the United States Government,
 *		and is precluded from copyright protection.  It is hereby
 *		released into the public domain.
 *
 * WE MAKE NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOFTWARE FOR
 * ANY PURPOSE.  IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED
 * WARRANTY. WE SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY THE
 * USERS OF THIS SOFTWARE.
 *
 *		pieces of code are taken from xvps by kind
 *		permission of John Bradley.
 *
 * To-do:
 *		* printing of tables and forms fields (and any new features that appear)
 *
 */
#include "../config.h"
#include <varargs.h>

#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#ifdef __bsdi__
#include <sys/malloc.h>
#else
#ifndef VAXC
#include <malloc.h>
#endif
#endif
#include <time.h>
#include <sys/types.h>
#include "HTMLP.h"

/* Fix thanks to robm. */
#ifdef __alpha
#include <Xm/VaSimpleP.h>
#endif

#define CR '\015'
#define LF '\012'

#ifndef DISABLE_TRACE
extern int htmlwTrace;
#endif

extern int SwapElements();

#define USLETTER 	0
#define A4 		1

#if !defined(DEFAULT_PAGE_SIZE)
#define DEFAULT_PAGE_SIZE USLETTER
#endif

#define F_FULLCOLOR	0
#define F_GREYSCALE	1
#define F_BWDITHER 	2
#define F_REDUCED  	3

#define L_PAREN		'('
#define R_PAREN		')'
#define B_SLASH		'\\'
#define MAX_ASCII	'\177'


/* MONO returns total intensity of r,g,b components .33R+ .5G+ .17B */
#define MONO(rd,gn,bl) (((rd)*11 + (gn)*16 + (bl)*5) >> 13)

/* PSconst_out outputs to the postscript buffer an array of constant
 * strings
 */
#define PSconst_out(txt) {				\
	int n=(sizeof txt)/(sizeof txt[0]); 		\
	  	int i; 					\
	  	for (i=0; i<n; i++) { 			\
			PSprintf("%s\n", txt[i]) ; 	\
		} 					\
}

/* STREQ tests whether two strings are equal. */
#define STREQ(a, b)	(strcmp((a), (b)) == 0)

/* for regular-font, bold-font, italic-font, fixed-font */
typedef enum { RF, BF, IF, FF, FB, FI } PS_fontstyle;

static int PS_size, PS_len, PS_offset, PS_curr_page, PS_start_y, PS_hexi;
static int PS_page_offset;
static char *PS_string;
static float Points_Pixel;
static int Pixels_Page;
static PS_fontstyle PS_oldfn = RF;
static int PS_fontascent = 0;
static int PS_oldfs = 0;

static XColor fg_color, bg_color;


static int footnote_space  = 8;		/* Space from main text to footnotes */
static int footnote_ptsize = 8;		/* Point size for footnote text */
static int cur_ftn_no;			/* Current footnote number */
static int n_saved_ftns;		/* Number of saved footnotes on page */
static int ftn_array_size=0;		/* Size of allocated footnote array */
static char **footnotes=NULL;		/* Pointer to array of footnote pointers */

typedef struct {
    double	page_height;
    double	page_width;
    double	top_margin;
    double	bot_margin;
    double	left_margin;
    double	right_margin;
    double	text_height;
    double	text_width;
}
PAGE_DIMENS_T;

#define INCH	72
#define MM	INCH / 25.4

PAGE_DIMENS_T	page_dimens;
PAGE_DIMENS_T	a4_page_dimens = {
    297 * MM,
    210 * MM,
     20 * MM,
     20 * MM,
     20 * MM,
     20 * MM
};

PAGE_DIMENS_T	us_letter_page_dimens = {
    11  * INCH,		/* page_height */
    8.5 * INCH,		/* page_width  */
    0.9 * INCH,		/* top_margin  */
    0.7 * INCH,
    0.9 * INCH,
    0.9 * INCH
};


/* Globals to get button value in print dialog -- swp/AF */

int HTML_Print_Headers       = 1;	/* Flag whether page headers enabled */
int HTML_Print_Footers     = 1;	/* Flag whether footnote printing enabled */

/* Paper format (currently either A4 or letter).  This should be generalized. */

int HTML_Print_Paper_Size_A4 = DEFAULT_PAGE_SIZE;

extern int installed_colormap;
extern Colormap installed_cmap;

/*
 * GetDpi - return Dots-per-inch of the screen
 *
 * calculate the pixel density in dots per inch on the current widget
 * screen
 *
 */
static float
GetDpi(HTMLWidget hw)
{
    Screen *s = XtScreen(hw);
    float dpi;

    dpi = 25.4 * WidthOfScreen(s) / WidthMMOfScreen(s);
    if (dpi<1.0 || dpi>10000.0)
	dpi = 72.0;
    return dpi;
}


/*
 * PSprintf - dynamic string concatenation function.
 *
 *  In successive calls, the formatted string will be appended to the global
 *  output string Sp.
 *  It assumes that on each call, the length of the text appended to Sp
 *  is less than 1024.
 *  The format string is used as in printf, so you can use additional
 *  arguments.
 *
 *  When successful, PSprintf returns the number of characters printed
 *  in this call, otherwise it returns EOF (just as printf does)
 *
 */
static int
PSprintf(format, va_alist)
    char* format;
    va_dcl
{
    int 	len;
    char 	*s;
    va_list	args;

    if (PS_size - PS_len < 1024)
    {
	PS_size += 1024;
	if ((s = (char *) realloc(PS_string, PS_size)) == NULL)
	{
#ifndef DISABLE_TRACE
		if (htmlwTrace) {
			fprintf(stderr, "PSprintf malloc failed\n");
		}
#endif
		return(EOF);
	}
	PS_string = s;
    }
    va_start(args);
    len = vsprintf(PS_string+PS_len, format, args);
    /* this is a hack to make it work on systems were vsprintf(s,...)
     * returns s, instead of the len.
     */
    if (len != EOF && len != 0)
	PS_len += strlen(PS_string+PS_len);
    va_end(args);
    return(len);
}


/*
 * PShex - output hex byte
 *
 * Append the byte "val" to an internal string buffer in hexadecimal
 * format.  If the argument "flush" is True, or if the buffer has filled
 * up, flush the buffer to the larger postscript output buffer (using
 * PSprintf).
 *
 */
static int
PShex(unsigned char val, int flush)
{
    static unsigned char hexline[80];
    static char digit[] = "0123456789abcdef";

    if (!flush)
    {
	hexline[PS_hexi++] = (char) digit[((unsigned) val >>
					   (unsigned) 4) & (unsigned) 0x0f];
	hexline[PS_hexi++] = (char) digit[(unsigned) val &
					  (unsigned) 0x0f];
    }

    /* Changed from ">78" to ">77" on advice of
       debra@info.win.tue.nl (Paul De Bra). */

    if ((flush && PS_hexi) || (PS_hexi>77))
    {
	hexline[PS_hexi] = '\0';
	PS_hexi=0;
	return (PSprintf("%s\n", hexline));
    }
    return (0);
}


/*
 * PSfont - change font
 *
 * change local font in buf to "font"
 * fontfamily indicates if the overall style is times, helvetica, century
 * schoolbook or lucida.
 *
 */
static void
PSfont(HTMLWidget hw, XFontStruct *font, int fontfamily)
{
    PS_fontstyle fn;
    int style, size;
    int fs;

    static PS_fontstyle fontstyle[17] =
    {
	RF, IF, BF, FF, BF, BF, BF, BF, BF,
	BF, IF, FF, FF, FB, FI, FB, FI
    };

    static char fnchar[6][3] = {"RF", "BF", "IF", "FF", "FB", "FI"};

    /* fontsizes as set in gui.c and in HTML.c (listing font is only
     * defined in HTML.c)
     */
    static int fontsizes[4][3][17] =
    {
	/* times font sizes */
	{
	    {14, 14, 14, 14, 18, 17, 14, 12, 10, 8, 14, 12, 12, 14, 14, 12, 12},
	    {17, 17, 17, 17, 24, 18, 17, 14, 12, 10, 17, 14, 12, 17, 17, 14, 14},
	    {20, 20, 20, 20, 25, 24, 20, 18, 17, 14, 20, 18, 12, 20, 20, 18, 18}
	},
	/* helvetica sizes */
	{
	    {14, 14, 14, 14, 18, 17, 14, 12, 10, 8, 14, 12, 12, 14, 14, 12, 12},
	    {17, 17, 17, 17, 24, 18, 17, 14, 12, 10, 17, 14, 12, 17, 17, 14, 14},
	    {20, 20, 20, 20, 25, 24, 20, 18, 17, 14, 20, 18, 12, 20, 20, 18, 18}
	},
	/* new century schoolbook sizes */
	{
	    {14, 14, 14, 14, 18, 17, 14, 12, 10, 8, 14, 12, 12, 14, 14, 12, 12},
	    {18, 18, 18, 18, 24, 18, 17, 14, 12, 10, 18, 14, 12, 18, 18, 14, 14},
	    {20, 20, 20, 20, 25, 24, 20, 18, 17, 14, 20, 18, 12, 20, 20, 18, 18}
	},
	/* lucida sizes */
	{
	    {14, 14, 14, 14, 18, 17, 14, 12, 11, 10, 14, 12, 12, 14, 14, 12, 12},
	    {17, 17, 17, 17, 24, 18, 17, 14, 12, 10, 17, 14, 12, 17, 17, 14, 14},
	    {20, 20, 20, 20, 25, 24, 20, 18, 17, 14, 20, 18, 12, 20, 20, 18, 18}
	}
    };

    /* next is for each fontfamily the ascent value as given by the
     * medium sized bold x-font (the regular font has the same
     * ascent value for both the medium and the large size Century
     * font).
     * it is use in the check for the fontsize (small, medium, large)
     */
    static int medium_fontascent[4] = {
	14, 14, 16, 15
    };

    /* for each fontfamily, for each fontsize, and for each font style
     * give the ascent value, so the output from Postscript is correct.
     * If the value is given between parenthesis, then it is different
     * from the value as stored in the x-font.
     * Note that this is a fix, and need to be changed, if the browser
     * is fixed (in the current version 1.2 the baseline of various fonts
     * is not aligned very well).
     */
    static int fontascent[4][3][17] = {
	/*rg, itl, bld, fix,  h1,  h2,  h3,  h4,  h5,  h6,
	  add, pla, lis, fixbold, fixital, plabold, plaital, */
	/* times */
	{
	    {12, 11, 12, 10, 15, 14, 12, 10, 8, 6, 11, 9, 10, 10, 10, 9, 9},
	    {13, 13, 14, 12, 20, 15, 14, 12, 10, 8, 13, 10, 10, 12, 12, 10, 10},
	    {16, 15, 15, 13, 21, 20, 15, 15, 14, 12, 15, 13, 10, 13, 13, 13, 13}
	},
	/* helvetica */
	{
	    {12, 12, 12, 10, 15, 14, 12, 10, 9, 7, 12, 9, 10, 10, 10, 9, 9},
	    {14, 14, 14, 12, 22, 15, 14, 12, 10, 9, 14, 10, 10, 12, 12, 10, 10},
	    {16, 16, 16, 13, 22, 22, 16, 15, 14, 12, 16, 13, 10, 13, 13, 13, 13}
	},
	/* new century schoolbook */
	{
	    {12, 12, 13, 10, 16, 14, 13, 10, 9, 7, 12, 9, 10, 10, 10, 9, 9},
	    {16, 14, 16, 13, 22, 16, 14, 13, 10, 9, 14, 10, 10, 13, 13, 10, 10},
	    {17, 16, 17, 13, 22, 22, 17, 16, 14, 13, 16, 13, 10, 13, 13, 13, 13}
	},
	/* lucida bright */
	{
	    {11, 11, 11, 11, 15, 14, 11, 10, 9, 7, 11, 9, 10, 11, 10, 9, 9},
	    {14, 15, 14, 13, 20, 15, 14, 11, 10, 7, 15, 11, 10, 13, 13, 11, 10},
	    {17, 17, 17, 16, 21, 20, 17, 15, 14, 11, 17, 14, 10, 16, 13, 14, 13}
	}
    };

    /* NULL case - reflush old font or the builtin default: */
    if ((hw == NULL) || (font == NULL))
    {
	if (PS_oldfs != 0)
	    PSprintf( "%2s %d SF\n", fnchar[PS_oldfn], PS_oldfs);
	return;
    }
    /* added the next line in case xmosaic version 199.4 has more fonts */
    style = 3;

    if (font == hw->html.font) {
	style = 0;
    } else if (font == hw->html.italic_font) {
	style = 1;
    } else if (font == hw->html.bold_font) {
	style = 2;
    } else if (font == hw->html.fixed_font) {
	style = 3;
    } else if (font == hw->html.header1_font) {
	style = 4;
    } else if (font == hw->html.header2_font) {
	style = 5;
    } else if (font == hw->html.header3_font) {
	style = 6;
    } else if (font == hw->html.header4_font) {
	style = 7;
    } else if (font == hw->html.header5_font) {
	style = 8;
    } else if (font == hw->html.header6_font) {
	style = 9;
    } else if (font == hw->html.address_font) {
	style = 10;
    } else if (font == hw->html.plain_font) {
	style = 11;
    } else if (font == hw->html.listing_font) {
	style = 12;
    } else if (font == hw->html.fixedbold_font) {
	style = 13;
    } else if (font == hw->html.fixeditalic_font) {
	style = 14;
    } else if (font == hw->html.plainbold_font) {
	style = 15;
    } else if (font == hw->html.plainitalic_font) {
	style = 16;
    }

    /* check size, by looking at the size of the regular font */
    size = 1;
    if (hw->html.bold_font->ascent > medium_fontascent[fontfamily])
    {
	/* large font */
	size = 2;
    }
    else if (hw->html.bold_font->ascent < medium_fontascent[fontfamily])
    {
	/* small font */
	size = 0;
    }
    fn = fontstyle[style];
    fs = fontsizes[fontfamily][size][style];
    PS_fontascent = fontascent[fontfamily][size][style];

    if (fn != PS_oldfn || fs != PS_oldfs)
    {
	PSprintf( "%2s %d SF\n", fnchar[fn], fs);
	PS_oldfn=fn, PS_oldfs=fs;
    }
}


/*
 * PSshowpage - end of page function
 *
 * show the current page and restore any changes to the printer state.
 * Any accumulated footnotes are output and the outstanding footnote count
 * reset to zero.  Footnotes are preceded by a footnote rule and each footnote
 * is consists of a raised mark and the footnote text (i.e. the url).  The mark
 * is in a smaller font than the text.  The ideas are filched from LaTeX.
 */
static void
PSshowpage(void)
{
    PSprintf("restore\n");
    if (n_saved_ftns > 0)
    {
	int	i;

	PSprintf("gsave 0.2 setlinewidth newpath %.2f %.2f M %.2f 0 RL stroke\n",
		 page_dimens.left_margin,
		 (page_dimens.bot_margin + (footnote_ptsize * n_saved_ftns) + 4),
		 (page_dimens.text_width * 0.4));
	for (i = 0; n_saved_ftns; n_saved_ftns--, i++)
	{
	    PSprintf("newpath %.2f %.2f M RF %.2f SF (%d) S 3 -2 R RF %d SF (%s) S\n",
		     page_dimens.left_margin,
		     page_dimens.bot_margin + 5 + (n_saved_ftns - 1) * footnote_ptsize,
		     (0.7 * footnote_ptsize), cur_ftn_no - n_saved_ftns,
		     footnote_ptsize, footnotes[i]);
	}
	PSprintf("grestore\n");
    }
    PSprintf("showpage\n");
}



/*
 * PSnewpage - begin a fresh page
 *
 * increment the page count and handle the structured comment
 * conventions
 *
 */
static void
PSnewpage(void)
{
    PS_curr_page++;

    /* the PostScript reference Manual states that the Page: Tag
       should have a label and a ordinal; otherwise programs like
       psutils fail    -gustaf */

    PSprintf("%%%%Page: %d %d\n", PS_curr_page, PS_curr_page);
    PSprintf("save\n");
    if (HTML_Print_Headers)
	PSprintf("%d ", PS_curr_page);
    PSprintf("NP\n");
    PSfont( NULL, NULL, 0);	/* force re-flush of last font used */
}



/*
 * PSinit_latin1 - handle ISO encoding
 *
 * print out initializing PostScript text for ISO Latin1 font encoding
 * This code is copied from the Idraw program (from Stanford's InterViews
 * package), courtesy of Steinar Kjaernsr|d, steinar@ifi.uio.no
 *
 */
static void
PSinit_latin1(void)
{

    static char *txt[] = {

	"/reencodeISO {",
	"dup dup findfont dup length dict begin",
	"{ 1 index /FID ne { def }{ pop pop } ifelse } forall",
	"/Encoding ISOLatin1Encoding D",
	"currentdict end definefont",
	"} D",
	"/ISOLatin1Encoding [",
	"/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef",
	"/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef",
	"/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef",
	"/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef",
	"/space/exclam/quotedbl/numbersign/dollar/percent/ampersand/quoteright",
	"/parenleft/parenright/asterisk/plus/comma/minus/period/slash",
	"/zero/one/two/three/four/five/six/seven/eight/nine/colon/semicolon",
	"/less/equal/greater/question/at/A/B/C/D/E/F/G/H/I/J/K/L/M/N",
	"/O/P/Q/R/S/T/U/V/W/X/Y/Z/bracketleft/backslash/bracketright",
	"/asciicircum/underscore/quoteleft/a/b/c/d/e/f/g/h/i/j/k/l/m",
	"/n/o/p/q/r/s/t/u/v/w/x/y/z/braceleft/bar/braceright/asciitilde",
	"/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef",
	"/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef",
	"/.notdef/dotlessi/grave/acute/circumflex/tilde/macron/breve",
	"/dotaccent/dieresis/.notdef/ring/cedilla/.notdef/hungarumlaut",
	"/ogonek/caron/space/exclamdown/cent/sterling/currency/yen/brokenbar",
	"/section/dieresis/copyright/ordfeminine/guillemotleft/logicalnot",
	"/hyphen/registered/macron/degree/plusminus/twosuperior/threesuperior",
	"/acute/mu/paragraph/periodcentered/cedilla/onesuperior/ordmasculine",
	"/guillemotright/onequarter/onehalf/threequarters/questiondown",
	"/Agrave/Aacute/Acircumflex/Atilde/Adieresis/Aring/AE/Ccedilla",
	"/Egrave/Eacute/Ecircumflex/Edieresis/Igrave/Iacute/Icircumflex",
	"/Idieresis/Eth/Ntilde/Ograve/Oacute/Ocircumflex/Otilde/Odieresis",
	"/multiply/Oslash/Ugrave/Uacute/Ucircumflex/Udieresis/Yacute",
	"/Thorn/germandbls/agrave/aacute/acircumflex/atilde/adieresis",
	"/aring/ae/ccedilla/egrave/eacute/ecircumflex/edieresis/igrave",
	"/iacute/icircumflex/idieresis/eth/ntilde/ograve/oacute/ocircumflex",
	"/otilde/odieresis/divide/oslash/ugrave/uacute/ucircumflex/udieresis",
	"/yacute/thorn/ydieresis",
	"] D",
	"[RF BF IF FF FB FI] {reencodeISO D} forall"
    };

    PSconst_out(txt);
}


/*
 * PSinit - initialize Postscript output
 *
 * does the initialization per html document
 *
 */
static void
PSinit(void)
{
    PS_size = PS_len = PS_offset = PS_hexi = PS_page_offset = 0;
    PS_start_y = 0;
    PS_string = (char *) malloc(1);
    PS_oldfs = 0;
    PS_oldfn = RF;
    PS_curr_page = 0 ;
    n_saved_ftns = 0;
    cur_ftn_no = 1;
}



/*
 * PSheader - initialize Postscript output
 *
 * Prints out the prolog.  The following PostScript macros are defined
 *	D	def - define a macro
 *	E	exch - exhange parameters
 *	M	moveto
 *	R	rmoveto
 *	L	lineto
 *	RL	rlineto
 *	SQ	draw a unit square
 *	U	underline a string
 *	B	draw a bullet
 *	OB	draw an open bullet
 *	HR	draw a horizontal rule
 *	SF	set font
 *	RF	roman font (dependent on font family)
 *	BF	bold font (dependent on font family)
 *	IF	italic font (dependent on font family)
 *	FF	fixed font (courier)
 *	FB	fixed bold font (courier bold)
 *	FI	fixed italic font (courier oblique)
 *	nstr	buffer for creating page number string
 *	pageno	literal "Page "
 *	url	URL of document
 *	title	title of document
 *	date	date modified/printed
 */
static void
PSheader(char *title, int font, char *url, char *time_str)
{
    static char *notitle="Untitled";
    int set_to_null=0;
    static char *fontname[] = {
	/* in order: regular, bold, italic */
	"Times-Roman", "Times-Bold", "Times-Italic",
	"Helvetica", "Helvetica-Bold", "Helvetica-Oblique",
	"NewCenturySchlbk-Roman", "NewCenturySchlbk-Bold",
	"NewCenturySchlbk-Italic",
	/* this is a nasty trick, I have put Times in place of
	 * Lucida, because most printers don't have Lucida font
	 */
	"Times-Roman", "Times-Bold", "Times-Italic"
	    /* "Lucida", "Lucida-Bold", "Lucida-Italic" */
    };

    static char *txt[] = {
	"/M {moveto} D",
	"/S {show} D",
	"/R {rmoveto} D",
	"/L {lineto} D",
	"/RL {rlineto} D",
	"/SQ {newpath 0 0 M 0 1 L 1 1 L 1 0 L closepath} D",
	"/U {gsave currentpoint currentfont /FontInfo get /UnderlinePosition get",
	" 0 E currentfont /FontMatrix get dtransform E pop add newpath moveto",
	" dup stringwidth rlineto stroke grestore S } D",
	"/B {/r E D gsave -13 0  R currentpoint ",
	"  newpath r 0 360 arc closepath fill grestore } D",
	"/OB {/r E D gsave -13 0  R currentpoint ",
	"  newpath r 0 360 arc closepath stroke grestore } D",
	"/HR {/l E D gsave 0 1 RL l 0 RL 0 -1 RL l neg 0 RL stroke grestore } D",
	"/SF {E findfont E scalefont setfont } D",
	"/FF {/Courier } D",
	"/FB {/Courier-Bold } D",
	"/FI {/Courier-Oblique } D"
	};

    char		time_buf[40];
    time_t		clock = time(NULL);

#if !defined(VMS) || defined (__DECC)
    strftime(time_buf, sizeof(time_buf),
	     "Printed %a %b %e %T %Y", localtime(&clock));
#else
    sprintf(time_buf,"Printed %s",asctime(localtime(&clock)));
#endif

/* Always show the print date -- SWP */
    time_str = time_buf;

/*
    if (!time_str || (time_str[0] == '\0'))
    {
	time_str = time_buf;
    }
*/
    PSprintf("%%!PS-Adobe-1.0\n");
    PSprintf("%%%%Creator: NCSA Mosaic, Postscript by Ameet Raval, Frans van Hoesel\n");
    PSprintf("%%%%         and Andrew Ford\n");

    if (!title) {
	title=notitle;
	set_to_null=1;
    }

    {
	char *tmp;
	for (tmp = title; *tmp; tmp++)
	    if (*tmp == CR || *tmp == LF)
                *tmp = ' ';
	PSprintf("%%%%Title: %s\n", title);
    }

    PSprintf("%%%%CreationDate: %s\n", time_buf + 8);
    PSprintf("%%%%Pages: (atend)\n");
    PSprintf("%%%%PageOrder: Ascend\n");
    PSprintf("%%%%BoundingBox: %d %d %d %d\n",
	     (int)page_dimens.left_margin,
	     (int)(page_dimens.bot_margin - 12),
	     (int)(page_dimens.left_margin + page_dimens.text_width + 0.5),
	     (int)(page_dimens.bot_margin + page_dimens.text_height + 12.5));
    PSprintf("%%%%DocumentFonts: %s %s %s Courier Courier-Bold Courier-Oblique\n",
	     fontname[font*3], fontname[font*3+1], fontname[font*3+2]);
    PSprintf("%%%%EndComments\n");
    PSprintf("save /D {def} def /E {exch} D\n");
    PSprintf("/RF {/%s} D\n", fontname[font*3]);
    PSprintf("/BF {/%s} D\n", fontname[font*3+1]);
    PSprintf("/IF {/%s} D\n", fontname[font*3+2]);
    PSprintf("/nstr 6 string D /pgno (Page ) D\n");
    PSprintf("/url (%s) D\n", url);
    PSprintf("/title (%s) D\n", title);
    PSprintf("/date (%s) D\n", time_str);
    PSconst_out(txt);

    /* Output the newpage definition. */

    PSprintf("/NP {");
    if (HTML_Print_Headers)
    {
	PSprintf("gsave 0.4 setlinewidth\n");
	PSprintf("  newpath %.2f %.2f M %.2f 0 RL stroke",
		 page_dimens.left_margin,
		 (page_dimens.bot_margin + page_dimens.text_height),
		 page_dimens.text_width);
	PSprintf("  newpath %.2f %.2f M %.2f 0 RL stroke\n",
		 page_dimens.left_margin, page_dimens.bot_margin,
		 page_dimens.text_width);
	PSprintf("  BF 12 SF %.2f %.2f M (%s) S\n",
		 page_dimens.left_margin,
		 (page_dimens.bot_margin + page_dimens.text_height + 6), title);
	PSprintf("  nstr cvs dup stringwidth pop pgno stringwidth pop add\n");
	PSprintf("  %.2f E sub %.2f M pgno S S\n",
		 (page_dimens.left_margin + page_dimens.text_width),
		 (page_dimens.bot_margin + page_dimens.text_height + 6));
	PSprintf("  BF 10 SF %.2f %.2f M (%s) S\n",
		 page_dimens.left_margin, page_dimens.bot_margin - 12, url);
	PSprintf("  (%s) dup stringwidth pop %.2f E sub %.2f M S grestore\n",
		 time_str, page_dimens.left_margin + page_dimens.text_width,
		 page_dimens.bot_margin - 12);
    }
    PSprintf("  %.2f %.2f translate %.5f %.5f scale } D\n",
	     page_dimens.left_margin,
	     page_dimens.bot_margin + page_dimens.text_height,
	     Points_Pixel, Points_Pixel);
    PSinit_latin1();

    PSprintf("%%%%EndProlog\n");

    if (set_to_null) {
	title=NULL;
    }
}



/*
 * PStrailer - write postscript trailer
 *
 */
static void
PStrailer(void)
{

    PSprintf("%%%%Trailer\n");
    PSprintf("restore\n");
    PSprintf("%%%%Pages: %d\n", PS_curr_page);
    if (footnotes)
    {
	free(footnotes);
	footnotes = NULL;
	ftn_array_size = 0;
    }
}


/*
 * PSmoveto - move to new x,y location
 *
 * if the Y value does not fit on the current page, begin a new page
 * (I think in the current implementation, this never happens)
 *
 */
static void
PSmoveto(int x, int y)
{
    if (y > PS_start_y + Pixels_Page)
    {
	PS_start_y = y;
	PSshowpage();
	PSnewpage();
    }
    PS_offset = 0;
    PSprintf( "%d %d M\n", x, -(y - PS_start_y));
}


/*
 * PSmove_offset - set Y-offset
 *
 * do a relative vertical move, whenever the offset changes
 *
 */
static void
PSmove_offset(int offset)
{
    if (offset != PS_offset)
    {
	PSprintf("0 %d R\n", PS_offset - offset );
	PS_offset = offset;
    }
}


/*
 * Return an indication of whether or not the current element has a footnote.
 *
 * an element has a footnote if it is text or an image and its anchorHRef is not null.
 * If the element is a textual element with an anchorHRef, that has been split across
 * lines then it should be followed by a linefeed element and a text element with the
 * same anchorHRef.  In this case say that the element doesn't have a footnote so as
 * to avoid duplicate footnotes.
 */
static int
has_footnote(struct ele_rec *el)
{
    int			rc = 0;
    struct ele_rec 	*next;
    char		*anchorHRef;

    if (el == NULL) {
	return 0;
    }

    anchorHRef = el->anchorHRef;

    if (anchorHRef != NULL)
    {
	switch (el->type)
	{
	case E_TEXT:
	case E_IMAGE:
	    for (next = el->next; el; el = next, next = el->next)
	    {
		if (next == NULL)
		{
		    rc = 1;
		    break;
		}
		else if (next->anchorHRef == NULL) {
			rc = 1;
			break;
		}
		else if (!STREQ(next->anchorHRef, anchorHRef))
		{
		    rc = 1;
		    break;
		}
		else if (   (next->type == E_TEXT)
			 || (next->type == E_IMAGE))
		{
		    rc = 0;
		    break;
		}
	    }
	    break;

	default:
	    break;
	}
    }
    return rc;
}


/*
 * PSfootnote - output a footnote mark and store the footnote
 *
 * the footnote mark is placed at the current point, enclosed in a gsave/grestore
 * pair so that the position of the following output is not affected.
 * The reference is stored in a malloced array (which may need to be expanded), to
 * be output at the end of the page.
 */
static void
PSfootnote(char *href, double height)
{
    PSprintf("gsave 0 %.2f R RF %d SF (%d) S grestore\n",
	     height, footnote_ptsize, cur_ftn_no++);

    if (n_saved_ftns == ftn_array_size)
    {
	ftn_array_size += 16;
	if (!footnotes) {
		footnotes = (char **)calloc(ftn_array_size,sizeof(char *));
	}
	else {
		footnotes = (char **)realloc((void *)footnotes,
				     (ftn_array_size * sizeof(char *)));
	}
	if (footnotes == NULL)
	{
#ifndef DISABLE_TRACE
		if (htmlwTrace) {
			fprintf(stderr, "PSfootnote realloc failed\n");
		}
#endif

		return;
	}
    }

    footnotes[n_saved_ftns++] = href;
}


/*
 * PStext - output text
 *
 * show text "t", and protect special characters if needed
 * if Underline is non-zero, the text is underlined.
 *
 */
static void
PStext(HTMLWidget hw, struct ele_rec *eptr, int fontfamily, String s)
{
    String 		s2;
    String 		stmp;
    unsigned char	ch;
    int 		underline = eptr->underline_number;
    int		 	ascent;

    PSfont(hw, eptr->font, fontfamily);	/* set font */
    if (PS_fontascent == 0)
	ascent = eptr->font->ascent;
    else
	ascent = PS_fontascent;
    PSmove_offset(eptr->y_offset + ascent);


    /* Allocate a string long enough to hold the original string with
       every character stored as an octal escape (worst case scenario). */

    s2 = (String) malloc(strlen(s) * 4 + 1);
    if (s2 == NULL)
    {
#ifndef DISABLE_TRACE
	if (htmlwTrace) {
		fprintf(stderr, "PStext malloc failed\n");
	}
#endif

	return;
    }

    /*  For each char in s, if it is a special char, insert "\"
     *  into the new string s2, then insert the actual char
     */
    for (stmp = s2; (ch = *s++) != '\0';)
    {
	if ((ch == L_PAREN) || (ch == R_PAREN) || (ch == B_SLASH))
	{
	    *stmp++ = B_SLASH;
	    *stmp++ = ch;
	}
	else if (ch > (unsigned char) MAX_ASCII)
	{
	    /*  convert to octal */
	    *stmp++ = B_SLASH;
	    *stmp++ = ((ch >> 6) & 007) + '0';
	    *stmp++ = ((ch >> 3) & 007) + '0';
	    *stmp++ = (ch & 007) + '0';
	}
	else
	{
	    *stmp++ = ch;
	}
    }
    *(stmp) = '\0';
    PSprintf("(%s)%c\n", s2, (underline)?'U':'S');
    if (HTML_Print_Footers && has_footnote(eptr))
    {
	PSfootnote(eptr->anchorHRef, 0.7 * ascent);
    }
    free(s2);
}


/*
 * PSbullet - output a bullet
 *
 * The bullet is normally filled, except for a bullet with an indent level
 * of two. The size of the higher level bullets is just somewhat smaller
 *
 */
static void
PSbullet(HTMLWidget hw, struct ele_rec *eptr, int fontfamily)
{
    int 	width  = (  eptr->font->max_bounds.lbearing
		          + eptr->font->max_bounds.rbearing);
    int 	offset = eptr->y_offset + eptr->font->ascent;
    int		level  = eptr->indent_level;
    double 	size   = eptr->line_height / 5.5;

    if (size < 1.1) size = 1.1;
    if (level > 2)  size /= 1.33333;

    /* the next line is a hack to get a good position of the
     * bullet in most practical cases, otherwise the
     * bullet may appear just a bit too low (for large fonts)
     * What is does is to compare the lineheight with
     * the lineheight of the next element, to correct
     * for the possibly too large y_offset
     */

    if (   (eptr->next != NULL)
	&& (   (eptr->next->type == E_TEXT)
	    || (eptr->next->type == E_IMAGE)))
    {
	offset += (eptr->line_height - eptr->next->line_height);
    }

    PSfont(hw, eptr->font, fontfamily);
    PSmove_offset(offset - width/4);
    PSprintf(" %f %s\n", size, (level == 2) ? "OB" : "B");
}


/*
 * PShrule - draw a horizontal line with the given width
 *
 * nothing special, just draw a line, from the current position to
 * the right side of the paper.
 *
 */
static void
PShrule(HTMLWidget hw, struct ele_rec *eptr, int fontfamily)
{
    int length = hw->html.doc_width;

    PSmove_offset(eptr->y_offset);
    PSprintf("%d HR\n", length);
}




/*
 * PStable - draw a table
 *
 * Currently just draw a box of the dimensions of the table.
 *
 */
static void
PStable(HTMLWidget hw, struct ele_rec *eptr, int fontfamily)
{
    struct table_rec	*tptr = eptr->table_data;
    int width  = tptr->width;
    int height = tptr->height;

#if 0
    PSmove_offset(eptr->y_offset);
    PSprintf("gsave currentpoint %d sub translate ", height);
    PSprintf("%d %d scale\n", width, height);
    PSprintf("SQ stroke\n");
    PSprintf("grestore\n");
#endif
}


/*
 * PSwidget - draw a widget (form field)
 *
 * Currently just draw a grey box of the dimensions of the field.
 * This is nowhere near complete but is a first step.
 * The widget record type field gives the type of field:
 *	0	textfield
 *	1	checkbox
 *	2	radiobox
 *	3	pushbutton
 *	4	password
 *	5	option menu
 */
static void
PSwidget(HTMLWidget hw, struct ele_rec *eptr, int fontfamily)
{
    struct wid_rec *wptr = eptr->widget_data;
    int w = wptr->width;
    int h = wptr->height;

#if 1	/* Comment out? */
    PSmove_offset(eptr->y_offset);
    PSprintf("gsave currentpoint %d sub translate ", h);
    PSprintf("%d %d scale\n", w, h);
    PSprintf("SQ 0.9 setgray fill\n");
    PSprintf("grestore\n");
#endif
}


/*
 * PSrle_encode - perform run length encoding
 *
 * does the run-length encoding. This is done to reduce the file size and
 * therefore the time to send the file to the printer. You get longer
 * processing time instead.
 *
 * rle is encoded as such:
 *  <count> <value>			# 'run' of count+1 equal pixels
 *  <count | 0x80> <count+1 data bytes>	# count+1 non-equal pixels
 * count can range between 0 and 127
 *
 * returns length of the rleline vector
 *
*/
static int
PSrle_encode(unsigned char *scanline,
	     unsigned char *rleline,
	     int wide)
{
    int  i, j, blocklen, isrun, rlen;
    unsigned char block[256], pix;

    blocklen = isrun = rlen = 0;

    for (i = 0; i < wide; i++)
    {
	/*  there are 5 possible states:
	 *   0: block empty.
	 *   1: block is a run, current pix == previous pix
	 *   2: block is a run, current pix != previous pix
	 *   3: block not a run, current pix == previous pix
	 *   4: block not a run, current pix != previous pix
	 */

	pix = scanline[i];

	if (!blocklen)
	{
	    /* case 0:  empty */
	    block[blocklen++] = pix;
	    isrun = 1;
	}
	else if (isrun)
	{
	    if (pix == block[blocklen-1])
	    {
		/*  case 1:  isrun, prev==cur */
		block[blocklen++] = pix;
	    }
	    else
	    {
		/*  case 2:  isrun, prev!=cur */
		if (blocklen>1)
		{
		    /*  we have a run block to flush */
		    rleline[rlen++] = blocklen-1;
		    rleline[rlen++] = block[0];
		    /*  start new run block with pix */
		    block[0] = pix;
		    blocklen = 1;
		}
		else
		{
		    /*  blocklen<=1, turn into non-run */
		    isrun = 0;
		    block[blocklen++] = pix;
		}
	    }
	}
	else
	{
	    /* not a run */
	    if (pix == block[blocklen-1])
	    {
		/* case 3: non-run, prev==cur */
		if (blocklen>1)
		{
		    /*  have a non-run block to flush */
		    rleline[rlen++] = (blocklen-1) | 0x80;
		    for (j=0; j<blocklen; j++)
			rleline[rlen++] = block[j];
		    /*  start new run block with pix */
		    block[0] = pix;
		    blocklen = isrun = 1;
		}
		else
		{
		    /*  blocklen<=1 turn into a run */
		    isrun = 1;
		    block[blocklen++] = pix;
		}
	    }
	    else
	    {
		/* case 4:  non-run, prev!=cur */
		block[blocklen++] = pix;
	    }
	}

	/* max block length.  flush */
	if (blocklen == 128)
	{
	    if (isrun)
	    {
		rleline[rlen++] = blocklen-1;
		rleline[rlen++] = block[0];
	    }
	    else
	    {
		rleline[rlen++] = (blocklen-1) | 0x80;
		for (j=0; j<blocklen; j++)
		    rleline[rlen++] = block[j];
	    }
	    blocklen = 0;
	}
    }

    /* flush last block */
    if (blocklen)
    {
	if (isrun)
	{
	    rleline[rlen++] = blocklen-1;
	    rleline[rlen++] = block[0];
	}
	else
	{
	    rleline[rlen++] = (blocklen-1) | 0x80;
	    for (j=0; j<blocklen; j++)
		rleline[rlen++] = block[j];
	}
    }

    return rlen;
}


/*
 * PScolor_image - created postscript colorimage operator
 *
 * spits out code that checks if the PostScript device in question
 * knows about the 'colorimage' operator.  If it doesn't, it defines
 * 'colorimage' in terms of image (ie, generates a greyscale image from
 * RGB data)
 *
*/

static void
PScolor_image(void)
{
    static char *txt[] = {

	"% define 'colorimage' if it isn't defined",
	"%   ('colortogray' and 'mergeprocs' come from xwd2ps",
	"%	 via xgrab)",
	"/colorimage where   % do we know about 'colorimage'?",
	"  { pop }		   % yes: pop off the 'dict' returned",
	"  {				 % no:  define one",
	"	/colortogray {  % define an RGB->I function",
	"	  /rgbdata exch store	% call input 'rgbdata'",
	"	  rgbdata length 3 idiv",
	"	  /npixls exch store",
	"	  /rgbindx 0 store",
	"	  /grays npixls string store  % str to hold the result",
	"	  0 1 npixls 1 sub {",
	"		grays exch",
	"		rgbdata rgbindx	   get 20 mul	% Red",
	"		rgbdata rgbindx 1 add get 32 mul	% Green",
	"		rgbdata rgbindx 2 add get 12 mul	% Blue",
	"		add add 64 idiv	  % I = .5G + .31R + .18B",
	"		put",
	"		/rgbindx rgbindx 3 add store",
	"	  } for",
	"	  grays",
	"	} bind def\n",
	/* Utility procedure for colorimage operator.
	 * This procedure takes two procedures off the
	 * stack and merges them into a single procedure
	 */
	"	/mergeprocs { % def",
	"	  dup length",
	"	  3 -1 roll dup length dup 5 1 roll",
	"	  3 -1 roll add array cvx dup",
	"	  3 -1 roll 0 exch putinterval",
	"	  dup 4 2 roll putinterval",
	"	} bind def\n",
	"	/colorimage { % def",
	/* remove 'false 3' operands */
	"	  pop pop",
	"	  {colortogray} mergeprocs",
	"	  image",
	"	} bind def",
	/* end of 'false' case */
	"  } ifelse"
    };

    PSconst_out(txt);
}


/*
 * PScolormap - write colormap
 *
 * spits out code for the colormap of the following image
 * if !color, it spits out a mono-ized graymap
 *
*/
static void
PScolormap(int color,
	   int nc,
	   int *rmap,
	   int *gmap,
	   int *bmap)
{
    int i;

    /*  define the colormap */
    PSprintf("/cmap %d string def\n\n\n", nc * ((color) ? 3 : 1));

    /*  load up the colormap */
    PSprintf("currentfile cmap readhexstring\n");

    for (i=0; i<nc; i++)
    {
	if (color)
	    PSprintf("%02x%02x%02x ", rmap[i]>>8,
		     gmap[i]>>8, bmap[i]>>8);
	else
	    PSprintf("%02x ", MONO(rmap[i], gmap[i], bmap[i]));
	if ((i%10) == 9)
	    PSprintf("\n");
    }
    PSprintf("\n");
    PSprintf("pop pop\n"); /* lose return values from readhexstring */
}


/*
 * PSrle_cmapimage - define rlecmapimage operator
 *
 */
static void
PSrle_cmapimage(int color)
{

    static char *txt[] = {

	/* rlecmapimage expects to have 'w h bits matrix' on stack */
	"/rlecmapimage {",
	"  /buffer 1 string def",
	"  /rgbval 3 string def",
	"  /block  384 string def",
	"  { currentfile buffer readhexstring pop",
	"	/bcount exch 0 get store",
	"	bcount 128 ge",
	"	{ ",
	"	  0 1 bcount 128 sub",
	"	{ currentfile buffer readhexstring pop pop"
    };

    static char *txt_color[] = {
	"		/rgbval cmap buffer 0 get 3 mul 3 getinterval store",
	"		block exch 3 mul rgbval putinterval",
	"	  } for",
	"	  block  0  bcount 127 sub 3 mul  getinterval",
	"	}",
	"	{ ",
	"	  currentfile buffer readhexstring pop pop",
	"	  /rgbval cmap buffer 0 get 3 mul 3 getinterval store",
	"	  0 1 bcount { block exch 3 mul rgbval putinterval } for",
	"	  block 0 bcount 1 add 3 mul getinterval",
	"	} ifelse",
	"  }",
	"  false 3 colorimage",
	"} bind def"
    };

    static char *txt_gray[] = {
	"		/rgbval cmap buffer 0 get 1 getinterval store",
	"		block exch rgbval putinterval",
	"	  } for",
	"	  block  0  bcount 127 sub  getinterval",
	"	}",
	"	{ ",
	"	  currentfile buffer readhexstring pop pop",
	"	  /rgbval cmap buffer 0 get 1 getinterval store",
	"	  0 1 bcount { block exch rgbval putinterval } for",
	"	  block 0 bcount 1 add getinterval",
	"	} ifelse",
	"  }",
	"  image",
	"} bind def"
    };

    PSconst_out(txt);
    if (color)
    {
	PSconst_out(txt_color);
    }
    else
    {
	PSconst_out(txt_gray);
    }
}


/*
 * PSwrite_bw - write B&W image
 *
 * Write the given image array 'pic' (B/W stippled, 1 byte per pixel,
 * 0=blk,1=wht) out as hexadecimal, max of 72 hex chars per line.  If
 * 'flipbw', then 0=white, 1=black.  Returns '0' if everythings fine,
 * 'EOF' if writing failed.
 *
*/
static int
PSwrite_bw(unsigned char *pic, int w, int h, int flipbw)
{
    int	i, j;
    int	err=0;
    unsigned char outbyte, bitnum, bit;

    outbyte = bitnum = 0;
    for (i=0; i<h && err != EOF; i++) {
	for (j=0; j<w && err != EOF; j++) {
	    bit = *(pic++);
	    outbyte = (outbyte<<1) | ((bit)&0x01);
	    bitnum++;

	    if (bitnum==8) {
		if (flipbw)
		    outbyte = ~outbyte & 0xff;
		err=PShex(outbyte, False);
		outbyte = bitnum = 0;
	    }
	}
	if (bitnum) {	/*  few bits left over in this row */
	    outbyte <<= 8-bitnum;
	    if (flipbw)
		outbyte = ~outbyte & 0xff;
	    err=PShex(outbyte, False);
	    outbyte = bitnum = 0;
	}
    }
    err=PShex('\0', True);	/*  Flush the hex buffer if needed */

    return err;
}


/*
 * PSimage - generate image Postscript code
 *
 * Draw the image, unless there was no image, in which case an empty grey
 * rectangle is shown.
 * If anchor is set, a black border is shown around the image.
 * Positioning is not exactly that of Xmosaic's screen, but close enough.
 *
*/

static void
PSimage(HTMLWidget hw, struct ele_rec *eptr, int fontfamily)
{
    ImageInfo 		*img = eptr->pic_data;
    unsigned char 	*imgp = img->image_data;
    int 		anchor = (eptr->anchorHRef != NULL);
    int 		ncolors = img->num_colors;
    int 		i, j;
    int 		w = img->width;
    int 		h = img->height;
    int 		slen, colorps, colortype, bits;
    int 		err=0;
    int 		extra = 0;


    /* Isgray returns true if the nth color index is a gray value */
#	define Isgray(i,n) (i->reds[n]==i->greens[n] && i->reds[n]==i->blues[n])
    /* Is_bg returns true if the nth color index is the screen background */
#	define Is_bg(i,n)  (i->reds[n]==bg_color.red &&			\
		i->greens[n]==bg_color.green && i->blues[n]==bg_color.blue)
    /* Is_fg returns true if the nth color index is the screen foreground */
#	define Is_fg(i,n)  (i->reds[n]==fg_color.red &&			\
		i->greens[n]==fg_color.green && i->blues[n]==fg_color.blue)


    PSmove_offset(eptr->y_offset);
    if (anchor)
    {
	/*  draw an outline by drawing a slightly larger black square
	 *  below the actual image
	 */
	PSprintf("gsave currentpoint %d sub translate ", h);
	PSprintf("0 -2 translate %d %d scale\n", w+4, h+4);
	PSprintf("SQ fill\n");
	PSprintf("grestore\n");
	extra = 4;
    }

    if (imgp == NULL)
    {
	/*  image was not available... do something instead
	 *  draw an empty square for example
	 */
	PSprintf("gsave currentpoint %d sub translate", h);
	if (anchor)
	    PSprintf(" 2 0 translate");
	else
	    PSprintf(" 0 2 translate");
	PSprintf(" %d %d scale\n", w, h);
	PSprintf("0.9 setgray SQ fill\n");
	PSprintf("grestore\n");
	/*  move currentpoint just right of image */
	PSprintf("%d 0 R\n", w+extra);
	return;
    }

    /*  this is a hack to see if the image is Black & White,
     *  Greyscale or 8 bit color
     *  assume it's bw if it has only one or two colors, both some grey's
     *  assume it's greyscale if all the colors (>2) are grey
     *  Images with only one color do occur too.
     */

    if (   (   (ncolors == 2)
	    && (   (Isgray(img,0) && Isgray(img,1))
	        || (Is_bg(img,0) && Is_fg(img,1))
	        || (Is_fg(img,0) && Is_bg(img,1)) ))
	|| (   (ncolors == 1)
	    && (Isgray(img,0)
		|| Is_bg(img,0)
		|| Is_fg(img,0))))
    {
	colortype = F_BWDITHER;
	slen = (w+7)/8;
	bits = 1;
	colorps = 0;
    } else {
	colortype = F_GREYSCALE;
	slen = w;
	bits = 8;
	colorps = 0;
	for (i=0; i<ncolors; i++) {
	    if (!Isgray(img,i)) {
		colortype = F_REDUCED;
		slen = w*3;
		bits = 8;
		colorps = 1;
		break;
	    }
	}
    }

    /*  build a temporary dictionary */
    PSprintf("20 dict begin\n\n");

    /*  define string to hold a scanline's worth of data */
    PSprintf("/pix %d string def\n\n", slen);

    /*  position and scaling */
    PSprintf("gsave currentpoint %d sub translate", h);
    if (anchor)
	PSprintf(" 2 0 translate");
    else
	PSprintf(" 0 2 translate");
    PSprintf(" %d %d scale\n", w, h);

    if (colortype == F_BWDITHER)
    {
	/*  1-bit dither code uses 'image' */
	int flipbw = 0;

	/*  set if color#0 is 'white' */
	if ((ncolors == 2 &&
	     MONO(img->reds[0], img->greens[0],img->blues[0]) >
	     MONO(img->reds[1], img->greens[1], img->blues[1])) ||
	    (ncolors == 1 &&
	     MONO(img->reds[0], img->greens[0],img->blues[0]) >
	     MONO(127, 127, 127) ))
	{
	    flipbw=1;
	}

	/*  dimensions of data */
	PSprintf("%d %d %d\n", w, h, bits);

	/*  mapping matrix */
	PSprintf("[%d 0 0 %d 0 %d]\n\n", w, -h, h);

	PSprintf("{currentfile pix readhexstring pop}\n");
	PSprintf("image\n");

	/*  write the actual image data */
	err = PSwrite_bw(imgp, w, h, flipbw);
    }
    else
    {
	/*  all other formats */
	unsigned char *rleline = (unsigned char *) NULL;
	int rlen;

	/*  if we're using color, make sure 'colorimage' is defined */
	if (colorps)
	    PScolor_image();

	PScolormap(colorps, ncolors, img->reds, img->greens, img->blues);
	PSrle_cmapimage(colorps);

	/*  dimensions of data */
	PSprintf("%d %d %d\n", w, h, bits);
	/*  mapping matrix */
	PSprintf("[%d 0 0 %d 0 %d]\n", w, -h, h);
	PSprintf("rlecmapimage\n");

	rleline = (unsigned char *) malloc(w * 2);
	if (!rleline)
	{
#ifndef DISABLE_TRACE
		if (htmlwTrace) {
			fprintf(stderr,"failed to malloc space for rleline\n");
		}
#endif

		return;
	}

	for (i=0; i<h && err != EOF; i++)
	{
	    rlen = PSrle_encode(imgp, rleline, w);
	    imgp += w;
	    for (j=0; j<rlen && err != EOF; j++)
		err=PShex(rleline[j], False);
	    err=PShex('\0', True);	/*  Flush the hex buffer */
	}
	free(rleline);
    }

    /*  stop using temporary dictionary */
    PSprintf("end\n");
    PSprintf("grestore\n");

    /*  move currentpoint just right of image */
    PSprintf("%d 0 R\n", w + extra);
    if (HTML_Print_Footers && has_footnote(eptr))
    {
	PSmove_offset(0);
	PSfootnote(eptr->anchorHRef, 2.0);
    }


    /* forget about the macro's */
#	undef Isgray
#	undef Is_fg
#	undef Is_bg
}



/*
 * ParseTextToPSString - entry point for postscript output
 *
 * Parse all the formatted text elements from start to end
 * into an ascii text string, and return it.
 * Very like ParseTextToString() except the text is prettied up
 * into Postscript to show headers and the like.
 * space_width and lmargin tell us how many spaces
 * to indent lines.
 * Because this routine is only used to print whole documents,
 * some parameters are not needed at all!
 * Also it assumes that you are indeed printing the whole document, and
 * not just a selected portion of it. It therefore can assume that
 * only for the first page the initialization is needed, and only
 * the last page has the trailers. You cannot use ParseTextToPSString()
 * as you can use ParseTextToString() because of this initialization code.
 *
 * the fontfamily parameter is new
 * The font is encoded as:
 *	0: times (default for now)
 *	1: helvetica
 *	2: new century schoolbook
 *	3: lucida
 */
String ParseTextToPSString(HTMLWidget	 	hw,
			   struct ele_rec	*elist,
			   struct ele_rec	*startp,
			   struct ele_rec	*endp,
			   int			start_pos,
			   int			end_pos,
			   int			space_width,
			   int			lmargin,
			   int			fontfamily,
			   char   		*url,
			   char	   		*time_str)
{
    int			xpos, ypos, epos;
    int			height;
    double		pagewidth;
    int			line = -1;
    struct		ele_rec	*eptr;
    struct		ele_rec	*start;
    struct		ele_rec	*end;
    struct		ele_rec	*last;
    struct		ele_rec	*tmpptr;
    unsigned long 	fg_pixel, bg_pixel;
    int			footnotes_this_page = 0;
    int			footnotes_this_line;
    int			reserved_space;

    if (startp == NULL)
	return(NULL);


    /*
     * Get the foreground and background colors so we can check later
     * for black&white documents
     */
    XtVaGetValues (hw->html.view,
#ifdef MOTIF
		   XtNforeground, &fg_pixel,
#endif
		   XtNbackground, &bg_pixel,
		   NULL);
#ifndef MOTIF
    XtVaGetValues ((Widget)hw,
		   XtNforeground, &fg_pixel,
		   NULL);
#endif
    fg_color.pixel = fg_pixel;
    bg_color.pixel = bg_pixel;
    XQueryColor(XtDisplay(hw->html.view),
		(installed_colormap ?
		 installed_cmap :
		 DefaultColormap(XtDisplay(hw->html.view),
				DefaultScreen(XtDisplay(hw->html.view)))),
		&fg_color);
    XQueryColor(XtDisplay(hw->html.view),
		(installed_colormap ?
		 installed_cmap :
		 DefaultColormap(XtDisplay(hw->html.view),
				 DefaultScreen(XtDisplay(hw->html.view)))),
		&bg_color);

    /*  this piece of code is needed if the user selects a portion
     *  of the document with the mouse.
     *  I think it will never be used, but I left it in anyway. F.
     */
    if (SwapElements(startp, endp, start_pos, end_pos))
    {
	start = endp;
	end = startp;
	epos = start_pos;
	start_pos = end_pos;
	end_pos = epos;
    }
    else
    {
	start = startp;
	end = endp;
    }

    /* Setup page size according to user preference. */

    if (HTML_Print_Paper_Size_A4)
	page_dimens = a4_page_dimens;
    else
	page_dimens = us_letter_page_dimens;

    page_dimens.text_height = (  page_dimens.page_height
			       - page_dimens.top_margin
			       - page_dimens.bot_margin);
    page_dimens.text_width  = (  page_dimens.page_width
			       - page_dimens.left_margin
			       - page_dimens.right_margin);

    /* Calculate the number of Postscript points per pixel of current
     * screen, and the height of the page in pixels (used in figuring
     * when we've hit the bottom of the page).
     */
    Points_Pixel = 72.0 / GetDpi(hw);
#ifdef OLD
    pagewidth = hw->html.doc_width;
#else /* gustaf fix */
    pagewidth = hw->html.view_width;
#endif /* gustaf fix */

    /* reduce the scaling if the width used for formatting is greater
     * than 8 * 72 pixels (8 inch)
     * In theory, this is not what you want for A4 paper (only 8.27 inch
     * wide), but I guess that the hw->html.doc_width includes some
     * left and right margins, so it seems to work in practice.
     */
    if (pagewidth > page_dimens.text_width)
	Points_Pixel = Points_Pixel * page_dimens.text_width / pagewidth;
    Pixels_Page = (int) (page_dimens.text_height / Points_Pixel);

    PSinit();
    PSheader(hw->html.title, fontfamily, url, time_str);
    PSnewpage();

    last = start;
    eptr = start;

    while ((eptr != NULL) && (eptr != end))
    {
	/* Skip the special internal text added for multi-page
	 * documents.
	 */
	if (eptr->internal == True)
	{
	    if (eptr->type == E_LINEFEED)
	    {
		PS_page_offset += eptr->line_height;
	    }
	    eptr = eptr->next;
	    continue;
	}

	/* check if this is a newline */
	if (line != eptr->line_number)
	{
	    /* calculate max height */
	    height = 0;
	    footnotes_this_line = 0;
	    line = eptr->line_number;
	    tmpptr = eptr;
	    while (tmpptr != NULL && tmpptr->line_number == line)
	    {
		if (tmpptr->line_height > height)
		    height = tmpptr->line_height;
		tmpptr = tmpptr->next;
		if (HTML_Print_Footers && has_footnote(tmpptr))
		    footnotes_this_line++;
	    }
	    ypos = eptr->y - PS_page_offset ;
	    xpos = eptr->x - lmargin;
	    if (xpos < 0)
		xpos = 0;

	    /* check if line fits completly on page */

	    reserved_space = 0;
	    if (footnotes_this_page || footnotes_this_line)
	    {
		reserved_space = (  (  footnote_space
				     + (  footnote_ptsize
					* (  footnotes_this_page
					   + footnotes_this_line)))
				  / Points_Pixel);
	    }

	    if (ypos + height + reserved_space > PS_start_y + Pixels_Page)
	    {
		PS_start_y = ypos;
		PSshowpage();
		PSnewpage();
		footnotes_this_page = 0;
	    }
	    footnotes_this_page += footnotes_this_line;
	    PSmoveto( xpos, ypos);
	}


	switch (eptr->type)
	{
	case E_TEXT:
	    PStext(hw, eptr, fontfamily,
		   (String)((eptr == start) ? (eptr->edata + start_pos) : eptr->edata));
	    break;

	case E_BULLET:
	    PSbullet(hw, eptr, fontfamily);
	    break;

	case E_IMAGE:
	    PSimage(hw, eptr, fontfamily);
	    break;

	case E_LINEFEED:
	    break;

	case E_HRULE:
	    PShrule(hw, eptr, fontfamily);
	    break;

	case E_TABLE:
	    PStable(hw, eptr, fontfamily);
	    break;

	case E_WIDGET:
	    PSwidget(hw, eptr, fontfamily);
	    break;

	}
	last = eptr;
	eptr = eptr->next;
    }

    PSshowpage();
    PStrailer();

    return (PS_string);
}

