/*
  postscript.c

  $Revision: 1.3 $
  $Modtime:   31 Jul 1997 21:55:28  $

  Author:  DTJ  10/8/93
  Modification History
  MIB 19-April-2000 DR RRCcl00842
      added #if block for __DECC_VER to include <stdlib.h>

*/

#if defined(__DECC_VER)          /* MIB 19-April-2000 */
#   if(__DECC_VER >= 60290003) 
#include <stdlib.h>
#  endif
#endif

#include "glibc.h"
#include <unistd.h>
#include <time.h>

/* Used to write out PostScript lines that don't need comments.  Another
   nice feature here is that by giving the string as an ARGUMENT rather
   than passing it as the print field itself, we can use the percent sign (%)
   with impunity without having to worry about printf interpreting it. */

#define W(str)  fprintf(f_PostScript, "%s\n", str)


#if _X11R3
#define XGetGCValues(a, b, c, d)	0
#endif


#if _VMS
int	strcasecmp();
int	strncasecmp();
char	*strdup();
#endif


/**************************** Constants (private) **********************/

#define		FILENAME_LEN	81


/***************************** Type definitions ************************/

typedef struct {
  double	xoffset, yoffset;
  double	scaling;
  double	rotation;
  struct {
    int		llx, lly;
    int		urx, ury;
  }		bb;
  double	mindotsize;
} LAYOUT;

typedef struct nodestruct {
  struct nodestruct *next;
  char	*name;
} NODE;

/********************* Global variables (exportable) *******************/

attr	FILE           *f_PostScript = NULL;	/* non-NULL: PS printing ON */
attr	char		TextDash[1024] = "[1 3] 2 setdash";
attr	char		LineDash[1024] = "[2 2] 2 setdash";
attr	int		PSfontsize     = 12;


/**************** Private variables (global to this module only) *******/

attr	static	char		GraphicsClipPath[1024] = "";
attr	static	char		RubberClipPath  [1024] = "";
attr	static	Bool		Interactive = FALSE;
attr	static	int		VectorSize = 128;
attr	static	NODE		*DocFonts = NULL;


/* Function definitions */

static void ps_determineLayout (LAYOUT *L);
static void ps_setGLineAttr (int ic);
static char *strfield (const char *s, char delimiter);
static char *strcasestr (const char *s1, const char *s2);
static char *fontfamily (const char *fontname, int *roman);
static void x2psfontconv (const char *Xfontname,
		char *PSfontname, char *ptsize);
static void addDocFont (const char *s);
static double findHCenv (const char *s, double defaultVal,
		         const char *strmenu[]);
static Bool menuitem (char *menustr, Bool chosen, const char *s,
		      double *defaultVal, const char *submenu[]);
static double figure_offset (double anchor, double margin,
		double papersize, double draw_size);

/***************************** Macros (private) ************************/

/* The NormalFG macro is TRUE if the current foreground is white and the
   background is black. */

#define		NormalFG	(Mx->xi <= 0)


/***********************************************************************/
/********************************* Code ********************************/
/***********************************************************************/


/***************************** Font Conversion *************************/

#define	MAX_FONTFAMILIES	9
struct fontstruct {
  char	*name;
  Bool	roman;
};

attr struct fontstruct	FontFamilies[MAX_FONTFAMILIES] = {
				  { "AGaramond", FALSE },
				  { "AvantGarde", FALSE },
				  { "Bookman", FALSE },
				  { "Courier", FALSE },
				  { "Helvetica", FALSE },
				  { "NewCenturySchlbk", TRUE },
				  { "Palatino", TRUE },
				  { "Symbol", FALSE },
				  { "Times", TRUE }
				};



/* strfield

Like strtok() except that the delimiters are considered significant,
that is, a string of two delimiters indicates an empty field.
Also, the delimiter may only be one character.

*/

static char	*strfield (const char *s, char delimiter)

{
  static char	*sptr = NULL;
  char		*t, *word;

  if (s) sptr = (char *)s;

  if (!sptr)
    return(NULL);
  else {
    t = strchr(sptr, delimiter);
    if (t) { t[0] = '\0'; t++; }
    word = sptr;
    sptr = t;
    return(word);
  }

}



/* strcasestr

Returns the position in s1 where s2 occurs, without regard to case.
Brute-force algorithm.

*/

static char	*strcasestr (const char *s1, const char *s2)

{
  int	i, ls2;

  ls2 = strlen(s2);
  for (i = 0; s1[i] != '\0'; i++) {
    if (strncasecmp(s1 + i, s2, ls2) == 0)
      return (char *)(s1 + i);
  }
  return(NULL);
}



/* fontfamily

Finds a nearest match to the given font family.
The algorithm is:
  1)	Try to make a complete, case insensitive match.
  2)	Find a (case sensitive) substring
  3)	Find a (case insensitive) substring

For any of the above steps, if we make a "match", return a pointer to that
full named string.

The integer passed back in the second argument is TRUE if the family is
default Roman, that is, without any other attributes, the '-r-' X style
should be interpreted as a Roman attribute.  FALSE means that the family
does not have a Roman attribute.

*/

static char	*fontfamily (const char *fontname, int *roman)

{
  int	i;

  for (i = 0; i < MAX_FONTFAMILIES; i++)
    if (strcasecmp(fontname, FontFamilies[i].name) == 0) {
      *roman = FontFamilies[i].roman;
      return(FontFamilies[i].name);
    }

  for (i = 0; i < MAX_FONTFAMILIES; i++)
    if (strstr(fontname, FontFamilies[i].name)) {
      *roman = FontFamilies[i].roman;
      return(FontFamilies[i].name);
    }

  for (i = 0; i < MAX_FONTFAMILIES; i++)
    if (strcasestr(fontname, FontFamilies[i].name)) {
      *roman = FontFamilies[i].roman;
      return(FontFamilies[i].name);
    }

  *roman = FALSE;
  return (char *)(fontname);
}


/* x2psfontconv

Converts X fonts into PostScript fonts.
The PostScript API dictates 13 standard fonts that most PS drivers should
support.

This function matches a given X fullname to a standard font name.

Arguments:	(IN)	Xfontname	Xlib font name to convert
		(OUT)	PSfontname	Standard PostScript font name
		(OUT)	ptsize		Point size of the font, as a string

Assumptions:  the given X font name will be hyphen-delimited and will have
	      the vendor name as the first field.
	      Also that the X font name is "longhand", with all fields filled
	      in.

*/

static void	x2psfontconv (const char *Xfontname,
			      char *PSfontname, char *ptsize)

{
  char	tmp[1024];
  char	*t, *family, *style;
  char	c;
  int	i, j;
  Bool	isRoman, word;

/****** Code *******/

  strcpy(tmp, Xfontname);		/* we're going to clobber it with
					   strfield */
  strcpy(PSfontname, "");

/* Get strfield started.  If the first field is empty, we know there's a
   leading dash:  the vendor will be the next field.  If not, we just
   threw away the vendor. */
  t = strfield(tmp, '-');
  if (t[0] == '\0') t = strfield(NULL, '-');

/*
   Basic heuristic:
   X font name					PS font name
   ===========					============
   unknown case					initial letters uppercase
   spaces btw words				no spaces btw words
   hyphens between all fields			hyphen between family name
						  and other characteristics
   bold						Bold
   i						Italic
   o						Oblique
   r 						Roman (for certain families)
   medium					<nothing>
*/


  strcpy(PSfontname, "");
  j = 0;
  if (family = strfield(NULL, '-')) {
    for (i = 0, word = FALSE; (c = family[i]) != '\0'; i++)
      if (word) {
        if (isspace(c)) word = FALSE; else PSfontname[j++] = c;
      }
      else				/* not in a word */
      {
        if (!isspace(c)) {
	  word = TRUE;
	  PSfontname[j++] = toupper(c);
        }
      }
  PSfontname[j]   = '\0';
  }
  else {
    strcpy(PSfontname, "Courier");
    j = strlen(PSfontname);
  }

/* Special case:  Schoolbook -> Schlbk */
  if (t = strstr(PSfontname, "Schoolbook")) strcpy(t, "Schlbk");

/* Find closest match */
  strcpy(PSfontname, fontfamily(PSfontname, &isRoman));

/* Sort of a special case:  If the font still has "Itc" on the front after
   coming back from fontfamily, get rid of it; it's a cinch the laserprinter
   doesn't support it. */
  if (strncmp(PSfontname, "Itc", 3) == 0 && isupper(PSfontname[3]))
    strcpy(PSfontname, PSfontname + 3);

  j = strlen(PSfontname);
  PSfontname[j++] = '-';
  PSfontname[j]   = '\0';

/* Translate styles.

   There are two "style" fields:  The first one is usually "bold" or
   The first one is a whole word, specifying an attribute such as
   "bold"; capitalize the first letter and append it, unless it's the word
   "medium", in which case it should be omitted.

   The second is a single letter and seems to ALWAYS be either:
     "o" (oblique), "i" (italic), or "r" (regular).
*/
  style = strfield(NULL, '-');
  if (style && strcasecmp(style, "medium") != 0) {
    style[0] = toupper(style[0]);
    strcat(PSfontname, style);
  }

/* Note that the Roman attribute is kind of special.  It only appears with
   some font families, and it only appears if there are no other attributes. */
  if (style = strfield(NULL, '-')) {
    switch (tolower(style[0])) {
      case 'o' :  strcat(PSfontname, "Oblique"); break;
      case 'i' :  strcat(PSfontname, "Italic");  break;
      case 'r' :
	if (PSfontname[strlen(PSfontname) - 1] == '-'  &&  isRoman)
	  strcat(PSfontname, "Roman");
	break;
    }
  }

/* Fill in the ptsize argument.  The point size is three fields after
   the second style.  Remember to copy it since return values from
   strfield are temporary. */

  strfield(NULL, '-');
  strfield(NULL, '-');
  if (t = strfield(NULL, '-'))
    strcpy(ptsize, t);
  else
    strcpy(ptsize, "");

/* Now take off any trailing dashes */
  j = strlen(PSfontname) - 1;
  while (PSfontname[j] == '-') PSfontname[j--] = '\0';
  j++;

}


/***************************** End Font Conversion *********************/



/*
   mx_psdashes
   
   Must be called whether or not f_PostScript is defined.  If f_PostScript
   is ever defined, it will need this information.  The dash information
   is only available in mx_open, so we must capture it here, as this
   function is called from mx_open.

   Arguments:

     s		(OUT)	string to write PostScript into
     offset	(IN)	the offset or "phase" of the dashing
     dashes	(IN)	a byte array of dash lengths (usu. [on, off])
     ndashes	(IN)	elements in the dashes array
     factor	(IN)	a factor to multiply all the values by.

  The <factor> argument is here as a workaround for a difference between
  X and PostScript.  Apparently, when drawing with width = 0, for fast,
  single lines (as is always done), dashes are multiplied by two.  This
  causes the screen output to look one way and the PostScript output,
  another.  If you know that what will be drawn will need larger dashes
  for PostScript, put the multiplier in the <factor> argument.
   
*/

void mx_psdashes (char *s, int offset, const char *dashes, 
		  int ndashes, int factor)

{
  char	t[256];
  int	i;

  strcpy(s, "[ ");
  for (i = 0; i < ndashes; i++) {
    sprintf(t, "%d ", dashes[i] * factor);
    strcat(s, t);
  }
  sprintf(t, "] %d setdash", offset * factor);
  strcat(s, t);
}



/* mx_psopen

Prepares to start printing PostScript.
Opens the given filename for writing and assigns the file pointer to
  f_PostScript.  All other routines test to see if f_PostScript is non-NULL;
  if it is, write to it.

The status of the operation is written into *status.
Any errors are written into errstr.

If showerror is 1 (TRUE), print out the full error as reported by C.

*/

void mx_psopen (const char *fname, int_4 *status, char *errstr, 
		int interact, int lfname, int lerrstr)

{
  char		  cname[FILENAME_LEN];
  char		  cerrstr[1024];
  char		  temp[1024];
  time_t	  timeval;
  int		  fg, bg;
  LAYOUT	  layout;


  /************************ Code **********************/

  *status = 0;

  if (f_PostScript) {
    strcpy(cerrstr, "A PostScript file is already open");
    *status = 1;
    strc2f(cerrstr, strlen(cerrstr), errstr, lerrstr, C2F_FILL);
    return;
  }

  strf2c(fname, lfname, cname, FILENAME_LEN, F2C_UNIXFILE);

  f_PostScript = fopen(cname, "w");
  if (!f_PostScript) {
    sprintf(cerrstr, "%s: could not open file", cname);
    *status = 1;
    strc2f(cerrstr, strlen(cerrstr), errstr, lerrstr, C2F_FILL);
    return;
  }

  Interactive = interact;

  ps_determineLayout(&layout);

  W("%!PS-Adobe-1.0");
  W("%%Title: X-Midas PostScript plot");
  W("%%Creator: X-Midas");
  sprintf(temp, "%%%%For: %s\n", getlogin());
  W(temp);
  W("%%Pages: 1");
  time(&timeval);
  fprintf(f_PostScript, "%%%%CreationDate: %s", ctime(&timeval));
  W("%%DocumentFonts: (atend)");
  fprintf(f_PostScript, "%s: %d %d %d %d\n", "%%BoundingBox",
		layout.bb.llx, layout.bb.lly, layout.bb.urx, layout.bb.ury);
  W("%%EndComments");
  W("\n");

/* Save previous context; create new dictionary for our own definitions.
   I don't know why PostScript requires me to bind the return of 'save'
   to a symbol rather than just leaving it on the stack, but it does. */

  W("/XMidasPicture save def\n");
  W("50 dict begin\n");

/* Useful macros */

  W("/max { 2 copy gt { pop } { exch pop } ifelse } bind def");

/* Determine inversion.  Normal X-Midas plotting mode is white on black,
   and the user may have chosen colors which work with this arrangment.
   For most programs, we expect that XOR should be like the foreground. */
   
  if (NormalFG)
    { fg = 1; bg = 0; }
  else
    { fg = 0; bg = 1; }

  fprintf(f_PostScript, "/fg  { %d setgray } bind def\n", fg);
  fprintf(f_PostScript, "/bg  { %d setgray } bind def\n", bg);
  fprintf(f_PostScript, "/xor { %d setgray } bind def\n", fg);
  fprintf(f_PostScript, "\n");

  W("/dl {\t\t% x1 y1 x2 y2 dl = Draw Line");
  W("  gsave");
  W("  newpath moveto lineto stroke grestore");
  W("} bind def\n");
  W("/tl {\t\t% x1 y1 x2 y2 tl = Text Line");
  W("  gsave 1 setlinewidth fg");
  W("  newpath moveto lineto stroke grestore");
  W("} bind def\n");
  W("/dt {\t\t% string size x y dt = Draw Text");
  W("  gsave 1 setlinewidth moveto 1 -1 scale");
  W("  /Courier findfont exch scalefont setfont show grestore");
  W("} bind def\n");
  W("/M { moveto } bind def");
  W("/L { lineto } bind def\n");
  W("/R {\t\t% w h x y R = Rectangle");
  W("  moveto 2 copy 0 exch rlineto 0 rlineto -1 mul 0 exch rlineto");
  W("  closepath pop");
  W("} bind def\n");
  W("/sq {\t\t% x y r sq = Square of radius r (side = 2r)");
  W("  newpath abs 3 1 roll moveto dup -1 mul dup rmoveto");
  W("  2 mul dup 0 rlineto dup 0 exch rlineto -1 mul 0 rlineto closepath");
  W("} bind def\n");
  W("/Dot {\t\t% x y r Dot = circle of radius r");
  fprintf(f_PostScript, "  %f max newpath 0 360 arc closepath",
			layout.mindotsize);
  W("} bind def\n");
  W("/Plus {\t\t% x y r Plus = Plus symbol of radius r (side = 2r)");
  W("  newpath 1 copy 4 1 roll sub moveto 1 copy 2 mul dup 0 exch");
  W("  rlineto exch -1 mul dup rmoveto 0 rlineto");
  W("} bind def\n");
  W("/XSym {\t\t% x y r XSym = X symbol of radius r (side = 2r)");
  W("  newpath 1 copy 4 2 roll moveto -1 mul dup rmoveto 2 mul dup dup");
  W("  rlineto dup -1 mul dup 0 rmoveto rlineto");
  W("} bind def\n");
  W("/Tri {\t\t% x y r Tri = Triangle in circle of radius r");
  W("  newpath 1 copy 4 1 roll sub moveto 1 copy .8 mul dup 3 2 roll");
  W("  1.5 mul rlineto -2 mul 0 rlineto closepath");
  W("} bind def\n");
  W("/Char {\t\t% x y r c Char = Draw ASCII Character");
  W("  4 2 roll moveto exch dup -1 mul exch rmoveto");
  W("  1 -1 scale show 1 -1 scale");
  W("} bind def\n");
  W("/cl { newpath R clip } bind def\n");
  W("/nc { initclip }       bind def\n");
  W("\n% More byte miser definitions\n");
  W("/GS { gsave }        bind def");
  W("/GR { grestore }     bind def");
  W("/SL { setlinewidth } bind def");
  W("/SC { setrgbcolor }  bind def");
  W("/NP { newpath }      bind def");
  W("/S  { stroke }	  bind def");
  W("/CP { closepath }    bind def");
  W("");

  W("% TDy = Text Dash Yes");
  W("% LDy = Line Dash Yes");
  W("% Dn  = Dash no - applied to both Text and Line contexts");
  fprintf(f_PostScript, "/TDy { %s } bind def\n", TextDash);
  fprintf(f_PostScript, "/LDy { %s } bind def\n", LineDash);
  fprintf(f_PostScript, "/Dn  { [] 0 setdash } bind def\n");

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

  W("\n% ****** Translation/Rotation ******\n");

  fprintf(f_PostScript, "%f rotate\n", layout.rotation);

/* Default user space in PostScript is 1/72 inch.  */

/* Measure margins before scaling.  Since we're not scaled yet, move
   in the negative Y direction. */

  fprintf(f_PostScript, "%f 72 mul %f 72 mul translate\n",
				layout.xoffset, layout.yoffset);

/* The layout is determined without respect to switched Y coordinates.
   Switch the Y coordinates here. */
  fprintf(f_PostScript, "%f dup -1 mul scale\n", layout.scaling);

  W("\n% ********* Plotting Code **********\n");

  return;

}


/* mx_psclip

Assigns a clipping region to either the main graphics "context" or the
rubber (xor) "context".  The PostScript strings describing these regions
are the global strings GraphicsClipPath and RubberClipPath, and are
printed out every time a drawing call is made.

*/

void mx_psclip (int ic, short x, short y, short w, short h)

{
  int		anyclip;

  if (f_PostScript) {
    anyclip = w | h;
    if (ic == L_XORColor) {
      if (anyclip)
	sprintf(RubberClipPath, "%d %d %d %d cl ", w, h, x, y);
      else
	strcpy(RubberClipPath, "nc ");
    }
    else {
      if (anyclip)
	sprintf(GraphicsClipPath, "%d %d %d %d cl ", w, h, x, y);
      else
	strcpy(GraphicsClipPath, "nc ");
    }
  }
}



/* ps_setGLineAttr

Writes out the PostScript line style for the graphics context indicated
by <ic>, a context index.

Called by psdraw_line, psdraw_vector, and any routine whose attributes
must be determined by the <ic>.

*/

static void ps_setGLineAttr (int ic)

{
  XGCValues	values;
  XColor	xc;
  GC		gc;

  switch (ic) {
    case L_XORColor: gc = G.gcr; break;
    case L_BGColor: gc = G.gcti; break;
    case L_FGColor: gc = G.gct; break;
    default: gc = G.gcg;
  }

  if (!XGetGCValues(G.D,gc,GCForeground|GCLineStyle|GCLineWidth,&values)) {
    if (Mx->clast==L_XORColor || Mx->clast==L_NoColor)
      values.foreground = Mx->white;
    else
      values.foreground = Mx->pixel[Mx->clast-1];
    values.line_style = LineSolid;
    values.line_width = 1;
  }

  if (values.line_style != LineSolid)
    fprintf(f_PostScript, "LDy ");
  else
    fprintf(f_PostScript, "Dn ");

/* In X, linewidth of 0 is the same as a linewidth of 1, for drawing lines.
   The 0 linewidth simply indicates "fast" plotting.  In PostScript, 0
   linewidth is invisible. */

  fprintf(f_PostScript, "%d SL ", max(1, values.line_width));

  switch (ic) {
    case L_BGColor:				/* background */
      fprintf(f_PostScript, "bg\n");
      break;
    case L_FGColor:				/* foreground */
      fprintf(f_PostScript, "fg\n");
      break;
    case L_XORColor:				/* xor (rubberband) */
      fprintf(f_PostScript, "xor\n");
      break;
    default:
      if (G.BW)
	fprintf(f_PostScript, "fg\n");
      else {
	xc.pixel = values.foreground;
	XQueryColor(G.D, G.C_id, &xc);
	fprintf(f_PostScript, "%f %f %f SC\n",
                (float)xc.red/65535.0, 
                (float)xc.green/65535.0,
                (float) xc.blue/65535.0);
      }
      break;
  }			/* switch */
}



/* mx_psdraw_line

Draws a single line with the given <ic> attributes.

*/

void mx_psdraw_line (int ic, short x1, short y1, short x2, short y2)

{

  if (f_PostScript) {

    fprintf(f_PostScript, "GS ");
    ps_setGLineAttr(ic);

    if (ic == L_XORColor)
      fprintf(f_PostScript, RubberClipPath);
    else
      fprintf(f_PostScript, GraphicsClipPath);
    fprintf(f_PostScript, "%d %d %d %d dl GR\n", x1, y1, x2, y2);
  }
}



/* mx_psdraw_objects

Draws disconnected objects.

Arguments:
  <ic>		Index of the graphics context (attributes of the lines).
		Clipping is done according to the <ic>, at the boundaries
		set by mx_psclip().
  <pix>		x, y locations of the centers of the objects.
  <npix>	Number of objects to draw.
  <radius>	Farthest point in the object from its center.
		(Objects are usually symmetrical.)
  <kind>	A PostScript string usually naming a procedure.  The
		x, y, and radius will be printed (put on the PostScript
		stack) just before calling the procedure in <kind>.

*/

void mx_psdraw_objects (int ic, const XPoint pix[], int npix,
			int radius, const char *kind)

{
  int		i;

  if (f_PostScript) {
    fprintf(f_PostScript, "GS ");

    if (ic == L_XORColor)
      fprintf(f_PostScript, RubberClipPath);
    else
      fprintf(f_PostScript, GraphicsClipPath);
    ps_setGLineAttr(ic);

    if (kind[0] == '(') fprintf(f_PostScript,
      "1 setlinewidth /Courier findfont %d scalefont setfont\n", PSfontsize);

    for (i = 0; i < npix; i++)
      fprintf(f_PostScript, "%d %d %d %s\n", pix[i].x, pix[i].y, radius, kind);

    fprintf(f_PostScript, "GR\n");
  }

}



/* mx_psdraw_vector

Sets up a PostScript path (connected vector) and then performs a given
operation on it.

Arguments:
  <ic>		Index of the graphics context (attributes of the lines).
		Clipping is done according to the <ic>, at the boundaries
		set by mx_psclip().
  <pix>		x, y locations of each point to connect.  Points will be
		connected in the order they are present in <pix>.
  <npix>	Number of points in <pix>.
  <kind>	A PostScript string usually naming a procedure, indicating
		what should be done with the path just described.  It is
		not printed (invoked) until the path has been written.
		For example, to connect lines, <kind> might be
		"(S) stroke"; to draw a filled polygon just described,
		"(CP) closepath fill".

*/

void mx_psdraw_vector (int ic, const XPoint pix[], int npix, const char *kind)

{
  int		i;

  if (f_PostScript) {
    fprintf(f_PostScript, "GS ");

    if (ic == L_XORColor)
      fprintf(f_PostScript, RubberClipPath);
    else
      fprintf(f_PostScript, GraphicsClipPath);

    fprintf(f_PostScript, "NP ");

    ps_setGLineAttr(ic);

    fprintf(f_PostScript, "%d %d M\n", pix[0].x, pix[0].y);
    for (i = 1; i < npix; i++) {
      fprintf(f_PostScript, "%d %d L\n", pix[i].x, pix[i].y);
      if (i % VectorSize == 0)
	fprintf(f_PostScript, "%s %d %d M\n", kind, pix[i].x, pix[i].y);
    }
    fprintf(f_PostScript, "%s GR\n", kind);
  }
}




/* mx_psdraw_arc

Calling sequence is identical to mx_draw_arc or mx_fill_arc with the
addition of a <filled> argument which is TRUE if the arc is to be filled.
There is no <kind> argument like the others because the drawing of a
non-filled arc is a little different than a filled arc.

A new path is always begun, so that a "closepath" in the <kind> argument
will make a "pie wedge" path.

Notes:
  a1 (starting angle) and a2 (arc size) are specified in 64ths of a degree.
  PostScript requires degree units.

  PostScript requires absolute starting and ending degree measurements;
  not relative.

  PostScript arcs always have the same dimensions, so they must be drawn
  first and then scaled according to the ratio of w/h.

*/

void mx_psdraw_arc (int ic, int x, int y, int w, int h,
		    int a1, int a2, bool filled)

{
  double	xscale;
  double	rx, ry;
  double	da1, da2;

  if (f_PostScript) {
    fprintf(f_PostScript, "GS ");
    ps_setGLineAttr(ic);
    xscale = (double)w / (double)h;
    rx = x + w/2.0;
    ry = y + h/2.0;
    fprintf(f_PostScript,
		"%f %f M %f %f translate ",
		rx, ry, rx, ry);
    fprintf(f_PostScript, "%f -1 scale ", xscale);
    da1 = a1 / 64.0;
    da2 = da1 + a2 / 64.0;
    if (filled)  fprintf(f_PostScript, "NP ");
    fprintf(f_PostScript, "0 0 M ");
    if (!filled) fprintf(f_PostScript, "NP ");
    fprintf(f_PostScript, "0 0 %f %f %f arc ", h/2.0, da1, da2);
    if (filled)
      fprintf(f_PostScript, "CP fill ");
    else
      fprintf(f_PostScript, "S ");
    fprintf(f_PostScript, "GR\n");
  }
}



/* mx_psdraw_rect

Describes a rectangle and performs a procedure.

Arguments:
  <ic>		Index of the graphics context (attributes of the lines).
		Clipping is done according to the <ic>, at the boundaries
		set by mx_psclip().
  <x>, <y>	Absolute coordinates of the upper left corner.
  <w>, <h>	The width and height.
  <kind>	PostScript command to invoke when path is complete.
		The path will be a closed rectangle; <kind> indicates
		what procedures should be done before rendering it
		(e.g. "(S) stroke", "fill").

*/

void mx_psdraw_rect (int ic, int x, int y, int w, int h, const char *kind)

{
  if (f_PostScript) {
    fprintf(f_PostScript, "GS NP ");
    ps_setGLineAttr(ic);
    fprintf(f_PostScript, "%d %d %d %d R %s GR\n", w, h, x, y, kind);
  }
}




/* mx_pstextline

Draws a "text" line; that is, draws a line using the attributes of the
text context, from the first pair of coordinates to the second.

Note that ps_setGLineAttr is NOT called.  The only attribute extracted
from the text context is whether it is dashed or not.  It will always
be drawn in the foreground color.

*/

void mx_pstextline (short x1, short y1, short x2, short y2)

{
  XGCValues	values;

  if (f_PostScript) {
    if (XGetGCValues(G.D, G.gct, GCLineStyle, &values) &&
	values.line_style != LineSolid)
      fprintf(f_PostScript, "TDy ");
    else
      fprintf(f_PostScript, "Dn ");
    fprintf(f_PostScript, "%d %d %d %d tl\n", x1, y1, x2, y2);
  }
}



/* mx_pstext

  Writes the text given in		<s>
  with a length of			<ls>,
  with the attributes in		<ic>,
  in the size specified by PSfontsize, 
  at the location specified by 		<x>, <y>

  PSfontsize is a global variable, set in mx_setfont (glibc1.c).

*/

void mx_pstext (int ic, int x, int y, const char *s, int ls, bool fill)

{
  if (f_PostScript) {
    if (fill) {			/* If fill, draw bg box */
      int h = Mx->text_h - 1;	/* height of box */
      int descent = h / 4;	/* descent of box below y */
      XGCValues values;
      fprintf(f_PostScript, "GS NP Dn 1 SL ");
      if (ic==L_BGColor)
	fprintf(f_PostScript, "fg\n");
      else if (ic==L_FGColor || G.BW ||
	!XGetGCValues(G.D, G.gcg, GCBackground, &values))
	  fprintf(f_PostScript, "bg\n");
      else {
	XColor xc;
	xc.pixel = values.background;
	XQueryColor(G.D, G.C_id, &xc);
	fprintf(f_PostScript, "%f %f %f SC\n", (float)xc.red/65535.0, 
		(float)xc.green/65535.0, (float)xc.blue/65535.0);
      }
      fprintf(f_PostScript, "%d %d %d %d R fill GR\n",
	Mx->text_w*ls, h, x, y-h+descent);
    }

    fprintf(f_PostScript, "GS ");
    ps_setGLineAttr(ic);
/*    fprintf(f_PostScript, "%d %d M 1 -1 scale ", x, y); */
    fprintf(f_PostScript, "(%*.*s) %d %d %d dt\n",
			ls, ls, s, PSfontsize, x, y);
    fprintf(f_PostScript, "GR\n");
  }
}



/* addDocFont

Adds the given font to the unique list of fonts used in the document,
if it is a new one.

*/

static void addDocFont (const char *s)

{
  NODE	*np;

  for (np = DocFonts; np; np = np->next)
    if (strcmp(np->name, s) == 0) return;
  np = (NODE *)malloc(sizeof(NODE));
  np->name = strdup((char *)s);
  np->next = DocFonts;
  DocFonts = np;
}



/* mx_pstdraw

  Writes the "annotation" text in the structure		<tn>
  at the position specified by				<xpos>, <ypos>
  with the area behind it
  blanked out in the background color if		<filled>
  is TRUE.  (Otherwise, the "white space" is
  transparent to what is already drawn.)

*/

void mx_pstdraw (const XFANNOTATION *tn, short xpos, short ypos, bool filled)

{
  char		colorname[32], cfont[1024], PSfont[80], fontsize[10];

  XColor	xc;

  if (f_PostScript) {

/* In PostScript, if the background is filled, we have to draw it in
   ourselves.  Always fill with the background (0). */

    strf2c(tn->color, sizeof(tn->color),
	   colorname, sizeof(colorname),
	   F2C_TRIM);
    fprintf(f_PostScript, "GS 1 SL ");

    if (tn->pixel < 0)
      xc.pixel = Mx->white;
    else
      xc.pixel = tn->pixel;

    XQueryColor(G.D, G.C_id, &xc);
    fprintf(f_PostScript, "%f %f %f SC\n",
		(float)xc.red/65535.0, 
		(float)xc.green/65535.0, 
		(float)xc.blue/65535.0);

    if (filled)
      mx_psdraw_rect(0, xpos, ypos - tn->a, tn->w, tn->h, "fill");
    fprintf(f_PostScript, "%d %d M 1 -1 scale ", xpos, ypos);

    strf2c(tn->font, sizeof(tn->font), cfont, sizeof(cfont), F2C_TRIM);
    x2psfontconv(cfont, PSfont, fontsize);
    addDocFont(PSfont);
    fprintf(f_PostScript, "/%s findfont ", PSfont);
    if (fontsize[0] == '\0')
      fprintf(f_PostScript, "12 scalefont ");
    else
      fprintf(f_PostScript, "%s scalefont ", fontsize);

    fprintf(f_PostScript, "setfont\n(%*.*s) show GR\n",
			tn->size, tn->size, tn->text);
  }

}



/* mx_psclose

   Writes a trailer and closes the file.
   Passes back the full name of the PostScript file that we have been
   writing.

*/

void mx_psclose ()

{
  NODE	*np, *axe;

  if (f_PostScript) {
    W("\nend\t\t\t% pops our dictionary off the stack");
    W("\nXMidasPicture restore\t% release all VM associated w/this page");
    W("\nshowpage");
    W("\n%%Trailer\n");
    fprintf(f_PostScript, "%s: Courier", "%%DocumentFonts");
    np = DocFonts;
    while (np) {
      fprintf(f_PostScript, " %s", np->name);
      free(np->name);
      axe = np;
      np = np->next;
      free(axe);
    }
    DocFonts = NULL;			/* empty for next time */
    fprintf(f_PostScript, "\n");
    fclose(f_PostScript);
    f_PostScript = NULL;
  }
}



/* findHCenv

Finds the value of a hardcopy environment variable.  These variables
begin with XM_HC.  strmenu is a string vector which, if non-null, contains
a list of all valid string values for a menu.

*/

static double	findHCenv (const char *s, double defaultVal,
			   const char *strmenu[])

{
  char		symbol[FILENAME_LEN], symval[FILENAME_LEN];
  char		csym[FILENAME_LEN];
  int		i, lsymbol, lsymval;
  double	dval;

/* Remove spaces from the given attribute name, uppercase it, and append
   it to XM_HC to get the environment variable name. */

  strcpy(symbol, "XM_HC");
  lsymbol = strlen(symbol);
  for (i = 0; s[i] != '\0'; i++)
    if (!isspace(s[i]))
      symbol[lsymbol++] = toupper(s[i]);
  symbol[lsymbol] = '\0';

  lsymval = m_import(symbol, symval, lsymbol, sizeof(symval) - 1);
  if (lsymval <= 0)
    return(defaultVal);
  else if (m_a2d(symval, &dval, lsymval) <= 0) {
    if (strmenu) {
      strf2c(symval, lsymval, csym, sizeof(csym), F2C_TRIM);
      for (i = 0; strmenu[i]; i++)
	if (strcasecmp(strmenu[i], csym) == 0)
	  return((double)(i + 1));
    }
    return(defaultVal);
  }
  else
    return(dval);
}



/* menuitem

Used to build a main menu with current settings listed.  Those with a
submenu vector will have an ellipsis following to indicate that a change
requires a menu to come up; those with double precision values will have
those values written into the menu item itself.

If the "chosen" variable is TRUE, it means that the user has selected this
item in order to change it.  Bring up a dprompt or menu as appropriate and
build the menu name based on that.

The given default value is an IN/OUT parameter.  It is the current default
and it is also where the new user-chosen information is written, if the
item has been "chosen".

*/

static Bool menuitem (char *menustr, Bool chosen, const char *s,
		      double *defaultVal, const char *submenu[])

{
  char		userstr[1024];
  int		i, ls;
  double	dval;
  Bool		changed = FALSE;

  dval = *defaultVal;
  if (chosen) {
    if (submenu) {
      sprintf(userstr, "%s;", s);
      for (i = 0; submenu[i]; i++) {
	strcat(userstr, submenu[i]);
	strcat(userstr, ",");
      }
      ls = strlen(userstr);
      userstr[--ls] = '\0';
      dval = mx_menu((int)dval, userstr, ls);
    }
    else {
      sprintf(userstr, "%s: ", s);
      ls = strlen(userstr);
      mx_dprompt(userstr, &dval, ls);
    }
  *defaultVal = dval;
  changed = TRUE;
  }
  if (submenu)
    sprintf(userstr, "%s (%s),", s, submenu[(int)dval - 1]);
  else
    sprintf(userstr, "%s = %6.2f,", s, dval);
  strcat(menustr, userstr);
  return(changed);
}


/* ps_determineLayout

Reads all of the XM_HC* environment symbols and determines the components
of the given LAYOUT structure.

It determines the drawing area that the user has asked for and then fits
the plot to that region.

*/

static double figure_offset (double anchor, double margin,
			     double papersize, double draw_size)

{

  switch ((int)anchor) {
    case 1:				/* left/top */
      return (margin);
      break;
    case 2:				/* center */
      return ((papersize - draw_size) / 2.0 + margin);
      break;
    case 3:				/* right/bottom */
      return ((papersize - draw_size) + margin);
      break;
  }
  return(margin);
}


static void	ps_determineLayout (LAYOUT *L)

{
  static	const char *bwVec[] =
				{ "White on Black", "Black on White", NULL };
  static	const char *orientVec[] = { "Landscape", "Portrait", NULL };
  static	const char *hanchorVec[] = { "Left", "Center", "Right", NULL };
  static	const char *vanchorVec[] = { "Top", "Center", "Bottom", NULL };

  double	papershort, paperlong;
  double	bwmode, orientation;
  double	ptop, pbottom, pleft, pright;
  double	hanchor, vanchor;
  double	margin, hmargin, wmargin;
  double	topmargin, bottommargin;
  double	leftmargin, rightmargin;
  double	draw_width, draw_height;
  double	paperheight, paperwidth;
  double	availheight, availwidth;
  double	xscale, yscale, major_scale;
  double	xinches, yinches;

  int		item, count, lmenustr;
  char		menustr[4096];

#define SWAPSIDES { \
  if (orientation == 2.0) { \
    paperheight	= paperlong; \
    paperwidth	= papershort; \
    topmargin	= ptop; \
    bottommargin = pbottom; \
    leftmargin  = pleft; \
    rightmargin	= pright; \
    L->rotation	= 0.0; \
  } \
  else { \
    paperheight	= papershort; \
    paperwidth	= paperlong; \
    topmargin	= pleft; \
    bottommargin = pright; \
    leftmargin	= pbottom; \
    rightmargin	= ptop; \
    L->rotation	= 90.0; \
  } \
}

/***************************** Determine Setup ***************************/

/*
  Algorithm:
    1.  Determine default settings using findHCenv.
    2.	If interactive, construct a menu.
    	If not, bash through with findHCenv settings only.
    3.	Keep looping through as long as the menu selection is not 0.

*/

  VectorSize	= findHCenv("Vector Size",	128.0,	NULL);

  ptop		= findHCenv("PMTop",		0.5,	NULL);
  pbottom	= findHCenv("PMBottom",		0.5,	NULL);
  pleft		= findHCenv("PMLeft",		0.5,	NULL);
  pright	= findHCenv("PMRight",		0.5,	NULL);

  papershort	= findHCenv("Paper Short",	8.5,	NULL);
  paperlong	= findHCenv("Paper Long",	11.0,	NULL);

  bwmode	= NormalFG ? 1.0 : 2.0;
  orientation	= findHCenv("Orientation",	1.0,	orientVec);
  SWAPSIDES

  major_scale	= findHCenv("Scale",		1.0,	NULL);

  hanchor	= findHCenv("Horiz Anchor",	2.0,	hanchorVec);
  vanchor	= findHCenv("Vert Anchor",	2.0,	vanchorVec);

  margin	= findHCenv("Margin",		0.5,	NULL);
  hmargin	= findHCenv("Height Margin",	margin,	NULL);
  wmargin	= findHCenv("Width Margin",	margin,	NULL);

  topmargin	= findHCenv("Top Margin",
			max(topmargin, hmargin), NULL);
  bottommargin	= findHCenv("Bottom Margin",
			max(bottommargin, hmargin), NULL);
  leftmargin	= findHCenv("Left Margin",
			max(leftmargin, wmargin), NULL);
  rightmargin	= findHCenv("Right Margin",
			max(rightmargin, wmargin), NULL);

  L->mindotsize	= findHCenv("Min Dot Size",	0.001,	NULL);

/* TRICK BELOW:
   In order to be able to add user-changeable items in the future in a clean
   way, without having to dink with hard-coded menu item numbers, the macro
   CHOSEN is provided below which will evaluate to TRUE if the given item
   was chosen off of the previous menu.  This trick works because the menu
   is rebuilt every time; as the menu is being built, if one of the items
   has been chosen by the user, either a prompt or a submenu is brought up
   AT THAT MOMENT to help them choose the change, and their change becomes
   a part of the next menu.
*/

#define	CHOSEN	(item == ++count)
  item = 0;
  if (Interactive) {
    do {
      count = 0;
      strcpy(menustr, "SETTINGS;");
      if (menuitem(menustr, CHOSEN, "FG/BG Colors", &bwmode, bwVec))
	mx_setbgfg(L_FGColor, L_BGColor, -Mx->xi);
      if (menuitem(menustr, CHOSEN, "Orientation", &orientation, orientVec))
	SWAPSIDES
      (void)menuitem(menustr, CHOSEN, "Paper Width", &paperwidth, NULL);
      (void)menuitem(menustr, CHOSEN, "Paper Height", &paperheight, NULL);
      (void)menuitem(menustr, CHOSEN, "Scale", &major_scale, NULL);
      (void)menuitem(menustr, CHOSEN, "Horiz Anchor", &hanchor,
	hanchorVec);
      (void)menuitem(menustr, CHOSEN, "Vert Anchor", &vanchor, vanchorVec);
      (void)menuitem(menustr, CHOSEN, "Top Margin", &topmargin, NULL);
      (void)menuitem(menustr, CHOSEN, "Bottom Margin", &bottommargin, NULL);
      (void)menuitem(menustr, CHOSEN, "Left Margin", &leftmargin, NULL);
      (void)menuitem(menustr, CHOSEN, "Right Margin", &rightmargin, NULL);
/* Trim off the final comma */
      lmenustr = strlen(menustr);
      menustr[--lmenustr] = '\0';
    }
    while (item = mx_menu(0, menustr, lmenustr));
  }	/* if Interactive */

/************************* Begin Layout Computations *******************/

  availwidth	= paperwidth -  (rightmargin + leftmargin);
  availheight	= paperheight - (topmargin   + bottommargin);

/* We now have the size of the drawable box, in inches.  Further
   specify this box with more environment symbols. */

  draw_width	= availwidth  * major_scale;
  draw_height	= availheight * major_scale;

  xscale   = (draw_width  * 72) / Mx->width;
  yscale   = (draw_height * 72) / Mx->height;
  L->scaling  = min(xscale, yscale);		/* preserve aspect ratio */

  xinches  = (Mx->width *  L->scaling) / 72.0;
  yinches  = (Mx->height * L->scaling) / 72.0;

  L->xoffset 	= figure_offset(hanchor, leftmargin, availwidth, xinches);
  L->yoffset 	= figure_offset(vanchor, topmargin,  availheight, yinches);

/*
   If we're in portrait orientation, then the translation must go all
   the way up the page so that the drawing will work downward from there.
   Otherwise, make the offset negative.
   
   The assignment of bounding box coordinates also depends on orientation,
   since they must be expressed in default user PostScript coordinates
   (72 pts / inch, in portrait mode with x, y at the lower left and both
   increasing toward the edge of the page).
*/

  if (L->rotation == 0.0) {
    L->yoffset = paperheight - L->yoffset;
    L->bb.llx = 72 * L->xoffset;
    L->bb.lly = 72 * (L->yoffset - yinches);
    L->bb.urx = 72 * (L->xoffset + xinches);
    L->bb.ury = 72 * L->yoffset;
  }
  else {
    L->bb.llx = 72 * L->yoffset;
    L->bb.lly = 72 * L->xoffset;
    L->bb.urx = 72 * (L->yoffset + yinches);
    L->bb.ury = 72 * (L->xoffset + xinches);
    L->yoffset = L->yoffset * -1.0;
  }

}
