plt - Software for 2D Plots 2.5

File: <base>/plt/src/text.c (14,729 bytes)
/* file: text.c		Paul Albrecht		September 1987
			Last revised:	      14 November 2002
Text-handling functions for plt

Copyright (C) Paul Albrecht 1988

Recent changes (by George Moody, george@mit.edu):
  30 March 2001: added label concatenation
  11 April 2001: fixed nasty buffer overrun in MakeGraphTitle, general cleanup
  21 October 2002: moved formerly global variables here from plt.h
  14 November 2002: fixed missing casts in azmem calls
_______________________________________________________________________________
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., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA.

You may contact the maintainer by e-mail (george@mit.edu) or postal mail
(MIT Room E25-505A, Cambridge, MA 02139 USA).  For updates to this software,
please visit PhysioNet (http://www.physionet.org/).
_______________________________________________________________________________
*/

#include <time.h>
#include "plt.h"


/* Font group object definition. */
typedef	struct {
    char *name;		/* name of this font group 			*/
    char *font;		/* name of the font 				*/
    double ps;		/* point size of the font group			*/
    double lw;		/* line width for the font group		*/
    double gray;	/* gray scale:  0=black, 1=white		*/
    char *lm;		/* line mode associated with the font group	*/
    char *color;	/* color of the font				*/
    char *gelem;	/* where on the graph this font group is used	*/
} *FgPtr;

/* Prototypes of functions defined in this module. */
void TextInit(int psdel);
void MakeGraphTitle(char **argv);
void TextDraw(LblPtr l);
void ReadStrings(PStrPtr ps);
char *JustMap(char *userTbc);
void FontGroupSelect(char *dflt_fgName, char *fgName);
void FontGroupDef(char *fgName, char *specs);
int getstr(char **cpp, char **strp);

static FgPtr FontGroupCat(char *fgName, char *changes);
static void FontGroupModify(FgPtr fg, char *changes);
static FgPtr FontGroupFind(char *fgName, Boolean mustFind);
static FgPtr FontGroupInit(char *fgName);
static int getdbl(char **cpp, double *dblp);
static void checklm(FgPtr fg);

/* TextInit initalizes the font groups a, f, l, p, and t.  Its single input
   specifies how much the default point sizes should be increased (if psdel>0)
   or decreased.  plt's main function calls TextInit(0), and WindowDef calls
   TextInit(-1) in order to decrease point sizes slightly when creating
   subwindows. */
void TextInit(int psdel)
{
  char changes[MAXLINE];

  /* The "Ea", "Ef", etc. attributes are relics of the Masscomp bit-mapped
     display driver from classic plt.  They appear to be vestigial, but I've
     left them in for now.  -- GBM */
  sprintf(changes, "P%g,W3,Ea", 14.0+psdel);
  FontGroupDef("a", changes);
  sprintf(changes, "P%g,W3,Ef", 14.0+psdel);
  FontGroupDef("f", changes);
  sprintf(changes, "P%g,W3,El", 15.0+psdel);
  FontGroupDef("l", changes);
  FontGroupDef("p", "P12,W2,Ep");
  sprintf(changes, "P%g,W4,Et", 16.0+psdel);
  FontGroupDef("t", changes);
}

/* Draw the plot title (creating one if none was specified). */
void MakeGraphTitle(char **argv)
{
  Ptype	n, x, y, xsize, ysize;
  char *ptr, *ctime(), *getenv(), str[50], titleText[60], title2[40];
  int alen, targmax;
  time_t seconds;

  if (!(omode & TITLES))
    return;
  if (title.text == 0) {
    /* No title specified by user -- generate a title from the argument list,
       user name, date, and time. */
    titleText[0] = 0;
    /* Generate the end of the title first. */
    strcpy(title2, "-- <");
    if (ptr = getenv("LOGNAME")) {
      strncat(title2, ptr, 20);
      strcat(title2, "-");
    }
    time(&seconds);
    strftime(title2+strlen(title2), 14, "%H:%M-%b %e>", localtime(&seconds));
    targmax = sizeof(titleText)-strlen(title2)-6; /* allow space for " ... " */
    ptr = titleText;
    /* Allow up to targmax characters from the argument list in the title. */
    while (*argv) {
      int titlelen = 0;

      if (titlelen + (alen = strlen(*argv)) < targmax) {
	strcpy(ptr, *argv++);
	ptr += alen;
	*ptr++ = ' ';
	titlelen += alen+1;
      }
      else {
	strncpy(ptr, *argv, alen = targmax - titlelen);
	ptr += alen;
	strcpy(ptr, " ... ");
	ptr += 5;
	break;
      }
    }
    strcpy(ptr, title2);
    title.text = titleText;
  }
  else if (strlen(title.text) == 0)
    return;
  FontGroupSelect("t", title.fgName);
  x = (xwmin + xwmax)/2;
  if (theight == 0) {
    strsize(title.text, &xsize, &ysize, 0.0);
    y = ywmax + (int)((yfract+0.4)*ysize + yfract*yinch/12);
    title.just = "CC";	/* changed from "CB" -- GBM */
  }
  else y = ywmin + (int)((ywmax-ywmin)*theight);
  plabel(title.text, x, y, title.just, 0.0);
}

/* Draw a user-defined label. */
void TextDraw(LblPtr l)
{
  short n, nc = 0, x, y, xsize, ysize;
  char c = 1, lbl[MAXLINE];

  if (l->text == 0 || *l->text == 0)
    return;
  FontGroupSelect("l", l->fgName);
  transform(&x, &y, l->xpos, l->ypos, 0.5, l->coord);
  if (l->xpos == DEFAULT) x = DEFAULT;
  if (l->ypos == DEFAULT) y = DEFAULT;
  while (c) {
    n = 0;
    do {
      c = l->text[nc++];
      lbl[n++] = (c == '\n') ? 0 : c;
    } while (c && c != '\n');
    plabel(lbl, x, y, l->just, l->angle);
    strsize(lbl, &xsize, &ysize, l->angle);
    if (fabs(l->angle) < 1) {
      if (y != DEFAULT) y -= ysize;
    }
    else
      if (x != DEFAULT) x += xsize;
  }
}

/* Read or append to a string array. */
void ReadStrings(PStrPtr ps)
{
  FILE *fp;
  short k;
  char c, *str, line[200];

  if (ps->file) {
    fp = fopen(ps->str, "r");
    if (fp == 0) err(YES, "Can't open string file %s", ps->str);
  }
  else str = ps->str;
  while (YES) {
    if (ps->file) {
      if (fgets(line, sizeof(line), fp) == NULL) break;
      k = strlen(line);
      if (line[k-1] == '\n') line[k-1] = 0;
    }
    else {
      if (*str == 0) break;
      for (k = 0; (c = str[k]) && c != ' ' && c != '\n'; k++)
	line[k] = c;
      line[k] = 0;
      str += (c == 0) ? k : k+1;
    }
    if (ps->n == ps->nmax)
      ps->list = (char **)azmem(ps->list, (size_t *)&ps->nmax, 50,
				sizeof(*ps->list));
    ps->list[ps->n++] = StringSave(line);
  }
  ps->just = (ps->just == 0) ? "CC" : JustMap(ps->just);
  if(ps->file) fclose(fp);
}

/* Convert a text box coordinate to a two character string. */
char *JustMap(char *userTbc)
{
  static char tbc[3];
  short n;

  for (n = 0; n < 2;  n++) {
    tbc[n] = (userTbc[n] == 0) ? 0 : userTbc[n];
    if ('a' <= tbc[n] && tbc[n] <= 'z')
      tbc[n] += 'A' - 'a';
  }

  if (!tbc[1]) {
    /* This block of code supports obsolete syntax and will be removed in
       a future release. */
    switch (tbc[0]) {
    case 'C': tbc[1] = 'C'; break;
    case 'D': tbc[0] = 'L'; /* fall through to case 'L' */
    case 'L': tbc[1] = 'B'; break;
    case 'U': tbc[0] = 'R'; /* fall through to case 'R' */
    case 'R': tbc[1] = 'B'; break;
    default:  err(YES, "unrecognized text box coordinate `%s'\n", userTbc);
      break;
    }
    err(NO, "Warning: obsolete text box coordinate `%s' converted to `%s'\n",
	    userTbc, tbc);
  }
  
  return (StringSave(tbc));
}

/* Set font type, point size, etc. */
void FontGroupSelect(char *dflt_fgName, char *fgName)
{
  PtrUnion arg0, arg1;
  FgPtr	fg;
  double scl;

  if (fgName != 0) {
    if ((fg=FontGroupFind(fgName,NO)) == 0)
      /* use fgName as changes to make to default fgroup */
      fg = FontGroupCat(dflt_fgName, fgName);
  }
  else {
    if (dflt_fgName == 0)
      err(YES, "Bad call to FontGroupSelect()");
    fg = FontGroupFind(dflt_fgName, YES);
  }

  /* The "graph element" is a relic of the Masscomp bitmap display driver from
     classic plt.  It appears to be vestigial, but I've left it in for now.
     -- GBM */
  arg0.c = fg->gelem;
  special(GRAPHELEMENT, arg0, arg1);

  /* Set the line mode (solid, dotted, etc.;  but only if it hasn't been
     changed).  This must be done before setting the point size, according
     to an old comment found here -- but why? */
  if (strcmp(old_lm,fg->lm) != 0) {
    arg0.c = fg->lm;
    special(SETLINEMODE, arg0, arg1);
  }

  /* Set the grey level, if it hasn't been set previously or if it has
     changed.  The interaction with the color setting is tricky. */
  if (oldGrayLevel < 0 || oldGrayLevel != fg->gray) {
    static char colorstring[8];

    oldGrayLevel = fg->gray;
    arg0.d = &oldGrayLevel;
    special(SETGRAY, arg0, arg1);
    sprintf(colorstring, "gray%02d", (int)(oldGrayLevel*100));
    fg->color = colorstring;
  }

  /* Set the foreground color. (The background color is always white in the
     current version of plt.) */
  if (fg->color != NULL && *fg->color != '\0') {
    arg0.c = fg->color;
    special(SETCOLOR, arg0, arg1);
  }

  /* Set the line width, if it hasn't been set previously or if it has
     changed. */
  if (old_lw < 0 ||  old_lw != fg->lw) {
    old_lw = fg->lw;
    arg0.d = &old_lw;
    special(SETLINEWIDTH, arg0, arg1);
  }

  /* Set the point size and the font family if they have not been set
     previously or if either has changed. */
  if (!fixed_font &&
      !(old_font && strcmp(fg->font, old_font) == 0 && fg->ps == old_ps)) {
	scl  = fg->ps/default_ps;
	chht = p->ch * scl + 0.5;
	chwd = p->cw * scl + 0.5;
	old_font = fg->font;
	old_ps = fg->ps;
	arg0.c = old_font;
	arg1.d = &old_ps;
	special(SETFONT, arg0, arg1);
  }
}

/* Define the parameters for a font group. */
void FontGroupDef(char *fgName, char *specs)
{
  if (specs) {
    if (fgName == 0 || strcmp(fgName,"all") == 0) {
      FontGroupDef("a", specs);
      FontGroupDef("f", specs);
      FontGroupDef("l", specs);
      FontGroupDef("p", specs);
      FontGroupDef("t", specs);
    }	
    else {
      FgPtr fg;

      if ((fg=FontGroupFind(fgName, NO)) == 0)
	fg = FontGroupInit(fgName);
      FontGroupModify(fg, specs);
    }
  }
}

/* Create a new font group as a modified version of an old one. */
static FgPtr FontGroupCat(char *fgName, char *changes)
{
  FgPtr fg, FontGroupInit();
  char *newfgName = zmem(1, strlen(fgName) + strlen(changes) + 1);

  sprintf(newfgName, "%s%s", fgName, changes);
  if (fg = FontGroupFind(newfgName, NO)) return (fg);
  fg = FontGroupInit(newfgName);
  *fg = *FontGroupFind(fgName, YES);
  fg->name = StringSave(newfgName);
  FontGroupModify(fg, changes);
  return (fg);
}

/* Modify an existing font group according to the specs in `changes'. */
static void FontGroupModify(FgPtr fg, char *changes)
{
  FgPtr fgnew;
  short errFlag = NO;
  char *cp = changes, *fgName;

  /* Skip opening parenthesis or bracket if present. */
  if (*cp == '(' || *cp == '[') cp++;
  while (*cp) {
    if (*cp == ')' || *cp == ']') {
      cp++;
      while (*cp == ' ')
	cp++;
      if (*cp == 0)
	break;
      /* go get the error */
    }
    switch (*cp++) {
    case ' ':
    case ',':
    case '\t':
      break;
    case GCOLOR:
      errFlag |= !getstr(&cp, &fg->color);
      break;
    case GELEMENT:	/* vestigial, see comments above */
      errFlag |= !getstr(&cp, &fg->gelem);
      break;
    case GFONT:
      errFlag |= !getstr(&cp, &fg->font);
      break;
    case GGRAY:
      errFlag |= !getdbl(&cp,&fg->gray);
      if (fg->gray > 1) fg->gray = 1;
      break;
    case GLINEMODE:
      errFlag |= !getstr(&cp, &fg->lm);
      checklm(fg);
      break;
    case GPS:
      errFlag |= !getdbl(&cp, &fg->ps);
      break;
    case GLINEWIDTH:
      errFlag |= !getdbl(&cp, &fg->lw);
      break;
    default:
      cp--;
      errFlag |= !getstr(&cp, &fgName);
      fgnew = FontGroupFind(fgName, NO);
      if (fgnew == 0) errFlag = YES;
      else {
	fgName = fg->name;
	*fg = *fgnew;
	fg->name = fgName;
      }
      break;
    }
    if (errFlag) {
      err(YES, "Font group syntax error `%s'", changes);
      break;
    }
  }
}

static FgPtr fgrps;
static Uint nfgrps;

static FgPtr FontGroupFind(char *fgName, Boolean mustFind)
{
  FgPtr fg;
  short n;

  for (n = 0; n < nfgrps; n++) {
    fg = &fgrps[n];
    if (strcmp(fg->name, fgName) == 0)
      return (fg);
  }
  if (mustFind)
    err(YES, "No font group `%s'", fgName);
  return (0);
}

static FgPtr FontGroupInit(char *fgName)
{
  FgPtr fg;
  static Uint maxfgrps;

  if (nfgrps == maxfgrps)		
    fgrps = (FgPtr)azmem(fgrps, (size_t *)&maxfgrps, 6, sizeof(*fgrps));
  fg = &fgrps[nfgrps++];
  fg->name = fgName;
  fg->font = DEFAULT_FONT;
  fg->ps = DEFAULT_PS;
  fg->lw = DEFAULT_LW;
  fg->gray = PS_BLACK;
  fg->lm = "solid";
  fg->color = "black";
  fg->gelem = "";
  return (fg);
}

int getstr(char **cpp, char **strp)
{
  short n;
  char *cp, str[MAXLINE];

  while (**cpp == ' ')
    (*cpp)++;
  n = 0;
  cp = *cpp;
  while (*cp) {
    if (*cp == ' ' || *cp == ',' || *cp == ')' || *cp == ']')
      break;
    str[n++] = *cp++;
  }
  str[n] = 0;
  if (*cpp != cp) {
    *cpp = cp;
    *strp = StringSave(str);
    return (YES);
  }
  else return (NO);
}

static int getdbl(char **cpp, double *dblp)
{
  double dbl, dec;
  char *cp, operation;

  while (**cpp == ' ')
    (*cpp)++;
  dbl = 1e-6;
  cp = *cpp;

  if (*cp == '+' || *cp == '-' || *cp == '*' || *cp == '/')
    operation = *cp++;
  else
    operation = 0;

  while ('0' <= *cp && *cp <= '9') {
    dbl = 10*dbl + ((*cp++ -'0')&0177);
  }
  if (*cp == '.') {
    dec = .1;
    cp++;
    while ('0' <= *cp && *cp <= '9') {
      dbl += dec * ((*cp++ -'0')&0177);
      dec *= 0.1;
    }
  }

  if (*cpp != cp) {
    *cpp = cp;
    switch(operation) {
    case '+':	*dblp += dbl;	break;
    case '-':	*dblp -= dbl;	break;
    case '*':	*dblp *= dbl;	break;
    case '/':	*dblp /= dbl;	break;
    default:	*dblp = dbl;	break;
    }
    return (YES);
  }
  else return (NO);
}

static void checklm(FgPtr fg)
{
  static struct	{
    char *name;
    short n;
  } *l, lm[] = {	/* the first 5 must appear first !!!!!!! ... */
    "solid", 0,
    "dotted", 1,
    "shortdashed", 2,
    "dotdashed", 3,
    "longdashed", 4,
    "0", 0,
    "1", 1,
    "2", 2,
    "3", 3,
    "4", 4,
    "5", 5,
    "dash", 2 };

  for (l = lm; l < ENDP(lm); l++)
    if (strcmp(l->name, fg->lm) == 0 ||
	strncmp(l->name, fg->lm, strlen(fg->lm)) == 0) {
      fg->lm = lm[l->n].name;	/* ... and this is why !!!!!!!! */
      return;
    }
  err(YES, "No linemode type `%s'", fg->lm);
}