/****************************************************************************
    Copyright (C) 1987-2005 by Jeffery P. Hansen

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    Last edit by hansen on Fri Sep  3 14:03:25 2004
****************************************************************************/
/*
    Postscript generator for tkgate.
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/time.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include "tkgate.h"

/*
 * Font change codes
 */
#if TKGATE_JSUPPORT
#define FCC_BEGINKANJI	0201
#define FCC_ENDKANJI	0201
#endif

/*
 * Hierarchy graph constants
 */
#define HG_FONTSIZE	12	/* Font size for heirarchy graph */
#define HG_ROOTX	50	/* Root x position */
#define HG_ROOTY	475	/* Root y position */
#define HG_BOTTOMY	50	/* Lowest y position */
#define HG_INDENT	20	/* Amount to indent for children */
#define HG_LINE		25	/* Hieght of a line */
#define HG_LINEINDENT	10	/* Indent for heirarchy line */
#define HG_BOXHEIGHT	20	/* Height of the module name box */
#define HG_NEXTCOLUMN	225	/* Space between columns */
/*

Folio          8.27 x 13 in.
Statement      5.5 x 8.5
C0		917mm  x 1297mm
C5		162 x 229 mm
C10		28 x 40 mm

A		8.5 x 11
B		11 x 17
C		17 x 22
D		22 x 34
E		34 x 44

yotsugiri	10 x 12in
mutsugiri	8 x 10 in
yatsugiri	6.5 x 8.5 in

kiku 4		227 x 306 mm
kiku 5		151 x 227 mm
 */

typedef struct hgnode HGNode;
struct hgnode {
  GModuleDef	*hg_module;		/* Module this node corresponds to */
  int		hg_maxDepth;		/* Maximum depth of tree */
  int		hg_expanded;		/* Non-zero if graph is expanded at this point */
  HGNode	*hg_nextSibling;	/* Next node in sibling list */
  HGNode	*hg_children;		/* Children of this node */
};


char *gateprolog[] = {
#include "psprolog.h"
  0
};

char *gateps_copyright[] = {
  "%",
  "% Copyright (C) 1987-2005 by Jeffery P. Hansen",
  "%    This program (the Postscript Prolog) is free software; you can redistribute",
  "%    it and/or modify it under the terms of the GNU General Public License",
  "%    as published by the Free Software Foundation; either version 2 of the",
  "%    License, or (at your option) any later version.",
  "%",
  "%    This program is distributed in the hope that it will be useful,",
  "%    but WITHOUT ANY WARRANTY; without even the implied warranty of",
  "%    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the",
  "%    GNU General Public License for more details.",
  "%",
  "%    You should have received a copy of the GNU General Public License",
  "%    along with this program; if not, write to the Free Software",
  "%    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.",
  "%",
  0
};

char *kanji_support_prolog[] = {
  "%",
  "% Replacement for regular show that switches between current font",
  "% and kanji font each time a special 'kanji swap' character is encountered",
  "%",
  "/kanji_show {",
  "  FCCToggleKanji search {",
  "    orig_show ",
  "    pop",
  "    in_kanji_text {",
  "      /in_kanji_text false def",
  "      swap_font setfont",
  "    } {",
  "      /in_kanji_text true def",
  "      /swap_font currentfont def",
  "      14 kfont",
  "    } ifelse",
  "    kanji_show",
  "  } {",
  "    orig_show",
  "  } ifelse",
  "} def",
  "",
  "%",
  "% v /s accum",
  "%",
  "/accum { dup cvx exec 3 -1 roll add def } def",
  "%",
  "% Replacement for regular stringwidth that switches between current font",
  "% and kanji font each time a special 'kanji swap' character is encountered",
  "%",
  "/kanji_stringwidth {",
  "  FCCToggleKanji search {",
  "    orig_stringwidth pop /accum_width accum",
  "    pop",
  "    in_kanji_text {",
  "      /in_kanji_text false def",
  "      swap_font setfont",
  "    } {",
  "      /in_kanji_text true def",
  "      /swap_font currentfont def",
  "      14 kfont",
  "    } ifelse",
  "    kanji_stringwidth",
  "  } {",
  "    orig_stringwidth pop /accum_width accum",
  "  } ifelse",
  "} def",
  "",
  "%",
  "% Override definition of show with kanji_show",
  "%",
  "/orig_show /show load def",
  "/show {",
  "  /in_kanji_text false def",
  "  /swap_font currentfont def",
  "  kanji_show",
  "} def",
  "",
  "%",
  "% Override definition of stringwidth with kanji_stringwidth",
  "%",
  "/orig_stringwidth /stringwidth load def",
  "/stringwidth {",
  "  /in_kanji_text false def",
  "  /swap_font currentfont def",
  "  /accum_width 0 def",
  "  kanji_stringwidth",
  "  accum_width 0",
  "} def",
  "",
  0
};

PaperSize paperSizes[] = {
  {"Letter",	"lettertray",	INCH(8.5),	INCH(11)},
  {"Legal",	"legaltray",	INCH(8.5),	INCH(14)},
  {"Tabloid",	"11x17tray",	INCH(11),	INCH(17)},
  {"Executive",	"executivetray",INCH(7.5),	INCH(10)},
  {"A4",	"a4tray",	MM(210),	MM(297)},
  {"A3",	"a3tray",	MM(297),	MM(420)},
  {"A2",	"a2tray",	MM(420),	MM(594)},
  {"A1",	"a1tray",	MM(594),	MM(841)},
  {"B5",	"b5tray",	MM(176),	MM(250)},
  {"B4",	"b4tray",	MM(250),	MM(353)},
  {"B3",	"b3tray",	MM(353),	MM(500)},
  {"B2",	"b2tray",	MM(500),	MM(707)},
  {"SRA2",	"sra2tray",	MM(450),	MM(640)},
  {"RA2",	"ra2tray",	MM(430),	MM(610)},
  {"yatsugiri",	"yatsugiri",	INCH(6.5),	INCH(8.5)},
  {"mutsugiri",	"mutsugiri",	INCH(8),	INCH(10)},
  {"yotsugiri",	"yotsugiri",	INCH(10),	INCH(12)},
  {0}
};


static char *recipe1[] = {
    "%	Pancakes - one serving",
    "%",
    "%  1 cup     Flour",
    "%  1 cup     Milk",
    "%  2         Eggs",
    "%  1   tbs   Melted Butter",
    "%  2   tsp   Sugar",
    "%  1/2 tsp   Salt",
    "%  1/2 tsp   Baking Soda",
    "%",
    "% Separate eggs placing whites aside in a clean bowl.  Combine flour and",
    "% milk in a separate bowl and mix until smooth.   Stir in melted butter,",
    "% sugar, salt and one of the egg yolks.  Now, using a clean mixer, whip",
    "% the egg whites until stiff.  Gently stir the egg whites into the batter.",
    "% This must be done by hand, or the pancakes will become flat.  Finally,",
    "% stir in the baking soda.",
    0
};

static char *recipe2[] = {
    "% Okonomiyaki - one serving",
    "%",
    "%  3 tbs     Flour",
    "%  3 tbs     Water",
    "%  1         Egg",
    "%  1/8 head  Cabbage (shredded)",
    "%  1 tbs     Tororo-imo (Japanese potato) [optional]",
    "%  1 tsp     Benishoga (Pickled ginger)",
    "%  1 tbs     carrot (shredded)",
    "%  1 tbs     leeks (shredded)",
    "%  1 tbs     geen pepper (shredded)",
    "%",
    "%  Fillings: beef, pork, vegtables",
    "%",
    "%  Toppings:",
    "%            Aonori (seaweed flakes)",
    "%            Katsuo-bushi (dried fish flakes)",
    "%            Japanese Tonkatsu Sauce",
    "%            Mayonaise",
    "%",
    "% Mix all of the basic ingedients together in a small bowl.  Place a small",
    "% quantity of filling on an oiled hot plate or griddle and fry for a few seconds.",
    "% Pour the okonomiyaki mixture from the bowl on top of the filling and smooth",
    "% out into a thick pancake-like shape.  Let cook for 5-7 min.  Turn over and cook",
    "% another 2-3 min.  Spread the tokatsu sauce and mayonaise on top of the okonomiyaki",
    "% then sprinkle with katuo-bushi and aonori.  Cut into 1/6ths and serve.",
    0
};

static char *recipe3[] = {
  "% Tofu",
  "% ",
  "% Ingredients (Japanese names in parentheses): ",
  "% ",
  "%      500g Soy Beans (Daizu) ",
  "%      20g Bittern (Nigari) ",
  "% ",
  "% You will also need: ",
  "% ",
  "%      Cheesecloth ",
  "%      Molds (milk cartons work fine) ",
  "%      Press (rolled up newspaper wrapped in plastic wrap) ",
  "%      Weights (plates, etc.) ",
  "%      Cooking thermometer ",
  "% ",
  "% Soak the soy beans in water overnight (10-20 hours). Be sure to",
  "% use enough water so the beans can double in size. Prepare the molds",
  "% using one milk carton per mold (you can use cheese molds if you have",
  "% them). Cut off one side of the carton, and punch numerous holes in the",
  "% sides and bottom to allow liquid to escape. Reinforce the molds by",
  "% using a pair of longs sticks (one on each side) holding them together",
  "% with rubber bands on the ends. You should have one piece of",
  "% cheesecloth per mold which should be big enough to fit squarely into",
  "% the mold and cover the tofu inside.",
  "% ",
  "% The next day, pour out the water, and pulverize the beans in a blender",
  "% using enough water to cover the beans (you will probably need to do",
  "% this in four to five batches). Pour the bean mixture into a very large",
  "% pot and add enough water to double the quantity of liquid. Bring the",
  "% liquid to a low boil and lower the temperature, skimming off any foam",
  "% that collects on the top. Boil for 20-30 min. stiring (and skimming)",
  "% constently. While it is boiling, disolve the bittern (the leftover",
  "% \"stuff\" that is formed when you extract salt from seawater) in a small",
  "% quantity of water. Place a piece of cheesecloth in a colander, and",
  "% pour the liquid through it into a large bowel. The liquid that passes",
  "% through is called \"soy milk\" and will become the tofu. The solid",
  "% material that remains in the cheesecloth is called \"okara\" and is",
  "% useful for various Japanese dishes (recipes will come later). For each",
  "% of the molds, fit the cheesecloth squarely into the corners. When the",
  "% temperature of the soy milk cools to 70C (158F) pour in the bittern",
  "% and stir genetly. When the tofu starts to separate from the water",
  "% (almost immediately), pour the mixture into the molds and cover with",
  "% cheesecloth. Next, place the plastic wrap covered newspapers on top of",
  "% the tofu (it should fit squarely) and place weights on top to press",
  "% out the liquid. After about 30 min., remove the tofu from the",
  "% molds. This should be done under water in a large pot or tub. Store",
  "% the tofu submerged in water.",
  0
};


static char **recipe_list[] = {
  recipe1,recipe2,recipe3
};
static int recipe_count = sizeof(recipe_list)/sizeof(recipe_list[0]);


/*
  Protect an postscript special charcters with a backslash.  Also, if
  we have enabled Japanese support, insert codes to switch fonts when
  we find bytes with the high bit set.
 */
#if TKGATE_JSUPPORT
char *filterParen(char *buf,const char *s)
{
  char *p;
  int in_kanji = 0;

  p = buf;
  while (*s) {
    char c;

    if ((*s & 0x80)) {
      if (!in_kanji)
	p += sprintf(p,"\\%03o",FCC_BEGINKANJI);
      in_kanji = 1;
    } else {
      if (in_kanji)
	p += sprintf(p,"\\%03o",FCC_ENDKANJI);
      in_kanji = 0;
    }

    c = (*s++ & 0x7f);

    if (strchr("()\\",c))
      *p++ = '\\';

    *p++ = c;
  }

  if (in_kanji)
    p += sprintf(p,"\\%03o",FCC_ENDKANJI);

  *p = 0;
  return buf;
}
#else
char *filterParen(char *buf,const char *s)
{
  char *p;

  p = buf;
  while (*s) {
    if (strchr("()\\",*s))
      *p++ = '\\';
    if (isprint(*s))
      *p++ = *s++;
    else
      p += sprintf(p,"\\%03o",((*s++)&0xff));
  }
  *p = 0;
  return buf;
}
#endif

/*
  Return s with all characters in trim removed.
 */
char *trimChars(char *buf,char *s,char *trim)
{
  char *p;

  p = buf;
  while (*s) {
    if (!strchr(trim,*s))
      *p++ = *s++;
    else
      s++;
  }
  *p = 0;
  return buf;
}


void PSDrawText(FILE *f,int x,int y,int fsize,const char *text,int just)
{
  static int curSize = -1;
  char buf[STRMAX];
  char vjust = 'R';
  char hjust = 'L';

  if ((just & BetweenLeftAndRight)) hjust = 'C';
  if ((just & AtRight)) hjust = 'R';
  if ((just & AtLeft)) hjust = 'L';

  if ((just & BetweenTopAndBottom)) vjust = 'C';
  if ((just & AtBaseline)) vjust = 'L';
  if ((just & AtTop)) vjust = 'T';
  if ((just & AtBottom)) vjust = 'B';

  if (fsize != curSize)
    fprintf(f,"%d rfont\n",fsize);
  curSize = fsize;

  fprintf(f,"(%s) /%c /%c %d %d pjshow\n",filterParen(buf,text),hjust,vjust,x,y);
}

void GPrintOpt_init(GPrintOpt *PO)
{
  PO->po_title = 0;
  PO->po_cmd = 0;
  PO->po_file = 0;
  PO->po_paper = 0;
  PO->po_orient = 0;
  PO->po_style = 0;
  PO->po_select = 0;
  PO->po_modlist = 0;
  PO->po_epsf = 0;
  PO->po_index = 0;
  PO->po_graph = 0;
  PO->po_4up = 0;
  PO->po_isDuplex = 0;
  PO->po_start = 0;
  PO->po_end = 0;
  PO->po_scale = 0;
  PO->po_incLib = 0;
  PO->po_encoding = 0;
}

/*
 * Set up defaults for command line printing
 */
void GPrintOpt_clDefault(GPrintOpt *PO)
{
  PO->po_title = "";
  PO->po_cmd = 0;
  PO->po_file = 0;
  PO->po_paper = "Letter";
  PO->po_orient = "landscape";
  PO->po_style = "partition";
  PO->po_select = "all";
  PO->po_modlist = "";
  PO->po_epsf = "0";
  PO->po_index = "0";
  PO->po_graph = "0";
  PO->po_4up = "1";
  PO->po_isDuplex = "0";
  PO->po_start = 0;
  PO->po_end = 0;
  PO->po_scale = 0;
  PO->po_incLib = "0";
  PO->po_encoding = 0;
}

GModLayout *new_GModLayout(GModuleDef *M)
{
  GModLayout *L = (GModLayout *) ob_malloc(sizeof(GModLayout),"GModLayout");
  assert(L);

  L->l_xbase = L->l_ybase = L->l_width = L->l_height = 0.0;
  L->l_isSmall = 0;
  L->l_xmin = L->l_xmax = L->l_ymin = L->l_ymax = 0;

  L->l_numRows = L->l_numCols = 1;

  L->l_mod = M;

  return L;
}

GModLayout *copyNew_GModLayout(GModLayout *L)
{
  GModLayout *cL = (GModLayout *) ob_malloc(sizeof(GModLayout),"GModLayout");
  assert(cL);

  *cL = *L;

  return cL;
}

void delete_GModLayout(GModLayout *L)
{
  ob_free(L);
}

GPage *new_GPage(int tp,int pn)
{
  GPage *P = (GPage *) ob_malloc(sizeof(GPage),"GPage");
  assert(P);

  P->pg_num = pn;
  P->pg_type = tp;
  P->pg_nmod = 0;

  return P;
}

void delete_GPage(GPage *P)
{
  ob_free(P);
}

int getUsedMods(NHash *H,GModLayout **L,GModuleDef *M,int incLib)
{
  int num = 0;
  HashElem *E;

  if (NHash_find(H,(nkey_t)M)) return 0;
  NHash_insert(H,(nkey_t)M,M);

  if (!M->is_lib || incLib)
    L[num++] = new_GModLayout(M);

  for (E = Hash_first(M->gates);E;E = Hash_next(M->gates,E)) {
    GCElement *g = (GCElement*) HashElem_obj(E);
    if (g->typeinfo->Code == BLOCK) {
      GModuleDef *CM = env_findModule(g->u.block.BlockFunction);
      if (CM) {
	num += getUsedMods(H,L+num,CM,incLib);
      }
    }
  }

  return num;
}

void delete_GPrint(GPrint *P)
{
  if (P->p_f) {
    if (P->p_isFile)
      fclose(P->p_f);
    else
      pclose(P->p_f);
  }

  if (P->p_mods) {
    int i;
    for (i = 0;i < P->p_numMods;i++)
      delete_GModLayout(P->p_mods[i]);
    ob_free(P->p_mods);
  }
  ob_free(P);
}

GPrint *new_GPrint(GPrintOpt *PO)
{
  GPrint *P = (GPrint *) ob_malloc(sizeof(GPrint),"GPrint");
  PaperSize *size = 0;
  int i;
  int incLib = 0;

  assert(P);

  P->p_opts = *PO;

  /*
   * Basic initialize so object is deletable.
   */
  P->p_isFile = 0;
  P->p_f = 0;
  P->p_mods = 0;

  P->p_isDuplex = (strcmp(PO->po_isDuplex,"1") == 0);
  P->p_isepsf   = (strcmp(PO->po_epsf,"1") == 0);
  P->p_style    = (PO->po_style && strcmp(PO->po_style,"fit") == 0) ? PS_FIT : PS_PARTITION;

  P->p_trace.start = P->p_trace.end = P->p_trace.scale = 0;
  if (PO->po_start) sscanf(PO->po_start,"%u",&P->p_trace.start);
  if (PO->po_end) sscanf(PO->po_end,"%u",&P->p_trace.end);
  if (PO->po_scale) sscanf(PO->po_scale,"%u",&P->p_trace.scale);
  if (PO->po_incLib) sscanf(PO->po_incLib,"%d",&incLib);

  if (PO->po_encoding) {
    if (strcmp(PO->po_encoding,"Latin1") == 0)
      P->p_encoding = PE_LATIN1;
    else if (strcmp(PO->po_encoding,"Standard") == 0)
      P->p_encoding = PE_STANDARD;
      
  } else
    P->p_encoding = PE_STANDARD;

  /*
    If this is a scope trace then there must be a positive range.
   */
  if (PO->po_start) {
    if (P->p_trace.start >= P->p_trace.end) {
      message(1,msgLookup("err.badrange"));		/* Non-positive range for scope trace is not allowed. */
      delete_GPrint(P);
      return 0;
    }
  }

  /*
    Open printer or file.
   */
  if (PO->po_cmd) {
    if (!(P->p_f = popen(PO->po_cmd,"w"))) {
      message(1,msgLookup("err.badlprcmd"),PO->po_cmd);		/* Unable to exectute printer command '%s'. */
      delete_GPrint(P);
      return 0;
    }
    P->p_isFile = 0;
  } else {
    if (!(P->p_f = fopen(PO->po_file,"w"))) {
      message(1,msgLookup("err.badpsopen"),PO->po_file);	/* Unable to open file '%s' for postscript output. */
      delete_GPrint(P);
      return 0;
    }
    P->p_isFile = 1;
  }

  /*
    Find paper size and orientation
   */
  for (i = 0;paperSizes[i].ps_size;i++) {
    size = &paperSizes[i];
    if (strcmp(size->ps_size,PO->po_paper) == 0)
      break;
  }
  P->p_size = size;

  if (strcmp(PO->po_orient,"landscape") == 0 && !P->p_isepsf) {
    P->p_orient = 1;
    P->p_width = size->ps_height;
    P->p_height = size->ps_width;
  } else {
    P->p_orient = 0;
    P->p_width = size->ps_width;
    P->p_height = size->ps_height;
  }

  P->p_uWidth = P->p_width - PAGE_LMARGIN - PAGE_RMARGIN;
  P->p_uHeight = P->p_height- PAGE_TMARGIN - PAGE_BMARGIN - PAGE_LBLOCK;

  P->p_mods = 0;
  P->p_numMods = 0;

  if (PO->po_select) {
    if (strcmp(PO->po_select,"all") == 0) {
      HashElem *E;
      P->p_mods = (GModLayout**) ob_malloc(Hash_numElems(XGate.circuit->moduleTable)*sizeof(GModLayout*),"GModLayout*[]");
      assert(P->p_mods);

      for (E = Hash_first(XGate.circuit->moduleTable);E;E = Hash_next(XGate.circuit->moduleTable,E)) {
	GModuleDef *M = (GModuleDef *) HashElem_obj(E);
	if (!M->is_lib || incLib)
	  P->p_mods[P->p_numMods++] = new_GModLayout(M);
      }
    } else if (strcmp(PO->po_select,"use") == 0) {
      NHash *H = new_NHash();

      P->p_mods = (GModLayout**) ob_malloc(Hash_numElems(XGate.circuit->moduleTable)*sizeof(GModLayout*),"GModLayout*[]");
      assert(P->p_mods);

      P->p_numMods = getUsedMods(H,P->p_mods,XGate.circuit->root_mod,incLib);
      delete_NHash(H);

    } else if (strcmp(PO->po_select,"sel") == 0) {
      char *T, *buf;

      P->p_numMods = 0;
      P->p_mods = (GModLayout**) ob_malloc(Hash_numElems(XGate.circuit->moduleTable)*sizeof(GModLayout*),"GModLayout*");
      assert(P->p_mods);

      buf = strdup(PO->po_modlist);
      for (T = strtok(buf," ,");T;T = strtok(0," ,")) {
	char buf2[STRMAX];
	GModuleDef *M = env_findModule(trimChars(buf2,T,"()+"));
	if (M)
	  P->p_mods[P->p_numMods++] = new_GModLayout(M);
      }
      free(buf);

    } else { /* cur or unknown */
      P->p_numMods = 1;
      P->p_mods = (GModLayout**) ob_malloc(sizeof(GModLayout*),"GModLayout*");
      assert(P->p_mods);
      P->p_mods[0] = new_GModLayout(XGate.circuit->es->env);
    }
  }

  P->p_numPages = P->p_numMods;

  return P;
}

static void GPrint_pageSetup(GPrint *P)
{
  const char *tray = P->p_size->ps_tray;
  const char *dup_mode = P->p_isDuplex ? "true" : "false";

  fprintf(P->p_f,"%%%%BeginSetup\n");
  fprintf(P->p_f,"%%%%IncludeResource: font Hevetica\n");
  fprintf(P->p_f,"%%%%IncludeResource: font Hevetica-Oblique\n");
  fprintf(P->p_f,"%%%%IncludeResource: font Hevetica-Bold\n");
  fprintf(P->p_f,"%%%%IncludeResource: font Hevetica-BoldOblique\n");
  fprintf(P->p_f,"%%%%IncludeResource: font Symbol\n");

  if (!P->p_isepsf) {
#if TKGATE_PSPAGESIZING
    fprintf(P->p_f,"%%%%BeginFeature: *PageSize %s\n",tray);
    fprintf(P->p_f,"statusdict /%s known {\n",tray);
    fprintf(P->p_f,"  statusdict begin\n");
    fprintf(P->p_f,"    %s\n",tray);
    fprintf(P->p_f,"  end\n");
    fprintf(P->p_f,"} if\n");
    fprintf(P->p_f,"%%%%EndFeature\n");
#endif
    if (dup_mode) {
      fprintf(P->p_f,"%%%%BeginFeature: *DuplexMode %s\n",dup_mode);
      fprintf(P->p_f,"statusdict /setduplexmode known {\n");
      fprintf(P->p_f,"  statusdict begin\n");
      fprintf(P->p_f,"    %s setduplexmode\n",dup_mode);
      fprintf(P->p_f,"  end\n");
      fprintf(P->p_f,"} if\n");
      fprintf(P->p_f,"%%%%EndFeature\n");
    }
  }

  fprintf(P->p_f,"%%%%EndSetup\n");
}

static void GPrint_outputPreamble(GPrint *P,int do_gates)
{
  static char *month[] = {"January","February","March","April","May","June","July",
			  "August","September","October","November","December"};
  struct passwd *pw;
  struct tm *tm;
  time_t clock[1];
  int i;
  HashElem *E;
  int n;
  char buf[STRMAX];
  int recipe_pos = random() % 15;

  pw = getpwuid(getuid());

  time(clock);
  tm = localtime(clock);

  fprintf(P->p_f,"%%!PS-Adobe-3.0%s\n",P->p_isepsf ? " EPSF-1.2" : "");
  fprintf(P->p_f,"%%%%Title: %s\n",P->p_opts.po_title);

  fprintf(P->p_f,"%%%%DocumentNeededResources: font");
  fprintf(P->p_f," %s",ps_cfont);
  fprintf(P->p_f," %s",ps_rfont);
  fprintf(P->p_f," %s",ps_bfont);
  fprintf(P->p_f," %s",ps_ifont);
  fprintf(P->p_f," %s",ps_bifont);
  fprintf(P->p_f," %s",ps_syfont);
#if TKGATE_JSUPPORT
  fprintf(P->p_f," %s",ps_kfont);
#endif
  fprintf(P->p_f,"\n");

  fprintf(P->p_f,"%%%%Creator: %s and TKGate %s\n",filterParen(buf,pw->pw_gecos),VERSIONS[0].name);
  fprintf(P->p_f,"%%%%CreationDate: %d %s %d\n",tm->tm_mday,month[tm->tm_mon],1900+tm->tm_year);
  if (!P->p_isepsf) {
    fprintf(P->p_f,"%%%%DocumentPaperSizes: %s\n",P->p_opts.po_paper);
    fprintf(P->p_f,"%%%%Pages: %d\n",P->p_numPages);
    fprintf(P->p_f,"%%%%Orientation: %s\n",P->p_orient ? "Landscape" : "Portrait");
  } else {
    if (P->p_mods && P->p_mods[0]) {
      GModLayout *ML = P->p_mods[0];
      fprintf(P->p_f,"%%%%BoundingBox: %d %d %d %d\n",
	      EPSF_MINX,EPSF_MINY,
	      EPSF_MINX+(ML->l_xmax-ML->l_xmin),EPSF_MINY+(ML->l_ymax-ML->l_ymin));
    }
  }

  fprintf(P->p_f,"%%%%EndComments\n");

  for (i = 0;gateps_copyright[i];i++)
    fprintf(P->p_f,"%s\n",gateps_copyright[i]);

#if TKGATE_JSUPPORT
  for (i = 0;kanji_support_prolog[i];i++)
    fprintf(P->p_f,"%s\n",kanji_support_prolog[i]);
#endif

  for (i = 0;gateprolog[i];i++)
    fprintf(P->p_f,"%s\n",gateprolog[i]);

  if (do_gates) {
    n = 0;
    for (E = Hash_first(GateIdxHash);E;E = Hash_next(GateIdxHash,E)) {
      GGateInfo *gi = (GGateInfo*) HashElem_obj(E);

      if (++n == recipe_pos) {
	int r_idx;
	char **r;

	fprintf(P->p_f,"%% ...and now for something completely different...\n");
	fprintf(P->p_f,"%%\n");

	r_idx = random() % recipe_count;
	r = recipe_list[r_idx];

	for (i = 0;r[i];i++)
	  fprintf(P->p_f,"%s\n",r[i]);
      }

      if (!gi->psdef) continue;
      for (i = 0;gi->psdef[i];i++)
	fprintf(P->p_f,"%s\n",gi->psdef[i]);
    }
  }

  fprintf(P->p_f,"\n");
  fprintf(P->p_f,"/Author (%s) def\n",filterParen(buf,pw->pw_gecos));
  fprintf(P->p_f,"/Site (%s) def\n",filterParen(buf,tkg_siteName));
  fprintf(P->p_f,"/Date (%s %d, %d) def\n",month[tm->tm_mon],tm->tm_mday,1900+tm->tm_year);
  fprintf(P->p_f,"/CircuitName (%s) def\n",filterParen(buf,P->p_opts.po_title));
  fprintf(P->p_f,"/isepsf %s def\n",P->p_isepsf ? "true" : "false");
  fprintf(P->p_f,"/NumPages ( of %d) def\n",P->p_numPages);
  fprintf(P->p_f,"/GateVersion (TKGate %s) def\n",VERSIONS[0].name);
  fprintf(P->p_f,"/IsLandscape %s def\n",P->p_orient ? "true" : "false");
  fprintf(P->p_f,"/PageWidth %0.2f def\n",P->p_width);
  fprintf(P->p_f,"/PageHeight %0.2f def\n",P->p_height);
  fprintf(P->p_f,"/LMargin %d def\n",PAGE_LMARGIN);
  fprintf(P->p_f,"/RMargin %d def\n",PAGE_LMARGIN);
  fprintf(P->p_f,"/TMargin %d def\n",PAGE_LMARGIN);
  fprintf(P->p_f,"/BMargin %d def\n",PAGE_LMARGIN);
  fprintf(P->p_f,"/LBlock %d def\n",PAGE_LBLOCK);

#if TKGATE_JSUPPORT
  fprintf(P->p_f,"/FCCToggleKanji (\\%03o) def\n",FCC_BEGINKANJI);
#endif

  switch (P->p_encoding) {
  default :
  case PE_STANDARD :
    fprintf(P->p_f,"/cfont { /%s findfont exch scalefont setfont } bind def\n",ps_cfont);
    fprintf(P->p_f,"/rfont { /%s findfont exch scalefont setfont } bind def\n",ps_rfont);
    fprintf(P->p_f,"/bfont { /%s findfont exch scalefont setfont } bind def\n",ps_bfont);
    fprintf(P->p_f,"/ifont { /%s findfont exch scalefont setfont } bind def\n",ps_ifont);
    fprintf(P->p_f,"/bifont { /%s findfont exch scalefont setfont } bind def\n",ps_bifont);
    fprintf(P->p_f,"/syfont { /%s findfont exch scalefont setfont } bind def\n",ps_syfont);
    break;
  case PE_LATIN1 :
    fprintf(P->p_f,"/%s-Latin1 /%s findfont defLatin1\n",ps_cfont,ps_cfont);
    fprintf(P->p_f,"/%s-Latin1 /%s findfont defLatin1\n",ps_rfont,ps_rfont);
    fprintf(P->p_f,"/%s-Latin1 /%s findfont defLatin1\n",ps_bfont,ps_bfont);
    fprintf(P->p_f,"/%s-Latin1 /%s findfont defLatin1\n",ps_ifont,ps_ifont);
    fprintf(P->p_f,"/%s-Latin1 /%s findfont defLatin1\n",ps_bifont,ps_bifont);

    fprintf(P->p_f,"/cfont { /%s-Latin1 findfont exch scalefont setfont } bind def\n",ps_cfont);
    fprintf(P->p_f,"/rfont { /%s-Latin1 findfont exch scalefont setfont } bind def\n",ps_rfont);
    fprintf(P->p_f,"/bfont { /%s-Latin1 findfont exch scalefont setfont } bind def\n",ps_bfont);
    fprintf(P->p_f,"/ifont { /%s-Latin1 findfont exch scalefont setfont } bind def\n",ps_ifont);
    fprintf(P->p_f,"/bifont { /%s-Latin1 findfont exch scalefont setfont } bind def\n",ps_bifont);
    fprintf(P->p_f,"/syfont { /%s findfont exch scalefont setfont } bind def\n",ps_syfont);
    break;
  }
#if TKGATE_JSUPPORT
  fprintf(P->p_f,"/kfont { /%s findfont exch scalefont setfont } bind def\n",ps_kfont);
#endif

  fprintf(P->p_f,"%%%%EndProlog\n");
  GPrint_pageSetup(P);
  fprintf(P->p_f,"\n");
}

static void GPrint_outputTrailer(GPrint *P)
{
  fprintf(P->p_f,"%%%%Tailer\n");
}

void GPrint_printWireSize(GPrint *P,GWire *w)
{
  GWireNode *n = w->nodes;
  GWireNode *n2 = n->out ? n->out : n->in;
  int x,y,dx,dy;

  dx = n->x-n2->x;
  dy = n->y-n2->y;
  if (dx*dx+dy*dy < (15*15))
    return;

  x = n->x + 2*(n2->x - n->x)/3;
  y = n->y + 2*(n2->y - n->y)/3;

  if (n->x == n2->x)
    fprintf(P->p_f,"(%d) %d %d %d wirelabel\n",w->net->nbits,BetweenTopAndBottom|AtLeft,x+8,y);
  else
    fprintf(P->p_f,"(%d) %d %d %d wirelabel\n",w->net->nbits,BetweenLeftAndRight|AtBottom,x,y-3);

  fprintf(P->p_f,"%d %d sizesymbol\n",x,y);
}

static void GPrint_printModuleCPath(GPrint *P,GModLayout *L)
{
  GModuleDef *M = L->l_mod;
  GWireList *wl;

  for (wl = M->wires;wl;wl = wl->cdr) {
    GWire *w = wl->car;
    GWireNode *n = 0;

    /*
     * Draw critical path if set
     */
    if (w->cpath) {
      if (w->gate && w->gate->typeinfo->Code == JOINT) {
	int x = w->gate->xpos;
	int y = w->gate->ypos;

	fprintf(P->p_f,"%d %d joint_gray\n",x,y);
      }
      if (w->nodes->out) {
	fprintf(P->p_f,"gsave\n");
	fprintf(P->p_f,"8 setlinewidth\n");
	fprintf(P->p_f,".5 setgray\n");
	fprintf(P->p_f,"newpath\n");
      
	for (n = w->nodes;n;n = n->out) {
	  int x,y;

	  x = n->x;
	  y = n->y;

	  if (n == w->nodes) {
	    fprintf(P->p_f,"%d %d moveto\n",x,y);
	  } else {
	    fprintf(P->p_f,"%d %d lineto\n",x,y);
	  }
	}
	fprintf(P->p_f,"stroke\n");
	fprintf(P->p_f,"grestore\n");
      }      
    }
  }
}


static void GPrint_printModuleWires(GPrint *P,GModLayout *L)
{
  GModuleDef *M = L->l_mod;
  GWireList *wl;
  int last_was_multi = 0;

  for (wl = M->wires;wl;wl = wl->cdr) {
    GWire *w = wl->car;
    GWireNode *n = 0;
    GNet *net = w->net;
    int is_multi;

    if (w->nidx == w->net->decoration) {
      if (!net->ionet && net->show_name && net->signame) {
	int wx,wy,wp;

	SignalNamePos(w,&wx,&wy,0,&wp);
	fprintf(P->p_f,"(%s) %d %d %d wirelabel\n",w->net->signame,wp,wx,wy);
      }

      if (w->net->nbits > 1)
	GPrint_printWireSize(P,w);
    }

    if (!w->nodes->out) continue;	/* Print in one direction only */

    /*
     * Do the actual drawing of the wire.
     */
    is_multi = (w->net->nbits != 1);
    if (is_multi != last_was_multi) {
      if (is_multi)
	fprintf(P->p_f,"2 setlinewidth\n");
      else
	fprintf(P->p_f,"1 setlinewidth\n");
    }
    last_was_multi = is_multi;
    fprintf(P->p_f,"newpath\n");
    for (n = w->nodes;n;n = n->out) {
      int x,y;

      x = n->x;
      y = n->y;
      if (n->end && n->end->invert) {
	int x2,y2,xi,yi;

	xi = x;
	yi = y;

	if (n->out) {
	  x2 = n->out->x;
	  y2 = n->out->y;
	} else {
	  x2 = n->in->x;
	  y2 = n->in->y;
	}

	if (y == y2) {
	  if (x2 > x) {
	    xi += 2;
	    x += 4;
	  } else {
	    xi -= 2;
	    x -= 4;
	  }
	} else {
	  if (y2 > y) {
	    yi += 2;
	    y += 4;
	  } else {
	    yi -= 2;
	    y -= 4;
	  }
	}

	fprintf(P->p_f,"%d %d inverter\n",xi,yi);
      }

      if (n == w->nodes) {
	fprintf(P->p_f,"%d %d moveto\n",x,y);
      } else {
	fprintf(P->p_f,"%d %d lineto\n",x,y);
      }
    }
    fprintf(P->p_f,"stroke\n");
  }
}

/*
  Print a single module
 */
static void GPrint_printModule(GPrint *P,GModLayout *L)
{
  GModuleDef *M = L->l_mod;
  HashElem *E;

  GPrint_printModuleCPath(P,L);
  GPrint_printModuleWires(P,L);


  for (E = Hash_first(M->gates);E;E = Hash_next(M->gates,E)) {
    GCElement *g = (GCElement*) HashElem_obj(E);
    (*g->typeinfo->PSWrite)(P->p_f,L,g);
  }
}
/*

When pages are partitioned, we include a little overlap on the edges to
compensate for any gates that might have been cut in two.

        V
+-------+-+-------+
|       | |       |
|       | |       |
|       | |       |
+-------+-+-------+
|       | |       |
+-------+-+-------+<
|       | |       |
|       | |       |
|       | |       |
+-------+-+-------+
 */
static void GPrint_printLayout(GPrint *P,GModLayout *L)
{
  double xoff,yoff,scale;

  scale = 1.0;

  if (L->l_numRows <= 1 && L->l_numCols <= 1) {
    /*
     * The circuit is not paritioned.  Center it in the layout area by computing
     * the necessary offset.  If it is bigger than the layout area scale it
     * to make it fit.
     */
    int cwidth = (L->l_xmax-L->l_xmin);
    int cheight = (L->l_ymax-L->l_ymin);
    if (cwidth > L->l_width-PAGE_MODMARGIN*2 || cheight > L->l_height-PAGE_MODMARGIN*2) {
      double xscale = (double)(L->l_width-PAGE_MODMARGIN*2)/(double)cwidth;
      double yscale = (double)(L->l_height-PAGE_MODMARGIN*2)/(double)cheight;
      if (xscale < yscale)
	scale = xscale;
      else
	scale = yscale;
    }
    xoff = ((double)L->l_width/scale - (L->l_xmax+L->l_xmin))/2.0;
    yoff = ((double)L->l_height/scale - (L->l_ymax+L->l_ymin))/2.0;
  } else {
    /*
     * This is a piece of a partitioned circuit.
     */

    int xmin = L->l_xmin + L->l_c*L->l_width;
    int ymin = L->l_ymin + L->l_r*L->l_height;
    int xmax = xmin + L->l_width;
    int ymax = ymin + L->l_height;

    xoff = ((double)L->l_width/scale - (xmax+xmin))/2.0;
    yoff = ((double)L->l_height/scale - (ymax+ymin))/2.0;
  }

#if 0
  fprintf(P->p_f,"newpath\n");
  fprintf(P->p_f,"%d %d 3 0 360 arc closepath fill\n",PAGE_MODMARGIN,PAGE_MODMARGIN);
  fprintf(P->p_f,"%d %d 3 0 360 arc closepath fill\n",L->l_width-PAGE_MODMARGIN,PAGE_MODMARGIN);
  fprintf(P->p_f,"%d %d 3 0 360 arc closepath fill\n",L->l_width-PAGE_MODMARGIN,L->l_height-PAGE_MODMARGIN);
  fprintf(P->p_f,"%d %d 3 0 360 arc closepath fill\n",PAGE_MODMARGIN,L->l_height-PAGE_MODMARGIN);

  fprintf(P->p_f,"%d %d moveto\n",PAGE_MODMARGIN,PAGE_MODMARGIN);
  fprintf(P->p_f,"%d %d lineto stroke\n",L->l_width-PAGE_MODMARGIN,L->l_height-PAGE_MODMARGIN);
  fprintf(P->p_f,"%d %d moveto\n",L->l_width-PAGE_MODMARGIN,PAGE_MODMARGIN);
  fprintf(P->p_f,"%d %d lineto stroke\n",PAGE_MODMARGIN,L->l_height-PAGE_MODMARGIN);
#endif

  fprintf(P->p_f,"gsave\n");

  fprintf(P->p_f,"0 %d translate\n",L->l_height);
  if (scale != 1.0)
    fprintf(P->p_f,"%f %f scale\n",scale,scale);
  fprintf(P->p_f,"1 -1 scale\n");
  fprintf(P->p_f,"%f %f translate\n",xoff,yoff);

  GPrint_printModule(P,L);

  fprintf(P->p_f,"grestore\n");
}

static void GPrint_printModPage(GPrint *P,GPage *PG)
{
  char buf[STRMAX];
  GModLayout *L = PG->pg_mods[0];

  if (L->l_numCols > 1 || L->l_numRows > 1) {
    fprintf(P->p_f,"%d %d %d %d (%d of %d) (%s) BP_P\n",
	    L->l_r,L->l_c,L->l_numRows,L->l_numCols,PG->pg_num,P->p_numPages,
	    filterParen(buf,L->l_mod->Name));
    fprintf(P->p_f,"() %d %d %d %d BM\n",
	    L->l_xbase,L->l_ybase,L->l_width,L->l_height);
  } else {
    fprintf(P->p_f,"(%d of %d) (%s) BP\n",PG->pg_num,P->p_numPages,
	    filterParen(buf,L->l_mod->Name));

    fprintf(P->p_f,"() %d %d %d %d BM\n",
	    L->l_xbase,L->l_ybase,L->l_width,L->l_height);
  }
  GPrint_printLayout(P,L);
  fprintf(P->p_f,"EM\n");
  fprintf(P->p_f,"EP\n");
}

static void GPrint_printMod4Page(GPrint *P,GPage *PG)
{
  char buf[STRMAX],*p;
  int i;

  /*
   * Set up list of module names.
   */
  *buf = 0;
  p = buf;
  for (i = 0;i < PG->pg_nmod;i++) {
    GModLayout *L = PG->pg_mods[i];
    if (i) {
      strcat(p,", ");
      p+=2;
    }
    filterParen(p,L->l_mod->Name);
    p += strlen(p);
  }

  fprintf(P->p_f,"(%d of %d) (%s) BP\n",PG->pg_num,P->p_numPages,buf);
  for (i = 0;i < PG->pg_nmod;i++) {
    GModLayout *L = PG->pg_mods[i];
    fprintf(P->p_f,"(%s) %d %d %d %d BM_C\n",filterParen(buf,L->l_mod->Name),
	    L->l_xbase,L->l_ybase,L->l_width,L->l_height);
    GPrint_printLayout(P,L);
    fprintf(P->p_f,"EM\n");
  }
  fprintf(P->p_f,"EP\n");
}

static void GPrint_printIndexPage(GPrint *P,GPage *PG)
{
  int x,y;
  char *mod_list[MODMAX];	/* Module names */
  int start_page[MODMAX];	/* Start page of module */
  int end_page[MODMAX];		/* End page of module */
  int num_modules = 0;
  int i,j;

  for (i = 0;i < P->p_numPages;i++) {
    GPage *G = P->p_pages[i];
    if (G->pg_type != PT_MODULE && G->pg_type != PT_MOD4) continue;
    for (j = 0;j < G->pg_nmod;j++) {
      GModLayout *L = G->pg_mods[j];
      GModuleDef *M = L->l_mod;
      if (L->l_r == 0 && L->l_c == 0) {		/* It is assumed (0,0) is printed first */
	mod_list[num_modules] = M->Name;
	start_page[num_modules] = G->pg_num;
	num_modules++;
      }
      end_page[num_modules-1] = G->pg_num;
    }
  }


  fprintf(P->p_f,"(%d of %d) (<index>) BP\n",PG->pg_num,P->p_numPages);


  /*
    Set position so that next entry will cause a new column to be generated
    at the desired place.
   */
  x = PIDX_LMARGIN-PIDX_COLSEP;
  y = 0;

  for (i = 0;i < num_modules;i++) {
    char buf[STRMAX];

    y -= PIDX_LINESEP;
    if (y < 0) {
      x += PIDX_COLSEP;
      y = P->p_uHeight - PIDX_TMARGIN;
      fprintf(P->p_f,"%d bfont\n",PIDX_FONT);
      fprintf(P->p_f,"%d %d moveto (Modules) show\n",x,y);
      fprintf(P->p_f,"%d %d moveto (Page) show\n",x+PIDX_PGCOLSEP,y);
      fprintf(P->p_f,"%d %d moveto %d %d lineto stroke\n",x,y-PIDX_HDRLINEPOS,x+PIDX_LINELEN,y-PIDX_HDRLINEPOS);
      fprintf(P->p_f,"%d rfont\n",PIDX_FONT);
      y -= PIDX_HDRSEP;
    }

    fprintf(P->p_f,"%d %d moveto (%s) show\n",x,y,filterParen(buf,mod_list[i]));
    if (start_page[i] == end_page[i])
      fprintf(P->p_f,"%d %d moveto (%d) show\n",x+PIDX_PGCOLSEP,y,start_page[i]);
    else
      fprintf(P->p_f,"%d %d moveto (%d-%d) show\n",x+PIDX_PGCOLSEP,y,start_page[i],end_page[i]);
    
  }

  fprintf(P->p_f,"EP\n");
}

static HGNode *new_HGNode(GModuleDef *M)
{
  HGNode *G = (HGNode*) ob_malloc(sizeof(HGNode),"HGNode");

  G->hg_module = M;
  G->hg_maxDepth = 1;
  G->hg_expanded = 1;
  G->hg_nextSibling = 0;
  G->hg_children = 0;

  return G;
}

static void HGNode_addChild(HGNode *G,HGNode *CG)
{
  if (CG->hg_maxDepth+1 > G->hg_maxDepth)
    G->hg_maxDepth = CG->hg_maxDepth+1;
  CG->hg_nextSibling = G->hg_children;
  G->hg_children = CG;
}

static HGNode *build_HGNodeGraph(GModuleDef *M,NHash *H)
{
  HGNode *G;
  HashElem *E;
  NHash *LH = new_NHash();

  G = new_HGNode(M);

  if (NHash_find(H,(nkey_t)M)) {
    G->hg_expanded = 0;
  } else {
    G->hg_expanded = 1;
    NHash_insert(H,(nkey_t)M,M);
    for (E = Hash_first(M->gates);E;E = Hash_next(M->gates,E)) {
      GCElement *g = (GCElement*) HashElem_obj(E);
      if (g->typeinfo->Code == BLOCK) {
	GModuleDef *CM = env_findModule(g->u.block.BlockFunction);
	HGNode *CG;

	if (CM && !NHash_find(LH,(nkey_t)CM)) {
	  NHash_insert(LH,(nkey_t)CM,CM);
	  CG = build_HGNodeGraph(CM,H);
	  HGNode_addChild(G,CG);
	}
      }
    }
  }

  delete_NHash(LH);

  return G;
}

static void unbuild_HGNodeGraph(HGNode *G)
{
  if (!G) return;
  unbuild_HGNodeGraph(G->hg_nextSibling);
  unbuild_HGNodeGraph(G->hg_children);
  ob_free(G);
}

static void HGNodeGraph_print(HGNode *G,int level)
{
  int i;

  if (!G) return;

  for (i = 0;i < level;i++) printf(" ");
  printf("%s\n",G->hg_module->Name);
  HGNodeGraph_print(G->hg_children,level+1);
  HGNodeGraph_print(G->hg_nextSibling,level);
}

static int HGNode_translatePoint(int *x,int *y)
{
  int n = 0;

  while (*y < HG_BOTTOMY) {
    *y += HG_ROOTY-HG_BOTTOMY;
    *x += HG_NEXTCOLUMN;
    n++;
  }

  return n;
}

static void HGNode_draw(HGNode *G,GPrint *P,int x,int y)
{
  char buf[STRMAX];

  HGNode_translatePoint(&x,&y);
  fprintf(P->p_f,"(%s) %s %d %d hgnode\n",filterParen(buf,G->hg_module->Name),
	  (G->hg_expanded ? "false" : "true"),x,y);
}

static void HGNode_drawLine(GPrint *P,int x1,int y1,int x2,int y2)
{
  int n1 = HGNode_translatePoint(&x1,&y1);
  int n2 = HGNode_translatePoint(&x2,&y2);

  if (n1 == n2)
    fprintf(P->p_f,"%d %d %d %d line\n",x1,y1,x2,y2);
  else if (n1 < n2) {
    fprintf(P->p_f,"%d %d %d %d line\n",x1,y1,x1,HG_BOTTOMY-HG_BOXHEIGHT/2);
    fprintf(P->p_f,"%d %d %d %d line\n",x2,HG_ROOTY,x2,y2);
  } else {
    fprintf(P->p_f,"%d %d %d %d line\n",x1,HG_ROOTY,x1,y2);
    fprintf(P->p_f,"%d %d %d %d line\n",x2,y1,x2,HG_BOTTOMY-HG_BOXHEIGHT/2);
  }
}

static int HGNodeGraph_draw(HGNode *G,GPrint *P,int x,int y)
{
  int nc,ns;

  if (!G) return 0;

  HGNode_draw(G,P,x,y);

  if (G->hg_children) {
    HGNode_drawLine(P,x+HG_LINEINDENT,y-HG_BOXHEIGHT/2,x+HG_LINEINDENT,y-HG_LINE);
    HGNode_drawLine(P,x+HG_LINEINDENT,y-HG_LINE,x+HG_INDENT,y-HG_LINE);
    nc = HGNodeGraph_draw(G->hg_children,P,x+HG_INDENT,y-HG_LINE);
  } else
    nc = 0;

  if (G->hg_nextSibling) {
    HGNode_drawLine(P,x-HG_INDENT+HG_LINEINDENT,y,x-HG_INDENT+HG_LINEINDENT,y-HG_LINE*(nc+1));
    HGNode_drawLine(P,x-HG_INDENT+HG_LINEINDENT,y-HG_LINE*(nc+1),x,y-HG_LINE*(nc+1));
    ns = HGNodeGraph_draw(G->hg_nextSibling,P,x,y-HG_LINE*(nc+1));
  } else
    ns = 0;

  return nc + ns + 1;
}

static void GPrint_printGraphPage(GPrint *P,GPage *PG)
{
  NHash *H = new_NHash();
  HGNode *G = 0;

  G = build_HGNodeGraph(XGate.circuit->root_mod,H);

  fprintf(P->p_f,"(%d of %d) (<hierarchy>) BP\n",PG->pg_num,P->p_numPages);
  fprintf(P->p_f,"%d bfont\n",HG_FONTSIZE);
  HGNodeGraph_draw(G,P,HG_ROOTX,HG_ROOTY);
  fprintf(P->p_f,"EP\n");

#if 0
  for (E = Hash_first(H);E;E = Hash_next(H,E)) {
    NHashElem_key(E);
    HashElem_obj(E);
  }
#endif

  unbuild_HGNodeGraph(G);
  delete_NHash(H);
}

/*
 * Output a single line of trace data for a single signal.
 *
 * Parameters:
 *   P		Page on which to print
 *   T		Trace data to print
 *   y		y position of page of trace
 *   pg_tstart	time value at left edge of page.
 */
static void GPrint_outputTrace(GPrint *P,GTrace *T,int y,int pg_tstart)
{
  int tend = imin(pg_tstart + P->p_trace.e_per_line,P->p_trace.end);
  int tstart = imax(pg_tstart,P->p_trace.start);
  char buf[STRMAX];
  GValue *pV,*V;

  fprintf(P->p_f,"%d %d %f %d gline\n",PTRC_TXTMARGIN,y,P->p_uWidth-PTRC_RMARGIN,y);
  fprintf(P->p_f,"%d bfont\n",PTRC_FONTSIZE);
  fprintf(P->p_f,"(%s) %d %d prshow\n",filterParen(buf,T->Name),
	  PTRC_TXTMARGIN,y+PTRC_TRHEIGHT/2-PTRC_FONTSIZE/2);
  fprintf(P->p_f,"%d rfont\n",PTRC_FONTSIZE);

  /*
   * Scan for first value
   */
  pV = 0;
  for (V = T->First;V->v_next && V->v_next->v_time < tstart;pV = V, V = V->v_next);

  fprintf(P->p_f,"%d %d %d %d BT\n",
	  PTRC_LMARGIN-1,y,(int)P->p_uWidth-PTRC_LMARGIN-PTRC_RMARGIN+2,PTRC_TRHEIGHT);

  for (;V && V->v_time <= tend;pV = V, V = V->v_next) {
    int seg_start,seg_end;
    int subseg_start, subseg_end;
    double x1,x2;

    seg_start = V->v_time;
    seg_end = V->v_next ? V->v_next->v_time : imin(tend,P->p_trace.sim_end);
    subseg_start = imax(imax(seg_start,pg_tstart),tstart);
    subseg_end = imin(seg_end,tend);

    x1 = PTRC_LMARGIN+(subseg_start-pg_tstart)*P->p_trace.pt_per_e;
    x2 = PTRC_LMARGIN+(subseg_end-pg_tstart)*P->p_trace.pt_per_e;

    if (V->v_hexValue) {
      if (pV && (seg_start == subseg_start || pV->v_time > pg_tstart))
	fprintf(P->p_f,"%g %d %g %d line\n",x1,y+PTRC_TRHIGH,x1,y+PTRC_TRLOW);
      fprintf(P->p_f,"%g %d %g %d line\n",x1,y+PTRC_TRLOW,x2,y+PTRC_TRLOW);
      fprintf(P->p_f,"%g %d %g %d line\n",x1,y+PTRC_TRHIGH,x2,y+PTRC_TRHIGH);

      fprintf(P->p_f,"(%s) %g %d %g trshow\n",V->v_hexValue,x1+PTRC_HEXPOS,y+PTRC_TRLOW+3,x2-x1-PTRC_HEXPOS);
    } else {
      if (pV && (seg_start == subseg_start || pV->v_time > pg_tstart)) {
	switch (transition_type(pV->v_code,V->v_code)) {
	case 0x1 :
	  fprintf(P->p_f,"%g %d %g %d line\n",x1,y+PTRC_TRLOW,x1,y+PTRC_TRMID);
	  break;
	case 0x2 :
	  fprintf(P->p_f,"%g %d %g %d line\n",x1,y+PTRC_TRHIGH,x1,y+PTRC_TRMID);
	  break;
	case 0x3 :
	  fprintf(P->p_f,"%g %d %g %d line\n",x1,y+PTRC_TRHIGH,x1,y+PTRC_TRLOW);
	  break;
	}
      }

      switch (V->v_code) {
      case VC_ZERO :
	fprintf(P->p_f,"%g %d %g %d line\n",x1,y+PTRC_TRLOW,x2,y+PTRC_TRLOW);
	break;
      case VC_ONE :
	fprintf(P->p_f,"%g %d %g %d line\n",x1,y+PTRC_TRHIGH,x2,y+PTRC_TRHIGH);
	break;
      case VC_UNKNOWN :
	fprintf(P->p_f,"%g %d %g %d line\n",x1,y+PTRC_TRLOW,x2,y+PTRC_TRLOW);
	fprintf(P->p_f,"%g %d %g %d line\n",x1,y+PTRC_TRHIGH,x2,y+PTRC_TRHIGH);
	break;
      case VC_FLOAT :
	fprintf(P->p_f,"%g %d %g %d line\n",x1,y+PTRC_TRMID,x2,y+PTRC_TRMID);
	break;
      case VC_HIGH :
	fprintf(P->p_f,"%g %d %g %d line\n",x1,y+PTRC_TRHIGH,x2,y+PTRC_TRHIGH);
	fprintf(P->p_f,"%g %d %g %d line\n",x1,y+PTRC_TRMID,x2,y+PTRC_TRMID);
	break;
      case VC_LOW :
	fprintf(P->p_f,"%g %d %g %d line\n",x1,y+PTRC_TRLOW,x2,y+PTRC_TRLOW);
	fprintf(P->p_f,"%g %d %g %d line\n",x1,y+PTRC_TRMID,x2,y+PTRC_TRMID);
	break;
      }
    }
  }

  fprintf(P->p_f,"ET\n");
}

static void GPrint_outputTraceScale(GPrint *P,int y,int tstart)
{
  int tend = tstart + P->p_trace.e_per_line;
  int t;

  fprintf(P->p_f,"%d bfont\n",PTRC_FONTSIZE);
  for (t = tstart;t <= tend;t += P->p_trace.e_interval) {
    double x =  PTRC_LMARGIN + (t-tstart)*P->p_trace.pt_per_e;
    fprintf(P->p_f,"%g %d %g %d line\n",x,y,x,y-PTRC_SCTICK);

    if (t + P->p_trace.e_interval <= tend)
      fprintf(P->p_f,"(%d) %g %d rCT\n",t,x,y-PTRC_SCLABPOS);
  }
}


static void GPrint_printTracePage(GPrint *P,GPage *PG)
{
  extern GScope *Scope;
  int t,ts,top_y,y;

  fprintf(P->p_f,"(%d of %d) (%d-%d) BP_T\n",
	  PG->pg_num,P->p_numPages,
	  imax(PG->pg_traceStart,P->p_trace.start),
	  imin(PG->pg_traceEnd,P->p_trace.end));

  y = P->p_uHeight - PTRC_TMARGIN;

  for (ts = 0;ts < P->p_trace.ts_per_pg;ts++) {
    int l_start = PG->pg_traceStart+ts*P->p_trace.e_per_line;

    if (l_start > PG->pg_traceEnd) break;

    top_y = y;
    for (t = PG->pg_traceNum;t < Scope->NumTraces;t++) {
      GTrace *T = Scope->Traces[t];				/* Trace to display */
      y -= PTRC_TRHEIGHT;					/* Position of trace baseline */
      if (y - PTRC_SCHEIGHT < 0) break;				/* Page is full */

      GPrint_outputTrace(P,T,y,l_start);
    }

    fprintf(P->p_f,"%d %d %d %d bold_box\n",0,y-PTRC_SCBXHEIGHT,(int)P->p_uWidth,top_y-y+PTRC_SCBXHEIGHT);

    GPrint_outputTraceScale(P,y,l_start);

    y -= PTRC_SCHEIGHT;
  }

  fprintf(P->p_f,"EP\n");
}

static void GPrint_outputPages(GPrint *P)
{
  int i;

  for (i = 0;i < P->p_numPages;i++) {
    GPage *PG = P->p_pages[i];

    fprintf(P->p_f,"\n%%%%Page: %d %d\n",i+1,i+1);

    switch (PG->pg_type) {
    case PT_MODULE :
      GPrint_printModPage(P,PG);
      break;
    case PT_MOD4 :
      GPrint_printMod4Page(P,PG);
      break;
    case PT_INDEX :
      GPrint_printIndexPage(P,PG);
      break;
    case PT_GRAPH :
      GPrint_printGraphPage(P,PG);
      break;
    case PT_TRACE :
      GPrint_printTracePage(P,PG);
      break;
    }
  }
}

/*
 * Do special case for EPSF setup
 */
void GPrint_setupEPSFPage(GPrint *P)
{
  GModLayout *L = P->p_mods[0];
  int cwidth,cheight;
  GPage *PG;

  GModuleDef_getBBX(L->l_mod,&L->l_xmin,&L->l_xmax,&L->l_ymin,&L->l_ymax);
  cwidth  = L->l_xmax-L->l_xmin + 2*PAGE_MODMARGIN;
  cheight = L->l_ymax-L->l_ymin + 2*PAGE_MODMARGIN;

  L->l_isSmall = 0;
  L->l_numRows = 1;
  L->l_numCols = 1;


  P->p_pages = (GPage**) ob_malloc(sizeof(GPage*),"GPage*[]");
  assert(P->p_pages);

  
  L->l_xbase = EPSF_MINX;
  L->l_ybase = EPSF_MINY;
  L->l_width = L->l_xmax-L->l_xmin;
  L->l_height = L->l_ymax-L->l_ymin;

  PG = new_GPage(PT_MODULE,1);
  PG->pg_nmod = 1;
  PG->pg_mods[0] = L;
  P->p_pages[0] = PG;
  L->l_r = 0;
  L->l_c = 0;

  P->p_numPages = 1;
}

/*
 * Sort the modules in "hierarchy" order.
 */
void GPrint_hsortPages(GPrint *P)
{
  NHash *H = new_NHash();	/* Hash table for building hierarchy graph */
  NHash *Mhash = new_NHash();	/* Hash table for modules to be printed */
  HGNode *G = 0;
  List Q;		/* NOTE: lists do not have undo management.  We must clean up everything here. */
  GModLayout *L;
  int N = 0;
  int i;

  List_init(&Q);

  for (i = 0;i < P->p_numMods;i++) {
    L = P->p_mods[i];
    NHash_insert(Mhash,(nkey_t)L->l_mod,L);
  }


  //  P->p_numMods;
  G = build_HGNodeGraph(XGate.circuit->root_mod,H);
  List_addToHead(&Q,G);
  while (List_numElems(&Q) > 0) {
    GModuleDef *M;
    G = (HGNode*) List_popHead(&Q);
    M = G->hg_module;

    for (G = G->hg_children;G;G = G->hg_nextSibling) {
      List_addToTail(&Q,G);
    }

    if ((L = (GModLayout*) NHash_find(Mhash,(nkey_t)M))) {
      if (N < P->p_numMods)
	P->p_mods[N++] = L;
      NHash_remove(Mhash,(nkey_t)M);
    }
  }

  /*
   * If there were fewer sorted modules, then in the original list, this means
   * that we have unused modules.  In this case, we determine which modules are
   * unused and add them to the end of the list.
   */
  if (N != P->p_numMods) {
    HashElem *E;

    for (E = Hash_first(Mhash);E;E = Hash_next(Mhash,E)) {
      L = (GModLayout*) HashElem_obj(E);
      if (N < P->p_numMods)
	P->p_mods[N++] = L;
    }
  }

  List_uninit(&Q);
  unbuild_HGNodeGraph(G);
  delete_NHash(H);
  delete_NHash(Mhash);
}

void GPrint_setupPages(GPrint *P)
{
  int i,idx;
  int numSmall = 0;
  int numLarge = 0;

  /*
   * If this is an EPSF page, set things up a little different.
   */
  if (P->p_isepsf) {
    GPrint_setupEPSFPage(P);
    return;
  }

  GPrint_hsortPages(P);


  /*
    Compute page sizes and decide which ones are small
   */
  for (i = 0;i < P->p_numMods;i++) {
    GModLayout *L = P->p_mods[i];
    int cwidth,cheight;

    GModuleDef_getBBX(L->l_mod,&L->l_xmin,&L->l_xmax,&L->l_ymin,&L->l_ymax);
    cwidth  = L->l_xmax-L->l_xmin + 2*PAGE_MODMARGIN;
    cheight = L->l_ymax-L->l_ymin + 2*PAGE_MODMARGIN;


    if (*P->p_opts.po_4up == '1' && cwidth < P->p_uWidth/2 && cheight < P->p_uHeight/2) {
      L->l_isSmall = 1;
      numSmall++;
    } else {
      if (P->p_style == PS_FIT || (cwidth < P->p_uWidth && cheight < P->p_uHeight)) {
	numLarge++;
	L->l_numRows = 1;
	L->l_numCols = 1;
      } else {
	int j;
	/*
	  Recompute widthout margins added in
	 */
	cwidth  = L->l_xmax-L->l_xmin;
	cheight = L->l_ymax-L->l_ymin;

	for (j = 1;cwidth+j*(2*PAGE_MODMARGIN)+(j-1)*PAGE_OVERLAP > j*P->p_uWidth;j++);
	L->l_numCols = j;

	for (j = 1;cheight+j*(2*PAGE_MODMARGIN)+(j-1)*PAGE_OVERLAP > j*P->p_uHeight;j++);
	L->l_numRows = j;

	numLarge += L->l_numRows*L->l_numCols;
      }
    }
  }

  if (*P->p_opts.po_index == '1') numLarge++;
  if (*P->p_opts.po_graph == '1') numLarge++;

  P->p_numPages = numLarge + (numSmall+MODPGMAX-1)/MODPGMAX;
  P->p_pages = (GPage**) ob_malloc(P->p_numPages*sizeof(GPage*),"GPage*[]");
  assert(P->p_pages);

  idx = 0;
  if (*P->p_opts.po_index == '1') {
    P->p_pages[idx] = new_GPage(PT_INDEX,idx+1);
    idx++;
  }
  if (*P->p_opts.po_graph == '1') {
    P->p_pages[idx] = new_GPage(PT_GRAPH,idx+1);
    idx++;
  }

  /*
    Setup pages for large modules
    */
  if (numLarge) {
    for (i = 0;i < P->p_numMods;i++) {
      GModLayout *L = P->p_mods[i];
      if (L->l_isSmall) continue;

      L->l_xbase = PAGE_MODMARGIN;
      L->l_ybase = PAGE_MODMARGIN;
      L->l_width = P->p_uWidth - 2*PAGE_MODMARGIN;
      L->l_height = P->p_uHeight - 2*PAGE_MODMARGIN;

      if (L->l_numRows <= 1 && L->l_numCols <= 1) {
	GPage *PG = new_GPage(PT_MODULE,idx+1);
	PG->pg_nmod = 1;
	PG->pg_mods[0] = L;
	P->p_pages[idx++] = PG;
	L->l_r = 0;
	L->l_c = 0;
      } else {
	int r,c;
	for (r = 0;r < L->l_numRows;r++)
	  for (c = 0;c < L->l_numCols;c++) {
	    GPage *PG = new_GPage(PT_MODULE,idx+1);
	    GModLayout *PL = copyNew_GModLayout(L);
	    PL->l_r = r;
	    PL->l_c = c;
	    PG->pg_nmod = 1;
	    PG->pg_mods[0] = PL;
	    P->p_pages[idx++] = PG;
	  }
      }
    }
  }

  if (numSmall) {
    GPage *PG = 0;

    for (i = 0;i < P->p_numMods;i++) {
      GModLayout *L = P->p_mods[i];
      if (!L->l_isSmall) continue;

      if (!PG) {
	PG = new_GPage(PT_MOD4,idx+1);
	P->p_pages[idx++] = PG;
      }

      L->l_xbase = (PG->pg_nmod & 0x1) ? (P->p_uWidth/2) : 0;
      L->l_ybase = (PG->pg_nmod & 0x2) ? 0 : (P->p_uHeight/2);
      L->l_width = P->p_uWidth/2;
      L->l_height = P->p_uHeight/2;

      L->l_xbase += PAGE_MODMARGIN;
      L->l_ybase += PAGE_MODMARGIN;
      L->l_width -= 3*PAGE_MODMARGIN/2;
      L->l_height -= 3*PAGE_MODMARGIN/2;

      L->l_r = 0;
      L->l_c = 0;

      if (!(PG->pg_nmod & 0x2)) {
	L->l_ybase -= PAGE_MODMARGIN/2;
      }

      PG->pg_mods[PG->pg_nmod++] = L;
      if (PG->pg_nmod == MODPGMAX) PG = 0;
    }
  }

  P->p_numPages = idx;
}

/*
 * Print a circuit diagram document
 */
void GPrintOpt_print(GPrintOpt *PO)
{
  GPrint *P = new_GPrint(PO);

  if (!P) return;

  GPrint_setupPages(P);
  GPrint_outputPreamble(P,1);
  GPrint_outputPages(P);
  GPrint_outputTrailer(P);

  delete_GPrint(P);
}

/***********************************************************************/

static void choose_trace_interval(int *e_per_l,int *e_inter)
{
  double n,m;
  int l,h;

  /*
   * Power of ten >= to e_per_l
   */
  n = pow(10.0,ceil(log((double)*e_per_l)/log(10.0)));

  m = (n-*e_per_l)/n;

  if (m >= 0.8)
    m = 0.02;
  else if (m >= 0.6)
    m = 0.05;
  else
    m = 0.1;

  n = m*n;

  *e_inter = (int)n;

  l = (*e_per_l / *e_inter)* *e_inter;
  h = l + *e_inter;

  if (*e_per_l-l < h-*e_per_l)
    *e_per_l = l;
  else
    *e_per_l = h;
}


void GPrint_setupTracePages(GPrint *P)
{
  extern GScope *Scope;		/* The scope trace */
  int hspace;			/* Horizontal space for traces on page */
  int vspace;			/* Vertical space for traces on page */
  int tsheight;			/* Height of a full trace set and scale */
  int pg;			/* Page # */
  int e_start;			/* Starting epoch # on a page */

  pg = 0;

  /*
   * If no traces, output an empty page.
   */
  if (!Scope || Scope->NumTraces <= 0 || P->p_trace.start >= P->p_trace.end) {
    P->p_numPages = 1;
    P->p_pages = (GPage**) ob_malloc(sizeof(GPage*),"GPage*");
    assert(P->p_pages);
    P->p_pages[0] = new_GPage(PT_TRACE,1);
    return;
  }

  vspace = P->p_uHeight-PTRC_TMARGIN-PTRC_BMARGIN;		/* Max usable vert. space*/
  hspace = P->p_uWidth-PTRC_LMARGIN-PTRC_RMARGIN;		/* Max usable horz. space*/
  tsheight = Scope->NumTraces*PTRC_TRHEIGHT+PTRC_SCHEIGHT;	/* Ver. space per trace set */

  P->p_trace.sim_end = Scope->Time;

  P->p_trace.e_per_line = PT2CM(hspace)*P->p_trace.scale; /* Number of epochs per line */

  /*
   * Choose an interval and adjust epochs per line to be a multiple of the interval
   */
  choose_trace_interval(&P->p_trace.e_per_line,&P->p_trace.e_interval);

  /*
   * Compute translation from epochs to points.
   */
  P->p_trace.pt_per_e = (double)hspace/(double)P->p_trace.e_per_line;

  /*
   * Round start down and end up to a multiple of the interval
   */
  P->p_trace.istart = (P->p_trace.start/P->p_trace.e_interval)*P->p_trace.e_interval;
  P->p_trace.iend = ((P->p_trace.end+P->p_trace.e_interval-1)/P->p_trace.e_interval)*P->p_trace.e_interval;

  if (tsheight <= vspace) {					/* 1 or more trace sets on a page */

    /*
     * Compute the number of trace sets on a page, the number of epochs on a page and the
     * number of pages in the document.  The number of pages per trace set is one.
     */
    P->p_trace.ts_per_pg = (vspace/tsheight);
    P->p_trace.e_per_pg = P->p_trace.e_per_line*P->p_trace.ts_per_pg;
    P->p_numPages = (P->p_trace.iend-P->p_trace.istart+P->p_trace.e_per_pg-1)/P->p_trace.e_per_pg;
    P->p_trace.pg_per_ts = 1;

    P->p_pages = (GPage**) ob_malloc(P->p_numPages*sizeof(GPage*),"GPage*[]");
    assert(P->p_pages);

    for (e_start = P->p_trace.istart;e_start < P->p_trace.iend;e_start += P->p_trace.e_per_pg) {
      GPage *G;

      assert(pg < P->p_numPages);

      G = new_GPage(PT_TRACE,pg+1);
      P->p_pages[pg++] = G;

      G->pg_traceNum = 0;
      G->pg_traceStart = e_start;
      G->pg_traceEnd = e_start + P->p_trace.e_per_pg - 1;
      if (G->pg_traceEnd > P->p_trace.iend) 
	G->pg_traceEnd = P->p_trace.iend;
    }
  } else {						/* multiple pages per trace set */
    int maxtr = (vspace-PTRC_SCHEIGHT)/PTRC_TRHEIGHT;	/*   Maximum number of traces per page */
    int j;

    P->p_trace.pg_per_ts = (Scope->NumTraces+maxtr-1)/maxtr;
    P->p_trace.e_per_pg = P->p_trace.e_per_line;
    P->p_numPages = ((P->p_trace.iend-P->p_trace.istart+P->p_trace.e_per_pg-1)/P->p_trace.e_per_pg);
    P->p_numPages *= P->p_trace.pg_per_ts;
    P->p_trace.ts_per_pg = 1;

    P->p_pages = (GPage**) ob_malloc(P->p_numPages*sizeof(GPage*),"GPage*[]");
    assert(P->p_pages);

    pg = 0;
    for (e_start = P->p_trace.istart;e_start < P->p_trace.iend;e_start += P->p_trace.e_per_pg) {
      for (j = 0;j < P->p_trace.pg_per_ts;j++) {
	GPage *G;

	assert(pg < P->p_numPages);

	G = new_GPage(PT_TRACE,pg);
	P->p_pages[pg++] = G;

	G->pg_traceNum = j*maxtr;
	G->pg_traceStart = e_start;
	G->pg_traceEnd = e_start + P->p_trace.e_per_pg - 1;
	if (G->pg_traceEnd > P->p_trace.iend) 
	  G->pg_traceEnd = P->p_trace.iend;

      }
    }
  }

#if 0 
  printf("actual pages (p=%d (%gx%g) s=%d r=[%d..%d] ---> %d\n",(P->p_size-paperSizes),P->p_uWidth,P->p_uHeight,P->p_trace.scale,P->p_trace.start,P->p_trace.end,P->p_numPages);
#endif
}

/*
 * Print a scope trace document
 */
void GPrintOpt_tracePrint(GPrintOpt *PO)
{
  GPrint *P = new_GPrint(PO);

  if (!P) return;

  GPrint_setupTracePages(P);
  GPrint_outputPreamble(P,0);
  GPrint_outputPages(P);
  GPrint_outputTrailer(P);

  delete_GPrint(P);
}

/*
 * Utility function for estimating page count.
 */
int tracePageCountEst(const char *orient,int paper,int scale,int start,int end)
{
  extern GScope *Scope;		/* The scope trace */
  int hspace;			/* Horizontal space for traces on page */
  int vspace;			/* Vertical space for traces on page */
  int tsheight;			/* Height of a full trace set and scale */
  int numPages = 0;
  int e_per_line;
  int e_interval;
  int pt_per_e;
  int ts_per_pg;
  int pg_per_ts;
  int e_per_pg;
  double uHeight, uWidth;



  if (strcmp(orient,"landscape") == 0) {
    uWidth = paperSizes[paper].ps_height - PAGE_LMARGIN - PAGE_RMARGIN;
    uHeight = paperSizes[paper].ps_width - PAGE_TMARGIN - PAGE_BMARGIN - PAGE_LBLOCK;;
  } else {
    uWidth = paperSizes[paper].ps_width - PAGE_LMARGIN - PAGE_RMARGIN;;
    uHeight = paperSizes[paper].ps_height- PAGE_TMARGIN - PAGE_BMARGIN - PAGE_LBLOCK;
  }

  /*
   * If no traces, output an empty page.
   */
  if (!Scope || Scope->NumTraces <= 0 || start >= end) {
#if 0
    printf("est pages (p=%d (%gx%g) s=%d r=[%d..%d] ---> %d\n",paper,uWidth,uHeight,scale,start,end,1);
#endif
    return 1;
  }


  vspace = uHeight-PTRC_TMARGIN-PTRC_BMARGIN;			/* Max usable vert. space*/
  hspace = uWidth-PTRC_LMARGIN-PTRC_RMARGIN;			/* Max usable horz. space*/
  tsheight = Scope->NumTraces*PTRC_TRHEIGHT+PTRC_SCHEIGHT;	/* Ver. space per trace set */

  e_per_line = PT2CM(hspace)*scale;	 			/* Number of epochs per line */

  /*
   * Choose an interval and adjust epochs per line to be a multiple of the interval
   */
  choose_trace_interval(&e_per_line,&e_interval);

  /*
   * Compute translation from epochs to points.
   */
  pt_per_e = (double)hspace/(double)e_per_line;

  /*
   * Round start down to a multiple of the interval
   */
  start = (start/e_interval)*e_interval;

  /*
   * Round end up to a multiple of the interval
   */
  end = ((end+e_interval-1)/e_interval)*e_interval;

  if (tsheight <= vspace) {					/* 1 or more trace sets on a page */

    /*
     * Compute the number of trace sets on a page, the number of epochs on a page and the
     * number of pages in the document.  The number of pages per trace set is one.
     */
    ts_per_pg = (vspace/tsheight);
    e_per_pg = e_per_line*ts_per_pg;
    numPages = (end-start+e_per_pg-1)/e_per_pg;
  } else {						/* multiple pages per trace set */
    int maxtr = (vspace-PTRC_SCHEIGHT)/PTRC_TRHEIGHT;	/*   Maximum number of traces per page */

    pg_per_ts = (Scope->NumTraces+maxtr-1)/maxtr;
    e_per_pg = e_per_line;
    numPages = ((end-start+e_per_pg-1)/e_per_pg);
    numPages *= pg_per_ts;
  }

#if 0
  printf("est pages (p=%d (%gx%g) s=%d r=[%d..%d] ---> %d\n",paper,uWidth,uHeight,scale,start,end,numPages);
#endif
  return numPages;
}
