/*******************************************************************************
*                                                                              *
*                                   Viewmol                                    *
*                                                                              *
*                           P O S T S C R I P T . C                            *
*                                                                              *
*                 Copyright (c) Joerg-R. Hill, October 2003                    *
*                                                                              *
********************************************************************************
*
* $Id: postscript.c,v 1.6 2003/11/07 11:08:28 jrh Exp $
* $Log: postscript.c,v $
* Revision 1.6  2003/11/07 11:08:28  jrh
* Release 2.4
*
* Revision 1.5  2000/12/10 15:13:44  jrh
* Release 2.3
*
* Revision 1.4  1999/05/24 01:26:56  jrh
* Release 2.2.1
*
* Revision 1.3  1999/02/07 21:54:40  jrh
* Release 2.2
*
* Revision 1.2  1998/01/26 00:49:03  jrh
* Release 2.1
*
* Revision 1.1  1996/12/10  18:43:12  jrh
* Initial revision
*
*/
#include<pwd.h>
#include<stdio.h>
#include<time.h>
#include<unistd.h>
#include<sys/types.h>
#include<X11/Xlib.h>
#include<Xm/DrawingA.h>
#include<GL/gl.h>
#include<GL/glu.h>
#include "viewmol.h"
#include "dialog.h"

#define TOPSUNIT 72./25.4
#define EPS_GOURAUD_THRESHOLD 0.1 /* smaller values result in smoother shading */

extern struct WINDOW windows[];
extern struct MOLECULE *molecules;
extern int rgbMode;
extern char *textPointer;
extern Widget topShell;
extern int primitive, iwavef;

extern void getRGBColor(Widget, Pixel, float *, float *, float *);
extern char *getStringResource(Widget, char *);

static GLfloat pointSize;
static GLfloat lastred=(-1.), lastgreen=(-1.), lastblue=(-1.);
static int cyrillic=FALSE, bitmap=FALSE;

FILE *PostscriptInit(char *, double, double, double, int, int);
void writeVectorHeader(FILE *, int, int, GLfloat, GLfloat, double, double, GLfloat *, GLfloat *);
void writeBitmapHeader(FILE *, int, int, double, double, double);
void PostscriptClose(FILE *);
void PostscriptSetRGBColor(FILE *, GLfloat, GLfloat, GLfloat);
void PostscriptMoveto(FILE *, int, GLfloat, GLfloat);
void PostscriptLineto(FILE *, int, GLfloat, GLfloat);
void PostscriptTriangle(FILE *, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat);
void PostscriptTriangleColor(FILE *, GLfloat, GLfloat, GLfloat);
void PostscriptPoint(FILE *, int, GLfloat, GLfloat);
void PostscriptLineStipple(FILE *, GLushort);
void PostscriptShowString(FILE *, int, GLfloat, GLfloat);

FILE *PostscriptInit(char *filename, double paperWidth, double paperHeight,
                     double fontHeight, int window, int smooth)
{
  FILE *file;
  time_t now;
  struct passwd *entry;
  GLfloat clearColor[4], viewport[4], clearIndex, xtrans, ytrans;
  double ratio, scale;
  int portrait, leftX, leftY;

  if ((file=fopen(filename, "w")) == NULL) return(NULL);
  portrait=paperWidth < paperHeight ? TRUE : FALSE;
  glGetFloatv(GL_VIEWPORT, viewport);
  ratio=(viewport[2]-viewport[0])/(viewport[3]-viewport[1]);
  if (paperHeight*ratio > paperWidth)
    scale=(paperWidth-20.)*TOPSUNIT/(viewport[2]-viewport[0]); 
  else
    scale=(paperHeight-20.)*TOPSUNIT/(viewport[3]-viewport[1]); 
  if (portrait)
  {
    xtrans=(paperWidth*TOPSUNIT-scale*(viewport[2]+viewport[0]))*0.5;
    ytrans=(paperHeight*TOPSUNIT-scale*(viewport[3]+viewport[1]))*0.5;
  }
  else
  {
    xtrans=(paperHeight*TOPSUNIT/scale+viewport[3]-viewport[1])*0.5;
    ytrans=(paperWidth*TOPSUNIT-scale*(viewport[2]+viewport[0]))*0.5;
  }
  if (rgbMode)
    glGetFloatv(GL_COLOR_CLEAR_VALUE, clearColor);
  else
  {
    glGetFloatv(GL_INDEX_CLEAR_VALUE, &clearIndex);
    getRGBColor(windows[VIEWER].widget, (Pixel)clearIndex, &clearColor[0], &clearColor[1], &clearColor[2]);
  }

  fprintf(file, "%%!PS-Adobe-3.0 EPSF-3.0\n");
  if (portrait)
  {
    leftX=(int)(scale*(viewport[0]+xtrans)+0.5);
    leftY=(int)(scale*(viewport[1]+ytrans)+0.5);
    fprintf(file, "%%%%BoundingBox: %d %d %d %d\n", leftX, leftY,
            (int)(scale*(viewport[2]-viewport[0]+xtrans)+0.5),
            (int)(scale*(viewport[3]-viewport[1]+ytrans)+0.5));
  }
  else
  {
    leftX=(int)(paperHeight*TOPSUNIT-xtrans*scale+0.5);
    leftY=(int)(scale*ytrans+0.5);
    fprintf(file, "%%%%BoundingBox: %d %d %d %d\n", leftX, leftY, (int)(scale*xtrans+0.5),
            (int)(scale*(viewport[2]-viewport[0]+ytrans)+0.5));
  }
  fprintf(file, "%%%%Creator: %s %s\n", PROGRAM, VERSION);
  now=time((time_t *)NULL);
  fprintf(file, "%%%%CreationDate: %s", ctime(&now));
  entry=getpwuid(getuid());
  fprintf(file, "%%%%For: %s\n", entry->pw_gecos);
  if (portrait)
    fprintf(file, "%%%%Orientation: Portrait\n");
  else
    fprintf(file, "%%%%Orientation: Landscape\n");
  fprintf(file, "%%%%Pages: 1\n");
  fprintf(file, "%%%%Title: %s\n", molecules[windows[window].set].title);
  fprintf(file, "%%%%EndComments\n");
  fprintf(file, "%%%%Page: 0 1\n\n");
  fprintf(file,"%% remember original state\n");
  fprintf(file,"/origstate save def\n\n");
  if (window == VIEWER && primitive == GLU_FILL && (windows[VIEWER].mode != WIREMODEL || iwavef != ALL_OFF))
    writeBitmapHeader(file, window, portrait, (int)(scale*xtrans+0.5), leftY, scale);
  else
    writeVectorHeader(file, smooth, portrait, xtrans, ytrans, scale, fontHeight, clearColor, viewport);

  return(file);
}

void writeVectorHeader(FILE *file, int smooth, int portrait, GLfloat xtrans,
                       GLfloat ytrans, double scale, double fontHeight, GLfloat *clearColor,
		       GLfloat *viewport)
{
  GLfloat lineWidth;
  char *word;

  word=getStringResource(topShell, "fontList");
  if (word && strstr(word, "koi8")) cyrillic=TRUE;
  if (cyrillic)
  {
    fprintf(file, "/Cyrillic findfont dup length dict begin\n");
    fprintf(file, "{1 index /FID ne {def} {pop pop} ifelse} forall\n");
    fprintf(file, "/Encoding[/NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL\n");
    fprintf(file, "/NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL\n");
    fprintf(file, "/NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /space\n");
    fprintf(file, "/exclam /quotedbl /numbersign /NUL /percent /NUL /quoteright\n");
    fprintf(file, "/parenleft /parenright /asterisk /plus /comma /endash /period\n");
    fprintf(file, "/slash /zero /one /two /three /four /five /six /seven /eight\n");
    fprintf(file, "/nine /colon /semicolon /less /equal /greater /question /NUL\n");
    fprintf(file, "/NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL\n");
    fprintf(file, "/NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL\n");
    fprintf(file, "/NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL\n");
    fprintf(file, "/NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL\n");
    fprintf(file, "/NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL\n");
    fprintf(file, "/NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL\n");
    fprintf(file, "/NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL\n");
    fprintf(file, "/NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL\n");
    fprintf(file, "/Oslash /NUL /NUL /NUL /NUL /NUL /NUL /NUL /oslash /NUL /NUL\n");
    fprintf(file, "/NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL\n");
    fprintf(file, "/NUL /NUL /NUL /NUL /NUL /NUL /NUL /NUL /bracketright /a /b\n");
    fprintf(file, "/c /d /e /f /g /h /i /y /k /l /m /n /o /p /aring /r /s /t /u\n");
    fprintf(file, "/j /v /exclamdown /bracketleft /z /w /hyphen /x /q /trademark\n");
    fprintf(file, "/braceright /A /B /C /D /E /F /G /H /I /Y /K /L /M /N /O /P\n");
    fprintf(file, "/Aring /R /S /T /U /J /V /fraction /braceleft /Z /W /Z /X /Q\n");
    fprintf(file, "/currency] def currentdict end\n");
    fprintf(file, "/Cyrillic-koi8 exch definefont pop\n");
    fprintf(file, "/Cyrillic-koi8 findfont %f scalefont setfont\n", fontHeight);
  }
  else
  {
    fprintf(file, "/Times-Roman findfont dup length dict begin\n");
    fprintf(file, "{1 index /FID ne {def} {pop pop} ifelse} forall\n");
    fprintf(file, "/Encoding ISOLatin1Encoding def currentdict end\n");
    fprintf(file, "/Times-Roman-ISOLatin1 exch definefont pop\n");
    fprintf(file, "/Times-Roman-ISOLatin1 findfont %f scalefont setfont\n", fontHeight);
  }
  if (smooth)
  {
    fprintf(file, "/threshold %f def\n", EPS_GOURAUD_THRESHOLD);
    fprintf(file, "/bd{bind def}bind def /triangle { aload pop   setrgbcolor  aload pop 5 3\n");
    fprintf(file, "roll 4 2 roll 3 2 roll exch moveto lineto lineto closepath fill } bd\n");
    fprintf(file, "/computediff1 { 2 copy sub abs threshold ge {pop pop pop true} { exch 2\n");
    fprintf(file, "index sub abs threshold ge { pop pop true} { sub abs threshold ge } ifelse\n");
    fprintf(file, "} ifelse } bd /computediff3 { 3 copy 0 get 3 1 roll 0 get 3 1 roll 0 get\n");
    fprintf(file, "computediff1 {true} { 3 copy 1 get 3 1 roll 1 get 3 1 roll 1 get\n");
    fprintf(file, "computediff1 {true} { 3 copy 2 get 3 1 roll  2 get 3 1 roll 2 get\n");
    fprintf(file, "computediff1 } ifelse } ifelse } bd /middlecolor { aload pop 4 -1 roll\n");
    fprintf(file, "aload pop 4 -1 roll add 2 div 5 1 roll 3 -1 roll add 2 div 3 1 roll add 2\n");
    fprintf(file, "div 3 1 roll exch 3 array astore } bd /G { computediff3 { 4\n");
    fprintf(file, "-1 roll aload 7 1 roll 6 -1 roll pop 3 -1 roll pop add 2 div 3 1 roll add\n");
    fprintf(file, "2 div exch 3 -1 roll aload 7 1 roll exch pop 4 -1 roll pop add 2 div 3 1\n");
    fprintf(file, "roll add 2 div exch 3 -1 roll aload 7 1 roll pop 3 -1 roll pop add 2 div 3\n");
    fprintf(file, "1 roll add 2 div exch 7 3 roll 10 -3 roll dup 3 index middlecolor 4 1 roll\n");
    fprintf(file, "2 copy middlecolor 4 1 roll 3 copy pop middlecolor 4 1 roll 13 -1 roll\n");
    fprintf(file, "aload pop 17 index 6 index 15 index 19 index 6 index 17 index 6 array\n");
    fprintf(file, "astore 10 index 10 index 14 index G 17 index 5 index 17\n");
    fprintf(file, "index 19 index 5 index 19 index 6 array astore 10 index 9 index 13 index\n");
    fprintf(file, "G 13 index 16 index 5 index 15 index 18 index 5 index 6\n");
    fprintf(file, "array astore 12 index 12 index 9 index G 17 index 16 index\n");
    fprintf(file, "15 index 19 index 18 index 17 index 6 array astore 10 index 12 index 14\n");
    fprintf(file, "index G 18 {pop} repeat } { aload pop 5 3 roll aload pop 7 3\n");
    fprintf(file, "roll aload pop 9 3 roll 4 index 6 index 4 index add add 3 div 10 1 roll 7\n");
    fprintf(file, "index 5 index 3 index add add 3 div 10 1 roll 6 index 4 index 2 index add\n");
    fprintf(file, "add 3 div 10 1 roll 9 {pop} repeat 3 array astore triangle } ifelse } bd\n");
  }
  fprintf(file, "/l {lineto} bind def /m {moveto} bind def /c {setrgbcolor} bind def\n");
  fprintf(file, "/sl {3 2 roll 3 2 roll moveto show} bind def\n");
  fprintf(file, "/sr {dup stringwidth pop -1 mul 4 3 roll add 3 2 roll moveto show} bind def\n");
  fprintf(file, "/sc {dup stringwidth pop -2 div 4 3 roll add 3 2 roll moveto show} bind def\n");
  fprintf(file, "%.2f %.2f scale\n", scale, scale);
  if (portrait)
    fprintf(file, "%.2f %.2f translate\n", xtrans, ytrans);
  else
    fprintf(file, "90 rotate %.2f %.2f translate\n", ytrans, -xtrans);
  glGetFloatv(GL_LINE_WIDTH, &lineWidth);
  fprintf(file, "%.2f setlinewidth\n", lineWidth);
  fprintf(file, "%f %f %f setrgbcolor\n", clearColor[0], clearColor[1], clearColor[2]);
  fprintf(file, "%.2f %.2f %.2f %.2f rectfill\n", viewport[0], viewport[1], viewport[2], viewport[3]);
  glGetFloatv(GL_POINT_SIZE, &pointSize);
  lastred=lastgreen=lastblue=(-1.);
  bitmap=FALSE;
}

void writeBitmapHeader(FILE *file, int window, int portrait, double xtrans, double ytrans, double scale)
{
  Dimension width, height;
  int bits=8;

  XtVaGetValues(windows[window].widget, XmNwidth, &width, XmNheight, &height, NULL);
  /* Make sure the image width is divisable by 4 */
  if (width % 4 != 0) width-=(width % 4);
  fprintf(file, "%% build a temporary dictionary\n");
  fprintf(file, "20 dict begin\n\n");
/*fprintf(file, "/readstring {\n");
  fprintf(file, "  currentfile exch readhexstring pop /RunLengthDecode filter\n");
  fprintf(file, "} bind def\n"); */
  fprintf(file, "%% define strings to hold a scanline's worth of data\n");
  fprintf(file, "/pix 2100 string def\n\n");
/*fprintf(file, "/rpicstr %d string def\n", width);
  fprintf(file, "/gpicstr %d string def\n", width);
  fprintf(file, "/bpicstr %d string def\n\n", width); */
  fprintf(file, "%% define space for color conversions\n");
  fprintf(file, "/grays 700 string def  %% space for gray scale line\n");
  fprintf(file, "/npixls 0 def\n");
  fprintf(file, "/rgbindx 0 def\n\n");
  fprintf(file, "%% lower left corner\n");
  if (portrait)
    fprintf(file, "%.2f %.2f translate\n", xtrans, ytrans);
  else
    fprintf(file, "90 rotate %.2f %.2f translate\n", ytrans, -xtrans);
  fprintf(file, "%% size of image (on paper, in Postscript coordinates, 1/72 inch)\n");
  fprintf(file, "%f %f scale\n", scale*width, scale*height);

  /* This code checks if the PostScript device in question knows about
     the 'colorimage' operator.  If it doesn't, it defines 'colorimage'
     in terms of image (ie, generates a greyscale image from RGB data) */

  fprintf(file, "%% define 'colorimage' if it isn't defined\n");
  fprintf(file, "%%   ('colortogray' and 'mergeprocs' come from xwd2ps\n");
  fprintf(file, "%%     via xgrab)\n");
  fprintf(file, "/colorimage where   %% do we know about 'colorimage'?\n");
  fprintf(file, "  { pop }           %% yes: pop off the 'dict' returned\n");
  fprintf(file, "  {                 %% no:  define one\n");
  fprintf(file, "    /colortogray {  %% define an RGB->I function\n");
  fprintf(file, "      /rgbdata exch store    %% call input 'rgbdata'\n");
  fprintf(file, "      rgbdata length 3 idiv\n");
  fprintf(file, "      /npixls exch store\n");
  fprintf(file, "      /rgbindx 0 store\n");
  fprintf(file, "      0 1 npixls 1 sub {\n");
  fprintf(file, "        grays exch\n");
  fprintf(file, "        rgbdata rgbindx       get 20 mul    %% Red\n");
  fprintf(file, "        rgbdata rgbindx 1 add get 32 mul    %% Green\n");
  fprintf(file, "        rgbdata rgbindx 2 add get 12 mul    %% Blue\n");
  fprintf(file, "        add add 64 idiv      %% I = .5G + .31R + .18B\n");
  fprintf(file, "        put\n");
  fprintf(file, "        /rgbindx rgbindx 3 add store\n");
  fprintf(file, "      } for\n");
  fprintf(file, "      grays 0 npixls getinterval\n");
  fprintf(file, "    } bind def\n\n");

  fprintf(file, "    %% Utility procedure for colorimage operator.\n");
  fprintf(file, "    %% This procedure takes two procedures off the\n");
  fprintf(file, "    %% stack and merges them into a single procedure.\n\n");

  fprintf(file, "    /mergeprocs { %% def\n");
  fprintf(file, "      dup length\n");
  fprintf(file, "      3 -1 roll\n");
  fprintf(file, "      dup\n");
  fprintf(file, "      length\n");
  fprintf(file, "      dup\n");
  fprintf(file, "      5 1 roll\n");
  fprintf(file, "      3 -1 roll\n");
  fprintf(file, "      add\n");
  fprintf(file, "      array cvx\n");
  fprintf(file, "      dup\n");
  fprintf(file, "      3 -1 roll\n");
  fprintf(file, "      0 exch\n");
  fprintf(file, "      putinterval\n");
  fprintf(file, "      dup\n");
  fprintf(file, "      4 2 roll\n");
  fprintf(file, "      putinterval\n");
  fprintf(file, "    } bind def\n\n");

  fprintf(file, "    /colorimage { %% def\n");
  fprintf(file, "      pop pop     %% remove 'false 3' operands\n");
  fprintf(file, "      {colortogray} mergeprocs\n");
  fprintf(file, "      image\n");
  fprintf(file, "    } bind def\n");
  fprintf(file, "  } ifelse          %% end of 'false' case\n");
  fprintf(file, "\n\n");

  fprintf(file, "%d %d %d\t\t\t%% dimensions of data\n", width, height, bits);
  fprintf(file, "[%d 0 0 %d 0 0]\t\t%% mapping matrix\n", width, height);
  fprintf(file, "{currentfile pix readhexstring pop}\n");
  fprintf(file, "false 3 colorimage\n");
/*fprintf(file, "{rpicstr readstring}\n");
  fprintf(file, "{gpicstr readstring}\n");
  fprintf(file, "{bpicstr readstring}\n");
  fprintf(file, "true 3 colorimage\n"); */
  bitmap=TRUE;
}

void PostscriptClose(FILE *file)
{
  fprintf(file, "showpage\n%%%%EOF\n");
  if (bitmap)
    fprintf(file, "end\n");
  fprintf(file, "origstate restore\n%%%%EOF\n");
  fclose(file);
}

void PostscriptSetRGBColor(FILE *file, GLfloat red, GLfloat green, GLfloat blue)
{
  if (red == lastred && green == lastgreen && blue == lastblue) return;
  lastred=red;
  lastgreen=green;
  lastblue=blue;
  fprintf(file, "%f %f %f c\n", red, green, blue);
}

void PostscriptMoveto(FILE *file, int portrait, GLfloat x, GLfloat y)
{
  fprintf(file, "%.2f %.2f m\n", x, y);
}

void PostscriptLineto(FILE *file, int portrait, GLfloat x, GLfloat y)
{
  fprintf(file, "%.2f %.2f l\n", x, y);
}

void PostscriptTriangle(FILE *file, GLfloat x1, GLfloat x2, GLfloat x3,
              GLfloat y1, GLfloat y2, GLfloat y3)
{
  fprintf(file, "[%.2f %.2f %.2f %.2f %.2f %.2f] ", x1, x2, x3, y1, y2, y3);
}

void PostscriptTriangleColor(FILE *file, GLfloat red, GLfloat green,
                             GLfloat blue)
{
  static int count=0;

  fprintf(file, "[%f %f %f] ", red, green, blue);
  count++;
  if (count == 1)
    fprintf(file, "\n");
  if (count == 3)
  {
    fprintf(file, "G\n");
    count=0;
  }
}

void PostscriptPoint(FILE *file, int portrait, GLfloat x, GLfloat y)
{
  fprintf(file, "%.2f %.2f %.2f 0 360 arc fill\n", x, y, pointSize*0.5);
}

void PostscriptLineStipple(FILE *file, GLushort style)
{
  int count;
  register int i, j, k;

  if (style == 0xffff)
    fprintf(file, "[] 0 setdash\n");
  else
  {
    fprintf(file, "[");
    j=style & 1;
    count=1;
    for (i=1; i<sizeof(GLushort)*8; i++)
    {
      k=(style & (1 << i)) >> i;
      if (k != j)
      {
        fprintf(file, "%d ", count);
        j=k;
        count=1;
      }
      else
        count++;
    }
    fprintf(file, "] 0 setdash\n");
  }
}

void PostscriptShowString(FILE *file, int dummy, GLfloat x, GLfloat y)
{
  register unsigned char *p;

  fprintf(file, "%.2f %.2f (", x, y);
  for (p=(unsigned char *)textPointer+1; *p; p++)
  {
    switch (*p)
    {
      case '(': fprintf(file, "\\(");
                break;
      case ')': fprintf(file, "\\)");
                break;
      default:  fprintf(file, "%c", *p);
                break;
    }
  }
  fprintf(file, ") s%c\n", textPointer[0]);
  textPointer+=strlen(textPointer)+1;
}
