plt - Software for 2D Plots 2.5
(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);
}