/*@z48.c:PDF back end@********************************************************/
/*                                                                           */
/*  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 PDF Back End module written by Vincent Tan, March 1998.             */
/*                                                                           */
/*  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:         z48.c                                                      */
/*  MODULE:       PDF back end                                               */
/*  EXTERNS:      PDFFile_Init(), PDFFile_BeginFontEncoding(),               */
/*                PDFFile_EndFontEncoding(), PDFFile_Cleanup(),              */
/*                PDFPage_Init(), PDFPage_Cleanup(), PDFPage_Write(),        */
/*                PDFPage_Push(), PDFPage_Pop(), PDFPage_Scale(),            */
/*                PDFPage_Translate(), PDFPage_Rotate(), PDFPage_SetVars(),  */
/*                PDFPage_WriteGraphic(), PDFPage_PrintUnderline(),          */
/*                PDFFont_AddFont(), PDFFont_Set(), PDFText_OpenXY(),        */
/*                PDFText_OpenX(), PDFText_Open(), PDFText_Kern(),           */
/*                PDFText_Close(), PDFHasValidTextMatrix()                   */
/*                                                                           */
/*****************************************************************************/
#include "externs.h"

/* ANSI headers */
#include <ctype.h>
#include <math.h>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#include <time.h>

/* zlib headers: define PDF_COMPRESSION = 0 if you don't have zlib library */
#if PDF_COMPRESSION
#include "zlib.h"
#endif

static void Assert(BOOLEAN condition, FILE_POS *inFilePos)
{
  if (!condition)		/* allows me to set a breakpoint here */
  assert(condition, inFilePos);
}

/* #define's and typedefs */
#undef USE_MATRICES
#undef _CALC_LARGEST_PAGE_OBJECT_

enum {
  kBase14FontCount = 14,    /* there are 14 base PDF fonts */
  kBufferSize = 1024	    /* size of buffer for skipping non-marking commands */
};

#if PDF_COMPRESSION
enum {
  kRawOutputBufferSize = 4096,		/* arbitrary choice */
  kCompressedOutputBufferSize = 4096	/* arbitrary choice */
};
#endif

enum {
  kNumberOfObjectsPerBlock = 256,	/* arbitrary choice */
  kNumberOfPagesPerBlock = 64		/* arbitrary choice */
};

typedef enum {
  kFitNoChange = 0,	/* special default case */
  kFit,			/* [ /Fit ]: fit the page to the window */
  kFitH,		/* [ /FitH top ]: fit the width of the page to window; */
			/* top specifies y-coord of the top edge of the window */
  kFitV,		/* [ /FitV left ]: fit the height of the page to the   */
			/* window. left specifies x-coord of left edge of win. */
  kFitR,		/* [ /FitR left bottom right top ]: fit the rectangle  */
			/* specified by left bottom right top in the window.   */
			/* If the height (top - bottom) and width (right-left) */
			/* imply different zoom factors, the numerically       */
			/* smaller zoom factor is used, to ensure that the     */
			/* specified rectangle fits in the window              */
  kFitB,		/* [ /FitB ]: fit the page's bounding box to window    */
  kFitBH,		/* [ /FitBH top ]: fit the width of the page's bound.  */
			/* box to the window. top specifies the y-coordinate   */
			/* of the top edge of the window                       */
  kFitBV,		/* [ /FitBV left ]: fit the height of the page's       */
			/* bounding box to the window. left specifies the      */
			/* x-coordinate of the left edge of the window         */

  kNumberOfDestLinkOptions
} PDF_LINK_DEST_OPTION;

enum eUnitsKeywords {
  k_in = 0,
  k_cm,
  k_pt,
  k_em,
  k_loutf,
  k_loutv,
  k_louts,
  kNumberOfUnitKeywords
};

enum eGraphicsKeywords {
  k_xsize = 0,
  k_ysize,
  k_xmark,
  k_ymark,
  kNumberOfGraphicsKeywords
};

enum eArithmeticKeywords {
  k_add = 0,
  k_sub,
  k_mul,
  k_div,
  k_sin,
  k_cos,
  k_pick,
  kNumberOfArithmeticKeywords
};

typedef enum {
  k_link_source = 0,	/* source of a link to an internal document target */
  k_link_external,	/* source of a link to an external document target */
  k_link_URI,		/* source of a link to an (external) URI target    */
  k_link_target,	/* internal document target                        */
  k_link_target_for_export,	/* external document target                */
  kNumberOfLinkKeywords
} PDF_LINK_KEYWORD;

enum {
  k_author = 0,
  k_title,
  k_subject,
  k_keywords,
  kNumberOfDocInfoKeywords
};


/* basic types */
#ifdef	USE_MATRICES
typedef	double			t_matrix[9];
#endif

typedef	char		t_tempbuf[512];
typedef	unsigned int	PDF_OBJECT_NUM;
typedef	PDF_OBJECT_NUM	PDF_PAGE_OBJECT_NUM;  /* an object number that can */
					      /* refer ONLY to page object */
typedef	unsigned int	PDF_FONT_NUM;
typedef	unsigned int	PDF_PAGE_NUM;
typedef	unsigned int	PDF_FILE_OFFSET;


/* font list */
struct t_font_list_entry {
  struct t_font_list_entry *m_next_font_entry;
  FULL_CHAR *m_PDF_font_name;
  FULL_CHAR *m_short_font_name;
  FULL_CHAR *m_actual_font_name;
  PDF_OBJECT_NUM m_font_encoding_obj;	/* valid for entire PDF file */
  PDF_OBJECT_NUM m_pdf_object_number;	/* valid for entire PDF file */
  BOOLEAN m_font_resource_in_pdf;	/* TRUE when PDF file has       */
					/* /Type /Font resource         */
  BOOLEAN m_in_use;			/* used on a per-page basis */
};

typedef struct t_font_list_entry t_font_list_entry, *t_font_list_entry_ptr;


/* offsets of all objects (for xref list) */
typedef	PDF_FILE_OFFSET t_offset_array[kNumberOfObjectsPerBlock];

struct t_offset_block {
  struct t_offset_block *m_next_block;
  t_offset_array m_block;
};

typedef struct t_offset_block t_offset_block, *t_offset_block_ptr;


/* for /Pages object */
typedef PDF_PAGE_OBJECT_NUM t_page_array[kNumberOfPagesPerBlock];

struct t_page_block {
  struct t_page_block *m_next_block;
  t_page_array m_block;
};

typedef	struct t_page_block t_page_block, *t_page_block_ptr;


/* for font encodings */
struct	t_font_encoding_entry {
  struct t_font_encoding_entry* m_next_entry;
  PDF_OBJECT_NUM m_object_num;
  FULL_CHAR *m_font_encoding;
};

typedef struct t_font_encoding_entry
  t_font_encoding_entry, *t_font_encoding_entry_ptr;


/* for qsave/qrestore [see PDFPage_Push()] */
struct t_qsave_entry {
  struct t_qsave_entry *m_next_entry;
  int m_page_h_origin, m_page_v_origin;
  float m_page_h_scale_factor, m_page_v_scale_factor;
};

typedef struct t_qsave_entry t_qsave_entry, *t_qsave_entry_ptr;


/* for qsave/qrestore [see PDFPage_Push()] */
struct t_qsave_marking_entry {
  struct t_qsave_marking_entry* m_next_entry;
  unsigned int m_buffer_pos;
};

typedef	struct t_qsave_marking_entry t_qsave_marking_entry, *t_qsave_marking_entry_ptr;


/* target of link annotations */
struct t_target_annot_entry {
  struct t_target_annot_entry* m_next_entry;

  /* all of the following are always defined */
  FULL_CHAR *m_name;
  PDF_PAGE_OBJECT_NUM m_page_object_num;

  /* these are in PDF's default user space coordinates */
  int m_ll_x;
  int m_ll_y;
  int m_ur_x;
  int m_ur_y;

  BOOLEAN m_for_export;
};

typedef struct t_target_annot_entry t_target_annot_entry, *t_target_annot_entry_ptr;

/* source of link annotations */
struct t_source_annot_entry {
  struct t_source_annot_entry* m_next_entry;

  t_target_annot_entry* m_target;	/* if is a link and this is NULL then */
					/* the link is a fwd link and remains */
					/* unresolvable until the page is     */
					/* encountered - instead, the m_name  */
					/* field is defined; m_target will be */
					/* NULL for URI type links            */

  FULL_CHAR *m_name;			/* this string is defined if m_target */
					/* is NULL otherwise it is null       */
					/* for URI links, this contains the   */
					/* URI to link to                     */
  FULL_CHAR *m_file_spec;		/* only defined for link_type ==      */
					/* k_link_external                    */

  /* all of the following are always defined */
  /* these are in PDF's default user space coordinates */
  int m_ll_x;
  int m_ll_y;
  int m_ur_x;
  int m_ur_y;

  PDF_OBJECT_NUM m_this_object_num;	/* obj num of this "/Type /Annot" obj */
  PDF_PAGE_OBJECT_NUM m_this_page_object_num;	/* obj num of the page that   */
					/* this annot lies in                 */
  PDF_LINK_DEST_OPTION m_dest_option;
  PDF_LINK_KEYWORD m_link_type;

  BOOLEAN m_written_to_PDF_file;
};

typedef struct t_source_annot_entry t_source_annot_entry, *t_source_annot_entry_ptr;


/* matrices */
#ifdef USE_MATRICES
struct t_matrix_entry {
  struct t_matrix_entry* m_next_entry;
  t_matrix m_matrix;
};

typedef struct t_matrix_entry t_matrix_entry, *t_matrix_entry_ptr;
#endif


/* statics */

/* general */
static BOOLEAN g_PDF_debug;

/* objects */
static PDF_OBJECT_NUM		g_next_objnum;
static t_offset_block_ptr	g_obj_offset_list;	/* first block       */
static t_offset_block_ptr	g_cur_obj_offset_block;

/* fonts */
static t_font_list_entry_ptr	g_font_list;		/* backwards         */
static t_font_encoding_entry_ptr g_font_encoding_list;	/* backwards         */

/* pages */
static PDF_PAGE_NUM		g_page_count;		/* current page num, */
							/* starting at 1     */
static PDF_PAGE_OBJECT_NUM	g_page_object_num;	/* obj num of current*/
							/* "/Type /Page" obj,*/
							/* corr. to page     */
							/* num g_page_count  */
static t_page_block_ptr		g_page_block_list;	/* first block       */
static t_page_block_ptr		g_cur_page_block;
static PDF_OBJECT_NUM		g_pages_root;

/* document */
static int g_doc_h_bound;
static int g_doc_v_bound;
static FULL_CHAR* g_doc_author;
static FULL_CHAR* g_doc_title;
static FULL_CHAR* g_doc_subject;
static FULL_CHAR* g_doc_keywords;

/* link annotations */
static t_target_annot_entry_ptr g_target_annot_list;
static BOOLEAN g_has_exported_targets;

/* globals for each page */
/* these indicate what kind of content the page has */
static BOOLEAN g_page_uses_fonts;
static BOOLEAN g_page_has_text;
static BOOLEAN g_page_has_graphics;

/* these are only defined when the page has some content */
static PDF_OBJECT_NUM g_page_contents_obj_num;
static PDF_OBJECT_NUM g_page_length_obj_num;
static PDF_FILE_OFFSET g_page_start_offset;

/* valid after a PDF_Push and PDF_Pop */
static t_qsave_entry_ptr g_qsave_stack;

static t_qsave_marking_entry_ptr g_qsave_marking_stack;	/* implemented as a   */
							/* linked list; pts   */
							/* to top of stack    */
static BOOLEAN g_in_buffering_mode;
static char g_buffer[kBufferSize];			/* this buffer is used*/
							/* for removing redundant operations */
static unsigned int g_buffer_pos;

/* valid after a link annotation has been defined */
static t_source_annot_entry_ptr g_source_annot_list;

#ifdef USE_MATRICES
static t_matrix g_cur_matrix;
static t_matrix_entry_ptr g_matrix_stack;
#endif

/* track these values in case they are ever required */
static float g_page_h_scale_factor, g_page_v_scale_factor;
static int g_page_h_origin, g_page_v_origin;
static int g_page_line_width;

/* magic keywords (actually they will appear in Lout documents as "__in", "__cm", etc.) */
static char *g_unit_keywords[kNumberOfUnitKeywords] =
{
  "in", "cm", "pt", "em", "loutf", "loutv", "louts"	/* MUST be followed by a fp number */
};

static char *g_graphic_keywords[kNumberOfGraphicsKeywords] =
{
  "xsize", "ysize", "xmark", "ymark"	/* like macros, these expand to the actual value */
};

static char *g_arithmetic_keywords[kNumberOfArithmeticKeywords] =
{
  /* syntax: "__mul(x, y)" emits (x * y) to 2 decimal places              */
  /*                                                                      */
  /* Notes:                                                               */
  /*                                                                      */
  /*    sin and cos expect their arguments in degrees                     */
  /*                                                                      */
  /*    for negation, use "__sub(0, arg)"                                 */
  /*                                                                      */
  /*    __pick(i, expr1, expr2, expr3...) picks the ith expr from the     */
  /*    list of expr the "," are optional (if they are not used, you      */
  /*    should separate values with whitespace)                           */

  "add", "sub", "mul", "div", "sin", "cos", "pick"	/* like macros, these expand to the actual value */
};

static char *g_link_keywords[kNumberOfLinkKeywords] =
{
  /* syntax: "__link_source=<<name_of_target_link [dest_link_option]>>"   */
  /*                                                                      */
  /* example: "__link_source=<<chapter6>>"                                */
  /* example: "__link_source=<<part7 __FitH>>"                            */

  "link_source=<<",

  /* syntax: "__link_external=<<name_of_target_link __link_to=file_spec>>"*/
  /* syntax: "__link_external=<<name_of_target_link __link_to=<< /FS /URL /F (url)>>>>" */
  /*                                                                      */
  /* **	note the special format required for URL links **                 */
  /*                                                                      */
  /* example: "__link_external=<<chapter6 __link_to=/usr/bin/file.pdf>>"  */
  /* example: "__link_external=<<chapter6 __link_to=<< /FS /URL /F        */
  /*          (ftp://ftp.cs.usyd.edu.au/jeff/lout/user.pdf) >>>>"         */

  "link_external=<<",

  /* syntax: "__link_URI=<<URL>>"                                         */
  /*                                                                      */
  /* example: "__link_URI=<<http://www.adobe.com>>"                       */

  "link_URI=<<",

  /* syntax: "__link_target=<<name_of_target_link>>" where                */
  /* name_of_target_link is in this PDF file; name_of_target_link CANNOT  */
  /* be accessed by external documents in links                           */
  /*                                                                      */
  /* example: "__link_target=<<my_internal_target>>"                      */

  "link_target=<<",

  /* syntax: "__link_target_for_export=<<name_of_target_link>>" where     */
  /* name_of_target_link is in this file; name_of_target_link can be      */
  /* accessed by external documents in links                              */
  /*                                                                      */
  /* example: "__link_target_for_export=<<my_exported_target>>"           */

  "link_target_for_export=<<"
};

static char *g_dest_link_options[kNumberOfDestLinkOptions] =
{
  /* see PDF_LINK_DEST_OPTION for descriptions of the meanings of these */
  "__FitNoChange",
  "__Fit",
  "__FitH",
  "__FitV",
  "__FitR",
  "__FitB",
  "__FitBH",
  "__FitBV"
};

static char* g_external_file_spec_keyword[1] =
{
  "__link_to="
};

static char* g_doc_info_keywords[kNumberOfDocInfoKeywords] =
{
  "author=", "title=", "subject=", "keywords="
};

static int g_units[kNumberOfUnitKeywords];

static int g_graphics_vars[kNumberOfGraphicsKeywords];

/* text state */
static BOOLEAN g_TJ_pending;
static BOOLEAN g_ET_pending;
static BOOLEAN g_valid_text_matrix;	/* true when BT...ET block open */


/* expressions */
static int g_expr_depth = 0;
static int g_expr_index;
static t_tempbuf g_expr;

/* links */
static int g_link_depth = 0;
static int g_link_index;
static t_tempbuf g_link;
static PDF_LINK_KEYWORD g_link_keyword;

/* the 14 base fonts */
static char *g_standard_base_14_fonts[kBase14FontCount] = {
  "Courier",
  "Courier-Bold",
  "Courier-Oblique",
  "Courier-BoldOblique",
  "Helvetica",
  "Helvetica-Bold",
  "Helvetica-Oblique",
  "Helvetica-BoldOblique",
  "Symbol",
  "Times",
  "Times-Bold",
  "Times-Italic",
  "Times-BoldItalic",
  "ZapfDingbats"
};


#if PDF_COMPRESSION
static BOOLEAN		g_apply_compression;
static z_stream		g_comp_stream;           /* zlib compression stream */
static unsigned char*	g_raw_buffer_ptr;

/* compression buffers */
static unsigned char	g_raw_output[kRawOutputBufferSize];
static unsigned char	g_compressed_output[kCompressedOutputBufferSize];
#endif

/* for calculating largest page object */
#ifdef _CALC_LARGEST_PAGE_OBJECT_
static PDF_FILE_OFFSET g_max_page_length = 0;
#endif

BOOLEAN PDFHasValidTextMatrix(void)	/* this is called from z24.c */
{
  return g_valid_text_matrix;
}


/*****************************************************************************/
/*                                                                           */
/*  t_offset_block_ptr  PDFObject_FindOffsetBlock(PDF_OBJECT_NUM in_obj_num) */
/*                                                                           */
/*  Find the offset block for the given object number.                       */
/*                                                                           */
/*****************************************************************************/

static t_offset_block_ptr PDFObject_FindOffsetBlock(PDF_OBJECT_NUM in_obj_num,
  unsigned int* out_block_pos)
{
  int wanted_block_num = (in_obj_num - 1) / kNumberOfObjectsPerBlock;
  int block_pos = (in_obj_num - 1) % kNumberOfObjectsPerBlock;
  t_offset_block_ptr the_block = g_obj_offset_list;

  Assert((in_obj_num > 0) && (in_obj_num < g_next_objnum), no_fpos);

  /* find block */
  while (wanted_block_num != 0) {
    Assert(the_block != NULL, no_fpos);
    the_block = the_block->m_next_block;
    wanted_block_num--;
  }
  Assert(the_block != NULL, no_fpos);

  if (out_block_pos != NULL)
    *out_block_pos = block_pos;
  return the_block;
}


/*****************************************************************************/
/*                                                                           */
/*  PDF_OBJECT_NUM PDFObject_New(FILE* in_fp)                                */
/*                                                                           */
/*  Return the next available object number.                                 */
/*                                                                           */
/*****************************************************************************/

static PDF_OBJECT_NUM PDFObject_New(/* FILE* in_fp */)
{
  int wanted_block_num = (g_next_objnum - 1) / kNumberOfObjectsPerBlock;
  int block_pos = (g_next_objnum - 1) % kNumberOfObjectsPerBlock;
  t_offset_block_ptr the_block = g_cur_obj_offset_block;

  /* if first obj in a block then allocate the block */
  if (block_pos == 0)
  {
    the_block = (t_offset_block_ptr) malloc(sizeof(t_offset_block));
    if (the_block == NULL)
      Error(48, 1, "PDFObject_New: run out of memory", FATAL, no_fpos);
    if (wanted_block_num == 0)  /* if first block in file */
    {
      Assert(g_obj_offset_list == NULL, no_fpos);
      g_obj_offset_list = the_block;
    }
    else
    {
      Assert(g_cur_obj_offset_block != NULL, no_fpos);
      g_cur_obj_offset_block->m_next_block = the_block;
    }
    the_block->m_next_block = NULL;	/* don't forget to init this! */
    g_cur_obj_offset_block = the_block;
  }
  else
  {
    Assert(the_block != NULL, no_fpos);
  }

  /* initialise the offset of this object to zero (it hasn't been written    */
  /* to the PDF file yet)                                                    */
  the_block->m_block[block_pos] = 0;                  /* ftell(in_fp);       */

  return g_next_objnum++;
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFObject_WriteRef(FILE* in_fp, PDF_OBJECT_NUM in_object_number)    */
/*                                                                           */
/*  Return the next available object number and write a reference to it.     */
/*                                                                           */
/*****************************************************************************/

static void PDFObject_WriteRef(FILE* in_fp, PDF_OBJECT_NUM in_object_number)
{
  fprintf(in_fp, "%u 0 R", in_object_number);
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFObject_WriteObj(FILE* in_fp, PDF_OBJECT_NUM in_object_number)    */
/*                                                                           */
/*  Write the object's definition (and remember its file position).          */
/*                                                                           */
/*****************************************************************************/

static void PDFObject_WriteObj(FILE* in_fp, PDF_OBJECT_NUM in_object_number)
{
  unsigned int block_pos;
  t_offset_block_ptr block =
    PDFObject_FindOffsetBlock(in_object_number, &block_pos);
  Assert(block->m_block[block_pos]==0, no_fpos); /* offset shd be "unknown"  */
  block->m_block[block_pos] = ftell(in_fp);	 /* remember offset for xref */

  fprintf(in_fp, "%u 0 obj\n", in_object_number);
}

/*****************************************************************************/
/*                                                                           */
/*  PDF_OBJECT_NUM PDFObject_WriteNewObj(FILE* in_fp)                        */
/*                                                                           */
/*  Return the next available object number and write its definition.        */
/*                                                                           */
/*****************************************************************************/

static PDF_OBJECT_NUM PDFObject_WriteNewObj(FILE* in_fp)
{
  PDF_OBJECT_NUM next_ref = PDFObject_New(/* in_fp */);
  PDFObject_WriteObj(in_fp, next_ref);
  return next_ref;
}


/*****************************************************************************/
/*                                                                           */
/*  PDF_OBJECT_NUM PDFObject_WriteNextRef(FILE* in_fp)                       */
/*                                                                           */
/*  Return the next available object number and write a reference to it.     */
/*                                                                           */
/*****************************************************************************/

/* ***
static PDF_OBJECT_NUM PDFObject_WriteNextRef(FILE* in_fp)
{
  PDF_OBJECT_NUM next_ref = PDFObject_New(in_fp);
  PDFObject_WriteRef(in_fp, next_ref);
  return next_ref;
}
*** */


/*****************************************************************************/
/*                                                                           */
/* void PDFFile_BeginFontEncoding(FILE* in_fp, const char* in_encoding_name) */
/*                                                                           */
/*  Begin font encoding.                                                     */
/*                                                                           */
/*****************************************************************************/

void PDFFile_BeginFontEncoding(FILE* in_fp, const char* in_encoding_name)
{
  PDF_OBJECT_NUM encoding_num;
  t_font_encoding_entry_ptr encoding_entry;

  /* TO FILL IN: create entry in font-encoding list and add this encoding    */
  if (g_PDF_debug)
    fprintf(in_fp, "%%\n%% font encoding '%s':\n%%\n", in_encoding_name);

  encoding_num = PDFObject_WriteNewObj(in_fp);
  fputs("<<\n/Type /Encoding\n/Differences [ 0\n", in_fp);

  /* add font encoding to list of encodings (assume we don't get passed the  */
  /* same encoding more than once)                                           */
  encoding_entry =
    (t_font_encoding_entry_ptr) malloc(sizeof(t_font_encoding_entry));
  if (encoding_entry == NULL)
    Error(48, 2, "PDFFile_BeginFontEncoding: run out of memory",FATAL,no_fpos);

  encoding_entry->m_font_encoding =
    (FULL_CHAR*) malloc(strlen(in_encoding_name) + 1);
  if (encoding_entry->m_font_encoding == NULL)
    Error(48, 3, "PDFFile_BeginFontEncoding: out of memory", FATAL, no_fpos);

  encoding_entry->m_next_entry = g_font_encoding_list;
  encoding_entry->m_object_num = encoding_num;
  strcpy((char*) encoding_entry->m_font_encoding, (char*) in_encoding_name);
  g_font_encoding_list = encoding_entry;
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFFile_EndFontEncoding(FILE* in_fp)                                */
/*                                                                           */
/*  End of font encoding.                                                    */
/*                                                                           */
/*****************************************************************************/

void PDFFile_EndFontEncoding(FILE* in_fp)
{
  fputs("]\n>>\nendobj\n", in_fp);
}


/*****************************************************************************/
/*                                                                           */
/* const FULL_CHAR* PDFFont_FindListEntry(const FULL_CHAR* in_real_font_name)*/
/*                                                                           */
/*  Try to find the font list entry with the specified real font name;       */
/*  return the entry's reference if found else return NULL.                  */
/*                                                                           */
/*****************************************************************************/

static t_font_list_entry_ptr
  PDFFont_FindListEntry(const FULL_CHAR* in_real_font_name)
{
  t_font_list_entry_ptr entry = g_font_list;

  while (entry != NULL) {
    if (strcmp((char*)in_real_font_name, (char*)entry->m_actual_font_name)==0)
      break;
    entry = entry->m_next_font_entry;
  }
  return entry;
}


/*****************************************************************************/
/*                                                                           */
/*  const FULL_CHAR*                                                         */
/*    PDFFont_FindListEntry_Short(const FULL_CHAR* in_short_font_name)       */
/*                                                                           */
/*  Try to find the font list entry with the specified real font name;       */
/*  return the entry's reference if found else return NULL.                  */
/*                                                                           */
/*****************************************************************************/

static t_font_list_entry_ptr
  PDFFont_FindListEntry_Short(const FULL_CHAR* in_short_font_name)
{
  t_font_list_entry_ptr entry = g_font_list;

  while (entry != NULL) {
    if (strcmp((char*)in_short_font_name, (char*)entry->m_short_font_name)==0)
      break;
    entry = entry->m_next_font_entry;
  }
  return entry;
}


/*****************************************************************************/
/*                                                                           */
/*  const t_font_list_entry_ptr PDFFont_NewListEntry(                        */
/*   const FULL_CHAR* in_short_font_name, const FULL_CHAR* in_real_font_name)*/
/*                                                                           */
/*  Create a new font entry and return the short name of the font.           */
/*                                                                           */
/*****************************************************************************/

static t_font_list_entry_ptr
  PDFFont_NewListEntry(const FULL_CHAR* in_short_font_name,
			const FULL_CHAR* in_real_font_name,
			PDF_OBJECT_NUM in_font_encoding_obj)
{
  PDF_FONT_NUM next_font_num = 0;
  t_font_list_entry_ptr new_entry = g_font_list;
  /* t_font_list_entry_ptr last_font_list_entry = NULL; */

  /* find next available font number */
  {
    while (new_entry != NULL) {
      next_font_num++;
      new_entry = new_entry->m_next_font_entry;
    }
  }

  /* make a new font list entry */
  {
    char PDF_font_name[64] = "/F";
    char num[32];

    new_entry = (t_font_list_entry_ptr) malloc(sizeof(t_font_list_entry));
    if (new_entry == NULL)
      Error(48, 4, "PDFFont_NewListEntry: run out of memory", FATAL, no_fpos);

    sprintf(num, "%u", next_font_num);
    strcat(PDF_font_name, num);

    new_entry->m_PDF_font_name =
      (FULL_CHAR*) malloc(strlen((char*) PDF_font_name) + 1);
    if (new_entry->m_PDF_font_name == NULL)
      Error(48, 5, "PDFFont_NewListEntry: run out of memory", FATAL, no_fpos);
    strcpy((char*) new_entry->m_PDF_font_name, PDF_font_name);

    new_entry->m_short_font_name =
      (FULL_CHAR*) malloc(strlen((char*) in_short_font_name) + 1);
    if (new_entry->m_short_font_name == NULL)
      Error(48, 6, "PDFFont_NewListEntry: run out of memory", FATAL, no_fpos);
    strcpy((char*) new_entry->m_short_font_name, (char*) in_short_font_name);

    new_entry->m_actual_font_name =
      (FULL_CHAR*) malloc(strlen((char*) in_real_font_name) + 1);
    if (new_entry->m_actual_font_name == NULL)
      Error(48, 7, "PDFFont_NewListEntry: run out of memory", FATAL, no_fpos);
    strcpy((char*) new_entry->m_actual_font_name, (char*) in_real_font_name);

    new_entry->m_font_encoding_obj = in_font_encoding_obj;

    new_entry->m_pdf_object_number = 0;	/* don't give this font resource an  */
					/* object number until needed        */
    /* new_entry->m_in_use = TRUE; */	/* should be cleared after each page */
    /* g_page_uses_fonts = TRUE;   */
    new_entry->m_font_resource_in_pdf = FALSE;	/* not in PDF file yet       */

    new_entry->m_next_font_entry = g_font_list;
    g_font_list = new_entry;
  }
  debug1(DPD, D, "new PDF font entry with short name %s",
    new_entry->m_short_font_name);
  return new_entry;
}


/*****************************************************************************/
/*                                                                           */
/*  const t_font_list_entry_ptr PDFGetFont(const char* in_real_font_name)    */
/*                                                                           */
/*  Return the reference of a font entry. Never returns NULL.                */
/*                                                                           */
/*****************************************************************************/
/*
static t_font_list_entry_ptr PDFGetFont(const FULL_CHAR* in_real_font_name)
{
  t_font_list_entry_ptr entry = PDFFont_FindListEntry(in_real_font_name);
  if (entry == NULL)
    entry = PDFFont_NewListEntry(in_real_font_name);
  return entry;
}
*/

/*****************************************************************************/
/*                                                                           */
/*  PDFFont_WriteObjectRef(FILE* in_fp, t_font_list_entry_ptr in_font_entry) */
/*                                                                           */
/*  Write a reference to the object to the file.                             */
/*                                                                           */
/*****************************************************************************/

static void PDFFont_WriteObjectRef(FILE* in_fp,
  const t_font_list_entry* in_font_entry)
{
  Assert(in_font_entry->m_pdf_object_number != 0, no_fpos);
  PDFObject_WriteRef(in_fp, in_font_entry->m_pdf_object_number);
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFFont_WriteObject(FILE* in_fp,                                    */
/*    t_font_list_entry_ptr in_font_entry)                                   */
/*                                                                           */
/*  Write a reference to the object to the file.                             */
/*                                                                           */
/*****************************************************************************/

static void PDFFont_WriteObject(FILE* in_fp, t_font_list_entry_ptr in_font_entry)
{
  if (in_font_entry->m_pdf_object_number == 0)
    in_font_entry->m_pdf_object_number = PDFObject_New(/* in_fp */);
  PDFObject_WriteObj(in_fp, in_font_entry->m_pdf_object_number);
}


/*****************************************************************************/
/*                                                                           */
/*  BOOLEAN PDFFont_IsOneOfTheBase14Fonts(const FULL_CHAR* in_real_font_name)*/
/*                                                                           */
/*  Returns true if given font is one of the base 14 fonts.                  */
/*                                                                           */
/*****************************************************************************/

static BOOLEAN PDFFont_IsOneOfTheBase14Fonts(const FULL_CHAR* in_real_font_name)
{
  int i;
  for (i = 0; i < kBase14FontCount; i++)
    if (strcmp(g_standard_base_14_fonts[i], (char*) in_real_font_name) == 0)
      return TRUE;
  return FALSE;
}


/*****************************************************************************/
/*                                                                           */
/*  PDFFont_WriteFontResource(FILE* in_fp,                                   */
/*    t_font_list_entry_ptr in_font_entry)                                   */
/*                                                                           */
/*  Writes out the PDF idea of a Font resource.                              */
/*                                                                           */
/*****************************************************************************/

static void PDFFont_WriteFontResource(FILE* in_fp,
  t_font_list_entry_ptr in_font_entry)
{
  if (! in_font_entry->m_font_resource_in_pdf)
  {
    in_font_entry->m_font_resource_in_pdf = TRUE;

    if (g_PDF_debug)
      fprintf(in_fp, "%%\n%% declare use of font %s:\n%%\n",
	in_font_entry->m_actual_font_name);

    PDFFont_WriteObject(in_fp, in_font_entry);
    fputs("<<\n/Type /Font\n/Subtype /Type1\n", in_fp);
    fprintf(in_fp, "/Name %s\n", (char*) in_font_entry->m_PDF_font_name);
    fprintf(in_fp, "/BaseFont /%s\n", (char*) in_font_entry->m_actual_font_name);
    if (! PDFFont_IsOneOfTheBase14Fonts(in_font_entry->m_actual_font_name))
    {
      /* ***
      fputs("/FirstChar 0"\n, in_fp);	- we don't do first chars (yet)
      fputs("/LastChar 255\n", in_fp);	- we don't do last chars (yet)
      fputs("/Widths ", in_fp);		- we don't do last chars (yet)
      fputs("/FontDescriptor ", in_fp);	- we don't do font descriptors (yet)
      *** */
    }

    if (in_font_entry->m_font_encoding_obj != 0)
    {
      fputs("/Encoding ", in_fp);
      PDFObject_WriteRef(in_fp, in_font_entry->m_font_encoding_obj);
      fputs("\n", in_fp);
    }

    /* ***
    else
      Error(48, 8, "PDFFont_WriteFontResource: a font has no encoding",
	WARN, no_fpos);
    *** */
    fputs(">>\nendobj\n", in_fp);
  }
}


/*****************************************************************************/
/*                                                                           */
/*  PDFFont_WriteFontResource_name(FILE* in_fp,                              */
/*    const FULL_CHAR* in_real_font_name)                                    */
/*                                                                           */
/*  Writes out the PDF idea of a Font resource.                              */
/*                                                                           */
/*****************************************************************************/

/* ***
static void PDFFont_WriteFontResource_name(FILE* in_fp,
  const FULL_CHAR* in_real_font_name)
{
  t_font_list_entry_ptr entry = PDFFont_FindListEntry(in_real_font_name);
  Assert(entry != NULL, no_fpos);
  PDFFont_WriteFontResource(in_fp, entry);
}
*** */


/*****************************************************************************/
/*                                                                           */
/*  const FULL_CHAR* PDFGetPDFFontName(const FULL_CHAR* in_real_font_name)   */
/*                                                                           */
/*  Return the short name of a font.                                         */
/*                                                                           */
/*****************************************************************************/

/* ***
static const FULL_CHAR* PDFGetPDFFontName(const FULL_CHAR* in_real_font_name)
{
  t_font_list_entry_ptr entry = PDFFont_FindListEntry(in_real_font_name);
  Assert(entry != NULL, no_fpos);
  return entry->m_PDF_font_name;
}
*** */


/*****************************************************************************/
/*                                                                           */
/* PDF_OBJECT_NUM PDFFont_FindFontEncoding(FULL_CHAR* in_font_encoding_name) */
/*                                                                           */
/*  Return the object number of a given font encoding.                       */
/*                                                                           */
/*****************************************************************************/

static PDF_OBJECT_NUM PDFFont_FindFontEncoding(
  const FULL_CHAR* in_font_encoding_name)
{
  t_font_encoding_entry_ptr entry = g_font_encoding_list;

  /* these two lines are Uwe patch Jul 18, 2000 */
  if (in_font_encoding_name == NULL)
    return 0;

  while (entry != NULL)
  {
    if (strcmp((char*)in_font_encoding_name, (char*)entry->m_font_encoding)==0)
      break;
    entry = entry->m_next_entry;
  }
  return (entry != NULL) ? entry->m_object_num : 0;
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFFont_AddFont(                                                    */
/*           FILE* in_fp, const FULL_CHAR* in_short_font_name,               */
/*           const FULL_CHAR* in_real_font_name)                             */
/*                                                                           */
/*  Add this font to the list of fonts that the document uses. Also remember */
/*  that this font is "in use" (output when page resources are written).     */
/*                                                                           */
/*****************************************************************************/

void PDFFont_AddFont(FILE* in_fp, const FULL_CHAR* in_short_font_name,
  const FULL_CHAR* in_real_font_name, const FULL_CHAR* in_font_encoding_name)
{
  t_font_list_entry_ptr entry = PDFFont_FindListEntry(in_real_font_name);
  debug4(DPD, D, "PDFFont_AddFont(-, %s, %s, %s) [new = %s]",
    in_short_font_name, in_real_font_name,
    (in_font_encoding_name != (FULL_CHAR *) NULL ? in_font_encoding_name : (const FULL_CHAR*) ""),
    bool(entry == NULL));
  /* *** this attempted bug fix by Jeff K. problem may be multiple font
	 entries for the same font
  if (entry == NULL)
  *** */
  if (TRUE)
    entry = PDFFont_NewListEntry(in_short_font_name, in_real_font_name,
      PDFFont_FindFontEncoding(in_font_encoding_name));

  /* ***
  entry->m_in_use = TRUE;
  g_page_uses_fonts = TRUE;
  *** */
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFPage_SetVars(int xsize, int ysize, int xmark, int ymark,         */
/*    int loutf, int loutv, int louts)                                       */
/*                                                                           */
/*  Writes a string to the page's stream.                                    */
/*                                                                           */
/*****************************************************************************/

void PDFPage_SetVars(int xsize, int ysize, int xmark, int ymark,
  int loutf, int loutv, int louts)
{
  g_graphics_vars[k_xsize] = xsize;
  g_graphics_vars[k_ysize] = ysize;
  g_graphics_vars[k_xmark] = xmark;
  g_graphics_vars[k_ymark] = ymark;

  g_units[k_loutf] = loutf;
  g_units[k_loutv] = loutv;
  g_units[k_louts] = louts;
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFPage_FlushCompressedBuffer(FILE* in_fp)                          */
/*                                                                           */
/*  Flushes the compressed output buffer to the page's stream.               */
/*                                                                           */
/*****************************************************************************/

#if PDF_COMPRESSION
static void PDFPage_FlushCompressedBuffer(FILE* in_fp)
{
  int err;

  Assert(g_apply_compression, no_fpos);
  do {
    err = deflate(&g_comp_stream, Z_FINISH);

    fwrite(g_compressed_output,
      sizeof(g_compressed_output) - g_comp_stream.avail_out, 1, in_fp);
    g_comp_stream.next_out = g_compressed_output;
    g_comp_stream.avail_out = sizeof(g_compressed_output);
  } while (err == Z_OK);

  if (err != Z_STREAM_END)
    Error(48, 9, "PDFPage_FlushCompressedBuffer: zlib error occurred",
      FATAL, no_fpos);

  err = deflateEnd(&g_comp_stream);
  if (err != Z_OK)
    Error(48, 10, "PDFPage_FlushCompressedBuffer: zlib error occurred",
      FATAL, no_fpos);
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFPage_FlushRawBuffer(FILE* in_fp)                                 */
/*                                                                           */
/*  Attempts to compress the raw buffer; also flushes the compressed output  */
/*  buffer to the page's stream if it is full.                               */
/*                                                                           */
/*****************************************************************************/

static void PDFPage_FlushRawBuffer(FILE* in_fp)
{
  int err;

  /* compress the raw buffer */
  Assert(g_apply_compression, no_fpos);
  g_comp_stream.next_in = g_raw_output;
  g_comp_stream.avail_in = (uInt) (g_raw_buffer_ptr - g_raw_output);
  Assert(g_comp_stream.avail_out != 0, no_fpos);

  /* always compress to the point where the raw buffer is empty */
  do {
    err = deflate(&g_comp_stream, Z_NO_FLUSH);
    /* bug fix from newman-andy@yale.edu Feb 23 2000 if (err != Z_OK) */
    if ( err != Z_OK && g_comp_stream.avail_in != 0 )
      Error(48, 11, "PDFPage_FlushRawBuffer: zlib error occurred",FATAL,no_fpos);

    /* IF compressed output buffer is full THEN flush it to disk and reset it */
    if (g_comp_stream.avail_out == 0)
    {
      if (fwrite(g_compressed_output, sizeof(g_compressed_output), 1, in_fp)!=1)
	Error(48, 12, "PDFPage_FlushRawBuffer: write error occurred",
	  FATAL, no_fpos);
      g_comp_stream.next_out = g_compressed_output;
      g_comp_stream.avail_out = sizeof(g_compressed_output);
    }
  } while (g_comp_stream.avail_in != 0);

  /* reset raw buffer for next call */
  g_raw_buffer_ptr = g_raw_output;
}
#endif


/*****************************************************************************/
/*                                                                           */
/*  void PDFPage_WriteStream(FILE* in_fp, char* in_str)                      */
/*                                                                           */
/*  Writes a string to the page's stream.                                    */
/*                                                                           */
/*****************************************************************************/

static void PDFPage_WriteStream(FILE* in_fp, char* in_str)
{
  if (*in_str == 0)
    return;

#if PDF_COMPRESSION
  if (g_apply_compression)
  {
    unsigned int total = strlen(in_str);
    char *ptr = in_str;

    while (total != 0)
    {
      unsigned int len = total;
      BOOLEAN needToFlush =
	((g_raw_buffer_ptr + len) > (g_raw_output + sizeof(g_raw_output)));
      if (needToFlush)
	len = g_raw_output + sizeof(g_raw_output) - g_raw_buffer_ptr;
      memcpy(g_raw_buffer_ptr, ptr, len);

      ptr += len;
      g_raw_buffer_ptr += len;
      total -= len;

      /* IF need to flush raw buffer THEN do so */
      if (needToFlush) PDFPage_FlushRawBuffer(in_fp);
    } /* while still have bytes to process */
  }
  else
#endif
    fputs(in_str, in_fp);
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFPage_Begin(FILE* in_fp)                                          */
/*                                                                           */
/*  Begins the page's stream.                                                */
/*                                                                           */
/*****************************************************************************/

static void PDFPage_Begin(FILE* in_fp)
{
  if (g_page_contents_obj_num == 0)
  {
    t_tempbuf str;

    if (g_PDF_debug)
      fprintf(in_fp, "%%\n%% page %u's contents:\n%%\n", g_page_count);

    g_page_contents_obj_num = PDFObject_WriteNewObj(in_fp);
    g_page_length_obj_num = PDFObject_New(/* in_fp */);
    fputs("<< /Length ", in_fp);
    PDFObject_WriteRef(in_fp, g_page_length_obj_num);
#if PDF_COMPRESSION
    if (g_apply_compression) fputs(" /Filter /FlateDecode", in_fp);
#endif
    fputs(" >>\nstream\n", in_fp);
    g_page_start_offset = ftell(in_fp);

#if PDF_COMPRESSION
    if (g_apply_compression)
    {
      int err;

      g_raw_buffer_ptr = g_raw_output;
      g_comp_stream.zalloc = (alloc_func) Z_NULL;
      g_comp_stream.zfree = (free_func) Z_NULL;
      g_comp_stream.opaque = (voidpf) Z_NULL;

      err = deflateInit(&g_comp_stream, Z_DEFAULT_COMPRESSION);
      if (err != Z_OK)
	Error(48, 13, "PDFPage_Begin: zlib error occurred", FATAL, no_fpos);

      g_comp_stream.next_out = g_compressed_output;
      g_comp_stream.avail_out = sizeof(g_compressed_output);
    }
#endif

#ifndef USE_MATRICES
    sprintf(str, "%.2f 0 0 %.2f 0 0 cm\n",
      g_page_h_scale_factor, g_page_v_scale_factor);
    PDFPage_WriteStream(in_fp, str);
#endif
    sprintf(str, "%u w\n", g_page_line_width);
    PDFPage_WriteStream(in_fp, str);
  }
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFPage_FlushBuffer(FILE* in_fp)                                    */
/*                                                                           */
/*  Flush the buffer to the page's stream and turn off buffering mode.       */
/*                                                                           */
/*****************************************************************************/

static void PDFPage_FlushBuffer(FILE* in_fp)
{
  if (g_in_buffering_mode)
  {
    g_in_buffering_mode = FALSE;

    /* empty the stack since it's no longer needed */
    while (g_qsave_marking_stack != NULL)
    {
      t_qsave_marking_entry_ptr entry = g_qsave_marking_stack;
      g_qsave_marking_stack = entry->m_next_entry;
      free(entry);
    }

    /* output the buffer */
    PDFPage_WriteStream(in_fp, g_buffer);
  }
}


/*****************************************************************************/
/*                                                                           */
/*  PDF_FILE_OFFSET PDFPage_End(FILE* in_fp)                                 */
/*                                                                           */
/*  Ends the page's stream.                                                  */
/*                                                                           */
/*****************************************************************************/

static PDF_FILE_OFFSET PDFPage_End(FILE* in_fp)
{

  /* if page has no marks on it then write out an empty stream */
  if (g_in_buffering_mode)
  {
    g_buffer_pos = 0;
    g_buffer[0] = '\0';			/* force empty string to be written  */
    PDFPage_FlushBuffer(in_fp);	  /* all I really want is to empty the stack */
  }

  /* IF applying compression THEN first flush the raw buffer and then flush */
  /* the compressed buffer (must be performed in that order!)               */
#if PDF_COMPRESSION
  if (g_apply_compression)
  {
    if ((g_raw_buffer_ptr > g_raw_output) && (g_raw_buffer_ptr[-1] == '\n'))
      g_raw_buffer_ptr--;		/* remove last linefeed */

    PDFPage_FlushRawBuffer(in_fp);
    PDFPage_FlushCompressedBuffer(in_fp);
    fputs("\n", in_fp);
  }
#endif

  /* close page's stream */
  Assert(g_page_contents_obj_num != 0, no_fpos);
  {
    PDF_FILE_OFFSET page_length = ftell(in_fp) - g_page_start_offset;

    /* close page's stream */
    fputs("endstream\nendobj\n", in_fp);
    return page_length;
  }
  return 0;
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFPage_Write(FILE* in_fp, char* in_str)                            */
/*                                                                           */
/*  Writes a string to the page's stream.                                    */
/*                                                                           */
/*****************************************************************************/

void PDFPage_Write(FILE* in_fp, char* in_str)
{
  if (*in_str == 0)
    return;

  PDFPage_Begin(in_fp);	/* write page content's hdr "<< /Length >> stream"...*/

  /* IF trying to remove redundant operations THEN */
  if (g_in_buffering_mode)
  {

    /* if buffer will overflow then turn off buffering and flush buffer */
    unsigned int len = strlen(in_str);
    if ( (g_buffer_pos + len) > (kBufferSize-1) )	/* -1 for NULL char */
    {
      PDFPage_FlushBuffer(in_fp);
      PDFPage_WriteStream(in_fp, in_str);
    }
    else
    {
      strcpy(g_buffer + g_buffer_pos, in_str);		/* save into buffer */
      g_buffer_pos += len;
    }
  }
  else
  {
    if (g_TJ_pending)
    {
      g_TJ_pending  = FALSE;				/* clear it */
      PDFPage_WriteStream(in_fp, ")]TJ\n");
    }

    if (g_ET_pending)
    {
      g_ET_pending  = FALSE;				/* clear it */
      PDFPage_WriteStream(in_fp, "ET\n");
      g_valid_text_matrix = FALSE;			/* Td is not allowed */
    }

    PDFPage_WriteStream(in_fp, in_str);
  }
}


/*****************************************************************************/
/*                                                                           */
/*  PDFPage_Push(FILE* in_fp)                                                */
/*                                                                           */
/*  Saves the current graphics state.                                        */
/*                                                                           */
/*****************************************************************************/

void PDFPage_Push(FILE* in_fp)
{

  /* push origin coords */
  {
    t_qsave_entry_ptr entry = (t_qsave_entry_ptr) malloc(sizeof(t_qsave_entry));
    if (entry == NULL)
      Error(48, 14, "PDFPage_Push: run out of memory", FATAL, no_fpos);

    entry->m_page_h_origin = g_page_h_origin;
    entry->m_page_v_origin = g_page_v_origin;
    /* entry->m_page_h_scale_factor = g_page_h_scale_factor; */
    /* entry->m_page_v_scale_factor = g_page_v_scale_factor; */
    entry->m_next_entry = g_qsave_stack;
    g_qsave_stack = entry;
  }

  /* if buffering text */
  if (g_in_buffering_mode)
  {

    /* push current state */
    t_qsave_marking_entry_ptr entry =
      (t_qsave_marking_entry_ptr) malloc(sizeof(t_qsave_marking_entry));
    if (entry == NULL)
      Error(48, 15, "PDFPage_Push: run out of memory", FATAL, no_fpos);

    entry->m_next_entry = g_qsave_marking_stack;    /* next-to-top-of-stack */
    entry->m_buffer_pos = g_buffer_pos;
    g_qsave_marking_stack = entry;		    /* new TOS              */
    /* g_in_buffering_mode = TRUE; */
  }

  /* write out push op */
  PDFPage_Write(in_fp, "q\n");
}


/*****************************************************************************/
/*                                                                           */
/*  PDFPage_Pop(FILE* in_fp)                                                 */
/*                                                                           */
/*  Restores the current graphics state.                                     */
/*                                                                           */
/*****************************************************************************/

void PDFPage_Pop(FILE* in_fp)
{

  /* pop origin coords */
  {
    t_qsave_entry_ptr entry = g_qsave_stack;

    g_page_h_origin = entry->m_page_h_origin;
    g_page_v_origin = entry->m_page_v_origin;
    /* g_page_h_scale_factor = entry->m_page_h_scale_factor; */
    /* g_page_v_scale_factor = entry->m_page_v_scale_factor; */

    g_qsave_stack = entry->m_next_entry;

    free(entry);
  }

  /* if no marks on page since last push (and thus there should be a stack) */
  if (g_in_buffering_mode)
  {

    /* pop state: behave as if the q...Q never existed */
    t_qsave_marking_entry_ptr entry = g_qsave_marking_stack;

    Assert(entry != NULL, no_fpos);

    g_qsave_marking_stack = entry->m_next_entry;	/* new TOS */
    g_buffer_pos   = entry->m_buffer_pos;
    g_buffer[g_buffer_pos] = '\0';	/* chop off unwanted text */

    free(entry);
  }
  else
  {
    Assert(g_qsave_marking_stack == NULL, no_fpos);
    PDFPage_Write(in_fp, "\nQ\n");
  }
}


/*****************************************************************************/
/*                                                                           */
/*  PDFFont_Set(FILE* in_fp, FULL_LENGTH in_font_size,                       */
/*    FULL_CHAR* in_font_name)                                               */
/*                                                                           */
/*  Sets the font name and size for subsequent text write statements.        */
/*                                                                           */
/*****************************************************************************/

void PDFFont_Set(FILE* in_fp, FULL_LENGTH in_font_size,
  FULL_CHAR *in_short_font_name)
{
  t_tempbuf str;

  t_font_list_entry_ptr entry = PDFFont_FindListEntry_Short(in_short_font_name);
  if( entry == NULL )
  {
    Error(48, 42, "cannot find font entry for name %s", FATAL, no_fpos,
      in_short_font_name);
  }
  /* Assert(entry != NULL, no_fpos); */
#ifdef USE_MATRICES
  sprintf(str, "%s %u Tf\n", entry->m_PDF_font_name,
    (int) (g_page_v_scale_factor * in_font_size));
#else
  sprintf(str, "%s %u Tf\n", entry->m_PDF_font_name, in_font_size);
  /* g_text_font_size_in_ems = g_page_v_scale_factor * in_font_size; */
#endif

#if 1

  /* font changes can occur within BT...ET blocks, so temporarily turn off   */
  /* g_ET_pending. I do it this way so that the qsave_marking_stack          */
  /* optimisation can still be applied (this avoids output such as           */
  /* "/F0 240 Tf /F0 240 Tf /F1 600 Tf" and instead produces "")             */
  if (g_TJ_pending)
  {
    g_TJ_pending = FALSE;		/* clear it */
    PDFPage_WriteStream(in_fp, ")]TJ\n");
  }

  {
    BOOLEAN cur_ET_pending = g_ET_pending;

    g_ET_pending = FALSE;		/* clear it */
    PDFPage_Write(in_fp, str);
    g_ET_pending = cur_ET_pending;	/* restore it */
  }
#else
  /* font changes can occur within BT...ET blocks, so bypass PDFPage_Write() */
  PDFPage_WriteStream(in_fp, str);
#endif
  entry->m_in_use = TRUE;
  g_page_uses_fonts = TRUE;
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFText_RMove(FILE* in_fp, int hdelta, int vdelta)                  */
/*                                                                           */
/*  Offsets text pen by the given offsets.                                   */
/*                                                                           */
/*****************************************************************************/

/* ***
void PDFText_RMove(FILE* in_fp, int hdelta, int vdelta)
{
  t_tempbuf str;

  g_tx_hpos += hdelta;
  g_tx_vpos += vdelta;
#if 1
  sprintf(str, "ET\n1 0 0 1 %d %d cm\nBT\n", hdelta, vdelta);
#else
  sprintf(str, "1 0 0 1 %d %d Tm\n", g_tx_hpos, g_tx_vpos);
#endif
  PDFPage_Write(in_fp, str);
}
*** */


/*****************************************************************************/
/*                                                                           */
/*  void PDFText_MoveTo(FILE* in_fp, int hpos, int vpos)                     */
/*                                                                           */
/*  Move text pen to the given coords.                                       */
/*                                                                           */
/*****************************************************************************/

/* ***
static void PDFText_MoveTo(FILE* in_fp, int hpos, int vpos)
{
  g_tx_hpos = 0;
  g_tx_vpos = 0;
#if 1
  PDFText_RMove(in_fp, hpos, vpos);
#else
  {
    t_tempbuf str;
    sprintf(str, "1 0 0 1 %d %d Tm\n", hpos, vpos);
    PDFPage_Write(in_fp, str);
  }
#endif
}
*** */


/*****************************************************************************/
/*                                                                           */
/*  void PDFText_OpenString(FILE* in_fp)                                     */
/*                                                                           */
/*  Open TJ block                                                            */
/*                                                                           */
/*****************************************************************************/

static void PDFText_OpenString(FILE* in_fp)
{
  if (g_TJ_pending)
    g_TJ_pending = FALSE;	/* clear it */
  else
    PDFPage_Write(in_fp, "[(");
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFText_MoveToXYAndOpen(FILE* in_fp, int hpos, int vpos)           */
/*                                                                           */
/*  Move text pen to the given coords.                                       */
/*                                                                           */
/*****************************************************************************/

static void PDFText_MoveToXYAndOpen(FILE* in_fp, int hpos, int vpos)
{
#if 1
  t_tempbuf str;
  sprintf(str, "1 0 0 1 %d %d Tm\n", hpos, vpos);
  PDFPage_Write(in_fp, str);
#else
  PDFText_MoveTo(in_fp, hpos, vpos);
#endif
  PDFText_OpenString(in_fp);
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFText_MoveToXAndOpen(FILE* in_fp, int hpos, int vpos)             */
/*                                                                           */
/*  Move text pen to the given coords.                                       */
/*                                                                           */
/*****************************************************************************/

static void PDFText_MoveToXAndOpen(FILE* in_fp, int hpos)
{
#if 1
  t_tempbuf str;
  sprintf(str, "%d 0 Td\n", hpos);
  PDFPage_Write(in_fp, str);
#else
  PDFText_MoveTo(in_fp, hpos, vpos);
#endif
  PDFText_OpenString(in_fp);
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFText_OpenBT(FILE* in_fp)                                         */
/*                                                                           */
/*  Opens a text object at the given coords.                                 */
/*                                                                           */
/*****************************************************************************/

static void PDFText_OpenBT(FILE* in_fp)
{
  PDFPage_FlushBuffer(in_fp);	/* about to mark page: flush buffered PDF    */

  g_page_has_text = TRUE;

  if (g_TJ_pending)
  {
    g_TJ_pending = FALSE;		/* clear it */
    PDFPage_WriteStream(in_fp, ")]TJ\n");
  }

  if (g_ET_pending)
    g_ET_pending = FALSE;		/* clear it */
  else
  {
    PDFPage_Write(in_fp, "BT\n");
    g_valid_text_matrix = TRUE;		/* Td is allowed */
  }
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFText_OpenXY(FILE* in_fp, int hpos, int vpos)                     */
/*                                                                           */
/*  Opens a text object at the given coords.                                 */
/*                                                                           */
/*****************************************************************************/

void PDFText_OpenXY(FILE* in_fp, int hpos, int vpos)
{
  PDFText_OpenBT(in_fp);
  PDFText_MoveToXYAndOpen(in_fp, hpos, vpos);
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFText_OpenX(FILE* in_fp, int hpos)                                */
/*                                                                           */
/*  Opens a text object at the given coords.                                 */
/*                                                                           */
/*****************************************************************************/

void PDFText_OpenX(FILE* in_fp, int hpos)
{
  PDFText_OpenBT(in_fp);
  PDFText_MoveToXAndOpen(in_fp, hpos);
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFText_Open(FILE* in_fp)                                           */
/*                                                                           */
/*  Opens a text object.                                                     */
/*                                                                           */
/*****************************************************************************/

void PDFText_Open(FILE* in_fp)
{
  if (g_TJ_pending)
  {
    g_TJ_pending = FALSE;		/* clear it */
    Assert(g_ET_pending == TRUE, no_fpos);
    g_ET_pending = FALSE;		/* clear it */
  }
  else
  {
    PDFText_OpenBT(in_fp);
    PDFText_OpenString(in_fp);
  }
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFText_Kern(FILE* in_fp, int in_kern)                              */
/*                                                                           */
/*  Apply kerning to a text string.                                          */
/*                                                                           */
/*  Note: in_kern is in 1/1000 of font size                                  */
/*                                                                           */
/*****************************************************************************/

void PDFText_Kern(FILE* in_fp, int in_kern)
{
  t_tempbuf str;

  /* sprintf(str, ")%d(", -in_kern * 1000 / g_text_font_size_in_ems); */
  sprintf(str, ")%d(", -in_kern);
  PDFPage_Write(in_fp, str);
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFText_Close(FILE* in_fp)                                          */
/*                                                                           */
/*  Closes a previously opened text object.                                  */
/*                                                                           */
/*****************************************************************************/

void PDFText_Close(FILE* in_fp)
{
  /* PDFPage_Begin(in_fp); - shouldn't be needed */
  Assert(g_page_contents_obj_num != 0, no_fpos);

  g_TJ_pending = TRUE;
  /* PDFPage_Write(in_fp, ")] TJ\n"); */
  g_ET_pending = TRUE;
}


#ifdef USE_MATRICES

/*****************************************************************************/
/*                                                                           */
/*  void PDF_Matrix_XY(double* in_out_x, double* in_out_y)                   */
/*                                                                           */
/*  Returns (x, y) after applying the current matrix:                        */
/*                                                                           */
/*                                  [ a b 0 ]                                */
/*  [ x y 1 ]       x       [ c d 0 ]       =       [ ax+cy+e  bx+dy+f  1 ]  */
/*                                  [ e f 1 ]                                */
/*                                                                           */
/*****************************************************************************/

static void PDF_Matrix_XY(double* in_out_x, double* in_out_y)
{
  double result_x, result_y;

  result_x = g_cur_matrix[0] * *in_out_x + g_cur_matrix[3] * *in_out_y +
    g_cur_matrix[6];
  result_y = g_cur_matrix[1] * *in_out_x + g_cur_matrix[4] * *in_out_y +
    g_cur_matrix[7];
  *in_out_x = result_x;
  *in_out_y = result_y;
}


/*****************************************************************************/
/*                                                                           */
/*  PDF_Matrix_Mul(t_matrix in_left, t_matrix in_right, t_matrix out_result) */
/*                                                                           */
/*  Multiplies the given matrices.                                           */
/*                                                                           */
/*  [ a b 0 ]               [ g h 0 ]               [ ag+bi   ah+bj   0 ]    */
/*  [ c d 0 ]       x       [ i j 0 ]       =       [ cg+di   ch+dj   0 ]    */
/*  [ e f 1 ]               [ k l 1 ]               [ eg+fi+k eh+fj+l 1 ]    */
/*                                                                           */
/*****************************************************************************/

static void PDF_Matrix_Mul(t_matrix in_left, t_matrix in_right,
  t_matrix out_result)
{
  t_matrix result;
  result[0] = in_left[0] * in_right[0] + in_left[1] * in_right[3];
  result[1] = in_left[0] * in_right[1] + in_left[1] * in_right[4];
  result[2] = 0;
  result[3] = in_left[3] * in_right[0] + in_left[4] * in_right[3];
  result[4] = in_left[3] * in_right[1] + in_left[4] * in_right[4];
  result[5] = 0;
  result[6] = in_left[6] * in_right[0] + in_left[7] * in_right[3] + in_right[6];
  result[7] = in_left[6] * in_right[1] + in_left[7] * in_right[4] + in_right[7];
  result[8] = 1;

  memcpy(out_result, result, sizeof(t_matrix));
}
#endif


/*****************************************************************************/
/*                                                                           */
/*  void  PDFPage_Scale(float in_h_scale_factor, float in_v_scale_factor)    */
/*                                                                           */
/*  Changes CTM by scale factor:                                             */
/*                                                                           */
/*  [  sh   0  0 ]                                                           */
/*  [   0  sv  0 ]                                                           */
/*  [   0   0  1 ]                                                           */
/*                                                                           */
/*****************************************************************************/

void PDFPage_Scale(FILE* in_fp, float in_h_scale_factor, float in_v_scale_factor)
{
#ifdef USE_MATRICES
  t_matrix m = { 0, 0, 0, 0, 0, 0, 0, 0, 1 };
  m[0] = in_h_scale_factor;
  m[4] = in_v_scale_factor;

  PDF_Matrix_Mul(m, g_cur_matrix, g_cur_matrix);
#else
  t_tempbuf str;

  sprintf(str, "%.2f 0 0 %.2f 0 0 cm\n", in_h_scale_factor, in_v_scale_factor);
  PDFPage_Write(in_fp, str);
#endif
  g_page_h_scale_factor *= in_h_scale_factor;
  g_page_v_scale_factor *= in_v_scale_factor;
}


/*****************************************************************************/
/*                                                                           */
/*  void  PDFPage_Rotate(FILE* in_fp, float in_angle_in_radians)             */
/*                                                                           */
/*  Changes CTM by rotation factor.                                          */
/*                                                                           */
/*  [  cos a  sin a  0 ]                                                     */
/*  [ -sin a  cos a  0 ]                                                     */
/*  [   0       0    1 ]                                                     */
/*                                                                           */
/*****************************************************************************/

void PDFPage_Rotate(FILE* in_fp, float in_angle_in_radians)
{
  float cos_radians = cos(in_angle_in_radians);
  float sin_radians = sin(in_angle_in_radians);
#ifdef USE_MATRICES
  t_matrix m = { 0, 0, 0, 0, 0, 0, 0, 0, 1 };
  m[0] = m[4] = cos_radians;
  m[1] = sin_radians;
  m[3] = -sin_radians;
  PDF_Matrix_Mul(m, g_cur_matrix, g_cur_matrix);
#else
  t_tempbuf str;

  sprintf(str, "%.2f %.2f %.2f %.2f 0 0 cm\n", cos_radians, sin_radians,
    -sin_radians, cos_radians);
  PDFPage_Write(in_fp, str);
#endif
}


/*****************************************************************************/
/*                                                                           */
/*  void  PDFPage_Translate(FILE* in_fp, float in_delta_h, float in_delta_v) */
/*                                                                           */
/*  Changes CTM by translation:                                              */
/*                                                                           */
/*  [  1  0  0 ]                                                             */
/*  [  0  1  0 ]                                                             */
/*  [  dh dv 1 ]                                                             */
/*                                                                           */
/*****************************************************************************/

void PDFPage_Translate(FILE* in_fp, float in_delta_h, float in_delta_v)
{
#ifdef USE_MATRICES
  t_matrix m = { 1, 0, 0, 0, 1, 0, 0, 0, 1 };
  m[6] = in_delta_h;
  m[7] = in_delta_v;
  PDF_Matrix_Mul(m, g_cur_matrix, g_cur_matrix);
#else
  t_tempbuf str;

  sprintf(str, "1 0 0 1 %.2f %.2f cm\n", in_delta_h, in_delta_v);
  PDFPage_Write(in_fp, str);
#endif
  g_page_h_origin += in_delta_h;
  g_page_v_origin += in_delta_v;
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFTargetAnnot_New(FULL_CHAR* in_annot_name, ...)                   */
/*                                                                           */
/*  Create a new target annotation entry.                                    */
/*                                                                           */
/*****************************************************************************/

static void PDFTargetAnnot_New(FULL_CHAR* in_annot_name,
  unsigned int in_annot_name_length, int in_ll_x, int in_ll_y, int in_ur_x,
  int in_ur_y, BOOLEAN in_for_export)
{
  t_target_annot_entry_ptr entry =
    (t_target_annot_entry_ptr) malloc(sizeof(t_target_annot_entry));

  if (entry == NULL)
    Error(48, 16, "PDFTargetAnnot_New: run out of memory", FATAL, no_fpos);

  entry->m_name = (FULL_CHAR*) malloc(in_annot_name_length + 1);
  if (entry->m_name == NULL)
    Error(48, 17, "PDFTargetAnnot_New: run out of memory", FATAL, no_fpos);
  memcpy(entry->m_name, in_annot_name, in_annot_name_length);
  entry->m_name[in_annot_name_length] = '\0';

  Assert(g_page_contents_obj_num != 0, no_fpos);
  entry->m_page_object_num = g_page_object_num;

  entry->m_ll_x = in_ll_x;
  entry->m_ll_y = in_ll_y;
  entry->m_ur_x = in_ur_x;
  entry->m_ur_y = in_ur_y;
  entry->m_for_export = in_for_export;

  entry->m_next_entry = g_target_annot_list;
  g_target_annot_list = entry;

  if (in_for_export)
    g_has_exported_targets = in_for_export;
}


/*****************************************************************************/
/*                                                                           */
/*  t_target_annot_entry_ptr PDFTargetAnnot_Find(FULL_CHAR* in_annot_name)   */
/*                                                                           */
/*  Finds an annotation. Returns NULL if not found.                          */
/*                                                                           */
/*****************************************************************************/

static t_target_annot_entry_ptr PDFTargetAnnot_Find(FULL_CHAR* in_annot_name)
{
  t_target_annot_entry_ptr entry = g_target_annot_list;

  /* this takes O(n) time; may need re-implementing if speed is a factor */
  while (entry != NULL)
  {
    if (strcmp((char*) in_annot_name, (char*) entry->m_name) == 0)
      break;
    entry = entry->m_next_entry;
  }

  return entry;
}


/*****************************************************************************/
/*                                                                           */
/*  PDFSourceAnnot_Write(FILE* in_fp,                                        */
/*    t_source_annot_entry_ptr in_source_entry)                              */
/*                                                                           */
/*  Write an annot which specifies the source and target of the link.        */
/*                                                                           */
/*****************************************************************************/

static void PDFSourceAnnot_Write(FILE* in_fp, t_source_annot_entry_ptr in_entry)
{
  t_target_annot_entry_ptr target;

  Assert(in_entry != NULL, no_fpos);

  target = in_entry->m_target;

  /* if it is an unresolved forward link then exit */
  if ( (in_entry->m_link_type == k_link_source) && (target == NULL) )
    return;

  /* green light: write it out */
  if (g_PDF_debug)
  {
    fprintf(in_fp, "%%\n%% annotation in page object # %u to %s:\n%%\n",
      in_entry->m_this_page_object_num, in_entry->m_target->m_name);
  }

  PDFObject_WriteObj(in_fp, in_entry->m_this_object_num);
  fprintf(in_fp, "<<\n/Type /Annot\n/Subtype /Link\n"
    /* this is what Adobe does (it's also more flexible) */
    "/Rect [ %d %d %d %d ]\n/Border [ 0 0 0 ]\n",
    /* "/BS << /Type /Border /S /U >>\n" */
    /* border appearance is "underline" */
    in_entry->m_ll_x, in_entry->m_ll_y, in_entry->m_ur_x, in_entry->m_ur_y);

  switch (in_entry->m_link_type)
  {
    case k_link_source:

      fprintf(in_fp, "/Dest [ ");
      PDFObject_WriteRef(in_fp, in_entry->m_target->m_page_object_num);
      switch (in_entry->m_dest_option)
      {
	case kFitNoChange:

	  fprintf(in_fp, " /XYZ null null null");
	  /* NB NO BREAK */

	case kFit:

	  fprintf(in_fp, " /Fit");
	  break;


	case kFitH:

	  /* [ /FitH top ]: fit the width of the page to the window; top    */
	  /* specifies the y-coordinate of the top edge of the window       */
	  fprintf(in_fp, " /FitH %u", target->m_ur_y);
	  break;


	case kFitV:

	  /* [ /FitV left ]: fit the height of the page to the window;      */
	  /* left specifies the x-coordinate of the left edge of the window */
	  fprintf(in_fp, " /FitV %u", target->m_ll_x);
	  break;


	case kFitR:

	  /* [ /FitR left bottom right top ]: fit the rectangle specified   */
	  /* by left bottom right top in the window. If the height (top -   */
	  /* bottom) and width (right - left) imply different zoom factors, */
	  /* the numerically smaller zoom factor is used, to ensure that    */
	  /* the specified rectangle fits in the window                     */
	  fprintf(in_fp, " /FitR %u %u %u %u", target->m_ll_x, target->m_ll_y,
	    target->m_ur_x, target->m_ur_y);
	  break;


	case kFitB:

	  /* [ /FitB ]: fit the page's bounding box to the window           */
	  fprintf(in_fp, " /FitB");
	  break;


	case kFitBH:

	  /* [ /FitBH top ]: fit the width of the page's bounding box to    */
	  /* the window. top specifies the y-coord of top edge of window    */
	  fprintf(in_fp, " /FitBH %u", target->m_ur_y);
	  break;


	case kFitBV:

	  /* [ /FitBV left ]: fit the height of the page' bounding box to   */
	  /* the window. left specifies the x-coordinate of the left edge   */
	  /* of the window                                                  */
	  fprintf(in_fp, " /FitBV %u", target->m_ll_x);
	  break;


	default:

	  Error(48, 18, "PDFSourceAnnot_Write: invalid link dest option",
	    FATAL, no_fpos);
      }
      fprintf(in_fp, " ]\n");
      break;


    case k_link_external:

#if 1	/* required wrapper for URLs is now in the Lout libraries */
      fprintf(in_fp, "/A << /Type /Action /S /GoToR /D (%s) /F\n"
	/* <= split across lines for LONG file specs */
	"(%s) >>\n", in_entry->m_name, in_entry->m_file_spec);
#else
      if (in_entry->m_file_spec[0] != '<')
      {
	/* note: destination/target is specified as a string, as is file spec */
	fprintf(in_fp, "/A << /Type /Action /S /GoToR /D (%s) /F\n"
	  /* <= split across lines for LONG file specs */
	  "(%s) >>\n", in_entry->m_name, in_entry->m_file_spec);
      }
      else
      {
        /* if file spec starts with '<' then URL, eg <http://www.adobe.com> */
	Assert(in_entry->m_file_spec[strlen((char*) in_entry->m_file_spec)-1]
	  == '>', no_fpos);
	fprintf(in_fp, "/A << /Type /Action /S /GoToR /D (%s) /F\n"
	  /* <= split across lines for LONG file specs */
	  "<< /FS /URL /F (%s) >> >>\n", in_entry->m_name, in_entry->m_file_spec);
      }
#endif
      break;


    case k_link_URI:

      fprintf(in_fp, "/A << /Type /Action /S /URI /URI\n"
	/* <= split across lines for LONG URI's */
	"(%s) >>\n", in_entry->m_name);
      break;


    case k_link_target:
    case k_link_target_for_export:
    case kNumberOfLinkKeywords:

      break;
  }

  fprintf(in_fp, ">>\nendobj\n");
  in_entry->m_written_to_PDF_file = TRUE;
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFSourceAnnot_New(FULL_CHAR* in_annot_name)                        */
/*                                                                           */
/*  Create an entry in the g_source_annot_list which links to in_annot_name. */
/*                                                                           */
/*****************************************************************************/

static t_source_annot_entry_ptr
PDFSourceAnnot_New(PDF_LINK_KEYWORD in_link_type, FULL_CHAR* in_annot_name,
  unsigned int in_annot_name_length, int in_ll_x, int in_ll_y, int in_ur_x,
  int in_ur_y, PDF_LINK_DEST_OPTION in_link_dest_option)
{
  t_target_annot_entry_ptr target = NULL;
  t_source_annot_entry_ptr entry =
    (t_source_annot_entry_ptr) malloc(sizeof(t_source_annot_entry));

  if (entry == NULL)
    Error(48, 19, "PDFSourceAnnot_New: run out of memory", FATAL, no_fpos);

  entry->m_ll_x = in_ll_x;
  entry->m_ll_y = in_ll_y;
  entry->m_ur_x = in_ur_x;
  entry->m_ur_y = in_ur_y;

  entry->m_this_object_num = PDFObject_New(/* in_fp */);
  entry->m_this_page_object_num = g_page_object_num;
  entry->m_link_type = in_link_type;
  Assert((in_link_dest_option >= kFitNoChange) &&
    (in_link_dest_option <= kFitBV), no_fpos);
  entry->m_dest_option = in_link_dest_option;
  entry->m_file_spec = NULL;
  entry->m_written_to_PDF_file = FALSE;

  if (in_link_type == k_link_source)
    target = PDFTargetAnnot_Find(in_annot_name);

  if (target != NULL)
  {
    entry->m_target = target;
    entry->m_name = NULL;
  }
  else
  {
    entry->m_target = NULL;	/* fwd link */
    entry->m_name = (FULL_CHAR*) malloc(in_annot_name_length + 1);
    if (entry->m_name == NULL)
      Error(48, 20, "PDFSourceAnnot_New: run out of memory", FATAL, no_fpos);
    memcpy(entry->m_name, in_annot_name, in_annot_name_length);
    entry->m_name[in_annot_name_length] = '\0';
  }

  entry->m_next_entry = g_source_annot_list;
  g_source_annot_list = entry;

  return entry;
}


/*****************************************************************************/
/*                                                                           */
/*  PDFSourceAnnot_Dispose(t_source_annot_entry_ptr in_source_annot)         */
/*                                                                           */
/*  Dispose of a source annot entry; returns the next entry in the list.     */
/*                                                                           */
/*****************************************************************************/

static t_source_annot_entry_ptr
  PDFSourceAnnot_Dispose(t_source_annot_entry_ptr in_source_annot)
{
  t_source_annot_entry_ptr next_entry = in_source_annot->m_next_entry;

  if (in_source_annot->m_name != NULL)
    free(in_source_annot->m_name);
  if (in_source_annot->m_file_spec != NULL)
    free(in_source_annot->m_file_spec);
  free(in_source_annot);
  return next_entry;
}


/*****************************************************************************/
/*                                                                           */
/*  float  PDFPage_GetFloat(FULL_CHAR* in_str)                               */
/*                                                                           */
/*  Outputs an appropriate PDF string for drawing a graphic element.         */
/*                                                                           */
/*****************************************************************************/

static FULL_CHAR *PDFPage_GetFloat(FULL_CHAR* in_str, float* out_value)
{
  if (sscanf((char*) in_str, "%f", out_value) == 1)
  {
    /* skip (presumed) floating point number: [ ]*[+|-][0-9.]* */
    while (isspace(*in_str))
      in_str++;
    if ( (*in_str == '-') || (*in_str == '+') )
      in_str++;
    while (isdigit(*in_str) || (*in_str == '.'))
      in_str++;
  }
  else Error(48, 21, "PDFPage_GetFloat: unable to evaluate number for Lout graphic keyword processing",
    FATAL, no_fpos);
  return in_str;
}


/*****************************************************************************/
/*                                                                           */
/*  int PDFKeyword_Find(int in_number_of_array_elements,                     */
/*    char* in_keyword_array[], FULL_CHAR* in_str)                           */
/*                                                                           */
/*  Return index into keyword array if an element matches the given string.  */
/*  Returns -1 if not found.                                                 */
/*                                                                           */
/*****************************************************************************/

static int PDFKeyword_Find(int in_number_of_array_elements,
  char* in_keyword_array[], FULL_CHAR* in_str)
{
  unsigned int i;

  /* look for keyword */
  for (i = 0; i < in_number_of_array_elements; i++)
  {
    unsigned int len = strlen(in_keyword_array[i]);

    if (memcmp(in_keyword_array[i], in_str, len) == 0)
      break;
  }

  return (i < in_number_of_array_elements) ? i : -1;
}


/*****************************************************************************/
/*                                                                           */
/*  FULL_CHAR *PDFPage_ProcessGraphicsKeyword(FULL_CHAR* charPtr, int i)     */
/*                                                                           */
/*  Processes a link keyword.                                                */
/*                                                                           */
/*****************************************************************************/

#if 0	/* this function is no longer used */

static FULL_CHAR *PDFPage_ProcessGraphicsKeyword(FULL_CHAR* charPtr, int i,
  char** strPtr)
{
  float value;

  /* if need be, expand this later to a full blown expression evaluator (ugh) */
  switch (*charPtr)
  {
    case '+':

      Assert(FALSE, no_fpos);
      charPtr = PDFPage_GetFloat(++charPtr, &value);
      sprintf(*strPtr, "%.2f", g_graphics_vars[i] + value);
      break;


    case '-':

      Assert(FALSE, no_fpos);
      charPtr = PDFPage_GetFloat(++charPtr, &value);
      sprintf(*strPtr, "%.2f", g_graphics_vars[i] - value);
      break;


    case '*':

      Assert(FALSE, no_fpos);
      charPtr = PDFPage_GetFloat(++charPtr, &value);
      sprintf(*strPtr, "%.2f", g_graphics_vars[i] * value);
      break;


    case '/':

      Assert(FALSE, no_fpos);
      charPtr = PDFPage_GetFloat(++charPtr, &value);
      Assert(value != 0, no_fpos);	/* not great since value is a float... */
      sprintf(*strPtr, "%.2f", g_graphics_vars[i] / value);
      break;


    default:

      sprintf(*strPtr, "%d", g_graphics_vars[i]);
      break;
  }
  *strPtr += strlen(*strPtr);
  return charPtr;
}

#endif


/*****************************************************************************/
/*                                                                           */
/*  void PDFPage_ProcessLinkKeyword(void)                                    */
/*                                                                           */
/*  Processes a link keyword.                                                */
/*                                                                           */
/*****************************************************************************/

static void PDFPage_ProcessLinkKeyword(void)
{
  FULL_CHAR* charPtr = (FULL_CHAR*) g_link;
  PDF_LINK_KEYWORD keyword = g_link_keyword;
  unsigned int link_len = 0;
  FULL_CHAR* link_name = charPtr;

  /* scan for name of link; scan until end of string or until ' __' reached  */
  /* (scan for name of link; scan until end of string or whitespace reached) */
#if 1

  FULL_CHAR* parm = NULL;
  debug1(DPD, D, "PDFPage_ProcessLinkKeyword(g_link = %s", g_link);

  while ((*charPtr != '\0') &&
    !(isspace(charPtr[0]) && (charPtr[1] == '_') && (charPtr[2] == '_')))
  {
    link_len++;
    charPtr++;
  }

  if (*charPtr != '\0')
    parm = ++charPtr;

  while (*charPtr != '\0')
    charPtr++;
#else
  while ((*charPtr != '\0') && ! isspace(*charPtr))
  {
    link_len++;
    charPtr++;
  }
#endif
  if (link_len == 0)
    Error(48, 22, "PDFPage_ProcessLinkKeyword: empty link-name / URI; ignored.",
      WARN, no_fpos);
  else
  {

    /* see documentaton for @Graphic for the meaning of the x, y parms */
    /* translate the object's box into PDF's default user space */
    int ll_x = g_page_h_origin * g_page_h_scale_factor;
    int ll_y = g_page_v_origin * g_page_v_scale_factor;
    int ur_x = (g_page_h_origin + g_graphics_vars[k_xsize]) * g_page_h_scale_factor;
    int ur_y = (g_page_v_origin + g_graphics_vars[k_ysize]) * g_page_v_scale_factor;

    /* remove this block later (it produces debugging output): */
#if 0
    {
      t_tempbuf strz = "PDFPage_ProcessLinkKeyword: ";
      switch (keyword)
      {
	case k_link_source:

	  strcat(strz, "link_source           =");
	  break;


	case k_link_external:

	  strcat(strz, "link_external         =");
	  break;


	case k_link_URI:

	  strcat(strz, "link_URI              =");
	  break;


	case k_link_target:

	  strcat(strz, "link_target           =");
	  break;


	case k_link_target_for_export:

	  strcat(strz, "link_target_for_export=");
	  break;
      }
      strcat(strz, (char*) link_name);
      fprintf(stderr, "%s", strz);
      /* Err or(48, 23, strz, WARN, no_fpos); */
    }
#endif
    switch (keyword)
    {
      case k_link_source:

	{
	  int j;

	  /* if there is a dest option specified then get it */
	  if (parm != NULL)
	  {
	    j = PDFKeyword_Find(kNumberOfDestLinkOptions, g_dest_link_options,
		charPtr);
	    if (j >= 0)		/* note signed comparison */
	      charPtr += strlen(g_dest_link_options[j]);
	    else
	    {
	      j = (int) kFitNoChange;	/* default */
	      /* must consume the rest of the string */
	      while (*charPtr != '\0')
		charPtr++;
	      link_len = charPtr - link_name;
	    }
	  }
	  else
	    j = (int) kFitNoChange;	/* default */

	  PDFSourceAnnot_New(keyword, link_name, link_len,
	    ll_x, ll_y, ur_x, ur_y, (PDF_LINK_DEST_OPTION) j);
	  break;
	}


      case k_link_external:
      case k_link_URI:

	{
	  t_source_annot_entry_ptr source;
	  source = PDFSourceAnnot_New(keyword, link_name, link_len, ll_x,
	    ll_y, ur_x, ur_y, (PDF_LINK_DEST_OPTION) 0 /* doesn't matter */);
	  if (keyword == k_link_external)
	  {
	    int j;

	    link_len = 0;

	    if (parm != NULL)
	    {
	      j = PDFKeyword_Find(1, g_external_file_spec_keyword, parm);
	      if (j == 0)
	      {
		parm += strlen(g_external_file_spec_keyword[0]);
		link_len = strlen((char*) parm);
#if 0
		/* scan for name of file spec; scan until end of string or */
		/* until whitespace reached                                */
		link_name = charPtr;
		while ((*charPtr != '\0') && ! isspace(*charPtr))
		{
		  link_len++;
		  charPtr++;
		}
#endif
	      }
	    }

	    if (link_len == 0)
	      Error(48, 24, "PDFPage_ProcessLinkKeyword: empty file spec",
		FATAL, no_fpos);
	    else
	    {
	      source->m_file_spec = (FULL_CHAR*) malloc(link_len + 1);
	      if (source->m_file_spec == NULL)
		Error(48, 25, "PDFPage_ProcessLinkKeyword: out of memory",
		  FATAL, no_fpos);
#if 1
	      strcpy((char*) source->m_file_spec, (char*) parm);
#else
	      memcpy(source->m_file_spec, link_name, link_len);
	      source->m_file_spec[link_len] = '\0';
#endif
	    }
	  }
	  break;
	}


      case k_link_target:
      case k_link_target_for_export:

	PDFTargetAnnot_New(link_name, link_len, ll_x, ll_y, ur_x, ur_y,
	  keyword == k_link_target_for_export);
	break;


      case kNumberOfLinkKeywords:
	break;

    } /* switch */
  } /* else */

  debug0(DPD, D, "PDFPage_ProcessLinkKeyword returning");
  /* return charPtr; */
} /* PDFPage_ProcessLinkKeyword */


/*****************************************************************************/
/*                                                                           */
/*  FULL_CHAR* PDFPage_ProcessDocInfoKeyword(FULL_CHAR* charPtr, int i)      */
/*                                                                           */
/*  Processes a document info keyword.                                       */
/*                                                                           */
/*****************************************************************************/

static FULL_CHAR *PDFPage_ProcessDocInfoKeyword(FULL_CHAR* charPtr, int i)
{
  switch (i)
  {
    case k_author:

      if (g_doc_author != NULL)
	free(g_doc_author);
      g_doc_author = (FULL_CHAR*) malloc(strlen((char*) charPtr) + 1);
      if (g_doc_author == NULL)
	Error(48, 26, "PDFPage_ProcessDocInfoKeyword: no memory for __author=",
	  WARN, no_fpos);
      else
	strcpy((char*) g_doc_author, (char*) charPtr);
      break;


    case k_title:

      if (g_doc_title != NULL)
	free(g_doc_title);
      g_doc_title = (FULL_CHAR*) malloc(strlen((char*) charPtr) + 1);
      if (g_doc_title == NULL)
	Error(48, 27, "PDFPage_ProcessDocInfoKeyword: no memory for __title=",
	  WARN, no_fpos);
       else
	strcpy((char*) g_doc_title, (char*) charPtr);
      break;


    case k_subject:

      if (g_doc_subject != NULL)
	free(g_doc_subject);
      g_doc_subject = (FULL_CHAR*) malloc(strlen((char*) charPtr) + 1);
      if (g_doc_subject == NULL)
	Error(47, 28, "PDFPage_ProcessDocInfoKeyword: no memory for __subject=",
	  WARN, no_fpos);
      else
	strcpy((char*) g_doc_subject, (char*) charPtr);
      break;


    case k_keywords:
      if (g_doc_keywords != NULL)
	free(g_doc_keywords);
      g_doc_keywords = (FULL_CHAR*) malloc(strlen((char*) charPtr) + 1);
      if (g_doc_keywords == NULL)
	Error(48, 29, "PDFPage_ProcessDocInfoKeyword: no memory for __keywords=",
	  WARN, no_fpos);
      else
	strcpy((char*) g_doc_keywords, (char*) charPtr);
      break;

  }
  return (charPtr + strlen((char*) charPtr));
}


/*****************************************************************************/
/*                                                                           */
/*  void  PDFPage_EvalExpr(char* inExpr)                                     */
/*                                                                           */
/*  Evaluate collected expression in the given expression buffer.            */
/*                                                                           */
/*****************************************************************************/

static char *PDFPage_EvalExpr(char* inExpr, float* outValue)
{
  int i;
  char* chp = inExpr;

  while (isspace( (int) *chp))	/* ignore leading white space */
    chp++;

  while (*chp == '_')
    chp++;

  while (*chp == '+')	/* ignore unary + */
    chp++;

  if (isdigit((int) *chp) || (*chp == '.'))
  {
    chp = (char*) PDFPage_GetFloat((FULL_CHAR*) chp, outValue);
  }
  else if (*chp == '-')	/* handle unary negation */
  {
    float val;

    chp = PDFPage_EvalExpr(++chp, &val);
    *outValue = -val;
  }
  else
  {
    i = PDFKeyword_Find(kNumberOfArithmeticKeywords,
	  g_arithmetic_keywords, (FULL_CHAR*) chp);
    if (i >= 0)
    {
      float val1, val2;

      chp += strlen(g_arithmetic_keywords[i]);

      while (isspace( (int) *chp))
	chp++;
      if (*chp != '(')
	Error(48, 30, "PDFPage_EvalExpr: '(' expected", FATAL, no_fpos);
      chp = PDFPage_EvalExpr(++chp, &val1);
      if ( (i <= k_div) || (i == k_pick) )
      {
	int count;

	if (i == k_pick)
	{
	  count = floor(val1);
	  Assert(count != 0, no_fpos);
	}
	else
	  count = 1;

	if (*chp != ',')
	  Error(48, 31, "PDFPage_EvalExpr: ',' expected", FATAL, no_fpos);

	do {
	  chp = PDFPage_EvalExpr(++chp, &val2);
	  if ((count != 1) && (*chp == ','))
	    ++chp;
	} while (--count != 0);
      }
      if (*chp != ')')
	Error(48, 32, "PDFPage_EvalExpr: ')' expected", FATAL, no_fpos);
      ++chp;
      switch (i)
      {

	case k_add:
	  *outValue = val1 + val2;
	  break;


	case k_sub:

	  *outValue = val1 - val2;
	  break;


	case k_mul:

	  *outValue = val1 * val2;
	  break;


	case k_div:

	  Assert(val2 != 0, no_fpos); /* not great since value is a float... */
	  *outValue = val1 / val2;
	  break;


	case k_sin:

	  *outValue = sin((double) val1 * (double) M_PI / (double) 180.0);
	  break;


	case k_cos:

	  *outValue = cos((double) val1 * (double) M_PI / (double) 180.0);
	  break;


	case k_pick:

	  *outValue = val2;
	  break;
      }
    }
    else
    {
      i = PDFKeyword_Find(kNumberOfGraphicsKeywords, g_graphic_keywords,
	    (FULL_CHAR*) chp);
      if (i >= 0)
      {
	chp += strlen(g_graphic_keywords[i]);
	*outValue = g_graphics_vars[i];
      }
      else
      {
	i = PDFKeyword_Find(kNumberOfUnitKeywords, g_unit_keywords, (FULL_CHAR*) chp);
	if (i >= 0)
	{
	  chp += strlen(g_unit_keywords[i]);
	  *outValue = g_units[i];
	}
	else
	{
	  Error(48, 33, "PDFPage_EvalExpr: __add, __sub, __mul, __div, or a unit keyword was expected",
	    FATAL, no_fpos);
	}
      }
    }
  }
  return chp;
}


/*****************************************************************************/
/*                                                                           */
/*  FULL_CHAR *PDFPage_CollectExpr(FULL_CHAR* charPtr)                       */
/*                                                                           */
/*  Collect expression into the expression buffer.                           */
/*                                                                           */
/*****************************************************************************/

static FULL_CHAR *PDFPage_CollectExpr(FULL_CHAR* charPtr, BOOLEAN* outHasResult,
  float* outResult)
{
  *outHasResult = FALSE;
  while (*charPtr != 0)
  {
    char ch;

    if ( g_expr_index >= sizeof(g_expr) )
      Error(48, 34, "PDFPage_CollectExpr: expression too long (max. 512 chars)",
	FATAL, no_fpos);

    g_expr[g_expr_index++] = ch = *charPtr++;
    if (ch == '(')
      g_expr_depth++;
    else if (ch == ')')
    {
      Assert(g_expr_depth != 0, no_fpos);
      g_expr_depth--;
      if (g_expr_depth == 0)
      {
	g_expr[g_expr_index] = '\0';	/* terminate the string */
	(char*) PDFPage_EvalExpr(g_expr, outResult);
	*outHasResult = TRUE;
	break;	/* exit while */
      }
    }
  }
  return charPtr;
}


/*****************************************************************************/
/*                                                                           */
/*  FULL_CHAR *PDFPage_CollectLink(FULL_CHAR* charPtr)                       */
/*                                                                           */
/*  Collect link into the link buffer.                                       */
/*                                                                           */
/*****************************************************************************/

static FULL_CHAR *PDFPage_CollectLink(FULL_CHAR* charPtr
  /*, BOOLEAN* outHasResult, float* outResult*/)
{
  debug1(DPD, D, "PDFPage_CollectLink(\"%s\")", charPtr);
  while (*charPtr != 0)
  {
    char ch;

    if ( g_link_index >= sizeof(g_link) )
      Error(48, 35, "PDFPage_CollectLink: link too long (max. 512 chars)",
	FATAL, no_fpos);

    g_link[g_link_index++] = ch = *charPtr++;
    if ((ch == '<') && (*charPtr == '<'))
    {
      g_link[g_link_index++] = *charPtr++;
      g_link_depth++;
    }
    else if ((ch == '>') && (*charPtr == '>'))
    {
      Assert(g_link_depth != 0, no_fpos);

      g_link_depth--;
      if (g_link_depth == 0)
      {
	/* I don't want the outermost '<<' '>>' pair */
	g_link[--g_link_index] = '\0';	/* terminate the string */

	PDFPage_ProcessLinkKeyword();

	charPtr++;
	break;	/* exit while */
      }
      else
	g_link[g_link_index++] = *charPtr++;
    }
  }
  debug0(DPD, D, "PDFPage_CollectLink returning");
  return charPtr;
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFPage_WriteGraphic(FILE* in_fp, FULL_CHAR* in_str)                */
/*                                                                           */
/*  Outputs an appropriate PDF string for drawing a graphic element.         */
/*                                                                           */
/*****************************************************************************/

void PDFPage_WriteGraphic(FILE* in_fp, FULL_CHAR* in_str)
{
  t_tempbuf str;

  FULL_CHAR *charPtr = in_str;
  char *strPtr = str;

  if (*charPtr == 0)
    return;

  /* if in collecting an expression mode then collect until terminating ')' */
  if (g_expr_depth != 0)
  {
    BOOLEAN hasResult;
    float value;

    charPtr = PDFPage_CollectExpr(charPtr, &hasResult, &value);
    if (hasResult)
    {
      sprintf(strPtr, "%.2f", value);
      strPtr += strlen(strPtr);
    }
  }

  /* if in collecting-a-link mode then collect until terminating '>>'        */
  if (g_link_depth != 0)
    charPtr = PDFPage_CollectLink(charPtr);

  /* scan the string for '__' otherwise output it */
  while (*charPtr != 0)
  {
    int i;
    float value;

    Assert(strPtr < (str + sizeof(t_tempbuf)), no_fpos);

    /* look for "__" (double underline) */
    if ( (charPtr[0] == '_') && (charPtr[1] == '_') )
    {
      charPtr += 2;

      /* "in", "cm", "pt", "em", "loutf", "loutv", "louts" */
#if 0
      i = PDFKeyword_Find(kNumberOfUnitKeywords, g_unit_keywords, charPtr);
      if (i >= 0)
      {
	Assert(FALSE, no_fpos);
	charPtr += strlen(g_unit_keywords[i]);	/* skip keyword */
	charPtr = PDFPage_GetFloat(charPtr, &value); /* get value */
	sprintf(strPtr, "%.2f", g_units[i] * value); /* output it */
	strPtr += strlen(strPtr);
      }
      else
#endif
      {

	/* "xsize", "ysize", "xmark", "ymark" */
	i = PDFKeyword_Find(kNumberOfGraphicsKeywords, g_graphic_keywords, charPtr);
	if (i >= 0)
	{
	  charPtr += strlen(g_graphic_keywords[i]);
#if 1
	  sprintf(strPtr, "%d", g_graphics_vars[i]);
	  strPtr += strlen(strPtr);
#else
	  charPtr = PDFPage_ProcessGraphicsKeyword(charPtr, i, &strPtr);
#endif
	}
	else
	{
	  /* "link_source=<<", "link_target=<<", "link_target_for_export=<<", "link_URI=<<" */
	  i = PDFKeyword_Find(kNumberOfLinkKeywords, g_link_keywords, charPtr);
	  if (i >= 0)
	  {
	    charPtr += strlen(g_link_keywords[i]);
#if 1
	    while (isspace(*charPtr))
	      charPtr++;

	    g_link_index = 0;
	    g_link_depth++;
	    g_link_keyword = (PDF_LINK_KEYWORD) i;
	    charPtr = PDFPage_CollectLink(charPtr);
#else
	    charPtr = PDFPage_ProcessLinkKeyword(charPtr, (PDF_LINK_KEYWORD) i);
#endif
	  } /* if */
	  else
	  {

	    /* "author=", "title=", "subject=", "keywords=" */
	    i = PDFKeyword_Find(kNumberOfDocInfoKeywords, g_doc_info_keywords, charPtr);
	    if (i >= 0)
	    {
	      charPtr += strlen(g_doc_info_keywords[i]);
	      charPtr = PDFPage_ProcessDocInfoKeyword(charPtr, i);
	    }
	    else
	    {

	      /* "add" "sub" "mul" "div", "sin", "cos" */
	      i = PDFKeyword_Find(kNumberOfArithmeticKeywords, g_arithmetic_keywords, charPtr);
	      if (i >= 0)
	      {
		strcpy(g_expr, g_arithmetic_keywords[i]);
		charPtr += strlen(g_arithmetic_keywords[i]);
		while (isspace(*charPtr))
		  charPtr++;
		if (*charPtr != '(')
		  Error(48, 36, "PDFPage_WriteGraphic: '(' expected", FATAL, no_fpos);

		strcat(g_expr, "(");
		g_expr_index = strlen(g_expr);
		g_expr_depth++;
		{
		  BOOLEAN hasResult;

		  charPtr = PDFPage_CollectExpr(++charPtr, &hasResult, &value);
		  if (hasResult)
		  {
		    sprintf(strPtr, "%.2f", value);
		    strPtr += strlen(strPtr);
		  }
		}
	      }
	      else
	      {
		/* alert user in case there was a spelling mistake */
		Error(48, 37, "PDFPage_WriteGraphic: '__' encountered while processing @Graphic", WARN, no_fpos);
		*strPtr++ = '_';
		*strPtr++ = '_';
	      } /* else */
	    } /* else */
	  } /* else */
	} /* else */
      } /* else */
    } /* if */
    else
    {
      *strPtr++ = *charPtr++;
    }
  }

  *strPtr = 0;

  PDFPage_FlushBuffer(in_fp);	/* this is a marking operation, so flush */
  PDFPage_Write(in_fp, str);
}


/*****************************************************************************/
/*                                                                           */
/* PDFPage_PrintUnderline(FILE* in_fp, int x1, int x2, int y, int thickness) */
/*                                                                           */
/*  Implements underlining (just draws a horizontal line).                   */
/*                                                                           */
/*****************************************************************************/

void PDFPage_PrintUnderline(FILE* in_fp, int in_x1, int in_x2, int in_y,
  int in_thickness)
{
  t_tempbuf str;

  /* this is a marking operation, so flush and turn off buffering */
  PDFPage_FlushBuffer(in_fp);

  /* fprintf(out_fp, "/ul { gsave setlinewidth dup 3 1 roll\n"); */
  /* fprintf(out_fp, "      moveto lineto stroke grestore } bind def\n");  */

  sprintf(str, "q %d w %d %d m %d %d l s Q\n",in_thickness,in_x1,in_y,in_x2,in_y);
  PDFPage_Write(in_fp, str);
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFPage_Init(FILE* in_fp, float in_scale_factor, int in_line_width) */
/*                                                                           */
/*  Inits the vars for the start of processing a new page.                   */
/*                                                                           */
/*  [ 0 1 2 ]               [ s 0 0 ]                                        */
/*  [ 3 4 5 ]       =       [ 0 s 0 ]                                        */
/*  [ 6 7 8 ]               [ 0 0 1 ]                                        */
/*                                                                           */
/*****************************************************************************/

void  PDFPage_Init(FILE* in_fp, float in_scale_factor, int in_line_width)
{

#ifdef USE_MATRICES
  g_cur_matrix[0] = g_cur_matrix[4] = in_scale_factor;
  g_cur_matrix[1] = g_cur_matrix[2] = g_cur_matrix[3] =
  g_cur_matrix[5] = g_cur_matrix[6] = g_cur_matrix[7] = 0;
  g_cur_matrix[8] = 1;
  g_matrix_stack = NULL;
#endif

  /* clear/init page vars */
  g_page_uses_fonts = FALSE;
  g_page_has_text  = FALSE;
  g_page_has_graphics = FALSE;

  g_page_contents_obj_num = 0; /* undefined */
  g_page_length_obj_num = 0; /* undefined */
  g_page_start_offset = 0; /* undefined */
  /* g_text_font_size_in_ems = 0; */ /* undefined */

  g_page_h_scale_factor = g_page_v_scale_factor = in_scale_factor;
  g_page_h_origin = g_page_v_origin = 0;
  g_page_line_width = in_line_width;

  /* ***
  g_graphics_vars[k_in] = IN;
  g_graphics_vars[k_cm] = CM;
  g_graphics_vars[k_pt] = PT;
  g_graphics_vars[k_em] = EM;
  *** */
  g_graphics_vars[k_xsize] = 0; /* undefined */
  g_graphics_vars[k_ysize] = 0; /* undefined */
  g_graphics_vars[k_xmark] = 0; /* undefined */
  g_graphics_vars[k_ymark] = 0; /* undefined */
  /* ***
  g_graphics_vars[k_loutf] = 0;
  g_graphics_vars[k_loutv] = 0;
  g_graphics_vars[k_louts] = 0;
  *** */

  /* No need to touch k_in other constant units */
  g_units[k_loutf] = 0; /* undefined */
  g_units[k_loutv] = 0; /* undefined */
  g_units[k_louts] = 0; /* undefined */

  g_ET_pending = FALSE;
  g_TJ_pending = FALSE;
  g_valid_text_matrix = FALSE;	/* Td is not allowed */

  /* mark all fonts "not in use" */
  {
    t_font_list_entry_ptr entry = g_font_list;
    while (entry != NULL) {
      entry->m_in_use = FALSE;	/* set the "in use" state to "not in use" */
      entry = entry->m_next_font_entry;
    }
  }

  /* init qsave stack */
  g_qsave_stack = NULL;

  /* init qsave_marking stack */
  g_qsave_marking_stack = NULL;
  g_buffer_pos   = 0;
  /* buffer contains empty string */
  g_buffer[0]    = '\0';
  /* try to chop entire stream if possible! Originally: FALSE; */
  /* turn on buffering only AFTER a save request */
  g_in_buffering_mode  = FALSE;
  /* try to chop entire stream if possible! Originally: FALSE; */
  /* turn on buffering only AFTER a save request */
  g_in_buffering_mode  = TRUE;

  /* bump page number */
  ++g_page_count;
  g_page_object_num  = PDFObject_New(/* in_fp */);
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFPage_Cleanup(FILE* in_fp)                                        */
/*                                                                           */
/*  Cleans up the processing after a page's contents have been written out.  */
/*                                                                           */
/*****************************************************************************/

void PDFPage_Cleanup(FILE* in_fp)
{
  BOOLEAN hasAnnot = FALSE;

  Assert(g_qsave_stack == NULL, no_fpos);

  /* if page has some content then close its stream object */
  if (g_page_contents_obj_num != 0)
  {
    PDF_FILE_OFFSET page_length = PDFPage_End(in_fp);

#ifdef _CALC_LARGEST_PAGE_OBJECT_
    if (page_length > g_max_page_length)
      g_max_page_length = page_length;
#endif

    /* write page's length object */
    if (g_PDF_debug)
      fprintf(in_fp, "%%\n%% length object for page %u:\n%%\n", g_page_count);

    PDFObject_WriteObj(in_fp, g_page_length_obj_num);
    fprintf(in_fp, "%u\nendobj\n", page_length);

    /* write out any used font resources */
    {
      t_font_list_entry_ptr entry = g_font_list;
      while (entry != NULL) {
	PDFFont_WriteFontResource(in_fp, entry);
	entry = entry->m_next_font_entry;
      }
    }
  }

  /* write out annotations */
  {
    t_source_annot_entry_ptr source = g_source_annot_list;

    while (source != NULL)
    {
      if (source->m_this_page_object_num == g_page_object_num)
      {

	/* even if the annotation(s) cannot be written out now, flag the */
	/* fact that this page has annotations                           */
	hasAnnot = TRUE;

	/* attempt to write out annotation */
	PDFSourceAnnot_Write(in_fp, source);
      } /* if annot entry belongs to this page */
      source = source->m_next_entry;
    } /* while */
  }

  /* start writing page object ("/Type /Page"); remember its number */
  {
    unsigned int wanted_block_num = (g_page_count - 1) / kNumberOfPagesPerBlock;
    unsigned int block_pos = (g_page_count - 1) % kNumberOfPagesPerBlock;
    t_page_block_ptr the_block = g_cur_page_block;

    /* if first obj in a block then allocate the block */
    if (block_pos == 0)
    {
      the_block = (t_page_block_ptr) malloc(sizeof(t_page_block));
      if (the_block == NULL)
        Error(48, 38, "PDFPage_Cleanup: run out of memory", FATAL, no_fpos);
      if (wanted_block_num == 0)	/* if first block in file */
      {
	Assert(g_page_block_list == NULL, no_fpos);
	g_page_block_list = the_block;
      }
      else
      {
	Assert(g_cur_page_block != NULL, no_fpos);
	g_cur_page_block->m_next_block = the_block;
      }
      the_block->m_next_block = NULL;	/* don't forget to init this! */
      g_cur_page_block = the_block;
    }
    else
    {
      Assert(the_block != NULL, no_fpos);
    }

    /* save object number of this page for later use in the /Pages list */
    if (g_PDF_debug)
      fprintf(in_fp, "%%\n%% page number %u:\n%%\n", g_page_count);
    the_block->m_block[block_pos] = g_page_object_num;
    PDFObject_WriteObj(in_fp, g_page_object_num);
    /* PDFObject_WriteNewObj(in_fp); */
  }

  /* write out /Page ID */
  fputs("<<\n/Type /Page\n", in_fp);

  /* write out page size and orientation */
  fprintf(in_fp, "/CropBox [ 0 0 %u %u ]\n",g_doc_h_bound,g_doc_v_bound);

  /* write out parent object ref */
  fputs("/Parent ", in_fp);
  PDFObject_WriteRef(in_fp, g_pages_root);
  fputs("\n", in_fp);

  /* write out contents object ref (if it exists) */
  if (g_page_contents_obj_num != 0)
  {
    fputs("/Contents ", in_fp);
    PDFObject_WriteRef(in_fp, g_page_contents_obj_num);
    fputs("\n", in_fp);
  }

  /* open resources dictionary */
  if (g_page_uses_fonts || g_page_has_text || g_page_has_graphics)
    fputs("/Resources\n<<\n", in_fp);

  /* write out font resources used */
  if (g_page_uses_fonts)
  {
    t_font_list_entry_ptr entry = g_font_list;
    fputs("/Font <<", in_fp);
    while (entry != NULL) {
      if (entry->m_in_use) {
	fprintf(in_fp, " %s ", entry->m_PDF_font_name);
	PDFFont_WriteObjectRef(in_fp, entry);
      }
      entry = entry->m_next_font_entry;
    }
    fputs(" >>\n", in_fp);
  }

  /* write out any procsets used */
  if (g_page_has_text || g_page_has_graphics)
  {
    fputs("/ProcSet [ /PDF", in_fp);
    if (g_page_has_text)
      fputs(" /Text", in_fp);
    fputs(" ]\n", in_fp);
  }

  /* close resources dictionary */
  if (g_page_uses_fonts || g_page_has_text || g_page_has_graphics)
    fputs(">>\n", in_fp);

  /* write out annot array */
  if (hasAnnot)
  {
    t_source_annot_entry_ptr entry = g_source_annot_list;
    t_source_annot_entry_ptr previous_entry = NULL;

    /* write out annotations */
    fputs("/Annots [", in_fp);
    while (entry != NULL)
    {
      if (entry->m_this_page_object_num == g_page_object_num)
      {
	fputs(" ", in_fp);
	PDFObject_WriteRef(in_fp, entry->m_this_object_num);

	/* if the annotation has just been written out above then delete it */
	if (entry->m_written_to_PDF_file)
	{
	  t_source_annot_entry_ptr next_entry = entry->m_next_entry;
	  if (g_source_annot_list == entry)
		g_source_annot_list = next_entry;
	  if (previous_entry != NULL)
		previous_entry->m_next_entry = next_entry;
	  PDFSourceAnnot_Dispose(entry);
	  entry = next_entry;
	}
	else /* annot is a fwd referring one: defer deleting it */
	{
	  previous_entry = entry;
	  entry = entry->m_next_entry;
	}
      } /* if annot entry belongs to this page */
      else /* annot does not belong to this page; skip it */
      {
	previous_entry = entry;
	entry = entry->m_next_entry;
      }
    } /* while */
    fputs(" ]\n", in_fp);
  } /* if */

  /* close object */
  fputs(">>\nendobj\n", in_fp);
}


/*****************************************************************************/
/*                                                                           */
/*  void PDFFile_Init(FILE* in_fp, int in_h_bound, int in_v_bound)           */
/*                                                                           */
/*  Initialize this module.                                                  */
/*                                                                           */
/*****************************************************************************/

void PDFFile_Init(FILE* in_fp, int in_h_bound, int in_v_bound,
  int in_IN, int in_CM, int in_PT, int in_EM)
{
  /* write PDF header */
  fputs("%PDF-1.2\n", in_fp);			/* identifies this as PDF   */
  fputs("\045\342\343\317\323\n", in_fp);	/* 0x25 0xE2 0xE3 0xCF 0xD3 */

  /* set debugging status */
#if DEBUG_ON
  g_PDF_debug = dbg[DPD].on[D] || dbg[DPD].on[DD] || dbg[DPD].on[DDD];
#else
  g_PDF_debug = FALSE;
#endif

#if PDF_COMPRESSION
  g_apply_compression = !g_PDF_debug;
#endif

  /* objects */
  g_next_objnum = 1; /* object numbers start at one */
  g_obj_offset_list = NULL;
  g_cur_obj_offset_block = NULL;

  /* fonts */
  g_font_list = NULL;
  g_font_encoding_list = NULL;

  /* pages */
  g_page_count = 0;
  g_page_block_list = NULL;
  g_cur_page_block = NULL;
  g_pages_root = PDFObject_New(/* in_fp */);

  /* doc */
  g_doc_h_bound = in_h_bound;
  g_doc_v_bound = in_v_bound;
  g_doc_author = NULL;
  g_doc_title = NULL;
  g_doc_subject = NULL;
  g_doc_keywords = NULL;

  /* link annotations */
  g_target_annot_list = NULL;
  g_has_exported_targets = FALSE;
  g_source_annot_list = NULL;

  /* units */
  g_units[k_in] = in_IN;
  g_units[k_cm] = in_CM;
  g_units[k_pt] = in_PT;
  g_units[k_em] = in_EM;

}


/*****************************************************************************/
/*                                                                           */
/*  void PDFFile_WritePagesObject(FILE* in_fp)                               */
/*                                                                           */
/*  Cleans up processing after all pages has been written out.               */
/*                                                                           */
/*****************************************************************************/

static void PDFFile_WritePagesObject(FILE* in_fp)
{
  unsigned int  i;
  t_page_block_ptr the_block  = g_page_block_list;

  if (g_PDF_debug)
    fprintf(in_fp, "%%\n%% root of pages tree:\n%%\n");

  /* write out the root of the Pages tree */
  PDFObject_WriteObj(in_fp, g_pages_root);
  fputs("<<\n", in_fp);
  fputs("/Type /Pages\n", in_fp);
  fputs("/Kids [ ", in_fp);
  for (i = 0; i < g_page_count; i++)
  {
    int block_pos = i % kNumberOfPagesPerBlock;
    PDFObject_WriteRef(in_fp, the_block->m_block[block_pos]);
    if (block_pos == (kNumberOfPagesPerBlock - 1))
    {
      the_block = the_block->m_next_block;
      /* Assert(the_block != NULL, no_fpos);  not always true! */
    }
    fputs(" ", in_fp);
  }
  fprintf(in_fp, " ]\n/Count %u\n", g_page_count);
  /* ***
  fprintf(in_fp, "/MediaBox [ 0 0 612 792 ]\n");
  fprintf(in_fp, "/MediaBox [ 0 0 %u %u ]\n",g_doc_h_bound,g_doc_v_bound);
  *** */
  fprintf(in_fp, "/MediaBox [ 0 0 %u %u ]\n", g_doc_h_bound, g_doc_v_bound);
  fputs(">>\nendobj\n", in_fp);
}


/*****************************************************************************/
/*                                                                           */
/*  PDF_FILE_OFFSET PDFFile_WriteXREF(FILE* in_fp)                           */
/*                                                                           */
/*  Writes out the XREF table.                                               */
/*                                                                           */
/*****************************************************************************/

static PDF_FILE_OFFSET PDFFile_WriteXREF(FILE* in_fp)
{
  int i;
  PDF_FILE_OFFSET xref_start;
  t_offset_block_ptr the_block = g_obj_offset_list;

  if (g_PDF_debug)
    fprintf(in_fp, "%%\n%% xref table:\n%%\n");

  xref_start = ftell(in_fp);
  fputs("xref\n", in_fp);
  fprintf(in_fp, "0 %u\n", g_next_objnum);
  fputs("0000000000 65535 f \n", in_fp);	/* object 0 is a deleted obj */
  Assert( (g_next_objnum == 1) || (the_block != NULL), no_fpos);
  for (i = 1; i < g_next_objnum; i++)	/* write out list of object offsets */
  {
    int block_pos = (i - 1) % kNumberOfObjectsPerBlock;

    /* always write an entry (even if the object doesn't exist) */
    fprintf(in_fp, "%010u 00000 n \n", the_block->m_block[block_pos]);

    if (the_block->m_block[block_pos] == 0)
    {
      t_tempbuf str;

      strcpy(str, "PDFFile_WriteXREF: undefined object number: ");
      sprintf(str + strlen(str), "%u", i);
      Error(48, 39, "%s", WARN, no_fpos, str);
    }

    if (block_pos == (kNumberOfObjectsPerBlock - 1))
    {
      the_block = the_block->m_next_block;
      /* Assert(the_block != NULL, no_fpos);  not always true! */
    }
  }
  return xref_start;
}

/*****************************************************************************/
/*                                                                           */
/*  void PDFFile_Cleanup(FILE* in_fp)                                        */
/*                                                                           */
/*  Cleans up processing after all pages has been written out.               */
/*                                                                           */
/*****************************************************************************/

void PDFFile_Cleanup(FILE* in_fp)
{
  PDF_FILE_OFFSET xref_start;	/* file offset of start of xref table */
  PDF_OBJECT_NUM catalog_obj_num;
  PDF_OBJECT_NUM info_obj_num;
  PDF_OBJECT_NUM dests_obj_num = 0;

  /* write out any unresolved link annotations.  This could be done earlier  */
  /* (in fact, it can be done as each new target is defined) but I've        */
  /* arbitrarily decided to do it here.                                      */
  {
    t_source_annot_entry_ptr source = g_source_annot_list;

    while (source != NULL)
    {
      t_target_annot_entry_ptr target;

      Assert(source->m_target == NULL, no_fpos);
      target = PDFTargetAnnot_Find(source->m_name);
      if (target != NULL)
      {
	source->m_target = target;
	PDFSourceAnnot_Write(in_fp, source);
      }
      source = source->m_next_entry;
    }
  }

  /* write out pages object */
  PDFFile_WritePagesObject(in_fp);

  /* if file has exported targets for links then write out /Dests dictionary */
  if (g_has_exported_targets)
  {
    t_target_annot_entry_ptr entry = g_target_annot_list;

    Assert(entry != NULL, no_fpos);	/* should be at least an entry! */

    /* write PDF 1.1 style /Dests dictionary */
    if (g_PDF_debug)
      fprintf(in_fp, "%%\n%% /Dests dictionary (exported links):\n%%\n");

    dests_obj_num = PDFObject_WriteNewObj(in_fp);
    fputs("<<\n", in_fp);

    while (entry != NULL)
    {
      if (entry->m_for_export)
      {
	fprintf(in_fp, "/%s [ ", entry->m_name);
	PDFObject_WriteRef(in_fp, entry->m_page_object_num);
	fprintf(in_fp, " /XYZ null null null ]\n");
      }
      entry = entry->m_next_entry;
    }
    fputs(">>\nendobj\n", in_fp);
  }

  /* write out catalog object */
  if (g_PDF_debug)
    fprintf(in_fp, "%%\n%% catalog:\n%%\n");

  catalog_obj_num = PDFObject_WriteNewObj(in_fp);
  fputs("<<\n", in_fp);
  fputs("/Type /Catalog\n", in_fp);
  fputs("/Pages ", in_fp);
  PDFObject_WriteRef(in_fp, g_pages_root);
  fputs("\n", in_fp);

  /* if file has exported targets for links then write out a /Dest dictionary */
  if (g_has_exported_targets)
  {
    fputs("/Dests ", in_fp);
    PDFObject_WriteRef(in_fp, dests_obj_num);
    fputs("\n", in_fp);
  }

  /* ***
  fputs("/PageMode ", in_fp);
  switch ()
  {
  }
  fputs("\n", in_fp);
  *** */

  fputs(">>\nendobj\n", in_fp);

  /* write out info object */
  if (g_PDF_debug)
    fprintf(in_fp, "%%\n%% Info object:\n%%\n");

  /* ***
  Author string (Optional) The name of the person who created the document.
  CreationDate Date (Optional) The date the document was created.
  ModDate Date (Optional) The date the document was last modified.
  Creator string (Optional) If the document was converted into a PDF document from another
  form, this is the name of the application that created the original document.
  Producer string (Optional) The name of the application that converted the document from its native
  format to PDF.
  Title string (Optional) The documentÕs title.
  Subject string (Optional) The subject of the document.
  Keywords string (Optional) Keywords associated with the document.

  example:

  /Creator (Adobe Illustrator)
  /CreationDate (D:19930204080603-08'00')
  /Author (Werner Heisenberg)
  /Producer (Acrobat Network Distiller 1.0 for Macintosh)
  *** */

  info_obj_num = PDFObject_WriteNewObj(in_fp);
  fputs("<<\n", in_fp);

  fprintf(in_fp, "/Creator (%s)\n", LOUT_VERSION);
  fprintf(in_fp, "/Producer (%s)\n", LOUT_VERSION);

  {
    time_t now;
    struct tm *date;

    /* I will presume that localtime() is Y2K compliant.  If it isn't    */
    /* on your system, feel free to tweak this code. :-)                 */

    now = time( NULL );
    date = localtime( &now );
    fprintf(in_fp, "/CreationDate (D:%.4d%.2d%.2d%.2d%.2d%.2d)\n",
      date->tm_year + 1900, date->tm_mon + 1, date->tm_mday,
      date->tm_hour, date->tm_min, date->tm_sec);
  }

  if (g_doc_author != NULL)
    fprintf(in_fp, "/Author (%s)\n", g_doc_author);

  if (g_doc_title != NULL)
    fprintf(in_fp, "/Title (%s)\n", g_doc_title);

  if (g_doc_subject != NULL)
    fprintf(in_fp, "/Subject (%s)\n", g_doc_subject);

  if (g_doc_keywords != NULL)
    fprintf(in_fp, "/Keywords (%s)\n", g_doc_keywords);

  fputs(">>\nendobj\n", in_fp);

  /* write out xref table */
  xref_start = PDFFile_WriteXREF(in_fp);

  /* write out trailer */
  /* *** uwe: comments can appear in the body only.
  if (g_PDF_debug)
    fprintf(in_fp, "%%\n%% trailer:\n%%\n");
  *** */

  fputs("trailer\n<<\n", in_fp);
  fprintf(in_fp, "/Size %u\n", g_next_objnum);
  fputs("/Root ", in_fp);
  PDFObject_WriteRef(in_fp, catalog_obj_num);
  fputs("\n/Info ", in_fp);
  PDFObject_WriteRef(in_fp, info_obj_num);

  fprintf(in_fp, " >>\nstartxref\n%u\n", xref_start);
  fputs("%%EOF\n", in_fp);

  /* memory deallocation (no need to dispose of the qsave_marking_stack  */
  /* because it's always empty after a page has been processed)          */

  while (g_obj_offset_list != NULL)
  {
    t_offset_block_ptr the_block = g_obj_offset_list;
    g_obj_offset_list = the_block->m_next_block;
    free(the_block);
  }

  while (g_font_encoding_list != NULL)
  {
    t_font_encoding_entry_ptr the_block = g_font_encoding_list;
    g_font_encoding_list = the_block->m_next_entry;
    free(the_block->m_font_encoding);
    free(the_block);
  }

  while (g_font_list != NULL)
  {
    t_font_list_entry_ptr the_block = g_font_list;
    g_font_list = the_block->m_next_font_entry;
    free(the_block->m_PDF_font_name);
    free(the_block->m_short_font_name);
    free(the_block->m_actual_font_name);
    free(the_block);
  }

  while (g_page_block_list != NULL)
  {
    t_page_block_ptr the_block = g_page_block_list;
    g_page_block_list = the_block->m_next_block;
    free(the_block);
  }

  while (g_source_annot_list != NULL)
  {
    t_source_annot_entry_ptr entry = g_source_annot_list;

    if (entry->m_target == NULL)
    {
      t_tempbuf str;
      strcpy(str, "PDFFile_Cleanup: unresolved link annotation named ");
      strcat(str, (char*) entry->m_name);
      Error(48, 40, "%s", WARN, no_fpos, str);
    }

    g_source_annot_list = PDFSourceAnnot_Dispose(entry);
  }

  while (g_target_annot_list != NULL)
  {
    t_target_annot_entry_ptr entry = g_target_annot_list;
    g_target_annot_list = entry->m_next_entry;
    free(entry->m_name);
    free(entry);
  }

#ifdef _CALC_LARGEST_PAGE_OBJECT_
  /* display largest page object */
  {
    t_tempbuf str;
    /* JK sprintf(str, "The largest page object is %u bytes long.", g_max_page_length); */
    Error(48, 41, "The largest page object is %u bytes long.", WARN, no_fpos, g_max_page_length);
  }
#endif
}
