/*
** Astrolog (Version 4.00) File: xgeneral.c
**
** IMPORTANT NOTICE: the graphics database and chart display routines
** used in this program are Copyright (C) 1991-1993 by Walter D. Pullen
** (cruiser1@stein.u.washington.edu). Permission is granted to freely
** use and distribute these routines provided one doesn't sell,
** restrict, or profit from them in any way. Modification is allowed
** provided these notices remain with any altered or edited versions of
** the program.
**
** The main planetary calculation routines used in this program have
** been Copyrighted and the core of this program is basically a
** conversion to C of the routines created by James Neely as listed in
** Michael Erlewine's 'Manual of Computer Programming for Astrologers',
** available from Matrix Software. The copyright gives us permission to
** use the routines for personal use but not to sell them or profit from
** them in any way.
**
** The PostScript code within the core graphics routines are programmed
** and Copyright (C) 1992-1993 by Brian D. Willoughby
** (brianw@sounds.wa.com). Conditions are identical to those above.
**
** The extended accurate ephemeris databases and formulas are from the
** calculation routines in the program "Placalc" and are programmed and
** Copyright (C) 1989,1991,1993 by Astrodienst AG and Alois Treindl
** (alois@azur.ch). The use of that source code is subject to
** regulations made by Astrodienst Zurich, and the code is not in the
** public domain. This copyright notice must not be changed or removed
** by any user of this program.
**
** Initial programming 8/28,30, 9/10,13,16,20,23, 10/3,6,7, 11/7,10,21/1991.
** X Window graphics initially programmed 10/23-29/1991.
** PostScript graphics initially programmed 11/29-30/1992.
** Last code change made 12/31/1993.
*/

#include "astrolog.h"

#ifdef GRAPH

int xpen, ypen;


/*
******************************************************************************
** Bitmap File Routines.
******************************************************************************
*/

/* Write the bitmap array to a previously opened file in a format that   */
/* can be read in by the Unix X commands bitmap and xsetroot. The 'mode' */
/* parameter defines how much white space is put in the file.            */

void WriteXBitmap(data, name, mode)
FILE *data;
char *name, mode;
{
  int x, y, i, temp = 0;
  uint value;

  fprintf(data, "#define %s_width %d\n" , name, chartx);
  fprintf(data, "#define %s_height %d\n", name, charty);
  fprintf(data, "static %s %s_bits[] = {",
    mode != 'V' ? "char" : "short", name);
  for (y = 0; y < charty; y++) {
    x = 0;
    do {

      /* Process each row, eight columns at a time. */

      if (y + x > 0)
        fprintf(data, ",");
      if (temp == 0)
        fprintf(data, "\n%s",
          mode == 'N' ? "  " : (mode == 'C' ? " " : ""));
      value = 0;
      for (i = (mode != 'V' ? 7 : 15); i >= 0; i--)
        value = (value << 1) +
          (!(PGET(bm, x+i, y)^(xreverse*15))^xreverse && (x + i < chartx));
      if (mode == 'N')
        putc(' ', data);
      fprintf(data, "0x");
      if (mode == 'V')
        fprintf(data, "%c%c",
          INTTOHEX(value >> 12), INTTOHEX((value >> 8) & 15));
      fprintf(data, "%c%c",
        INTTOHEX((value >> 4) & 15), INTTOHEX(value & 15));
      temp++;

      /* Is it time to skip to the next line while writing the file yet? */

      if ((mode == 'N' && temp >= 12) ||
          (mode == 'C' && temp >= 15) ||
          (mode == 'V' && temp >= 11))
        temp = 0;
      x += (mode != 'V' ? 8 : 16);
    } while (x < chartx);
  }
  fprintf(data, "};\n");
}


/* Write the bitmap array to a previously opened file in a simple boolean    */
/* Ascii rectangle, one char per pixel, where '#' represents an off bit and  */
/* '-' an on bit. The output format is identical to the format generated by  */
/* the Unix bmtoa command, and it can be converted into a bitmap with atobm. */

void WriteAscii(data)
FILE *data;
{
  int x, y, i;

  for (y = 0; y < charty; y++) {
    for (x = 0; x < chartx; x++) {
      i = PGET(bm, x, y);
      if (xcolor)
        putc(INTTOHEX(i), data);
      else
        putc(i ? '-' : '#', data);
    }
    putc('\n', data);
  }
}


/* Write the bitmap array to a previously opened file in the bitmap format  */
/* used in Microsoft Windows for its .bmp extension files. This is a pretty */
/* efficient format, only requiring one bit per pixel and a small header.   */

void WriteBmp(data)
FILE *data;
{
  int x, y;
  dword value;

  /* BitmapFileHeader */
  PutByte('B'); PutByte('M');
  PutLong(14+40 + (xcolor ? 64 : 8) +
    (long)4*charty*((chartx-1 >> (xcolor ? 3 : 5))+1));
  PutWord(0); PutWord(0);
  PutLong(14+40 + (xcolor ? 64 : 8));
  /* BitmapInfo / BitmapInfoHeader */
  PutLong(40);
  PutLong(chartx); PutLong(charty);
  PutWord(1); PutWord(xcolor ? 4 : 1);
  PutLong(0 /*BI_RGB*/); PutLong(0);
  PutLong(0); PutLong(0);
  PutLong(0); PutLong(0);
  /* RgbQuad */
  if (xcolor)
    for (x = 0; x < 16; x++) {
      PutByte(RGBB(rgbbmp[x])); PutByte(RGBG(rgbbmp[x]));
      PutByte(RGBR(rgbbmp[x])); PutByte(0);
    }
  else {
    PutLong(0);
    PutByte(255); PutByte(255); PutByte(255); PutByte(0);
  }
  /* Data */
  for (y = charty-1; y >= 0; y--) {
    value = 0;
    for (x = 0; x < chartx; x++) {
      if ((x & (xcolor ? 7 : 31)) == 0 && x > 0) {
        PutLong(value);
        value = 0;
      }
      if (xcolor)
        value |= (dword)PGET(bm, x, y) << ((x & 7 ^ 1) << 2);
      else
        if (PGET(bm, x, y))
          value |= (dword)1 << (x & 31 ^ 7);
    }
    PutLong(value);
  }
}


/* Output the bitmap in memory to a file. This basically consists of just    */
/* calling some routine to actually write a bitmap to a file, although we    */
/* need to prompt the user for a filename if it wasn't specified beforehand. */

void WriteFile()
{
  char line[STRING];
  FILE *data;

#ifdef PS
  if (psfile) {
  	PSend();
    return;
  }
#endif
  if (outputfile == NULL && (metafile || (xbitmap && bitmapmode == 'B')))
    fprintf(stdout, "(It is recommended to specify an extension of '.%s'.)\n",
      xbitmap ? "bmp" : "wmf");
  loop {
    if (outputfile == NULL) {
      sprintf(line, "Enter name of file to write %s to",
        xbitmap ? "bitmap" : "metafile");
      InputString(line, line);
      outputfile = line;
    }
    data = fopen(outputfile, "wb");
    if (data != NULL)
      break;
    else {
      PrintWarning("Couldn't create output file.");
      outputfile = NULL;
    }
  }
  if (xbitmap) {
    if (bitmapmode == 'B')
      WriteBmp(data);
    else if (bitmapmode == 'A')
      WriteAscii(data);
    else
      WriteXBitmap(data, outputfile, bitmapmode);
  }
#ifdef META
  else
    WriteMeta(data);
#endif
  fclose(data);
}


/*
******************************************************************************
** PostScript File Routines.
******************************************************************************
*/

#ifdef PS

/* Global variables used by the PostScript generator. */

FILE *psdata;
int strokecount = 0, currentlinecap = 0, currentdash = 0, currentfont = 0;
real currentlinewidth = 1.0;

/* Table of PostScript header alias lines used by the program. */

char PSfunctions[] =
"/languagelevel where{pop languagelevel}{1}ifelse\
 2 lt{\n\
/sf{exch findfont exch\
 dup type/arraytype eq{makefont}{scalefont}ifelse setfont}bind def\n\
/rf{gsave newpath\n\
4 -2 roll moveto\
 dup 0 exch rlineto exch 0 rlineto neg 0 exch rlineto closepath\n\
fill grestore}bind def\n\
/rc{newpath\n\
4 -2 roll moveto\
 dup 0 exch rlineto exch 0 rlineto neg 0 exch rlineto closepath\n\
clip newpath}bind def\n\
}{/sf/selectfont load def/rf/rectfill load def\
/rc/rectclip load def}ifelse\n\
/center{0 begin gsave dup 4 2 roll\
 translate newpath 0 0 moveto\
 false charpath flattenpath pathbbox\
 /URy exch def/URx exch def/LLy exch def/LLx exch def\
 URx LLx sub 0.5 mul LLx add neg URy LLy sub 0.5 mul LLy add neg\
 0 0 moveto rmoveto\
 show grestore end}bind def\n\
/center load 0 4 dict put\n\
/c{setrgbcolor}bind def\n\
/d{moveto 0 0 rlineto}bind def\n\
/l{4 2 roll moveto lineto}bind def\n\
/t{lineto}bind def\n\
/el{newpath matrix currentmatrix 5 1 roll translate scale\
 0 0 1 0 360 arc setmatrix stroke}bind def\n";


/* Write a command to flush the PostScript buffer. */

void PSforcestroke()
{
  if (strokecount > 0) {            /* render any existing path */
    fprintf(psdata, "stroke\n");
    strokecount = 0;
    xpen = -1;                      /* Invalidate PolyLine cache */
  }
}


/* Indicate that a certain number of PostScript commands have been done. */

void PSstroke(n)
int n;
{
  strokecount += n;
  if (strokecount > 5000)    /* Whenever we reach a certain limit, flush. */
    PSforcestroke();
}


/* Set the type of line end to be used by PostScript commands. If linecap */
/* is true, then the line ends are rounded, otherwise they are squared.   */

void PSlinecap(linecap)
int linecap;
{
  if (linecap != currentlinecap) {
    PSforcestroke();
    fprintf(psdata, "%d setlinecap\n", linecap);
    currentlinecap = linecap;
  }
}


/* Set the dash length to be used by PostScript line commands. */

void PSdash(dashoff)
int dashoff;
{
  if (dashoff != currentdash) {
    PSforcestroke();
    if (dashoff)
      fprintf(psdata, "[%d %d", PSMUL, dashoff * PSMUL);
    else
      fprintf(psdata, "[");
    fprintf(psdata, "]0 setdash\n");
    currentdash = dashoff;
  }
}


/* Set a linewidth size to be used by PostScript figure primitive commands. */

void PSlinewidth(linewidth)
int linewidth;
{
  real oldlinewidth = currentlinewidth;

  if (linewidth != currentlinewidth) {
    PSforcestroke();
    fprintf(psdata, "%d setlinewidth\n", linewidth);
    currentlinewidth = linewidth;
  }
}


/* Set a system font and size to be used by PostScript text commands. */

void PSfont(psfont)
int psfont;
{
  int temp;

  if (psfont != currentfont && xfont) {
    if (psfont <= 2) {
      temp = psfont == 1 ? 32*PSMUL : 23*PSMUL;
    	fprintf(psdata, "/Astro[%d 0 0 -%d 0 0]sf\n", temp, temp);
    } else if (psfont == 3) {
      temp = 26*PSMUL;
      fprintf(psdata, "/Times-Roman[%d 0 0 -%d 0 0]sf\n", temp, temp);
    } else {
      temp = 10*PSMUL;
      fprintf(psdata, "/Courier[%d 0 0 -%d 0 0]sf\n", temp, temp);
    }
    currentfont = psfont;
  }
}


/* Prompt the user for the name of a file to write the PostScript file to */
/* (if not already specified), open it, and write out file header info.   */

void PSbegin()
{
  char line[STRING];

  if (outputfile == NULL && epsfile)
    fprintf(stdout,
      "(It is recommended to specify an extension of '.eps'.)\n");
  loop {
    if (outputfile == NULL) {
      sprintf(line, "Enter name of file to write PostScript to");
      InputString(line, line);
      outputfile = line;
    }
    psdata = fopen(outputfile, "w");
    if (psdata != NULL)
      break;
    else {
      PrintWarning("Couldn't create output file.");
      outputfile = NULL;
    }
  }
  fprintf(psdata, "%%!PS-Adobe-2.0");
  if (epsfile)
    fprintf(psdata, " EPSF-2.0");
  fprintf(psdata, "\n%%%%Title: %s\n", outputfile);
  fprintf(psdata, "%%%%Creator: %s %s\n", appname, VERSION);
  fprintf(psdata, "%%%%CreationDate: %s\n", DATE);
  if (epsfile) {
    fprintf(psdata, "%%%%BoundingBox: 0 0 %d %d\n", chartx, charty);
    fprintf(psdata, "%%%%EndComments\n");
    fprintf(psdata, "%%%%BeginSetup\n");
    fprintf(psdata, PSfunctions, 6 * PSMUL, 6 * PSMUL);
    fprintf(psdata, "%%%%EndSetup\n");
    fprintf(psdata, "0 0 %d %d rc\n", chartx, charty);
  } else {
    fprintf(psdata, "%%%%Pages: 1 1\n");
    fprintf(psdata, "%%%%DocumentFonts: (atend)\n");
    fprintf(psdata, "%%%%BoundingBox: 9 9 603 783\n");    /* 8.5" x 11" */
    fprintf(psdata, "%%%%EndComments\n");
    fprintf(psdata, "%%%%BeginProcSet: common\n");
    fprintf(psdata, PSfunctions, 6 * PSMUL, 6 * PSMUL);
    fprintf(psdata, "%%%%EndProcSet\n");
    fprintf(psdata, "%%%%Page: 1 1\n");
  }
  PSfont(2);
  fprintf(psdata, "gsave\n");
  PSlinewidth(metawid/2);
  xpen = -1;
}


/* Write out trailing information to the PostScript file and close it. */

void PSend()
{
  PSforcestroke();
  if (epsfile)
    fprintf(psdata, "%%%%EOF\n");
  else {
    fprintf(psdata, "showpage\n");
    fprintf(psdata, "%%%%PageTrailer\n");
    fprintf(psdata, "%%%%Trailer\n");
    fprintf(psdata, "%%%%DocumentFonts: Times-Roman\n");
    if (xfont) {
      fprintf(psdata, "%%%%+ Courier\n");
      fprintf(psdata, "%%%%+ Astro\n");
    }
  }
  fclose(psdata);
}
#endif /* PS */


/*
******************************************************************************
** Metafile Routines.
******************************************************************************
*/

#ifdef META

/* Global variables used by the metafile generator. */

colpal metalinedes, metalineact = -1,    /* Desired and actual line color. */
  metafilldes,      metafillact = -1,    /* Desired and actual fill color. */
  metafontdes = -1, metafontact = -1,    /* Desired and actual text font.  */
  metatxtcdes = -1, metatxtcact = -1,    /* Desired and actual text color. */
  metatxtades = -1, metatxtaact = -1;    /* Desired/actual text alignment. */

/* Macros to output the various metafile commands we use. */

#define MetaRecord(S, R) MetaLong((long)(S)); MetaWord(R)
#define MetaSelectObject(O) MetaRecord(4, 0x12D); MetaWord(O)
#define MetaDeleteObject(O) MetaRecord(4, 0x1F0); MetaWord(O)
#define MetaSaveDc() MetaRecord(3, 0x01E)
#define MetaRestoreDc() MetaRecord(4, 0x127); MetaWord(-1)
#define MetaWindowOrg(X, Y) MetaRecord(5, 0x20B); MetaWord(Y); MetaWord(X)
#define MetaWindowExt(X, Y) MetaRecord(5, 0x20C); MetaWord(Y); MetaWord(X)
#define MetaCreatePen(S, W, C) MetaRecord(8, 0x2FA); MetaWord(S); \
  MetaWord(W); MetaWord(W); MetaLong(C)
#define MetaCreateBrush(S, C) MetaRecord(7, 0x2FC); \
  MetaWord(S); MetaLong(C); MetaWord(0 /* Not used */);
#define MetaCreateFont(S, X, Y, C) MetaRecord(12+(S), 0x2FB); MetaWord(Y); \
  MetaWord(X); MetaWord(0 /* Angle */); MetaWord(0 /* Not used */); \
  MetaWord(400 /* Normal Weight */); MetaWord(0 /* Italic, Underline */); \
  MetaWord(MAKEWORD(0 /* Strikeout */, C)); \
  MetaWord(MAKEWORD(4 /* TrueType */, 0 /* Clip */))
#define MetaBkMode(M) MetaRecord(4, 0x102); MetaWord(M)
#define MetaTextAlign(A) MetaRecord(4, 0x12E); MetaWord(A)
#define MetaTextColor(C) MetaRecord(5, 0x209); MetaLong(C);
#define MetaTextOut(X, Y, S) MetaRecord(7+((S)+1)/2, 0xA32); \
  MetaWord(Y); MetaWord(X); MetaWord(S); MetaWord(0 /* ETO */)
#define MetaRectangle(X1, Y1, X2, Y2) MetaRecord(7, 0x41B); \
  MetaWord(Y2); MetaWord(X2); MetaWord(Y1); MetaWord(X1)
#define MetaEllipse(X1, Y1, X2, Y2) MetaRecord(7, 0x418); \
  MetaWord(Y2); MetaWord(X2); MetaWord(Y1); MetaWord(X1)
#define MetaEscape(S) MetaRecord((S), 0x626); \
  MetaWord(15 /* MFCOMMENT */); MetaWord(((S)-5)*2 /* Bytes in comment */);


/* Output one 16 bit or 32 bit value into the metafile buffer stream. */

void MetaWord(w)
word w;
{
  if ((byte PTR)metacur - bm >= MAXMETA) {
    PrintError("Metafile would be more than %ld bytes.", MAXMETA);
    Terminate(_FATAL);
  }
  *metacur = w;
  metacur++;
}

void MetaLong(l)
long l;
{
  MetaWord(LOWORD(l));
  MetaWord(HIWORD(l));
}


/* Output any necessary metafile records to make the current actual     */
/* settings of line color, fill color, etc, be those that we know are   */
/* desired. This is generally called by the primitives routines before  */
/* any figure record is actually written into a metafile. We wait until */
/* the last moment before changing any settings to ensure that we don't */
/* output any unnecessary records, e.g. two select colors in a row.     */

void MetaSelect()
{
  if (metalinedes != metalineact) {
    MetaSelectObject(metalinedes);
    metalineact = metalinedes;
  }
  if (metafilldes != metafillact) {
    MetaSelectObject(16*4 + metafilldes);
    metafillact = metafilldes;
  }
  if (metafontdes != metafontact) {
    MetaSelectObject(16*5 + metafontdes);
    metafontact = metafontdes;
  }
  if (metatxtcdes != metatxtcact) {
    MetaTextColor(rgbbmp[metatxtcdes]);
    metatxtcact = metatxtcdes;
  }
  if (metatxtades != metatxtaact) {
    MetaTextAlign(metatxtades);
    metatxtaact = metatxtades;
  }
  xpen = -1;    /* Invalidate PolyLine cache */
}


/* Output initial metafile header information into our metafile buffer. */
/* We also setup and create all pen, brush, and font objects that may   */
/* possibly be used in the generation and playing of the picture.       */

void MetaInit()
{
  int i, j, k;

  metacur = (word PTR)bm;
  /* Placable Metaheader */
  MetaLong(0x9AC6CDD7L);
  MetaWord(0);      /* Not used */
  MetaWord(0); MetaWord(0);
  MetaWord(chartx); MetaWord(charty);
  MetaWord(chartx/6);  /* Units per inch */
  MetaLong(0L);     /* Not used */
  MetaWord(0x9AC6 ^ 0xCDD7 ^ chartx ^ charty ^ chartx/6);  /* Checksum */
  /* Metaheader */
  MetaWord(1);                   /* Metafile type */
  MetaWord(9);                   /* Size of header in words */
  MetaWord(0x300);               /* Windows version */
  MetaLong(0L);                  /* Size of entire metafile in words */
  MetaWord(16*5+1+(xfont>0)*4);  /* Number of objects in metafile */
  MetaLong(17L);                 /* Size of largest record in words */
  MetaWord(0);                   /* Not used */
  /* Setup */
  MetaEscape(17);
  MetaLong(MAKEQUAD('A', 's', 't', 'r'));  /* "Astr" */
  MetaWord(4);                             /* Creator */
  MetaLong(14L);                           /* Bytes in string */
  MetaLong(MAKEQUAD('A', 's', 't', 'r'));  /* "Astr" */
  MetaLong(MAKEQUAD('o', 'l', 'o', 'g'));  /* "olog" */
  MetaLong(MAKEQUAD(' ', '4', '.', '0'));  /* " 3.2" */
  MetaWord(MAKEWORD('0', 0));              /* "0"    */
  MetaSaveDc();
  MetaWindowOrg(0, 0);
  MetaWindowExt(chartx, charty);
  MetaBkMode(1 /* Transparent */);
  /* Colors */
  for (j = 1; j <= 4; j++)
    for (i = 0; i < 16; i++) {
      k = j <= 1 ? metawid : 0;
      MetaCreatePen(j <= 2 ? 0 : j-2 /* PS_SOLID; PS_DASH; PS_DOT */,
        k, rgbbmp[i]);
    }
  for (i = 0; i < 16; i++) {
    MetaCreateBrush(0 /* BS_SOLID */, rgbbmp[i]);
  }
  MetaCreateBrush(1 /* BS_NULL */, 0L);
  /* Fonts */
  if (xfont) {
    MetaCreateFont(5, 0, -8*SCALE, 2 /* Symbol Charset */);
    MetaWord(MAKEWORD(1 /* Draft */, 1 | 0x10 /* Fixed | Roman */));
    MetaLong(MAKEQUAD('W', 'i', 'n', 'g'));
    MetaLong(MAKEQUAD('d', 'i', 'n', 'g'));
    MetaWord(MAKEWORD('s', 0));

    MetaCreateFont(8, 0, -6*SCALE, 0 /* Ansi Charset */);
    MetaWord(MAKEWORD(0 /* Default */, 2 | 0x10 /* Variable | Roman */));
    MetaLong(MAKEQUAD('T', 'i', 'm', 'e'));
    MetaLong(MAKEQUAD('s', ' ', 'N', 'e'));
    MetaLong(MAKEQUAD('w', ' ', 'R', 'o'));
    MetaLong(MAKEQUAD('m', 'a', 'n', 0));

    MetaCreateFont(6, 6*METAMUL, 10*METAMUL, 0 /* Ansi Charset */);
    MetaWord(MAKEWORD(1 /* Draft */, 1 | 0x30 /* Fixed | Modern */));
    MetaLong(MAKEQUAD('C', 'o', 'u', 'r'));
    MetaLong(MAKEQUAD('i', 'e', 'r', ' '));
    MetaLong(MAKEQUAD('N', 'e', 'w', 0));

    MetaCreateFont(8, 0, -11*SCALE, 0 /* Ansi Charset */);
    MetaWord(MAKEWORD(0 /* Default */, 2 | 0 /* Variable | Don't Care */));
    MetaLong(MAKEQUAD('A', 's', 't', 'r'));
    MetaLong(MAKEQUAD('o', '-', 'S', 'e'));
    MetaLong(MAKEQUAD('m', 'i', 'B', 'o'));
    MetaLong(MAKEQUAD('l', 'd', 0, 0));
  }
}


/* Output trailing records to indicate the end of the metafile and then */
/* actually write out the entire buffer to the specifed file.           */

void WriteMeta(data)
FILE *data;
{
  word PTR w;
#if FALSE
  int i;

  for (i = 16*5+1+(xfont>0)*4; i >= 0; i--) {
    MetaDeleteObject(i);
  }
#endif
  MetaRestoreDc();
  MetaRecord(3, NULL);    /* End record */
  *(long PTR)(bm + 22 + 6) = ((long)((byte PTR)metacur - bm) - 22) / 2;
  for (w = (word PTR)bm; w < metacur; w++) {
    PutWord(*w);
  }
}
#endif /* META */


/*
******************************************************************************
** Core Graphic Procedures.
******************************************************************************
*/

/* Set the current color to use in drawing on the screen or bitmap array. */

void DrawColor(col)
colpal col;
{
#ifdef PS
  if (psfile) {
    if (colcur != col) {
      PSforcestroke();      /* Render existing path with current color */
      fprintf(psdata, "%.2f %.2f %.2f c\n",
        (real)RGBR(rgbbmp[col])/255.0, (real)RGBG(rgbbmp[col])/255.0,
        (real)RGBB(rgbbmp[col])/255.0);
    }
    colcur = col;
    return;
  }
#endif
#ifdef META
  if (metafile)
    metalinedes = col;
#endif
#ifdef X11
  else if (!xfile)
    XSetForeground(disp, gc, rgbind[col]);
#endif
#ifdef MSG
  else if (!xfile)
    _setcolor(col);
#endif
  colcur = col;
}


/* Set a single point on the screen. This is the most basic graphic function */
/* and is called by all the more complex routines. Based on what mode we are */
/* in, we either set a cell in the bitmap array or a pixel on the window.    */

void DrawPoint(x, y)
int x, y;
{
  if (xfile) {
    if (xbitmap) {
      /* Force the coordinates to be within the bounds of the bitmap array. */

      if (x < 0)
        x = 0;
      else if (x >= chartx)
        x = chartx-1;
      if (y < 0)
        y = 0;
      else if (y >= charty)
        y = charty-1;
      PSET(bm, x, y, colcur);
    }
#ifdef PS
    else if (psfile) {
      DrawColor(colcur);
      PSlinecap(TRUE);
      fprintf(psdata, "%d %d d\n", x, y);
      PSstroke(2);
    }
#endif
#ifdef META
    else {
      metafilldes = colcur;
      MetaSelect();
      MetaEllipse(x-metawid/2, y-metawid/2, x+metawid/2, y+metawid/2);
    }
#endif
  }
#ifdef X11
  else
    XDrawPoint(disp, pixmap, gc, x, y);
#endif
#ifdef MSG
  else
    _setpixel(offsetx + x, offsety + y);
#endif
}


/* Draw dot a little larger than just a single pixel at specified location. */

void DrawSpot(x, y)
int x, y;
{
#ifdef PS
  if (psfile) {
    PSlinewidth(currentlinewidth*3);
    DrawPoint(x, y);
    PSlinewidth(currentlinewidth/3);
    return;
  }
#endif
#ifdef META
  if (metafile) {
    metafilldes = colcur;
    MetaSelect();
    MetaEllipse(x-metawid, y-metawid, x+metawid, y+metawid);
    return;
  }
#endif
  DrawPoint(x, y);
  DrawPoint(x, y-1);
  DrawPoint(x-1, y);
  DrawPoint(x+1, y);
  DrawPoint(x, y+1);
}


/* Draw a filled in block, defined by the corners of its rectangle. */

void DrawBlock(x1, y1, x2, y2)
int x1, y1, x2, y2;
{
  int x, y;

  if (xfile) {
    if (xbitmap) {
      for (y = y1; y <= y2; y++)         /* For bitmap, we have to  */
        for (x = x1; x <= x2; x++)       /* just fill in the array. */
          PSET(bm, x, y, colcur);
    }
#ifdef PS
    else if (psfile) {
      DrawColor(colcur);
      fprintf(psdata, "%d %d %d %d rf\n",
        x1-metawid/4, y1-metawid/4, x2-x1+metawid/4, y2-y1+metawid/4);
    }
#endif
#ifdef META
    else {
      metafilldes = colcur;
      MetaSelect();
      MetaRectangle(x1-metawid/2, y1-metawid/2, x2+metawid/2, y2+metawid/2);
    }
#endif
  }
#ifdef X11
  else
    XFillRectangle(disp, pixmap, gc, x1, y1, x2, y2);
#endif
#ifdef MSG
  else
    _rectangle(_GFILLINTERIOR,
      offsetx + x1, offsety + y1, offsetx + x2, offsety + y2);
#endif
}


/* Draw a rectangle on the screen with specified thickness. This is just   */
/* like DrawBlock() except that we are only drawing the edges of the area. */

void DrawBox(x1, y1, x2, y2, xsiz, ysiz)
int x1, y1, x2, y2, xsiz, ysiz;
{
#ifdef META
  if (metafile)
    /* For thin boxes in metafiles, we can just output one rectangle record */
    /* instead of drawing each side separately as we have to do otherwise.  */
    if (xsiz <= 1 && ysiz <= 1) {
      metafilldes = 16;              /* Specify a hollow fill brush. */
      MetaSelect();
      MetaRectangle(x1, y1, x2, y2);
      return;
    }
#endif
  DrawBlock(x1, y1, x2, y1 + ysiz - 1);
  DrawBlock(x1, y1 + ysiz, x1 + xsiz - 1, y2 - ysiz);
  DrawBlock(x2 - xsiz + 1, y1 + ysiz, x2, y2 - ysiz);
  DrawBlock(x1, y2 - ysiz + 1, x2, y2);
}


/* Clear and erase the graphics screen or bitmap contents. */

void DrawClearScreen()
{
#ifdef PS
  if (psfile) {
    /* For PostScript charts first output page orientation information. */
    if (!epsfile) {
      if (modex == MODEL || modex == MODEW ||
        (modex == MODEZ && (todisplay & DASHZ0) == 0)) {
        /* Chartx and charty are reversed for Landscape mode. */
        fprintf(psdata, "%d %d translate\n",
          ((int)(8.5*72) + charty)/2, (chartx + 11*72)/2);
        fprintf(psdata, "-90 rotate\n");
      } else {
        /* Most charts are in Portrait mode */
        fprintf(psdata, "%d %d translate\n", (int)(8.5*72/2) - chartx/2,
        charty/2 + 11*72/2);
      }
    } else
      fprintf(psdata, "0 %d translate\n", charty);
    fprintf(psdata, "1 -1 scale\n");
    scale *= PSMUL; chartx *= PSMUL; charty *= PSMUL;
    fprintf(psdata, "1 %d div dup scale\n", PSMUL);
  }
#endif
#ifdef META
  if (metafile)
    MetaInit();    /* For metafiles first go write our header information. */
#endif

  /* Hack: If a comparison relationship chart is set and we're in the -Z  */
  /* horizon or -S space graphics chart modes (which normally is just the */
  /* same as single chart graphics) don't actually clear the screen.      */

  if (relation <= DASHr0 && xnow > 0 && (modex == MODEZ || modex == MODES))
    return;
#ifdef MSG
  if (!xfile)
    _clearscreen(_GCLEARSCREEN);
#endif
  DrawColor(off);
  DrawBlock(0, 0, chartx - 1, charty - 1);    /* Clear bitmap screen. */
}


/* Draw a line on the screen, specified by its endpoints. In addition, we */
/* have specified a skip factor, which allows us to draw dashed lines.    */

void DrawDash(x1, y1, x2, y2, skip)
int x1, y1, x2, y2, skip;
{
  static word PTR poly;
  int x = x1, y = y1, xadd, yadd, yinc, xabs, yabs, i, j = 0;

  if (skip < 0)
    skip = 0;
#ifdef ISG
  if (!xfile) {
    if (!skip) {
#ifdef X11
      /* For non-dashed X window lines, let's have the Xlib do it for us. */

      XDrawLine(disp, pixmap, gc, x1, y1, x2, y2);
#else
      /* For non-dashed lines, let's have the graphics library do it for us. */

      _moveto(offsetx + x1, offsety + y1);
      _lineto(offsetx + x2, offsety + y2);
#endif
      return;
    }
  }
#endif /* ISG */

#ifdef PS
  if (psfile) {

    /* For PostScript charts we can save file size if we output a LineTo  */
    /* command when the start vertex is the same as the end vertex of the */
    /* previous line drawn, instead of writing out both vertices.         */

    PSlinecap(TRUE);
    PSdash(skip);
    if (xpen != x1 || ypen != y1)
      fprintf(psdata, "%d %d %d %d l\n", x1, y1, x2, y2);
    else
      fprintf(psdata, "%d %d t\n", x2, y2);
    xpen = x2; ypen = y2;
    PSstroke(2);
    return;
  }
#endif
#ifdef META
  if (metafile) {

    /* For metafile charts we can really save file size for consecutive */
    /* lines sharing endpoints by consolidating them into a PolyLine.   */

    if (xpen != x1 || ypen != y1) {
      metalinedes = (metalinedes & 15) + 16*(skip > 3 ? 3 : skip);
      MetaSelect();
      poly = metacur;
      MetaRecord(8, 0x325);    /* Polyline */
      MetaWord(2); MetaWord(x1); MetaWord(y1);
    } else {
      *poly += 2;
      (*(poly+3))++;
      /* Note: We should technically update the max record size in the   */
      /* file header if need be here too, but it doesn't seem necessary. */
    }
    MetaWord(x2); MetaWord(y2);
    xpen = x2; ypen = y2;
    return;
  }
#endif

  /* If none of the above cases hold, we have to draw the line dot by dot. */

  xadd = x2 - x1 >= 0 ? 1 : 3;
  yadd = y2 - y1 >= 0 ? 2 : 4;
  xabs = abs(x2 - x1);
  yabs = abs(y2 - y1);

  /* Technically what we're doing here is drawing a line which is more    */
  /* horizontal then vertical. We always increment x by 1, and increment  */
  /* y whenever a fractional variable passes a certain amount. For lines  */
  /* that are more vertical than horizontal, we just swap x and y coords. */

  if (xabs < yabs) {
    SWAP(xadd, yadd);
    SWAP(xabs, yabs);
  }
  yinc = (xabs >> 1) - ((xabs & 1 ^ 1) && xadd > 2);
  for (i = xabs+1; i; i--) {
    if (j < 1)
      DrawPoint(x, y);
    j = j < skip ? j+1 : 0;
    switch (xadd) {
    case 1: x++; break;
    case 2: y++; break;
    case 3: x--; break;
    case 4: y--; break;
    }
    yinc += yabs;
    if (yinc - xabs >= 0) {
      yinc -= xabs;
      switch (yadd) {
      case 1: x++; break;
      case 2: y++; break;
      case 3: x--; break;
      case 4: y--; break;
      }
    }
  }
}


/* Draw a normal line on the screen; however, if the x coordinates are close */
/* to either of the two given bounds, then we assume that the line runs off  */
/* one side and reappears on the other, so draw the appropriate two lines    */
/* instead. This is used by the Ley line and astro-graph routines, which     */
/* draw lines running around the world and hence off the edges of the maps.  */

void DrawWrap(x1, y1, x2, y2, xmin, xmax)
int x1, y1, x2, y2;
{
  int xmid, ymid, i;

  if (x1 < 0) {           /* Special case for drawing world map. */
    DrawPoint(x2, y2);
    return;
  }
  xmid = (xmax-xmin) / 2;

  /* If endpoints aren't near opposite edges, just draw the line and return. */

  if (abs(x2-x1) < xmid) {
    DrawLine(x1, y1, x2, y2);
    return;
  }
  i = (xmax-xmin+1) + (x1 < xmid ? x1-x2 : x2-x1);

  /* Determine vertical coordinate where our line runs off edges of screen. */

  ymid = y1+(int)((real)(y2-y1)*
    (x1 < xmid ? (real)(x1-xmin) : (real)(xmax-x1))/(real)i + ROUND);
  DrawLine(x1, y1, x1 < xmid ? xmin : xmax, ymid);
  DrawLine(x2 < xmid ? xmin : xmax, ymid, x2, y2);
}


/* This routine, and its companion below, clips a line defined by its  */
/* endpoints to either above some line y=c, or below some line y=c. By */
/* passing in parameters in different orders, we can clip to vertical  */
/* lines, too. These are used by the DrawClip() routine below.         */

void ClipLesser(x1, y1, x2, y2, s)
int *x1, *y1, *x2, *y2, s;
{
  *x1 -= (int)((long)(*y1-s)*(*x2-*x1)/(*y2-*y1));
  *y1 = s;
}

void ClipGreater(x1, y1, x2, y2, s)
int *x1, *y1, *x2, *y2, s;
{
  *x1 += (int)((long)(s-*y1)*(*x2-*x1)/(*y2-*y1));
  *y1 = s;
}


/* Draw a line on the screen. This is just like DrawLine() routine earlier; */
/* however, first clip the endpoints to the window viewport before drawing. */

void DrawClip(x1, y1, x2, y2, xl, yl, xh, yh, skip)
int x1, y1, x2, y2, xl, yl, xh, yh, skip;
{
  if (x1 < xl)
    ClipLesser (&y1, &x1, &y2, &x2, xl);    /* Check left side of window. */
  if (x2 < xl)
    ClipLesser (&y2, &x2, &y1, &x1, xl);
  if (y1 < yl)
    ClipLesser (&x1, &y1, &x2, &y2, yl);    /* Check top side of window.  */
  if (y2 < yl)
    ClipLesser (&x2, &y2, &x1, &y1, yl);
  if (x1 > xh)
    ClipGreater(&y1, &x1, &y2, &x2, xh);    /* Check right of window.  */
  if (x2 > xh)
    ClipGreater(&y2, &x2, &y1, &x1, xh);
  if (y1 > yh)
    ClipGreater(&x1, &y1, &x2, &y2, yh);    /* Check bottom of window. */
  if (y2 > yh)
    ClipGreater(&x2, &y2, &x1, &y1, yh);
  DrawDash(x1, y1, x2, y2, skip);           /* Go draw the line.       */
}


/* Draw a circle or ellipse inside the given bounding rectangle. */

void DrawEllipse(x1, y1, x2, y2)
int x1, y1, x2, y2;
{
  int x, y, rx, ry, m, n, u, v, i;

  if (xfile) {
    x = (x1+x2)/2; y = (y1+y2)/2; rx = (x2-x1)/2; ry = (y2-y1)/2;
    if (xbitmap) {
      InitCircle();
      m = x + rx; n = y;
      for (i = 0; i <= DEGR; i += DEGINC) {
        u = x + (int)((real)rx*circ->x[i]); v = y + (int)((real)ry*circ->y[i]);
        u = MIN(u, x + rx-1); v = MIN(v, y + ry-1);
        DrawLine(m, n, u, v);
        m = u; n = v;
      }
    }
#ifdef PS
    else if (psfile) {
      PSlinecap(FALSE);
      PSforcestroke();
      fprintf(psdata, "%d %d %d %d el\n", rx, ry, x, y);
    }
#endif
#ifdef META
    else {
      metafilldes = 16;    /* Specify a hollow fill brush. */
      MetaSelect();
      MetaEllipse(x1+metawid/3, y1+metawid/3, x2+metawid/3, y2+metawid/3);
    }
#endif
  }
#ifdef X11
  else
    XDrawArc(disp, pixmap, gc, x1, y1, x2-x1, y2-y1, 0, 360*64);
#endif
#ifdef MSG
  else
    _ellipse(_GBORDER, offsetx + x1, offsety + y1, offsetx + x2, offsety + y2);
#endif
}


/* Print a string of text on the graphic window at specified location. To  */
/* do this we either use Astrolog's own "font" (6x10) and draw each letter */
/* separately, or else specify system fonts for PostScript and metafiles.  */

void DrawText(string, x, y, base)
char *string;
int x, y, base;
{
  int s = scale, c = colcur, len;

  len = StringLen(string);
  scale = 100 * scalet;
  x += SCALE;
  if (base >= FALSE)
    x -= len*FONTX*SCALE/2;
  if (!base)
    y -= FONTY*SCALE/2;
  else
    y -= (FONTY-3)*SCALE;
  DrawColor(off);
  DrawBlock(x, y, x+FONTX*SCALE*len, y+(FONTY-1)*SCALE);
  DrawColor(c);
#ifdef PS
  if (psfile && xfont) {
    PSfont(4);
    fprintf(psdata, "%d %d(%s)center\n",
      x + FONTX*SCALE*len/2, y + FONTY*SCALE/2, string);
    scale = s;
    return;
  }
#endif
  while (*string) {
#ifdef META
    if (metafile && xfont) {
      metafontdes = 3;
      metatxtcdes = colcur;
      metatxtades = 0x6 | 0 /* Center | Top */;
      MetaSelect();
      MetaTextOut(x, y, 1);
      MetaWord(MAKEWORD(*string, 0));
    } else
#endif
      DrawTurtle(asciidraw[*string-' '], x, y);
    x += FONTX*SCALE;
    string++;
  }
  scale = s;
}


/* Draw the glyph of a sign at particular coordinates on the screen.    */
/* To do this we either use Astrolog's turtle vector representation or  */
/* we may specify a system font character for PostScript and metafiles. */

void DrawSign(i, x, y)
int i, x, y;
{
#ifdef PS
  if (psfile && xfont) {
    PSfont(1);
    fprintf(psdata, "%d %d(%c)center\n", x, y, 'A' + i - 1);
    return;
  }
#endif
#ifdef META
  if (metafile && xfont) {
    metafontdes = 1;
    metatxtcdes = colcur;
    metatxtades = 0x6 | 0x8 /* Center | Bottom */;
    MetaSelect();
    MetaTextOut(x, y+4*SCALE, 1);
    MetaWord(MAKEWORD('^' + i - 1, 0));
    return;
  }
#endif
  DrawTurtle(signdraw[i], x, y);
}


/* Draw the number of a house at particular coordinates on the screen. */
/* We either use a turtle vector or write a number in a system font.   */

void DrawHouse(i, x, y)
int i, x, y;
{
#ifdef PS
  if (psfile && xfont) {
    PSfont(3);
	  fprintf(psdata, "%d %d(%d)center\n", x, y, i);
    return;
  }
#endif
#ifdef META
  if (metafile && xfont) {
    metafontdes = 2;
    metatxtcdes = colcur;
    metatxtades = 0x6 | 0x8 /* Center | Bottom */;
    MetaSelect();
    MetaTextOut(x, y+3*SCALE, 1 + (i>9));
    MetaWord(MAKEWORD(i > 9 ? '1' : '0'+i, i > 9 ? '0'+i-10 : 0));
    return;
  }
#endif
  DrawTurtle(housedraw[i], x, y);
}


/* Draw the glyph of an object at particular coordinates on the screen. */

void DrawObject(i, x, y)
int i, x, y;
{
  char glyph[4];
#ifdef PS
  static char objectchar[] = "dQRSTUVWXYZ     < ba ";
#endif
#ifdef META
  char c = 0;
#endif

  if (!xlabel)    /* If we are inhibiting labels, then do nothing. */
    return;

  /* For other planet centered charts, we have to remember that that     */
  /* particular planet's index now represents the Earth. If we are given */
  /* that index to draw, then change it so we draw the Earth instead.    */

  if (modex != MODES &&
    ((i == centerplanet && i > _MOO) || (centerplanet == 0 && i == _SUN)))
    i = 0;
  DrawColor(objectcolor[i]);
  if (i <= BASE) {
#ifdef PS
    if (psfile && xfont == 1 && objectchar[i] != ' ') {
      PSfont(2);
      fprintf(psdata, "%d %d(%c)center\n", x, y, objectchar[i]);
      return;
    }
#endif
#ifdef META
    if (metafile && xfont == 1) {
      if (i < _SUN)
        c = ';';
      else if (i <= _PLU) c = 'Q' + i - 1;
      else if (i == _NOD) c = '<';
      else if (i == _MC)  c = 'b';
      else if (i == _ASC) c = 'a';
    }
    if (c) {
      metafontdes = 4;
      metatxtcdes = colcur;
      metatxtades = 0x6 | 0x8 /* Center | Bottom */;
      MetaSelect();
      MetaTextOut(x, y+5*SCALE, 1);
      MetaWord(MAKEWORD(c, 0));
      return;
    }
#endif
    DrawTurtle(objectdraw[i], x, y);

  /* Normally we can just go draw the glyph; however, stars don't have */
  /* glyphs, so for these draw their three letter abbreviation.        */

  } else {
    sprintf(glyph, "%c%c%c", OBJNAM(i));
    DrawText(glyph, x, y, FALSE);
  }
}


/* Draw the glyph of an aspect at particular coordinates on the screen. */
/* Again we either use Astrolog's turtle vector or a system Astro font. */

void DrawAspect(i, x, y)
int i, x, y;
{
#ifdef PS
  static char aspectchar[] = "!\"#$'&%()+-       ";
#endif
#ifdef META
  char c = 0;
#endif

#ifdef PS
  if (psfile && xfont == 1 && aspectchar[i-1] != ' ') {
    PSfont(2);
    fprintf(psdata, "%d %d(%c)center\n", x, y, aspectchar[i-1]);
    return;
  }
#endif
#ifdef META
  if (metafile && xfont == 1) {
    if (i <= _TRI)
      c = '!' + i - 1;
    else if (i == _SEX) c = '\'';
    else if (i == _INC) c = '&';
    else if (i == _SSX) c = '%';
    else if (i == _SSQ) c = '(';
    else if (i == _SES) c = ')';
    else if (i == _QUI) c = '+';
    else if (i == _BQN) c = '-';
  }
  if (c) {
    metafontdes = 4;
    metatxtcdes = colcur;
    metatxtades = 0x6 | 0x8 /* Center | Bottom */;
    MetaSelect();
    MetaTextOut(x, y+5*SCALE, 1);
    MetaWord(MAKEWORD(c, 0));
    return;
  }
#endif
  DrawTurtle(aspectdraw[i], x, y);
}


/* Convert a string segment to a positive number, updating the string to  */
/* point beyond the number chars. Return 1 if the string doesn't point to */
/* a numeric value. This is used by the DrawTurtle() routine to extract   */
/* motion vector quantities from draw strings, e.g. the "12" in "U12".    */

int IntInString(str)
char **str;
{
  int num = 0, i = 0;

  loop {
    if (**str < '0' || **str > '9')
      return num > 0 ? num : (i < 1 ? 1 : 0);
    num = num*10+(**str)-'0';
    (*str)++;
    i++;
  }
}


/* This routine is used to draw complicated objects composed of lots of line */
/* segments on the screen, such as all the glyphs and coastline pieces. It   */
/* is passed in a string of commands defining what to draw in relative       */
/* coordinates. This is a copy of the format of the BASIC draw command found */
/* in PC's. For example, "U5R10D5L10" means go up 5 dots, right 10, down 5,  */
/* and left 10 - draw a box twice as wide as it is high.                     */

void DrawTurtle(lin, x0, y0)
char *lin;
int x0, y0;
{
  int i, j, x, y, deltax, deltay, blank, noupdate;
  char cmd;

  turtlex = x0; turtley = y0;
  while (cmd = CAP(*lin)) {
    lin++;

    /* 'B' prefixing a command means just move the cursor, and don't draw. */

    if (blank = cmd == 'B') {
      cmd = CAP(*lin);
      lin++;
    }

    /* 'N' prefixing a command means don't update cursor when done drawing. */

    if (noupdate = cmd == 'N') {
      cmd = CAP(*lin);
      lin++;
    }

    /* Here we process the eight directional commands. */

    switch (cmd) {
    case 'U': deltax =  0; deltay = -1; break;      /* Up    */
    case 'D': deltax =  0; deltay =  1; break;      /* Down  */
    case 'L': deltax = -1; deltay =  0; break;      /* Left  */
    case 'R': deltax =  1; deltay =  0; break;      /* Right */
    case 'E': deltax =  1; deltay = -1; break;      /* NorthEast */
    case 'F': deltax =  1; deltay =  1; break;      /* SouthEast */
    case 'G': deltax = -1; deltay =  1; break;      /* SouthWest */
    case 'H': deltax = -1; deltay = -1; break;      /* NorthWest */
    default: PrintError("Bad turtle subcommand.");  /* Shouldn't happen. */
    }
    x = turtlex;
    y = turtley;
    j = IntInString(&lin)*SCALE;    /* Figure out how far to draw. */
    if (blank) {
      turtlex += deltax*j;
      turtley += deltay*j;
    } else {
      if (psfile || metafile) {
        turtlex += deltax*j;
        turtley += deltay*j;
        DrawLine(x, y, turtlex, turtley);
      } else {
        DrawPoint(turtlex, turtley);
        for (i = 0; i < j; i++) {
          turtlex += deltax;
          turtley += deltay;
          DrawPoint(turtlex, turtley);
        }
      }
      if (noupdate) {
        turtlex = x;
        turtley = y;
      }
    }
  }
}
#endif /* GRAPH */

/* xgeneral.c */
