/* This file is part of `mogul'
 * Copyright (c) 1995 David Deaven
 *
 * In the following, "you" and "You" refer to any entity other than 
 * the Microsoft corporation and its subsidiaries.  All rights are
 * reserved by the author in connection with Microsoft and its
 * subsidiaries.  The Microsoft Network is explicity denied the right
 * to copy, reproduce, or transmit this software.
 *
 *   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.
 *
 * $Author: deaven $
 * $Id: dev_ps.c,v 2.1 1996/07/11 18:41:37 deaven Exp deaven $
 */

/* PostScript driver for mogul.
 * (c) 1995 David Deaven
 */

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <math.h>
#include "dmd/memory.h"
#include "mogul.h"

#define PIXMAX 9999	/* all points scaled to 0-PIXMAX (pixels) */
#define RESOLUTION 5.0	/* curve-drawing error, pixels */

static char *init[] = {
  "%%BeginProcSet: mogul",
  "% This is mogul 2.0",
  "% Author: David Deaven <deaven@ishmael.ameslab.gov>",
  "/mogulDict 30 dict def mogulDict begin",
  "/map {",
  " range div psize mul exch",
  " range div psize mul exch",
  "} def",
  "/mm {2.8346457 mul} def",
  "/K {3 copy /blue exch def /green exch def /red exch def setrgbcolor} def",
  "/L {map lineto} def /M {map moveto} def",
  "/S {lsize setlinewidth stroke} def",
  "/C {closepath fill} def",
  "/A {newpath range div psize mul 3 1 roll map 3 2 roll 0 360 arc} def",
  "/F {",
  "  /r exch def /y exch def /x exch def",
  "  currenthsbcolor pop /s exch def /h exch def",
  "  0 0.1 10.01 {/i exch def",
#if 0
  "    i 0.060 mul 0.40 add /c exch def",
  "    c red mul c green mul c blue mul setrgbcolor newpath",
#else
  "    h s 1 i 0.09 mul sub mul i 0.05 mul 0.5 add sethsbcolor newpath",
#endif
  "    x i 0.022 mul r mul sub ",
  "    y i 0.035 mul r mul add map",
  "    10 i sub 0.06 mul 0.4 add r mul range div psize mul",
  "    0 360 arc closepath fill",
  "  } for",
  "  red green blue setrgbcolor",
  "} def",
  "/T {",
  "  psize mul /fontsize exch def"
  "  /Helvetica findfont fontsize scalefont setfont",
  "  show",
  "} def",
  "/psize 200 mm def  /lsize 0.1 mm def",
  "end",
  "%%EndProcSet",
  NULL
};

struct ps_instance {
  FILE *fps;		/* PS file */
  double translate_pt[2]; /* multiple plots per page */
  int page;		/* PS page number */
  int nplot;		/* PS plot (on a page) number */
  int path;		/* segments in path */
  int xc, yc;		/* current point */
  int red, green, blue; /* current color */
  int encapsulated;	/* flag for EPS file */
  int plots_per_page;	/* how many separate plots per A4 page */
  double psize;		/* plot size in mm (largest direction) */
  double scale;		/* conversion to pixels */
  double vdist;
  int width, height;    /* width in "pixels" */
};

#define FLUSH_PATH(MDI) \
  if((MDI)->path) { \
    if(fprintf((MDI)->fps,"S\n") < 0) return -1; \
  } \
  (MDI)->path = 0;

/* Set the current color, so all subsequent operations will make
 * pixels in that color.  The specification is in RGB space,
 * with each component ranging from 0 to CMAX.
 */
static int PPcolor(Color c, MogulDeviceInstance mdi)
{
	struct ps_instance *W = mdi;
	if( c.r != W->red || c.g != W->green || c.b != W->blue )
	{
	  FLUSH_PATH(W);
	  W->red = c.r;
	  W->green = c.g;
	  W->blue = c.b;
	  if(fprintf(W->fps, "%.3g %.3g %.3g K\n", 
	    (double) c.r / (double) RGB_CMAX,
	    (double) c.g / (double) RGB_CMAX,
	    (double) c.b / (double) RGB_CMAX) < 0)
	  {
	    MogulErrno = MoErrSystem;
	    return -1;
	  }
	}

	return 0;
}

/* Add device-dependent "shading" to the circle of radius r in the
 * {u,v} plane.  This should produce a three-dimensional effect.
 */
static int PPshade(double x[3], double r, double u[3], double v[3], MogulDeviceInstance mdi)
{
	int i;
	double pc[3], pr[3];
	struct ps_instance *W = mdi;

	FLUSH_PATH(W);
	for(i=0; i<3; ++i) pc[i] = x[i] + r*u[i];
	perscl(pc, pr, W->vdist);
	perscl(x, pc, W->vdist);
	r = (pc[0] - pr[0]) * (pc[0] - pr[0]) +
	    (pc[1] - pr[1]) * (pc[1] - pr[1]) +
	    (pc[2] - pr[2]) * (pc[2] - pr[2]);
	r = W->scale * sqrt(r);
	pc[0] = pc[0] * W->scale + 0.5 * W->width;
	pc[1] = pc[1] * W->scale + 0.5 * W->height;
	if(fprintf(W->fps, "%d %d %d F\n", (int) pc[0], (int) pc[1], (int) r) < 0)
	{
	  MogulErrno = MoErrSystem;
	  return -1;
	}
	return 0;
}

/* Move without producing pixels to the specified point.
 */
static int PPmove(double x[3], MogulDeviceInstance mdi)
{
	double px[3];
	struct ps_instance *W = mdi;

	FLUSH_PATH(W);
	perscl(x, px, W->vdist);
	px[0] = px[0] * W->scale + 0.5 * W->width;
	px[1] = px[1] * W->scale + 0.5 * W->height;
	W->xc = px[0];
	W->yc = px[1];
	if(fprintf(W->fps, "%d %d M\n", W->xc, W->yc) < 0)
	{
	  MogulErrno = MoErrSystem;
	  return -1;
	}
	return 0;
}

/* Draw a line of pixels of the current color from the current
 * point to x, leaving x as the new current point.
 */
static int PPline(double x[3], MogulDeviceInstance mdi)
{
	double px[3];
	struct ps_instance *W = mdi;

	perscl(x, px, W->vdist);
	px[0] = px[0] * W->scale + 0.5 * W->width;
	px[1] = px[1] * W->scale + 0.5 * W->height;
	if(W->xc != (int) px[0] || W->yc != (int) px[1]) {
	  W->xc = px[0];
	  W->yc = px[1];
	  if(fprintf(W->fps, "%d %d L\n", W->xc, W->yc) < 0)
	  {
	    MogulErrno = MoErrSystem;
	    return -1;
	  }
	  ++(W->path);
	}
	return 0;
}

/* Draw an arc segment of the current color in the {u,v} plane.
 * If there's no fast way to do this in the device language,
 * use the routine Parc_from_lines() and the devices's Pline()
 * routine.
 */
static int PParc(double x[3], double r, double u[3], double v[3],
	double t0, double t1, MogulDeviceInstance mdi)
{
  struct ps_instance *W = mdi;
  return Parc_from_lines(x, r, u, v, t0, t1, RESOLUTION / W->scale, PPline, mdi);
}

/* Draw a complete circle.
 */
static int PPcircle(double x[3], double r, MogulDeviceInstance mdi)
{
	struct ps_instance *W = mdi;
	double px[3];
	r *= W->scale * perscl(x, px, W->vdist);
	px[0] = px[0] * W->scale + 0.5 * W->width;
	px[1] = px[1] * W->scale + 0.5 * W->height;
	if(fprintf(W->fps, "%d %d %.2f A\n", (int) px[0], (int) px[1], r) < 0)
	{
	  MogulErrno = MoErrSystem;
	  return -1;
	}
	++(W->path);
	return 0;
}

/* Draw the text string in the current color, starting
 * at the current position.  The letters should be fracheight
 * of the frame high.  The current point should be advanced.
 */
static int PPtext(char *s, double fracheight, MogulDeviceInstance mdi)
{
	struct ps_instance *W = mdi;
	if(fprintf(W->fps, "(%s) %g T\n", s, fracheight) < 0)
	{
	  MogulErrno = MoErrSystem;
	  return -1;
	}
	return 0;
}

/* Close a polygon constructed by calling Pmove(), Pline() and/or
 * Parc(), and fill it with pixels of the current color.
 */
static int PPclose(MogulDeviceInstance mdi)
{
	struct ps_instance *W = mdi;
	if(fprintf(W->fps, "C\n") < 0)
	{
	  MogulErrno = MoErrSystem;
	  return -1;
	}
	W->path = 0;
	return 0;
}

static char *followsKey(char *s, char *key)
{
	char *r;
	if(s && key && (r = strstr(s, key))) return (r + strlen(key));
	return NULL;
}

/* For PostScript, the device information keys recognized are
 * 
 * file=###		output filename (default stdout)
 * size=###x###		size of plot in mm (default 200x200)
 # plots_per_page=###	number of plots per page (default 1)
 */
static MogulDeviceInstance PPinit(char *devinfo, int encapsulated)
{
	int i;
	char *p;
	double dx, dy;
	struct ps_instance *W = NewPointer(W);

	W->fps = stdout;
	W->page = 0;
	W->nplot = 0;
	W->plots_per_page = 1;
	W->psize = 200.0;
	W->width = W->height = PIXMAX;
	W->vdist = 0.0;

	if(devinfo) {

	  if((p = followsKey(devinfo, "file=")))
	  {
	    char *tfn, *fn = mystrdup(p);
	    for(tfn = fn; *tfn != '\0'; tfn++) if(isspace(*tfn)) *tfn = '\0';

	    W->fps = fopen_with_default(fn, "w", stderr);
	    if(W->fps == stderr) {
	      fprintf(stderr, "ps init: cannot open `%s'\n", fn);
	      return NULL;
	    }

	    myfree(fn);
	  }

	  if((p = followsKey(devinfo, "size=")) && sscanf(p, "%lfx%lf", &dx, &dy) == 2)
	  {
	    if(dx > 0.0 && dy > 0.0) {
	      if(dx > dy) {
		W->psize = dx;
		W->width = PIXMAX;
		W->height = dy / dx * PIXMAX;
	      }
	      else {
		W->psize = dy;
		W->width = PIXMAX;
		W->height = dx / dy * PIXMAX;
	      }
	    }
	  }

	  if((p = followsKey(devinfo, "plots_per_page=")) && sscanf(p, "%d", &i) == 1)
	  {
	    if(i > 0) W->plots_per_page = i;
	  }

	}

	if((W->encapsulated = encapsulated))
	  fprintf(W->fps, "%%!PS-Adobe-3.0 EPSF-3.0\n");
	else
	  fprintf(W->fps, "%%!PS-Adobe-3.0\n");

	fprintf(W->fps,
	  "%%%%Creator: mogul 2.0 Copyright (c) 1994-1996 David Deaven\n");
	fprintf(W->fps,
	  "%%%%Title: %s\n", devinfo ? devinfo : "mogul plot");
	fprintf(W->fps, "%%%%Pages: (atend)\n");
	fprintf(W->fps, "%%%%BoundingBox: 0 0 %d %d\n",
	  (int) W->width * W->psize * (72.0 / (25.4 * PIXMAX)) + 1,
	  (int) W->height * W->psize * (72.0 / (25.4 * PIXMAX)) + 1);
	fprintf(W->fps, "%%%%EndComments\n");
	for(i=0; init[i] != NULL; ++i) fprintf(W->fps, "%s\n", init[i]);
	fprintf(W->fps, "%%%%EndProlog\n");
	W->page = 0;
	W->nplot = 0;

	return W;
}

static MogulDeviceInstance PPinit_ps(char *devinfo)
{
	return PPinit(devinfo, 0);
}

static MogulDeviceInstance PPinit_eps(char *devinfo)
{
	return PPinit(devinfo, 1);
}

static int is_white(Color c)
{
	if(c.r == RGB_CMAX && c.g == RGB_CMAX && c.b == RGB_CMAX)
	  return 1;
	return 0;
}

static int PPstart(MogulPlot p, MogulDeviceInstance mdi)
{
	struct ps_instance *W = mdi;

	{ double sx = W->width / p->attr.width, sy = W->height / p->attr.height;
	  W->scale = sx < sy ? sx : sy;
	}
#if defined(DEBUG)
	printf("dev_ps: screen size %dx%d camera %gx%g\n", 
	  W->width, W->height, p->attr.width, p->attr.height);
#endif

	W->vdist = p->attr.vdist;

	if(W->nplot == 0) {
	  ++(W->page);
	  if(fprintf(W->fps, "%%%%Page: %d %d\n", W->page, W->page) < 0)
	  {
	    MogulErrno = MoErrSystem;
	    return -1;
	  }
	}
	  
	W->translate_pt[0] = 0;
	W->translate_pt[1] = W->nplot * W->psize * 72.0 / 25.4;

	if(W->nplot > 0) {
	  if(fprintf(W->fps, "%.2f %.2f translate\n",
	    W->translate_pt[0], W->translate_pt[1]) < 0)
	  {
	    MogulErrno = MoErrSystem;
	    return -1;
	  }
	}

	if(fprintf(W->fps, "%% page %d plot %d follows\n", W->page, W->nplot + 1) < 0 ||
	   fprintf(W->fps, "mogulDict begin /range %d def 0 0 0 K\n", PIXMAX) < 0 ||
	   fprintf(W->fps, "/psize %f mm def\n", W->psize) < 0) 
	{
	  MogulErrno = MoErrSystem;
	  return -1;
	}
	W->red = W->green = W->blue = 0;
	W->xc = W->yc = 0;
	W->path = 0;

	/* Make a background only if the background color is not white.
	 */
	if(!is_white(p->attr.background)) {
	  if(PPcolor(p->attr.background, mdi)) return -1;
	  if(fprintf(W->fps,
	    "newpath 0 0 M %d 0 L %d %d L 0 %d L closepath fill\n",
	    W->width, W->width, W->height, W->height) < 0)
	  {
	    MogulErrno = MoErrSystem;
	    return -1;
	  }
	}

	return 0;
}

static int flush_page(struct ps_instance *W)
{
	if(!(W->encapsulated)) {
	  if(fprintf(W->fps, "showpage\n") < 0)
	  {
	    MogulErrno = MoErrSystem;
	    return -1;
	  }
	}
	W->nplot = 0;
	return 0;
}

static int PPend(MogulPlot p, MogulDeviceInstance mdi)
{
	struct ps_instance *W = mdi;

	FLUSH_PATH(W);

	if(fprintf(W->fps, "end\n") < 0)
	{
	  MogulErrno = MoErrSystem;
	  return -1;
	}
	if(W->nplot > 0) {
	  if(fprintf(W->fps, "-%.2f -%.2f translate\n", W->translate_pt[0], W->translate_pt[1]) < 0)
	  {
	    MogulErrno = MoErrSystem;
	    return -1;
	  }
	}

	if(++(W->nplot) >= W->plots_per_page) {
	  if(flush_page(W)) return -1;
	}

	return 0;
}

static int PPstop(MogulDeviceInstance mdi)
{
	struct ps_instance *W = mdi;

	if(W->fps)
	{
	  if(W->nplot > 0) {
	    if(flush_page(W)) return -1;
	  }
	  if(!(W->encapsulated)) {
	    if(fprintf(W->fps, "%%%%Pages: %d\n%%%%EOF\n", W->page) < 0)
	    {
	      MogulErrno = MoErrSystem;
	      return -1;
	    }
	  }
	  if(fclose(W->fps) < 0)
	  {
	    MogulErrno = MoErrSystem;
	    return -1;
	  }
	}
	W->fps = NULL;

	return 0;
}

static struct MogulDeviceRec dev = {
  "ps",
  "PostScript",
  PPcolor,
  PParc,
  PPcircle,
  PPshade,
  PPmove,
  PPline,
  PPclose,
  PPtext,
  PPinit_ps,
  PPstart,
  PPend,
  PPstop,
};

static struct MogulDeviceRec dev2 = {
  "eps",
  "Encapsulated PostScript",
  PPcolor,
  PParc,
  PPcircle,
  PPshade,
  PPmove,
  PPline,
  PPclose,
  PPtext,
  PPinit_eps,
  PPstart,
  PPend,
  PPstop,
};

MogulDevice dev_ps = &dev;
MogulDevice dev_eps = &dev2;
