/*@z49.c:PostScript Back End:PS_BackEnd@**************************************/
/*                                                                           */
/*  THE LOUT DOCUMENT FORMATTING SYSTEM (VERSION 3.42)                       */
/*  COPYRIGHT (C) 1991, 2008 Jeffrey H. Kingston                             */
/*                                                                           */
/*  Jeffrey H. Kingston (jeff@it.usyd.edu.au)                                */
/*  School of Information Technologies                                       */
/*  The University of Sydney 2006                                            */
/*  AUSTRALIA                                                                */
/*                                                                           */
/*  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 3, 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 USA   */
/*                                                                           */
/*  FILE:         z49.c                                                      */
/*  MODULE:       PostScript Back End                                        */
/*  EXTERNS:      PS_BackEnd                                                 */
/*                                                                           */
/*****************************************************************************/
#include "externs.h"

#define pnl fputs((char *) STR_NEWLINE, out_fp)
#define p0(str)                 { fputs(str, out_fp);                   pnl; }
#define p1(fmt, a1)             { fprintf(out_fp, fmt, a1);             pnl; }
#define p2(fmt, a1, a2)         { fprintf(out_fp, fmt, a1, a2);         pnl; }
#define p3(fmt, a1, a2, a3)     { fprintf(out_fp, fmt, a1, a2, a3);     pnl; }
#define p4(fmt, a1, a2, a3, a4) { fprintf(out_fp, fmt, a1, a2, a3, a4); pnl; }


/*****************************************************************************/
/*                                                                           */
/*  State variables for this module - general                                */
/*                                                                           */
/*****************************************************************************/
#define StartUpResource "LoutStartUp"
#define NO_FONT		0		/* actually stolen from z37.c        */
#define NO_COLOUR	0
#define NO_TEXTURE	0
#define MAX_GS		50		/* maximum depth of graphics states  */
#define	STRING_SIZE	16000		/* used by forms code                */

BOOLEAN			encapsulated;	/* TRUE if EPS file is wanted	     */
static int		wordcount;	/* atoms printed since last newline  */
static int		pagecount;	/* total number of pages printed     */
static BOOLEAN		prologue_done;	/* TRUE after prologue is printed    */
static OBJECT		needs;		/* Resource needs of included EPSFs  */
static OBJECT		supplied;	/* Resources supplied by this file   */
static OBJECT		incg_files;	 /* IncludeGraphicRepeated files */
static FILE		*out_fp;	/* file to print PostScript on       */


/*****************************************************************************/
/*                                                                           */
/*  State variables for this module - graphics state                         */
/*                                                                           */
/*****************************************************************************/

typedef struct
{
  FONT_NUM	gs_font;		/* font number of this state         */
  BOOLEAN	gs_baselinemark;	/* baseline mark in use              */
  COLOUR_NUM	gs_colour;		/* colour number of this state       */
  TEXTURE_NUM	gs_texture;		/* texture number of this state      */
  BOOLEAN	gs_cpexists;		/* TRUE if a current point exists    */
  FULL_LENGTH	gs_currenty;		/* if cpexists, its y coordinate     */
  short		gs_xheight2;		/* of font exists, half xheight      */
} GRAPHICS_STATE;

static GRAPHICS_STATE	gs_stack[MAX_GS];/* graphics state stack             */
static int		gs_stack_top;	/* top of graphics state stack       */

static FONT_NUM		currentfont;	/* font of most recent atom          */
static BOOLEAN		currentbaselinemark;	/* current baselinemark      */
static COLOUR_NUM	currentcolour;	/* colour of most recent atom        */
static TEXTURE_NUM	currenttexture;	/* texture of most recent atom       */
static short		currentxheight2;/* half xheight in current font      */
static BOOLEAN		cpexists;	/* true if a current point exists    */
static FULL_LENGTH	currenty;	/* if cpexists, its y coordinate     */


/*****************************************************************************/
/*                                                                           */
/*  SetBaseLineMarkAndFont(BOOLEAN blm, FONT_NUM f)                          */
/*                                                                           */
/*  Ensure these are the current baselinemark and font, both in              */
/*  currentbaselinemark and currentfont, and in the PostScript output.       */
/*                                                                           */
/*  The code allows for the possibility that the current font could be       */
/*  NO_FONT.  This is for DefineGraphicNames only, it should never           */
/*  happen otherwise.                                                        */
/*                                                                           */
/*****************************************************************************/

static void SetBaseLineMarkAndFont(BOOLEAN blm, FONT_NUM f)
{
  /* if baselinemark has changed then record change */
  if( blm != currentbaselinemark )
  { currentbaselinemark = blm;
    if( currentfont != NO_FONT )
      currentxheight2 = currentbaselinemark ? 0 : FontHalfXHeight(currentfont);
  }

  /* if font has changed then record and print change */
  if( f != currentfont )
  { currentfont = f;
    if( currentfont != NO_FONT )
    {
      currentxheight2 = currentbaselinemark ? 0 : FontHalfXHeight(currentfont);
      fprintf(out_fp, "%hd %s", FontSize(currentfont, nilobj),
        FontName(currentfont));
      if( ++wordcount >= 5 )
      { pnl;
        wordcount = 0;
      }
      else putc(' ', out_fp);
    }
  }
} /* end SetBaseLineMarkAndFont */


/*****************************************************************************/
/*                                                                           */
/*  SetColourAndTexture(COLOUR_NUM col, TEXTURE_NUM pat)                     */
/*                                                                           */
/*  Ensure these are the current colour and texture, both in currentcolour   */
/*  and currenttexture, and in the PostScript output.                        */
/*                                                                           */
/*****************************************************************************/

static void SetColourAndTexture(COLOUR_NUM col, TEXTURE_NUM pat)
{
  /* if colour has changed then record and print change */
  if( col != currentcolour )
  { currentcolour = col;
    if( currentcolour > 0 )
    { fprintf(out_fp, "%s", ColourCommand(currentcolour));
      if( ++wordcount >= 5 )
      {	pnl;
	wordcount = 0;
      }
      else putc(' ', out_fp);
    }
  }

  /* if texture has changed then record and print change */
  if( pat != currenttexture )
  { currenttexture = pat;
    if( currenttexture > 0 )
    { fprintf(out_fp, "%s", TextureCommand(currenttexture));
      if( ++wordcount >= 5 )
      {	pnl;
	wordcount = 0;
      }
      else putc(' ', out_fp);
    }
  }

} /* end SetColourAndTexture */


/*****************************************************************************/
/*                                                                           */
/*  Data structures for checking links                                       */
/*                                                                           */
/*  We keep a hash table of all dest points, and an ordinary list of all     */
/*  source points.  To check that no dest point appears twice, we consult    */
/*  the hash table once for each dest point to ensure it is not already      */
/*  there.  To check that every source point has an dest, we run through     */
/*  the list of source points at end of run and look each one up in the      */
/*  dest point hash table.                                                   */
/*                                                                           */
/*****************************************************************************/

/*****************************************************************************/
/*                                                                           */
/*  LINK_DEST_TABLE                                                          */
/*                                                                           */
/*  A symbol table permitting access to link dest name objects.              */
/*  The table will automatically enlarge to accept any number of entries.    */
/*                                                                           */
/*     ltab_new(newsize)         New empty table, newsize capacity           */
/*     ltab_insert(x, &S)        Insert new link dest name object x into S   */
/*     ltab_retrieve(str, S)     Retrieve link dest name object named str    */
/*     ltab_debug(S, fp)         Debug print of table S to file fp           */
/*                                                                           */
/*****************************************************************************/

typedef struct
{ int linktab_size;				/* size of table             */
  int linktab_count;				/* number of objects held    */
  OBJECT linktab_item[1];
} *LINK_DEST_TABLE;

#define	ltab_size(S)	(S)->linktab_size
#define	ltab_count(S)	(S)->linktab_count
#define	ltab_item(S, i)	(S)->linktab_item[i]

#define hash(pos, str, S)						\
{ FULL_CHAR *p = str;							\
  pos = *p++;								\
  while( *p ) pos += *p++;						\
  pos = pos % ltab_size(S);						\
}

static LINK_DEST_TABLE ltab_new(int newsize)
{ LINK_DEST_TABLE S;  int i;
  /* ifdebug(DMA, D, DebugRegisterUsage(MEM_LINK_TAB, 1,
    2*sizeof(int) + newsize * sizeof(OBJECT))); */
  S = (LINK_DEST_TABLE)
	  malloc(2*sizeof(int) + newsize * sizeof(OBJECT));
  if( S == (LINK_DEST_TABLE) NULL )
    Error(49, 1, "run out of memory enlarging link dest table", FATAL, no_fpos);
  ltab_size(S) = newsize;
  ltab_count(S) = 0;
  for( i = 0;  i < newsize;  i++ )  ltab_item(S, i) = nilobj;
  return S;
} /* end ltab_new */

static void ltab_insert(OBJECT x, LINK_DEST_TABLE *S);

static LINK_DEST_TABLE ltab_rehash(LINK_DEST_TABLE S, int newsize)
{ LINK_DEST_TABLE NewS;  int i;  OBJECT z, link, y;
  NewS = ltab_new(newsize);
  for( i = 0;  i < ltab_size(S);  i++ )
  { z = ltab_item(S, i);
    if( z != nilobj )
    {
      for( link = Down(z);  link != z;  link = NextDown(link) )
      {
	Child(y, link);
	ltab_insert(y, &NewS);
      }
    }
  }
  free(S);
  return NewS;
} /* end ltab_rehash */

static void ltab_insert(OBJECT x, LINK_DEST_TABLE *S)
{ int pos;  OBJECT z, link, y;
  if( ltab_count(*S) >= ltab_size(*S) - 1 )	/* one less since 0 unused */
    *S = ltab_rehash(*S, 2*ltab_size(*S));
  hash(pos, string(x), *S);
  if( ltab_item(*S, pos) == nilobj )  New(ltab_item(*S, pos), ACAT);
  z = ltab_item(*S, pos);
  for( link = Down(z);  link != z;  link = NextDown(link) )
  { Child(y, link);
    if( StringEqual(string(x), string(y)) )
    { Error(49, 2, "link name %s used twice (first at%s)",
	WARN, &fpos(x), string(x), EchoFilePos(&fpos(y)));
    }
  }
  Link(ltab_item(*S, pos), x);
  ltab_count(*S)++;
} /* end ltab_insert */

static OBJECT ltab_retrieve(FULL_CHAR *str, LINK_DEST_TABLE S)
{ OBJECT x, link, y;  int pos;
  hash(pos, str, S);
  x = ltab_item(S, pos);
  if( x == nilobj )  return nilobj;
  for( link = Down(x);  link != x;  link = NextDown(link) )
  { Child(y, link);
    if( StringEqual(str, string(y)) )  return y;
  }
  return nilobj;
} /* end ltab_retrieve */

#if DEBUG_ON
static void ltab_debug(LINK_DEST_TABLE S, FILE *fp)
{ int i;  OBJECT x, link, y;
  fprintf(fp, "  table size: %d;  current number of keys: %d%s",
    ltab_size(S), ltab_count(S), STR_NEWLINE);
  for( i = 0;  i < ltab_size(S);  i++ )
  { x = ltab_item(S, i);
    fprintf(fp, "ltab_item(S, %d) =", i);
    if( x == nilobj )
      fprintf(fp, " <nilobj>");
    else if( type(x) != ACAT )
      fprintf(fp, " not ACAT!");
    else for( link = Down(x);  link != x;  link = NextDown(link) )
    { Child(y, link);
      fprintf(fp, " %s",
	is_word(type(y)) ? string(y) : AsciiToFull("not-WORD!"));
    }
    fprintf(fp, "%s", STR_NEWLINE);
  }
} /* end ltab_debug */
#endif

static	LINK_DEST_TABLE	link_dest_tab;		/* the link dest names       */
static	OBJECT		link_source_list;	/* the link source names     */

/*****************************************************************************/
/*                                                                           */
/*  Print a number x on file fp.                                             */
/*                                                                           */
/*****************************************************************************/

#define printnum(x, fp)							\
{ char buff[20];  register int i, y;					\
  if( x >= 0 )  y = x;							\
  else { y = -x; putc(CH_MINUS, fp); }					\
  i = 0;								\
  do { buff[i++] = numtodigitchar(y % 10);				\
     } while( (y = (y / 10)) > 0 );					\
  do { --i; putc(buff[i], fp);						\
     } while( i );							\
}

/*****************************************************************************/
/*                                                                           */
/*  PS_PrintInitialize(FILE *fp)                                             */
/*                                                                           */
/*  Initialize this module; fp is the output file.                           */
/*                                                                           */
/*****************************************************************************/

static void PS_PrintInitialize(FILE *fp, BOOLEAN enc)
{
  debug0(DPO, DD, "PS_PrintInitialize(fp)");
  out_fp = fp;
  encapsulated = enc;
  prologue_done = FALSE;
  gs_stack_top = -1;
  currentfont = NO_FONT;
  currentbaselinemark = FALSE;
  currentcolour = NO_COLOUR;
  currenttexture = NO_TEXTURE;
  cpexists = FALSE;
  wordcount = pagecount = 0;
  New(needs, ACAT);
  New(supplied, ACAT);
  incg_files = NULL;
  debug0(DPO, DD, "PS_PrintInitialize returning.");
  link_dest_tab = ltab_new(200);
  New(link_source_list, ACAT);
} /* end PS_PrintInitialize */


/*****************************************************************************/
/*                                                                           */
/*  void PS_PrintLength(FULL_CHAR *buff, int length, int length_dim)         */
/*                                                                           */
/*  Print a length (debugging only)                                          */
/*                                                                           */
/*****************************************************************************/

static void PS_PrintLength(FULL_CHAR *buff, int length, int length_dim)
{
  sprintf( (char *) buff, "%.3fc", (float) length/CM);
}


/*****************************************************************************/
/*                                                                           */
/*  void PS_IncGRepeated(OBJECT x)                                           */
/*                                                                           */
/*  Declare x to be an @IncludeGraphicRepeated file of the given type        */
/*  (either @IncludeGraphicRepeated or @SysIncludeGraphicRepeated).          */
/*                                                                           */
/*****************************************************************************/

void PS_IncGRepeated(OBJECT x)
{
  if( incg_files == nilobj )
    New(incg_files, ACAT);
  Link(incg_files, x);
}


/*****************************************************************************/
/*                                                                           */
/*  int PS_FindIncGRepeated(OBJECT x, int typ)                               */
/*                                                                           */
/*  Find the number of @IncludeGraphicRepeated file string(x), or else       */
/*  return 0 if not known.                                                   */
/*                                                                           */
/*  Check that the type conforms with typ, if not then warn user.            */
/*                                                                           */
/*****************************************************************************/

static int PS_FindIncGRepeated(OBJECT x, int typ)
{ OBJECT link, y;  int i;
  if( incg_files != nilobj )
  {
    for( i=1, link=Down(incg_files); link!=incg_files; i++, link=NextDown(link))
    {
      Child(y, link);
      if( StringEqual(string(x), string(y)) )
      {
	if( typ == INCGRAPHIC && incg_type(y) == SINCGRAPHIC )
	{
          Error(49, 15, "use of %s rather than %s contradicts prior %s at %s",
	    WARN, &fpos(x), KW_INCGRAPHIC, KW_SINCGRAPHIC,
	    KW_SINCG_REPEATED, EchoFilePos(&fpos(y)));
	}
	else if( typ == SINCGRAPHIC && incg_type(y) == INCGRAPHIC )
	{
          Error(49, 16, "use of %s rather than %s contradicts prior %s at %s",
	    WARN, &fpos(x), KW_SINCGRAPHIC, KW_INCGRAPHIC,
	    KW_INCG_REPEATED, EchoFilePos(&fpos(y)));
	}
	else
	  return i;
      }
    }
  }
  return 0;
}


/*****************************************************************************/
/*                                                                           */
/*  void PS_PrintPageSetupForFont(OBJECT face, int font_curr_page,           */
/*    FULL_CHAR *font_name, FULL_CHAR *short_name)                           */
/*                                                                           */
/*  Print the page setup commands required to use a font on some page:       */
/*                                                                           */
/*    face            The font face record, defining which font we need      */
/*    font_curr_page  The current page number                                */
/*    font_name       The name of the font                                   */
/*    short_name      Internal short name for the font                       */
/*                                                                           */
/*****************************************************************************/

static void PS_PrintPageSetupForFont(OBJECT face, int font_curr_page,
  FULL_CHAR *font_name, FULL_CHAR *short_name)
{
  p1("%%%%IncludeResource: font %s", font_name);
  if( font_recoded(face) )
  {
    MapEnsurePrinted(font_mapping(face), font_curr_page);
    p4("/%s%s %s /%s LoutRecode", font_name, short_name,
      MapEncodingName(font_mapping(face)), font_name);
    p3("/%s { /%s%s LoutFont } def", short_name, font_name, short_name);
  }
  else p2("/%s { /%s LoutFont } def", short_name, font_name);
} /* end PrintPageSetupForFont */


/*****************************************************************************/
/*                                                                           */
/*  void PS_PrintPageResourceForFont(FULL_CHAR *font_name, BOOLEAN first)    */
/*                                                                           */
/*  Print page resource info on file fp for font font_name; first is true    */
/*  if this is the first resource on this page.                              */
/*                                                                           */
/*****************************************************************************/

static void PS_PrintPageResourceForFont(FULL_CHAR *font_name, BOOLEAN first)
{
  p2("%s font %s", first ? "%%PageResources:" : "%%+", font_name);
} /* end PS_PrintPageResourceForFont */


/*****************************************************************************/
/*                                                                           */
/*  static void PS_PrintMapping(MAPPING m)                                   */
/*                                                                           */
/*  Print mapping m onto out_fp.                                             */
/*                                                                           */
/*****************************************************************************/

static void PS_PrintMapping(MAPPING m)
{ MAP_VEC map = MapTable[m]; int i;
  p1("%%%%BeginResource: encoding %s", string(map->name));
  p1("/%s [", string(map->name));
  for( i = 0;  i < MAX_CHARS;  i++ )
    fprintf(out_fp, "/%s%s", string(map->vector[i]),
      (i+1)%8 != 0 ? " " : (char *) STR_NEWLINE);
  p0("] def");
  p0("%%EndResource");
  pnl;
} /* end PrintMapping */


/*****************************************************************************/
/*                                                                           */
/*  PS_BtoI(const unsigned char *buf)                                        */
/*                                                                           */
/*  Decode a 4 byte value from an EPSF header                                */
/*  in a machine independent way.                                            */
/*                                                                           */
/*****************************************************************************/

static long PS_BtoI(const unsigned char *buf)
{
  return ((buf[ 0 ] & 0xFF)) |
         ((buf[ 1 ] & 0xFF) << 8) |
         ((buf[ 2 ] & 0xFF) << 16) |
         ((buf[ 3 ] & 0xFF) << 24);
}


/*****************************************************************************/
/*                                                                           */
/*  PS_FindEPSSegment(FILE *fp, long *len)                                   */
/*                                                                           */
/*  Find the EPS segment of an EPS file                                      */
/*    If the file is an EPSF with a EPS segment                              */
/*      skip to the start of the EPS segment                                 */
/*      set the length to the length of the EPS segment                      */
/*    Otherwise if the file is plain EPS                                     */
/*      leave the file pointer at the start of the file                      */
/*      set the length to -1                                                 */
/*                                                                           */
/*****************************************************************************/

static void PS_FindEPSSegment(FILE *fp, long *len)
{
  enum epsf_header_enum {
    HEADER_LEN = 30,           /* length of header */
    HEADER_MAGIC_0 = 0xC5,     /* E */
    HEADER_MAGIC_1 = 0xD0,     /* P */
    HEADER_MAGIC_2 = 0xD3,     /* S */
    HEADER_MAGIC_3 = 0xC6,     /* F */
    HEADER_EPS_START_OFFSET = 4, /* offset of eps start */
    HEADER_EPS_LENGTH_OFFSET = 8 /* offset of eps length */
  };

  unsigned char epsf_header[ HEADER_LEN ];
  long original_position;
  long eps_start;

  *len = -1;

  /* check for a valid file pointer */
  if( fp == NULL )
  {
    return;
  }

  /* save the original file position */
  original_position = ftell(fp);
  if( original_position < 0 )
  {
    original_position = 0;
  }

  /* read and parse the header */
  rewind(fp);
  if( fread(epsf_header, 1, HEADER_LEN, fp) == HEADER_LEN &&
      epsf_header[0] == HEADER_MAGIC_0 &&
      epsf_header[1] == HEADER_MAGIC_1 &&
      epsf_header[2] == HEADER_MAGIC_2 &&
      epsf_header[3] == HEADER_MAGIC_3 &&
      (eps_start = PS_BtoI( &epsf_header[ HEADER_EPS_START_OFFSET ] )) > 0 &&
      (*len = PS_BtoI( &epsf_header[ HEADER_EPS_LENGTH_OFFSET ] )) >= 0 &&
      fseek(fp, eps_start, SEEK_SET) != -1 )
  {
    /* success locating the EPS segment */
  }
  else
  {
    /* this file has no preview */
    /* reset the file pointer */
    *len = -1;
    fseek(fp, original_position, SEEK_SET);
  }
}


/*****************************************************************************/
/*                                                                           */
/*  void PS_PrintEPSFile(FILE *fp, FILE_POS *pos, BOOLEAN strip_all)         */
/*                                                                           */
/*  Original by Jeff Kingston, modified Jul 2008 by William Bader mainly     */
/*  to allow EPS files to contain null characters.                           */
/*                                                                           */
/*  Print EPS file fp to out_fp.                                             */
/*                                                                           */
/*  If strip_all is TRUE, strip out all lines beginning with %%, else        */
/*  strip out only the most dangerous ones.                                  */
/*                                                                           */
/*****************************************************************************/
#define	SKIPPING	0
#define	READING_DNR	1
#define FINISHED	2

static BOOLEAN strip_out(FULL_CHAR *buff, BOOLEAN strip_all)
{
  if( strip_all && StringBeginsWith(buff, AsciiToFull("%%")) ) return TRUE;
  if( StringBeginsWith(buff, AsciiToFull("%%EOF"))     )  return TRUE;
  if( StringBeginsWith(buff, AsciiToFull("%%Trailer")) )  return TRUE;
  return FALSE;
} /* end strip_out */

static void PS_PrintEPSFile(FILE *fp, FILE_POS *pos, BOOLEAN strip_all)
{ int state, x, count;  OBJECT y;  long len;
  FULL_CHAR buff[MAX_LINE];
  debug0(DPO, DD, "[ PS_PrintEPSFile");

  assert( fp != NULL, "PS_PrintEPSFile: fp!" );
  /* state = (ReadOneLine(fp, buff, MAX_LINE) == 0) ? FINISHED : SKIPPING; */
  PS_FindEPSSegment(fp, &len);   /* left out by Jeff's mistake in 3.37 */
  state = (ReadOneBinaryLine(fp, buff, MAX_LINE, &count, &len) == 0) ?
    FINISHED : SKIPPING;
  while( state != FINISHED ) switch(state)
  {
    case SKIPPING:

      if( StringBeginsWith(buff, AsciiToFull("%%DocumentNeededResources:")) &&
	  !StringContains(buff, AsciiToFull("(atend)")) )
      { y = MakeWord(WORD, &buff[StringLength("%%DocumentNeededResources:")],
	      no_fpos);
        Link(needs, y);
	/* ***
	state = (ReadOneLine(fp, buff, MAX_LINE) == 0) ? FINISHED : READING_DNR;
	*** */
	state = (ReadOneBinaryLine(fp, buff, MAX_LINE, &count, &len) == 0) ?
	  FINISHED : READING_DNR;
      }
      else
      { if( StringBeginsWith(buff, AsciiToFull("%%LanguageLevel:")) )
	{
	  if( sscanf((char *) buff, "%%%%LanguageLevel: %d", &x) != 1 || x > 2 )
	    Error(49,10,"ignoring LanguageLevel comment in EPS file",WARN,pos);
	}
	if( StringBeginsWith(buff, AsciiToFull("%%Extensions:")) )
	  Error(49, 11, "ignoring Extensions comment in EPS file", WARN, pos);
	if( !strip_out(buff, strip_all) )
	{
	  /* ***
	  StringFPuts(buff, out_fp);
	  pnl;
	  *** */
	  fwrite(buff, 1, count, out_fp);
	}
	/* ***
	state = (ReadOneLine(fp, buff, MAX_LINE) == 0) ? FINISHED : SKIPPING;
	*** */
	state = (ReadOneBinaryLine(fp, buff, MAX_LINE, &count, &len) == 0) ?
	  FINISHED : SKIPPING;
      }
      break;

    case READING_DNR:

      if( StringBeginsWith(buff, AsciiToFull("%%+")) )
      {	y = MakeWord(WORD, &buff[StringLength(AsciiToFull("%%+"))], no_fpos);
	Link(needs, y);
	/* ***
	state = (ReadOneLine(fp, buff, MAX_LINE) == 0) ? FINISHED : READING_DNR;
	*** */
	state = (ReadOneBinaryLine(fp, buff, MAX_LINE, &count, &len) == 0) ?
	  FINISHED : READING_DNR;
      }
      else
      { if( !strip_out(buff, strip_all) )
	{
	  /* ***
	  StringFPuts(buff, out_fp);
	  pnl;
	  *** */
	  fwrite(buff, 1, count, out_fp);
	}
	/* ***
	state = (ReadOneLine(fp, buff, MAX_LINE) == 0) ? FINISHED : SKIPPING;
	*** */
	state = (ReadOneBinaryLine(fp, buff, MAX_LINE, &count, &len) == 0) ?
	  FINISHED : SKIPPING;
      }
      break;
  }
  pnl; /* in case the last line does not end with a line terminator */
  fclose(fp);
  debug0(DPO, DD, "] PS_PrintEPSFile returning.");
} /* end PS_PrintEPSFile */


/*****************************************************************************/
/*                                                                           */
/*  char *MediaName(int h, int v)                                            */
/*                                                                           */
/*  Return the PostScript MediaName attribute appropriate to a page of       */
/*  width h and height v.                                                    */
/*                                                                           */
/*  Communicated by Valeriy E. Ushakov, who wrote:                           */
/*                                                                           */
/*  "Attached is a patch to recognize known paper sizes and emit them as     */
/*  media name in DocumentMedia comment.  GhostView and other PostScript     */
/*  viewers recognize these names and display them to the user.  Thus user   */
/*  knows what paper size document uses without having to know the magic     */
/*  numbers."                                                                */
/*                                                                           */
/*****************************************************************************/

static const char *MediaName(int h, int v)
{
    struct paper {
       const char *name;
       FULL_LENGTH width, height;
    };

    /* names for known paper sizes */
    static const struct paper paper_map[] = {
       { "Letter",     612*PT,  792*PT },
       { "Tabloid",    792*PT, 1224*PT },
       { "Ledger",    1224*PT,  792*PT },
       { "Legal",      612*PT, 1008*PT },
       { "Statement",  396*PT,  612*PT },
       { "Executive",  540*PT,  720*PT },
       { "A3",         842*PT, 1190*PT },
       { "A4",         595*PT,  842*PT },
       { "A5",         420*PT,  595*PT },
       { "B4",         729*PT, 1032*PT },
       { "B5",         516*PT,  729*PT },
       { "Folio",      612*PT,  936*PT },
       { "Quarto",     610*PT,  780*PT },
       { "10x14",      720*PT, 1008*PT },
       { NULL,              0,       0 }
    };

    /* default media name */
    static const char *user_defined = "Plain";

    const struct paper *p;
    for (p = paper_map; p->name; ++p) {
       if ((h == p->width) && (v == p->height)) {
           return p->name;
       }
    }
    return user_defined;
}


/*****************************************************************************/
/*                                                                           */
/*  static void PrintBeforeFirstPage(FULL_LENGTH h, FULL_LENGTH v,           */
/*    FULL_CHAR *label)                                                      */
/*                                                                           */
/*  Print whatever is needed before the start of the first page: the         */
/*  PostScript prologue, augmented with any @PrependGraphic or               */
/*  @SysPrependGraphic files specified by the user.  The following           */
/*  PostScript operators are defined:                                        */
/*                                                                           */
/*    scale_factor  fnt       scale and set font                             */
/*    x_coordinate  x         move to x_coordinate, current y coordinate     */
/*    string        s         show string                                    */
/*    number        in        result is number inches                        */
/*    number        cm        result is number centimetres                   */
/*    number        pt        result is number points                        */
/*    number        sp        result is number spaces                        */
/*    number        vs        result is number vspaces                       */
/*    number        ft        result is number font-sizes                    */
/*                                                                           */
/*  as well as LoutGraphic, for use with the @Graphic operator:              */
/*                                                                           */
/*    xsize ysize xmark ymark fr vs sp LoutGraphic -                         */
/*                                                                           */
/*  Define xmark, ymark, xsize, ysize to be the positions of                 */
/*  these features of x, and define symbols ft, vs and sp                    */
/*  to be the current font size, line separation, and space width.           */
/*                                                                           */
/*****************************************************************************/

static void PS_PrintBeforeFirstPage(FULL_LENGTH h, FULL_LENGTH v,
  FULL_CHAR *label)
{ FILE_NUM fnum;  FULL_CHAR *p;
  debug2(DPO, DD, "PrintBeforeFirst(%s, %s)", EchoLength(h), EchoLength(v));

  /* print header comments for PostScript DSC 3.0 output */
  p0(encapsulated ? "%!PS-Adobe-3.0 EPSF-3.0" : "%!PS-Adobe-3.0");
  p1("%%%%Creator: %s", LOUT_VERSION);
  p1("%%%%CreationDate: %s", TimeString());
  p0("%%DocumentData: Binary");
  p0("%%DocumentNeededResources: (atend)");
  p0("%%DocumentSuppliedResources: (atend)");
  p3("%%%%DocumentMedia: %s %d %d 0 white ()", MediaName(h, v), h/PT, v/PT);
  p0("%%PageOrder: Ascend");
  p0("%%LanguageLevel: 2");
  p0("%%Pages: (atend)");
  p2("%%%%BoundingBox: 0 0 %d %d", h/PT, v/PT);
  p0("%%EndComments");

  /* print procedure definitions part of header */
  p0("%%BeginProlog");
  if( encapsulated )
  {
    p0("50 dict begin  % EPSF defs in own dict");
    pnl;
  }

  p1("%%%%BeginResource: procset %s", StartUpResource);
  p0("/cp_x 0 def");
  p0("/cp_y 0 def");
  p0("/louts 0 def");
  p0("/loutv 0 def");
  p0("/loutf 0 def");
  p0("/ymark 0 def");
  p0("/xmark 0 def");
  p0("/ysize 0 def");
  p0("/xsize 0 def");

  p0("/save_cp { currentpoint /cp_y exch def /cp_x exch def } bind def");
  p0("/restore_cp { cp_x cp_y moveto } bind def");
  p0("/outline { gsave 1 1 1 setrgbcolor dup show save_cp");
  p0("  grestore true charpath stroke restore_cp } bind def");
  p0("/m  { 3 1 roll moveto show } bind def");
  p0("/mo { 3 1 roll moveto outline } bind def");
  p0("/s  { exch currentpoint exch pop moveto show } bind def");
  p0("/so { exch currentpoint exch pop moveto outline } bind def");
  p0("/k  { exch neg 0 rmoveto show } bind def");
  p0("/ko { exch neg 0 rmoveto outline } bind def");
  p0("/r  { exch 0 rmoveto show } bind def");
  p0("/ro { exch 0 rmoveto outline } bind def");
  p0("/c  { gsave 3 1 roll rmoveto show grestore } bind def");
  p0("/co { gsave 3 1 roll rmoveto outline grestore } bind def");
  p0("/ul { gsave setlinewidth dup 3 1 roll");
  p0("      moveto lineto stroke grestore } bind def");
  p1("/in { %d mul } bind def", IN);
  p1("/cm { %.3f mul } bind def", CM);
  p1("/pt { %d mul } bind def", PT);
  p1("/em { %d mul } bind def", EM);
  p0("/sp { louts mul } def");
  p0("/vs { loutv mul } def");
  p0("/ft { loutf mul } def");
  p0("/dg {           } def");
  pnl;

  p0("/LoutGraphic {");
  p0("  /louts exch def");
  p0("  /loutv exch def");
  p0("  /loutf exch def");
  p0("  /ymark exch def");
  p0("  /xmark exch def");
  p0("  /ysize exch def");
  p0("  /xsize exch def");
  p0("} def");
  pnl;

  p0("/LoutGr2 { gsave translate LoutGraphic gsave } def");
  pnl;

  /* print definition used by Lout output to recode fonts                */
  /* adapted from PostScript Language Reference Manual (2nd Ed), p. 275  */
  /* usage: /<fullname> <encodingvector> /<originalname> LoutRecode -    */

  p0("/LoutFont");
  p0("{ findfont exch scalefont setfont");
  p0("} bind def");
  pnl;

  p0("/LoutRecode {");
  p0("  { findfont dup length dict begin");
  p0("    {1 index /FID ne {def} {pop pop} ifelse} forall");
  p0("    /Encoding exch def");
  p0("    currentdict end definefont pop");
  p0("  }");
  p0("  stopped pop");
  p0("} bind def");
  pnl;

  /* print definitions used by Lout output when including EPSF files     */
  /* copied from PostScript Language Reference Manual (2nd Ed.), p. 726  */
  /* but then revised to follow Adobe's Technical Note #5144             */

  p0("/PreEPSF_state 0 def");
  p0("/dict_stack 0 def");
  p0("/ops_count 0 def");

  p0("/LoutStartEPSF { % prepare for EPSF inclusion");
  p0("  /PreEPSF_state save def");
  p0("  /dict_stack countdictstack def");
  p0("  /ops_count count 1 sub def");
  p0("  20 dict begin");
  p0("  /showpage {} def");
  p0("  0 setgray 0 setlinecap");
  p0("  1 setlinewidth 0 setlinejoin");
  p0("  10 setmiterlimit [] 0 setdash newpath");
  p0("  /languagelevel where");
  p0("  { pop languagelevel");
  p0("    1 ne");
  p0("    { false setstrokeadjust false setoverprint");
  p0("    } if");
  p0("  } if");
  p0("} bind def");
  pnl;
  p0("/LoutEPSFCleanUp { % clean up after EPSF inclusion");
  p0("  count ops_count sub { pop } repeat");
  p0("  countdictstack dict_stack sub { end } repeat");
  p0("  PreEPSF_state restore");
  p0("} bind def");
  pnl;

  /* print textures definitions - see Expert's Guide for explanation */
  p0("% Find current texture (may be null)");
  p0("% - LoutCurrentP p");
  p0("/LoutCurrentP");
  p0("{");
  p0("  currentcolorspace");
  p0("  0 get /Pattern eq");
  p0("  {");
  p0("    [ currentcolor ]");
  p0("    dup length 1 sub get");
  p0("  }");
  p0("  {");
  p0("    null");
  p0("  } ifelse");
  p0("} def");
  pnl;

  p0("% Find current color and color space");
  p0("% - LoutCurrentCCS c cs");
  p0("/LoutCurrentCCS");
  p0("{");
  p0("  LoutCurrentP dup null eq");
  p0("  {");
  p0("    pop [ currentcolor ]");
  p0("    currentcolorspace");
  p0("  }");
  p0("  {");
  p0("    dup");
  p0("    /UnderlyingColor get");
  p0("    exch /UnderlyingColorSpace get");
  p0("  } ifelse");
  p0("} def");
  pnl;

  p0("% Install c, cs, and (a copy of) p into graphics state");
  p0("% c cs p LoutSetCCSP -");
  p0("/LoutSetCCSP");
  p0("{");
  p0("  dup null eq");
  p0("  {");
  p0("    pop setcolorspace");
  p0("    aload pop setcolor");
  p0("  }");
  p0("  {");
  p0("    % copy pattern dictionary");
  p0("    12 dict copy");
  pnl;

  p0("    % record cs and c in p");
  p0("    dup /UnderlyingColorSpace 3 index");
  p0("    put");
  p0("    dup /UnderlyingColor 4 index");
  p0("    put");
  pnl;

  p0("    % do setcolorspace and setcolor");
  p0("    dup /PaintType get 1 eq");
  p0("    {");
  p0("      [ /Pattern ] setcolorspace");
  p0("      setcolor");
  p0("      pop pop");
  p0("    }");
  p0("    {");
  p0("      [ /Pattern");
  p0("      4 -1 roll");
  p0("      ] setcolorspace");
  p0("      exch aload length 1 add");
  p0("      -1 roll");
  p0("      setcolor");
  p0("    } ifelse");
  p0("  } ifelse");
  p0("} bind def");
  pnl;

  p0("% num LoutSetGray -");
  p0("/LoutSetGray");
  p0("{");
  p0("  [ 2 1 roll ]");
  p0("  [ /DeviceGray ]");
  p0("  LoutCurrentP");
  p0("  LoutSetCCSP");
  p0("} bind def");
  pnl;

  p0("% r g b LoutSetRGBColor -");
  p0("/LoutSetRGBColor");
  p0("{");
  p0("  [ 4 1 roll ]");
  p0("  [ /DeviceRGB ]");
  p0("  LoutCurrentP");
  p0("  LoutSetCCSP");
  p0("} bind def");
  pnl;

  p0("% h s b LoutSetHSBColor -");
  p0("/LoutSetHSBColor");
  p0("{");
  p0("  gsave sethsbcolor");
  p0("  currentrgbcolor grestore");
  p0("  LoutSetRGBColor");
  p0("} bind def");
  pnl;

  p0("% c m y k LoutSetRGBColor -");
  p0("/LoutSetCMYKColor");
  p0("{");
  p0("  [ 5 1 roll ]");
  p0("  [ /DeviceCMYK ]");
  p0("  LoutCurrentP");
  p0("  LoutSetCCSP");
  p0("} bind def");
  pnl;

  p0("% p LoutSetTexture -");
  p0("/LoutSetTexture");
  p0("{");
  if( UseTexture )
  {
    p0("  LoutCurrentCCS");
    p0("  3 -1 roll");
    p0("  LoutSetCCSP");
  }
  else
    p0("  pop % textures not used");
  p0("} bind def");
  pnl;

  p0("% <scale > <scalex> <scaley> <rotate> <hshift> <vshift>");
  p0("% <painttype> <bbox> <xstep> <ystep> <paintproc> LoutMakeTexture p");
  p0("/LoutMakeTexture");
  p0("{");
  p0("  12 dict begin");
  p0("  /PaintProc exch def");
  p0("  /YStep exch def");
  p0("  /XStep exch def");
  p0("  /BBox exch def");
  p0("  /PaintType exch def");
  p0("  /PatternType 1 def");
  p0("  /TilingType 1 def");
  p0("  currentdict end");
  p0("  7 1 roll");
  p0("  matrix translate");
  p0("  5 1 roll");
  p0("  matrix rotate");
  p0("  4 1 roll");
  p0("  matrix scale");
  p0("  exch dup matrix scale");
  p0("  matrix concatmatrix");
  p0("  matrix concatmatrix");
  p0("  matrix concatmatrix");
  p0("  /makepattern where");
  p0("  {");
  p0("    pop makepattern");
  p0("  }");
  p0("  {");
  p0("    pop pop null");
  p0("  } ifelse");
  p0("} bind def");
  pnl;

  p0("/LoutTextureSolid");
  p0("{");
  p0("  null");
  p0("  LoutSetTexture");
  p0("} bind def");

  /* PostScript forms, if needed */

  if( incg_files != nilobj )
  {
    pnl;
    p0("/LoutReadFormEPS {");
    p1("  currentfile 0 (Lout_Marker_%s)", (char *) TimeString());
    p0("  /SubFileDecode filter exch 1");
    p1("  { 2 copy 4 index %d string readstring 4 1 roll", STRING_SIZE);
    p0("    put not { exit } if 1 add");
    p0("  } loop");
    p0("  1 add 2 copy () put pop currentglobal true setglobal exch");
    p0("  0 4 array put setglobal pop");
    p0("} bind def");
  }

  p0("%%EndResource");
  pnl;

  /* prepend files (assumed to be organized as DSC 3.0 Resources) */
  for( fnum = FirstFile(PREPEND_FILE);  fnum != NO_FILE;  fnum=NextFile(fnum) )
  { FULL_CHAR buff[MAX_LINE];  FILE *fp;
    if( (fp = OpenFile(fnum, FALSE, FALSE)) == null )
      Error(49, 3, "cannot open %s file %s", FATAL, PosOfFile(fnum),
	KW_PREPEND, FileName(fnum));
    else if( ReadOneLine(fp, buff, MAX_LINE) == 0 )
      Error(49, 4, "%s file %s is empty", WARN, PosOfFile(fnum),
	KW_PREPEND, FileName(fnum));
    else
    {
      if( StringBeginsWith(buff, AsciiToFull("%%BeginResource:")) )
      { OBJECT tmp;
	tmp = MakeWord(WORD, &buff[strlen("%%BeginResource:")], no_fpos);
	Link(supplied, tmp);
      }
      else
	Error(49, 5, "%s file %s lacks PostScript BeginResource comment",
	  WARN, PosOfFile(fnum), KW_PREPEND, FileName(fnum));
      StringFPuts(buff, out_fp);
      pnl;
      p2("%% %s file %s", KW_PREPEND, FileName(fnum));
      while( ReadOneLine(fp, buff, MAX_LINE) != 0 )
      {
	StringFPuts(buff, out_fp);
	pnl;
      }
      pnl;
      fclose(fp);
    }
  }

  p0("%%EndProlog");
  pnl;
  p0("%%BeginSetup");

  /* print one PostScript form for each @IncludeGraphicRepeated entry */
  if( incg_files != nilobj )
  { int fnum;  FILE *fp;  BOOLEAN junk, cp;  OBJECT link, x, full_name;
    p0("<< /MaxFormItem currentsystemparams /MaxFormCache get >> setuserparams");
    pnl;
    fnum = 1;
    for( link = Down(incg_files);  link != incg_files;  link = NextDown(link) )
    {
      int file_size = 0;
      int llx = 0, lly = 0, urx = 0, ury = 0;

      /* open graphic file string(x) */
      Child(x, link);
      fp = OpenIncGraphicFile(string(x), incg_type(x), &full_name,&fpos(x),&cp);
      if( fp == null )
	Error(49, 21, "cannot open %s file %s", FATAL, &fpos(x),
	  KW_INCG_REPEATED, string(x));

      /* find its bounding box and file size */
      junk = PS_FindBoundingBox(fp, &fpos(x), &llx, &lly, &urx, &ury);
      fseek(fp, 0L, SEEK_END);
      file_size = ftell(fp);
      rewind(fp);

      /* print the form resource for this file                           */
      /* NB tech note says file_size / STRING_SIZE + 2 but really means  */
      /* ceiling(file_size / STRING_SIZE) + 2 so we use + 3              */
      p1("%%%%BeginResource: form Form%d", fnum);
      p1("/Form%d", fnum);
      p0("10 dict begin");
      p0("  /FormType 1 def");
      p1("  /EPSArray %d array def", file_size / STRING_SIZE + 3);
      p0("  /AcquisitionProc {");
      p0("    EPSArray dup 0 get dup 0 get");
      p0("    dup 3 1 roll 1 add 0 exch put get");
      p0("  } bind def");
      pnl;
      p0("  /PaintProc {");
      p0("    begin");
      p0("      EPSArray 0 get 1 save put");
      p0("      EPSArray 0 get 2 countdictstack put");
      p0("      EPSArray 0 get 3 count 2 sub put");
      p0("      20 dict begin");
      p0("      /showpage {} def");
      p0("      0 setgray 0 setlinecap");
      p0("      1 setlinewidth 0 setlinejoin");
      p0("      10 setmiterlimit [] 0 setdash newpath");
      p0("      /languagelevel where");
      p0("      { pop languagelevel 1 ne");
      p0("        { false setstrokeadjust false setoverprint } if");
      p0("      } if");
      p0("      EPSArray 0 get 0 1 put");
      p0("      //AcquisitionProc 0 () /SubFileDecode filter");
      p0("      cvx exec");
      p0("      count EPSArray 0 get 3 get sub { pop } repeat");
      p0("      countdictstack EPSArray 0 get 2 get sub { end } repeat");
      p0("      EPSArray 0 get 1 get restore");
      p0("    end");
      p0("  } bind def");
      pnl;
      p4("  /BBox [ %d %d %d %d ] def", llx, lly, urx, ury);
      p0("  /Matrix [1 0 0 1 0 0] def");
      p1("currentdict end def %% Form%d", fnum);
      pnl;
      p1("Form%d /EPSArray get", fnum);
      p0("LoutReadFormEPS");
      PS_PrintEPSFile(fp, &fpos(x), TRUE);
      p1("Lout_Marker_%s", (char *) TimeString());
      p0("%%EndResource");
      pnl;

      /* remove any unpacked version and go to next file */
      if( cp ) StringRemove(AsciiToFull(LOUT_EPS));
      fnum++;
    }
  }

  /* encodings */
  MapPrintEncodings();

  /* pdfmark compatibility code, as in the pdfmark Reference Manual p10 */
  p0("/pdfmark where {pop} {userdict /pdfmark /cleartomark load put} ifelse");

  p0("%%EndSetup");
  pnl;
  fprintf(out_fp, "%%%%Page: ");
  for( p = label;  *p != '\0';  p++ )
    fputs(EightBitToPrintForm[*p], out_fp);
  p1(" %d", ++pagecount);
  p0("%%BeginPageSetup");
  FontPrintPageResources(out_fp);
  p0("/pgsave save def");
  FontPrintPageSetup(out_fp);
  FontAdvanceCurrentPage();
  p2("%.4f dup scale %d setlinewidth", 1.0 / PT, PT/2);
  p0("%%EndPageSetup");
  pnl;
  prologue_done = TRUE;
} /* end PS_PrintBeforeFirstPage */


/*****************************************************************************/
/*                                                                           */
/*  PS_PrintAfterLastPage()                                                  */
/*                                                                           */
/*  Clean up this module and close output stream.                            */
/*                                                                           */
/*****************************************************************************/

static void PS_PrintAfterLastPage(void)
{ OBJECT x, link;  BOOLEAN first_need;
  if( prologue_done )
  { 
    pnl;
    p0("pgsave restore");
    p0("showpage");
    if( encapsulated )
      p0("end % EPSF defs in own dict");
    pnl;
    p0("%%Trailer");

    /* print resource requirements (DSC 3.0 version) - fonts */
    first_need = FontNeeded(out_fp);

    /* print resource requirements (DSC 3.0 version) - included EPSFs  */
    for( link = Down(needs); link != needs; link = NextDown(link) )
    { Child(x, link);
      assert(is_word(type(x)), "PrintAfterLast: needs!" );
      p2("%s %s", first_need ? "%%DocumentNeededResources:" : "%%+", string(x));
      first_need = FALSE;
    }

    /* print resources supplied */
    p1("%%%%DocumentSuppliedResources: procset %s", StartUpResource);
    for( link = Down(supplied);  link != supplied;  link = NextDown(link) )
    { Child(x, link);
      p1("%%%%+ %s", string(x));
    }
    MapPrintPSResources(out_fp);

    p1("%%%%Pages: %d", pagecount);
    p0("%%EOF");
  }
} /* end PS_PrintAfterLastPage */


/*****************************************************************************/
/*                                                                           */
/*  PS_PrintBetweenPages(h, v, label)                                        */
/*                                                                           */
/*  Start a new output component, of size h by v; label is the page label    */
/*  to attach to the %%Page comment.                                         */
/*                                                                           */
/*****************************************************************************/

static void PS_PrintBetweenPages(FULL_LENGTH h, FULL_LENGTH v, FULL_CHAR *label)
{ FULL_CHAR *p;
  debug2(DPO, DD, "PrintBetweenPages(%d, %d)", h, v);

  pnl;
  gs_stack_top = 0;
  cpexists = FALSE;
  currentfont = NO_FONT;
  currentbaselinemark = FALSE;
  currentcolour = NO_COLOUR;
  currenttexture = NO_TEXTURE;
  if( encapsulated )
  { PS_PrintAfterLastPage();
    Error(49, 6, "truncating -EPS document at end of first page",
      FATAL, no_fpos);
  }
  p0("pgsave restore");
  p0("showpage");
  pnl;
  fprintf(out_fp, "%%%%Page: ");
  for( p = label;  *p != '\0';  p++ )
    fputs(EightBitToPrintForm[*p], out_fp);
  p1(" %d", ++pagecount);
  p0("%%BeginPageSetup");
  FontPrintPageResources(out_fp);
  p0("/pgsave save def");
  FontPrintPageSetup(out_fp);
  FontAdvanceCurrentPage();
  p2("%.4f dup scale %d setlinewidth", 1.0 / PT, PT/2);
  p0("%%EndPageSetup");
  wordcount = 0;
} /* end PS_PrintBetweenPages */


/*****************************************************************************/
/*                                                                           */
/*  static void PrintComposite(COMPOSITE *cp, BOOLEAN outline, FILE *fp)     */
/*                                                                           */
/*  Print composite character cp, assuming that the current point is         */
/*  set to the correct origin.  If outline is true, we want to print the     */
/*  composite character in outline.                                          */
/*                                                                           */
/*****************************************************************************/

static void PrintComposite(COMPOSITE *cp, BOOLEAN outline, FILE *fp)
{ debug1(DPO, DD, "PrintComposite(cp, %s, fp)", bool(outline));
  while( cp->char_code != '\0' )
  {
    debug4(DPO, DD, "  cp = %d printing code %d (%d, %d)", (int) cp,
      cp->char_code, cp->x_offset, cp->y_offset);
    fprintf(fp, "%d %d (%c)%s ", cp->x_offset, cp->y_offset,
      cp->char_code, outline ? "co" : "c");
    cp++;
  }
} /* end PrintComposite */


/*****************************************************************************/
/*                                                                           */
/*  PS_PrintWord(x, hpos, vpos)                                              */
/*                                                                           */
/*  Print non-empty word x; its marks cross at the point (hpos, vpos).       */
/*                                                                           */
/*****************************************************************************/

static void PS_PrintWord(OBJECT x, int hpos, int vpos)
{ FULL_CHAR *p, *q, *a, *b, *lig, *unacc;
  int ksize;  char *command;  MAPPING m;
  unsigned short *composite; COMPOSITE *cmp;

  debug7(DPO, D, "PrintWord( %s, %d, %d ) font %d colour %d texture %d%s",
    string(x), hpos, vpos, word_font(x), word_colour(x), word_texture(x),
    word_outline(x) ? " outline":"");
  TotalWordCount++;

  SetBaseLineMarkAndFont(word_baselinemark(x), word_font(x));
  SetColourAndTexture(word_colour(x), word_texture(x));

  /* convert ligature sequences into ligature characters */
  lig = finfo[word_font(x)].lig_table;
  p = q = string(x);
  do
  { 
    /* check for missing glyph (lig[] == 1) or ligatures (lig[] > 1) */
    if( lig[*q++ = *p++] )
    {
      if( lig[*(q-1)] == 1 || !word_ligatures(x) ) continue;
      else
      {	a = &lig[ lig[*(p-1)] + MAX_CHARS ];
	while( *a++ == *(p-1) )
	{ b = p;
	  while( *a == *b && *(a+1) != '\0' && *b != '\0' )  a++, b++;
	  if( *(a+1) == '\0' )
	  { *(q-1) = *a;
	    p = b;
	    break;
	  }
	  else
	  { while( *++a );
	    a++;
	  }
	}
      }
    }
  } while( *p );
  *q = '\0';

  /* move to coordinate of x */
  cmp = finfo[word_font(x)].cmp_table;
  composite = finfo[word_font(x)].composite;
  debug1(DPO, DDD, "  currentxheight2 = %d", currentxheight2);
  vpos = vpos - currentxheight2;
  if( cpexists && currenty == vpos )
  { printnum(hpos, out_fp);
    command = word_outline(x) ? "so" : "s";
  }
  else
  { currenty = vpos;
    printnum(hpos, out_fp);
    putc(' ', out_fp);
    printnum(currenty, out_fp);
    command = word_outline(x) ? "mo" : "m";
    cpexists = TRUE;
  }

  /* show string(x) */
  putc('(', out_fp);
  p = string(x);
  if( composite[*p] )
  {
    fprintf(out_fp, ")%s ", command);
    debug3(DPO, DD,
      "  calling PrintComposite(&cmp[composite[%d] = %d]); cmp_top = %d",
      (int) *p, composite[*p], finfo[word_font(x)].cmp_top);
    PrintComposite(&cmp[composite[*p]], word_outline(x), out_fp);
    printnum(finfo[word_font(x)].size_table[*p].right, out_fp);
    putc('(', out_fp);
    command = word_outline(x) ? "ro" : "r";
  }
  else fputs(EightBitToPrintForm[*p], out_fp);
  m = font_mapping(finfo[word_font(x)].font_table);
  unacc = MapTable[m]->map[MAP_UNACCENTED];
  /* acc   = MapTable[m]->map[MAP_ACCENT]; */
  for( p++;  *p;  p++ )
  { ksize = FontKernLength(word_font(x), unacc, *(p-1), *p);
    if( ksize != 0 )
    { fprintf(out_fp, ")%s %d(", command, -ksize);
      ++wordcount;
      command = word_outline(x) ? "ko" : "k";
    }
    if( composite[*p] )
    { fprintf(out_fp, ")%s ", command);
      debug3(DPO, DD,
	"  calling PrintComposite(&cmp[composite[%d] = %d]); cmp_top = %d",
	(int) *p, composite[*p], finfo[word_font(x)].cmp_top);
      PrintComposite(&cmp[composite[*p]], word_outline(x), out_fp);
      printnum(finfo[word_font(x)].size_table[*p].right, out_fp);
      putc('(', out_fp);
      command = word_outline(x) ? "ro" : "r";
    }
    else fputs(EightBitToPrintForm[*p], out_fp);
  }
  if( ++wordcount >= 5 )
  { p1(")%s", command);
    wordcount = 0;
  }
  else fprintf(out_fp, ")%s ", command);
  debug0(DPO, DD, "PrintWord returning");
} /* end PS_PrintWord */


/*****************************************************************************/
/*                                                                           */
/*  PS_PrintPlainGraphic(OBJECT x, FULL_LENGTH xmk, ymk, OBJECT z)           */
/*                                                                           */
/*  Print a plain graphic object                                             */
/*                                                                           */
/*****************************************************************************/

static void PS_PrintPlainGraphic(OBJECT x, FULL_LENGTH xmk,
  FULL_LENGTH ymk, OBJECT z)
{
  assert(FALSE, "PS_PrintPlainGraphic: this routine should never be called!");
} /* end PS_PrintPlainGraphic */


/*****************************************************************************/
/*                                                                           */
/*  PS_PrintUnderline(fnum, col, pat, xstart, xstop, ymk)                    */
/*                                                                           */
/*  Draw an underline suitable for font fnum, in colour col, from xstart to  */
/*  xstop at the appropriate distance below mark ymk.                        */
/*                                                                           */
/*****************************************************************************/

static void PS_PrintUnderline(FONT_NUM fnum, COLOUR_NUM col,
  TEXTURE_NUM pat, FULL_LENGTH xstart, FULL_LENGTH xstop, FULL_LENGTH ymk)
{
  debug5(DPO, DD, "PrintUnderline(fnt %d, col %d, xstart %s, xstop %s, ymk %s)",
    fnum, col, EchoLength(xstart), EchoLength(xstop), EchoLength(ymk));

  SetColourAndTexture(col, pat);

  /* now print the underline command */
  p4("%d %d %d %d ul", xstart, xstop, ymk - finfo[fnum].underline_pos,
    finfo[fnum].underline_thick);
  debug0(DPO, DD, "PrintUnderline returning.");
} /* end PS_PrintUnderline */


/*****************************************************************************/
/*                                                                           */
/*  PS_CoordTranslate(xdist, ydist)                                          */
/*                                                                           */
/*  Translate coordinate system by the given x and y distances.              */
/*                                                                           */
/*****************************************************************************/

static void PS_CoordTranslate(FULL_LENGTH xdist, FULL_LENGTH ydist)
{ debug2(DPO, DD, "PS_CoordTranslate(%s, %s)",
    EchoLength(xdist), EchoLength(ydist));
  p2("%d %d translate", xdist, ydist);
  cpexists = FALSE;
  debug0(DPO, DD, "PS_CoordTranslate returning.");
} /* end PS_CoordTranslate */


/*****************************************************************************/
/*                                                                           */
/*  PS_CoordRotate(amount)                                                   */
/*                                                                           */
/*  Rotate coordinate system by given amount (in internal DG units)          */
/*                                                                           */
/*****************************************************************************/

static void PS_CoordRotate(FULL_LENGTH amount)
{ debug1(DPO, DD, "PS_CoordRotate(%.1f degrees)", (float) amount / DG);
  p1("%.4f rotate", (float) amount / DG);
  cpexists = FALSE;
  debug0(DPO, DD, "CoordRotate returning.");
} /* end PS_CoordRotate */


/*****************************************************************************/
/*                                                                           */
/*  PS_CoordScale(ratio, dim)                                                */
/*                                                                           */
/*  Scale coordinate system by ratio in the given dimension.                 */
/*                                                                           */
/*****************************************************************************/

static void PS_CoordScale(float hfactor, float vfactor)
{
#if DEBUG_ON
  char buff[20];
#endif
  ifdebug(DPO, DD, sprintf(buff, "%.3f, %.3f", hfactor, vfactor));
  debug1(DPO, DD, "CoordScale(%s)", buff);
  p2("%.4f %.4f scale", hfactor, vfactor);
  cpexists = FALSE;
  debug0(DPO, DD, "CoordScale returning.");
} /* end PS_CoordScale */


/*****************************************************************************/
/*                                                                           */
/*  void PS_CoordHMirror(void)                                               */
/*                                                                           */
/*  Reflect coordinate system about y axis.                                  */
/*                                                                           */
/*****************************************************************************/

static void PS_CoordHMirror(void)
{
  debug0(DPO, DD, "CoordHMirror()");
  cpexists = FALSE;
  p0("[-1 0 0 1 0 0] concat");
  debug0(DPO, DD, "CoordHMirror returning.");
}


/*****************************************************************************/
/*                                                                           */
/*  void PS_CoordVMirror(void)                                               */
/*                                                                           */
/*  Reflect coordinate system about x axis.                                  */
/*                                                                           */
/*****************************************************************************/

static void PS_CoordVMirror(void)
{
  debug0(DPO, DD, "CoordVMirror()");
  cpexists = FALSE;
  p0("[1 0 0 -1 0 0] concat");
  debug0(DPO, DD, "CoordVMirror returning.");
}


/*****************************************************************************/
/*                                                                           */
/*  PS_SaveGraphicState(x)                                                   */
/*                                                                           */
/*  Save current coord system on stack for later restoration.                */
/*  Object x is just for error reporting, not really used at all.            */
/*                                                                           */
/*****************************************************************************/

static void PS_SaveGraphicState(OBJECT x)
{ debug0(DPO, DD, "SaveGraphicState()");
  p0("gsave");
  gs_stack_top++;
  if( gs_stack_top >= MAX_GS )
    Error(49, 7, "rotations, graphics etc. too deeply nested (max is %d)",
      FATAL, &fpos(x), MAX_GS);
  gs_stack[gs_stack_top].gs_font	= currentfont;
  gs_stack[gs_stack_top].gs_baselinemark= currentbaselinemark;
  gs_stack[gs_stack_top].gs_colour	= currentcolour;
  gs_stack[gs_stack_top].gs_texture	= currenttexture;
  gs_stack[gs_stack_top].gs_cpexists	= cpexists;
  gs_stack[gs_stack_top].gs_currenty	= currenty;
  gs_stack[gs_stack_top].gs_xheight2	= currentxheight2;
  debug0(DPO, DD, "PS_SaveGraphicState returning.");
} /* end PS_SaveGraphicState */


/*****************************************************************************/
/*                                                                           */
/*  PS_RestoreGraphicState(void)                                             */
/*                                                                           */
/*  Restore previously saved coordinate system.  NB we normally assume that  */
/*  no white space is needed before any item of output, but since this       */
/*  procedure is sometimes called immediately after PrintGraphicObject(),    */
/*  which does not append a concluding space, we prepend one here.           */
/*                                                                           */
/*****************************************************************************/

static void PS_RestoreGraphicState(void)
{ debug0(DPO, DD, "PS_RestoreGraphicState()");
  pnl;
  p0("grestore");
  currentfont	  = gs_stack[gs_stack_top].gs_font;
  currentbaselinemark	  = gs_stack[gs_stack_top].gs_baselinemark;
  currentcolour	  = gs_stack[gs_stack_top].gs_colour;
  currenttexture  = gs_stack[gs_stack_top].gs_texture;
  cpexists	  = gs_stack[gs_stack_top].gs_cpexists;
  currenty	  = gs_stack[gs_stack_top].gs_currenty;
  currentxheight2 = gs_stack[gs_stack_top].gs_xheight2;
  gs_stack_top--;
  debug0(DPO, DD, "PS_RestoreGraphicState returning.");
} /* end PS_RestoreGraphicState */


/*****************************************************************************/
/*                                                                           */
/*  PS_PrintGraphicObject(x)                                                 */
/*                                                                           */
/*  Print object x on out_fp                                                 */
/*                                                                           */
/*****************************************************************************/

static void PS_PrintGraphicObject(OBJECT x)
{ OBJECT y, link;
  debug3(DPO, DD, "PS_PrintGraphicObject(%s %s %s)",
    EchoFilePos(&fpos(x)), Image(type(x)), EchoObject(x));
  switch( type(x) )
  {
    case WORD:
    case QWORD:

      StringFPuts(string(x), out_fp);
      break;
	

    case ACAT:
    
      for( link = Down(x);  link != x;  link = NextDown(link) )
      {	Child(y, link);
	if( type(y) == GAP_OBJ )
	{
	  if( vspace(y) > 0 )  pnl;
	  else if( hspace(y) > 0 ) fputs(" ", out_fp);
	}
	else if( is_word(type(y)) || type(y) == ACAT )
	  PS_PrintGraphicObject(y);
	else if( type(y) == WIDE || is_index(type(y)) )
	{
	  /* ignore: @Wide, indexes are sometimes inserted by Manifest */
	}
	else
	{ Error(49, 8, "error in left parameter of %s",
	    WARN, &fpos(x), KW_GRAPHIC);
	  debug1(DPO, DD, "  type(y) = %s, y =", Image(type(y)));
	  ifdebug(DPO, DD, DebugObject(y));
	}
      }
      break;


    default:
    
      Error(49, 9, "error in left parameter of %s", WARN, &fpos(x), KW_GRAPHIC);
      debug1(DPO, DD, "  type(x) = %s, x =", Image(type(x)));
      ifdebug(DPO, DD, DebugObject(x));
      break;

  }
  debug0(DPO, DD, "PS_PrintGraphicObject returning");
} /* end PS_PrintGraphicObject */


/*****************************************************************************/
/*                                                                           */
/*  PS_DefineGraphicNames(x)                                                 */
/*                                                                           */
/*  Generate PostScript for xsize, ysize etc. names of graphic object.       */
/*                                                                           */
/*****************************************************************************/

static void PS_DefineGraphicNames(OBJECT x)
{ assert( type(x) == GRAPHIC, "PrintGraphic: type(x) != GRAPHIC!" );
  debug1(DPO, DD, "PS_DefineGraphicNames( %s )", EchoObject(x));
  debug1(DPO, DD, "  style = %s", EchoStyle(&save_style(x)));

  SetBaseLineMarkAndFont(baselinemark(save_style(x)), font(save_style(x)));
  SetColourAndTexture(colour(save_style(x)), texture(save_style(x))); 

  /* now print the actual command that defines the names */
  fprintf(out_fp, "%d %d %d %d %d %d %d LoutGraphic%s",
    size(x, COLM), size(x, ROWM), back(x, COLM), fwd(x, ROWM),
    currentfont <= 0 ? 12*PT : FontSize(currentfont, x),
    width(line_gap(save_style(x))), width(space_gap(save_style(x))),
    (char *) STR_NEWLINE);
  debug0(DPO, DD, "PS_DefineGraphicNames returning.");
} /* end PS_DefineGraphicNames */


/*****************************************************************************/
/*                                                                           */
/*  PS_SaveTranslateDefineSave(x, xdist, ydist)                              */
/*                                                                           */
/*  Equivalent to the sequence of calls                                      */
/*                                                                           */
/*      PS_SaveGraphicState(x)                                               */
/*      PS_CoordTranslate(xdist, ydist)                                      */
/*      PS_DefineGraphicNames(x)                                             */
/*      PS_SaveGraphicState(x)                                               */
/*                                                                           */
/*  but generates less PostScript in some cases, and never more.             */
/*                                                                           */
/*****************************************************************************/

static void PS_SaveTranslateDefineSave(OBJECT x, FULL_LENGTH xdist,
  FULL_LENGTH ydist)
{
  if( gs_stack_top >= MAX_GS - 1 ||
      font(save_style(x)) != currentfont ||
      colour(save_style(x)) != currentcolour ||
      texture(save_style(x)) != currenttexture )
  {
    /* do it bit by bit, will be rare anyway */
    PS_SaveGraphicState(x);
    PS_CoordTranslate(xdist, ydist);
    PS_DefineGraphicNames(x);
    PS_SaveGraphicState(x);
  }
  else
  {
    /* no font, colour, or texture changes, no stack overflow, so optimize */

    /* from Save */
    gs_stack_top++;
    gs_stack[gs_stack_top].gs_font	= currentfont;
    gs_stack[gs_stack_top].gs_baselinemark	= currentbaselinemark;
    gs_stack[gs_stack_top].gs_colour	= currentcolour;
    gs_stack[gs_stack_top].gs_texture	= currenttexture;
    gs_stack[gs_stack_top].gs_cpexists	= cpexists;
    gs_stack[gs_stack_top].gs_currenty	= currenty;
    gs_stack[gs_stack_top].gs_xheight2	= currentxheight2;

    /* from CoordTranslate */
    cpexists = FALSE;

    /* from Save */
    gs_stack_top++;
    gs_stack[gs_stack_top].gs_font	= currentfont;
    gs_stack[gs_stack_top].gs_baselinemark	= currentbaselinemark;
    gs_stack[gs_stack_top].gs_colour	= currentcolour;
    gs_stack[gs_stack_top].gs_texture	= currenttexture;
    gs_stack[gs_stack_top].gs_cpexists	= cpexists;
    gs_stack[gs_stack_top].gs_currenty	= currenty;
    gs_stack[gs_stack_top].gs_xheight2	= currentxheight2;

    /* accumulated output from all four calls, repackaged */
    fprintf(out_fp, "%d %d %d %d %d %d %d %d %d LoutGr2%s",
      size(x, COLM), size(x, ROWM), back(x, COLM), fwd(x, ROWM),
      currentfont <= 0 ? 12*PT : FontSize(currentfont, x),
      width(line_gap(save_style(x))), width(space_gap(save_style(x))),
      xdist, ydist, (char *) STR_NEWLINE);
      
  }
} /* end PS_SaveTranslateDefineSave */


/*****************************************************************************/
/*                                                                           */
/*  PS_FindBoundingBox(FILE *fp, FILE_POS *fpos, FULL_LENGTH *llx,           */
/*    FULL_LENGTH *lly, FULL_LENGTH *urx, FULL_LENGTH *ury)                  */
/*                                                                           */
/*  Find bounding box line in EPS file fp.  fpos is used for errors only.    */
/*                                                                           */
/*****************************************************************************/

BOOLEAN PS_FindBoundingBox(FILE *fp, FILE_POS *pos, FULL_LENGTH *llx,
  FULL_LENGTH *lly, FULL_LENGTH *urx, FULL_LENGTH *ury)
{ FULL_CHAR buff[MAX_LINE];
  long len;
  float fllx, flly, furx, fury;
  *llx = *lly = *urx = *ury = 0;

  /* if we don't even have a file, say so and return failure */
  if( fp == NULL )
  {
    Error(49, 17, "EPS file ignored (cannot open file)", WARN, pos);
    return FALSE;
  }

  /* skip to the EPS if this file has a preview */
  PS_FindEPSSegment(fp, &len);

  /* if the file is empty, say so and return failure */
  if( ReadOneLine(fp, buff, MAX_LINE) == 0 )
  {
    Error(49, 23, "EPS file ignored (file is empty)", WARN, pos);
    return FALSE;
  }

  /* if the file has a bad first line, say so and return failure */
  if( !StringBeginsWith(buff, AsciiToFull("%!")) )
  {
    Error(49, 19, "EPS file ignored (bad first line in file)", WARN, pos);
    return FALSE;
  }

  /* search through file for BoundingBox line */
  while( ReadOneLine(fp, buff, MAX_LINE) != 0 )
  {
    if( buff[0] == '%'
      && StringBeginsWith(buff, AsciiToFull("%%BoundingBox:"))
      && !StringContains(buff, AsciiToFull("(atend)")) )
    {
      if( sscanf( (char *) buff, "%%%%BoundingBox: %f %f %f %f",
	  &fllx, &flly, &furx, &fury) == 4 )
      {
	*llx = fllx;
	*lly = flly;
	*urx = furx;
	*ury = fury;
	debug4(DPO, D, "FindBoundingBox llx %.2f lly %.2f urx %.2f ury %.2f",
	  fllx, flly, furx, fury);
	return TRUE;
      }
      else
      {
	Error(49, 20, "EPS given zero size (bad BoundingBox line in file)",
	  WARN, pos);
	return FALSE;
      }
    }
  }

  /* if we get this far, we didn't find the bounding box line */
  Error(49, 18, "EPS given zero size (no BoundingBox line in file)",
    WARN, pos);
  return FALSE;
} /* end PS_FindBoundingBox */


/*****************************************************************************/
/*                                                                           */
/*  PS_PrintGraphicInclude(x, colmark, rowmark)                              */
/*                                                                           */
/*  Print graphic include file, with appropriate surrounds.  This code       */
/*  closely follows the PostScript Language Reference Manual, 2n ed.,        */
/*  pages 733-5, except we do not clip the included EPSF.                    */
/*                                                                           */
/*  Note to porters: Version 3.0 of the EPSF standard is not compatible      */
/*  with previous versions.  Thus, this output may crash your system.        */
/*  If you can find out which comment line(s) are causing the trouble,       */
/*  you can add to procedure strip_out to strip them out during the          */
/*  file inclusion step.  e.g. on my system %%EOF causes problems, so I      */
/*  strip it out.                                                            */
/*                                                                           */
/*  May 1994: I've just discovered that %%Trailer causes problems for        */
/*  the mpage Unix utility, so now I'm stripping it out as well.             */
/*                                                                           */
/*****************************************************************************/

static void PS_PrintGraphicInclude(OBJECT x, FULL_LENGTH colmark,
  FULL_LENGTH rowmark)
{ OBJECT y, full_name;  FILE *fp;  BOOLEAN compressed;  int fnum;
  debug0(DPO, DD, "PS_PrintGraphicInclude(x)");

  assert(type(x)==INCGRAPHIC || type(x)==SINCGRAPHIC, "PrintGraphicInclude!");
  assert(incgraphic_ok(x), "PrintGraphicInclude: !incgraphic_ok(x)!");

  /* open the include file and get its full path name */
  Child(y, Down(x));

  SetBaseLineMarkAndFont(baselinemark(save_style(x)), font(save_style(x)));
  SetColourAndTexture(colour(save_style(x)), texture(save_style(x)));

  fnum = PS_FindIncGRepeated(y, type(x));
  if( fnum != 0 )
  {
    /* print form */
    PS_SaveGraphicState(x);
    PS_CoordTranslate(colmark - back(x, COLM), rowmark - fwd(x, ROWM));
    PS_CoordScale( (float) PT, (float) PT );
    PS_CoordTranslate(-back(y, COLM), -back(y, ROWM));
    p1("Form%d execform", fnum);
    PS_RestoreGraphicState();
  }
  else
  {
    /* open the include file and get its full name etc. */
    fp = OpenIncGraphicFile(string(y), type(x),&full_name,&fpos(y),&compressed);
    assert( fp != NULL, "PS_PrintGraphicInclude: fp!" );

    /* print appropriate header code for EPS file inclusion */
    p0("LoutStartEPSF");
    PS_CoordTranslate(colmark - back(x, COLM), rowmark - fwd(x, ROWM));
    PS_CoordScale( (float) PT, (float) PT );
    PS_CoordTranslate(-back(y, COLM), -back(y, ROWM));
    p1("%%%%BeginDocument: %s", string(full_name));

    /* copy through the include file, except divert resources lines to needs */
    /* and strip out some comment lines that cause problems                  */
    PS_PrintEPSFile(fp, &fpos(y), FALSE);

    /* wrapup */
    DisposeObject(full_name);
    if( compressed )  StringRemove(AsciiToFull(LOUT_EPS));
    pnl;
    p0("%%EndDocument");
    p0("LoutEPSFCleanUp");
  }

  cpexists = FALSE;
  currentfont = NO_FONT;  /* added by JeffK 31/10/06 */
  wordcount = 0;
  debug0(DPO, DD, "PS_PrintGraphicInclude returning.");
} /* end PS_PrintGraphicInclude */


/*****************************************************************************/
/*                                                                           */
/*  char *ConvertToPDFName(name)                                             */
/*                                                                           */
/*  Convert string(name) to a suitable PDF label.  The result is in static   */
/*  memory and must be copied before the next call to ConvertToPDFName.      */
/*                                                                           */
/*  At present our algorithm is to prefix the label with "LOUT" and to       */
/*  replace all non-alphanumerics by one underscore.                         */
/*                                                                           */
/*****************************************************************************/
#define in_range(ch, a, b)	( (ch) >= (a) && (ch) <= (b) )
#define is_lower(ch)		in_range(ch, 'a', 'z')
#define is_upper(ch)		in_range(ch, 'A', 'Z')
#define is_digit(ch)		in_range(ch, '0', '9')
#define is_alphanum(ch)		(is_lower(ch) || is_upper(ch) || is_digit(ch))

static char *ConvertToPDFName(OBJECT name)
{ static char buff[200];
  char *q;
  FULL_CHAR *p;
  strcpy(buff, "LOUT");
  q = &buff[strlen(buff)];
  for( p = string(name);  *p != '\0';  p++ )
  {
    if( q >= &buff[199] )
      Error(49, 12, "tag %s is too long", FATAL, &fpos(name), string(name));
    if( is_alphanum(*p) )
      *q++ = (char) *p;
    else
      *q++ = '_';
  }
  *q++ = '\0';
  return buff;
}


/*****************************************************************************/
/*                                                                           */
/*  PS_LinkSource(name, llx, lly, urx, ury)                                  */
/*                                                                           */
/*  Print a link source point.                                               */
/*                                                                           */
/*****************************************************************************/

static void PS_LinkSource(OBJECT name, FULL_LENGTH llx, FULL_LENGTH lly,
  FULL_LENGTH urx, FULL_LENGTH ury)
{ debug5(DPO, DD, "PS_LinkSource(%s, %d, %d, %d, %d)", EchoObject(name),
    llx, lly, urx, ury);

  /* print the link source point */
  pnl;
  fprintf(out_fp,
    "[ /Rect [%d %d %d %d] %s %s /Subtype /Link /Dest /%s /ANN pdfmark%s",
    llx, lly, urx, ury, "/Border [0 0 0]", "/View [ /XYZ null null null ]",
    ConvertToPDFName(name), (char *) STR_NEWLINE);

  /* remember it so that at end of run can check if it has an dest point */
  Link(link_source_list, name);
  debug0(DPO, DD, "PS_LinkSource returning.");
} /* end PS_LinkSource */


/*****************************************************************************/
/*                                                                           */
/*  PS_LinkDest(name, llx, lly, urx, ury)                                    */
/*                                                                           */
/*  Print a link dest point (note llx etc are not used), after making sure   */
/*  that no previously printed dest point has the same name.                 */
/*                                                                           */
/*****************************************************************************/

static void PS_LinkDest(OBJECT name, FULL_LENGTH llx, FULL_LENGTH lly,
  FULL_LENGTH urx, FULL_LENGTH ury)
{ OBJECT prev;
  debug5(DPO, D, "PS_LinkDest(%s, %d, %d, %d, %d)", EchoObject(name),
    llx, lly, urx, ury);

  prev = ltab_retrieve(string(name), link_dest_tab);
  if( prev == nilobj )
  {
    /* not used previously, so print it and remember it */
    pnl;
    p1("[ /Dest /%s /DEST pdfmark", ConvertToPDFName(name));
    ltab_insert(name, &link_dest_tab);
  }
  else
  {
    /* used previously, so don't print it, and warn the user */
    Error(49, 13, "link destination %s ignored (there is already one at%s)",
      WARN, &fpos(name), string(name), EchoFilePos(&fpos(prev)));
  }
  debug0(DPO, D, "PS_LinkDest returning.");
} /* end PS_LinkDest */


/*****************************************************************************/
/*                                                                           */
/*  PS_LinkURL(url, llx, lly, urx, ury)                                      */
/*                                                                           */
/*  Print a URL link.                                                        */
/*                                                                           */
/*****************************************************************************/

static void PS_LinkURL(OBJECT url, FULL_LENGTH llx, FULL_LENGTH lly,
  FULL_LENGTH urx, FULL_LENGTH ury)
{ debug5(DPO, DD, "PS_LinkURL(%s, %d, %d, %d, %d)", EchoObject(url),
    llx, lly, urx, ury);

  if( is_word(type(url)) )
  {
    pnl;
    fprintf(out_fp,
      "[ /Rect [%d %d %d %d] %s /Action << %s /URI (%s) >> %s /ANN pdfmark%s",
      llx, lly, urx, ury, "/Border [0 0 0]", "/Subtype /URI", string(url),
      "/Subtype /Link", (char *) STR_NEWLINE);
  }
  else
    Error(49, 22, "%s ignored; left parameter not a simple word",
      WARN, &fpos(url), KW_LINK_URL);

  debug0(DPO, DD, "PS_LinkURL returning.");
} /* end PS_LinkSource */


/*****************************************************************************/
/*                                                                           */
/*  PS_LinkCheck(void)                                                       */
/*                                                                           */
/*  Called at end of run; will check that for every link source point there  */
/*  is a link dest point.                                                    */
/*                                                                           */
/*****************************************************************************/

static void PS_LinkCheck(void)
{ OBJECT y, link;
  debug0(DPO, DD, "PS_LinkCheck()");

  for( link=Down(link_source_list); link!=link_source_list; link=NextDown(link) )
  { Child(y, link);
    assert( is_word(type(y)), " PS_LinkCheck: !is_word(type(y))!");
    if( ltab_retrieve(string(y), link_dest_tab) == nilobj )
      Error(49, 14, "link name %s has no destination point", WARN, &fpos(y),
	string(y));
  }

  debug0(DPO, DD, "PS_LinkCheck returning.");
} /* end PS_LinkCheck */


/*****************************************************************************/
/*                                                                           */
/*  PS_BackEnd                                                               */
/*                                                                           */
/*  The record into which all of these functions are packaged.               */
/*                                                                           */
/*****************************************************************************/

static struct back_end_rec ps_back = {
  POSTSCRIPT,				/* the code number of the back end   */
  STR_POSTSCRIPT,			/* string name of the back end       */
  TRUE,					/* TRUE if @Scale is available       */
  TRUE,					/* TRUE if @Rotate is available      */
  TRUE,					/* TRUE if @HMirror, @VMirror avail  */
  TRUE,					/* TRUE if @Graphic is available     */
  TRUE,					/* TRUE if @IncludeGraphic is avail. */
  FALSE,				/* TRUE if @PlainGraphic is avail.   */
  TRUE,					/* TRUE if fractional spacing avail. */
  TRUE,					/* TRUE if actual font metrics used  */
  TRUE,					/* TRUE if colour is available       */
  PS_PrintInitialize,
  PS_PrintLength,
  PS_PrintPageSetupForFont,
  PS_PrintPageResourceForFont,
  PS_PrintMapping,
  PS_PrintBeforeFirstPage,
  PS_PrintBetweenPages,
  PS_PrintAfterLastPage,
  PS_PrintWord,
  PS_PrintPlainGraphic,
  PS_PrintUnderline,
  PS_CoordTranslate,
  PS_CoordRotate,
  PS_CoordScale,
  PS_CoordHMirror,
  PS_CoordVMirror,
  PS_SaveGraphicState,
  PS_RestoreGraphicState,
  PS_PrintGraphicObject,
  PS_DefineGraphicNames,
  PS_SaveTranslateDefineSave,
  PS_PrintGraphicInclude,
  PS_LinkSource,
  PS_LinkDest,
  PS_LinkURL,
  PS_LinkCheck,
};

BACK_END PS_BackEnd = &ps_back;


/*****************************************************************************/
/*                                                                           */
/*  PS_NullBackEnd                                                           */
/*                                                                           */
/*  A null (non-printing) version of the PostScript back end.                */
/*                                                                           */
/*****************************************************************************/

static void PS_NullPrintInitialize(FILE *fp, BOOLEAN enc)
{}

static void PS_NullPrintPageSetupForFont(OBJECT face, int font_curr_page,
  FULL_CHAR *font_name, FULL_CHAR *short_name)
{}

static void PS_NullPrintPageResourceForFont(FULL_CHAR *font_name,
  BOOLEAN first)
{}

static void PS_NullPrintMapping(MAPPING m)
{}

static void PS_NullPrintBeforeFirstPage(FULL_LENGTH h, FULL_LENGTH v,
  FULL_CHAR *label)
{}

static void PS_NullPrintBetweenPages(FULL_LENGTH h, FULL_LENGTH v,
  FULL_CHAR *label)
{}

static void PS_NullPrintAfterLastPage(void)
{}

static void PS_NullPrintWord(OBJECT x, int hpos, int vpos)
{}

static void PS_NullPrintPlainGraphic(OBJECT x, FULL_LENGTH xmk,
  FULL_LENGTH ymk, OBJECT z)
{}

static void PS_NullPrintUnderline(FONT_NUM fnum, COLOUR_NUM col,
  TEXTURE_NUM pat, FULL_LENGTH xstart, FULL_LENGTH xstop, FULL_LENGTH ymk)
{}

static void PS_NullCoordTranslate(FULL_LENGTH xdist, FULL_LENGTH ydist)
{}

static void PS_NullCoordRotate(FULL_LENGTH amount)
{}

static void PS_NullCoordScale(float hfactor, float vfactor)
{}

static void PS_NullCoordHMirror(void)
{}

static void PS_NullCoordVMirror(void)
{}

static void PS_NullSaveGraphicState(OBJECT x)
{}

static void PS_NullRestoreGraphicState(void)
{}

static void PS_NullPrintGraphicObject(OBJECT x)
{}

static void PS_NullDefineGraphicNames(OBJECT x)
{}

static void PS_NullSaveTranslateDefineSave(OBJECT x, FULL_LENGTH xdist,
  FULL_LENGTH ydist)
{}

static void PS_NullPrintGraphicInclude(OBJECT x, FULL_LENGTH colmark,
  FULL_LENGTH rowmark)
{}

static void PS_NullLinkSource(OBJECT name, FULL_LENGTH llx, FULL_LENGTH lly,
  FULL_LENGTH urx, FULL_LENGTH ury)
{}

static void PS_NullLinkDest(OBJECT name, FULL_LENGTH llx, FULL_LENGTH lly,
  FULL_LENGTH urx, FULL_LENGTH ury)
{}

static void PS_NullLinkURL(OBJECT url, FULL_LENGTH llx, FULL_LENGTH lly,
  FULL_LENGTH urx, FULL_LENGTH ury)
{}

static void PS_NullLinkCheck(void)
{}

static struct back_end_rec ps_null_back = {
  POSTSCRIPT,				/* the code number of the back end   */
  STR_POSTSCRIPT,			/* string name of the back end       */
  TRUE,					/* TRUE if @Scale is available       */
  TRUE,					/* TRUE if @Rotate is available      */
  TRUE,					/* TRUE if @HMirror, @VMirror avail  */
  TRUE,					/* TRUE if @Graphic is available     */
  TRUE,					/* TRUE if @IncludeGraphic is avail. */
  FALSE,				/* TRUE if @PlainGraphic is avail.   */
  TRUE,					/* TRUE if fractional spacing avail. */
  TRUE,					/* TRUE if actual font metrics used  */
  TRUE,					/* TRUE if colour is available       */
  PS_NullPrintInitialize,
  PS_PrintLength,
  PS_NullPrintPageSetupForFont,
  PS_NullPrintPageResourceForFont,
  PS_NullPrintMapping,
  PS_NullPrintBeforeFirstPage,
  PS_NullPrintBetweenPages,
  PS_NullPrintAfterLastPage,
  PS_NullPrintWord,
  PS_NullPrintPlainGraphic,
  PS_NullPrintUnderline,
  PS_NullCoordTranslate,
  PS_NullCoordRotate,
  PS_NullCoordScale,
  PS_NullCoordHMirror,
  PS_NullCoordVMirror,
  PS_NullSaveGraphicState,
  PS_NullRestoreGraphicState,
  PS_NullPrintGraphicObject,
  PS_NullDefineGraphicNames,
  PS_NullSaveTranslateDefineSave,
  PS_NullPrintGraphicInclude,
  PS_NullLinkSource,
  PS_NullLinkDest,
  PS_NullLinkURL,
  PS_NullLinkCheck,
};

BACK_END PS_NullBackEnd = &ps_null_back;
