/*
 *      Text.c
 *
 *      The AthenaTools Plotter Widget Set - Version 6.0
 *
 *      klin, Tue Jul  7 13:59:47 1992
 *      klin, Mon Jul 27 14:18:02 1992, patchlevel 2
 *                                      AtTextDestroy() generates a core
 *                                      dump when destroying NULL. Reported
 *                                      and fixed by Gustaf Neumann
 *                                      (neumann@dec4.wu-wien.ac.at)
 *                                      AtTextDraw() and AtTextDrawJustified()
 *                                      changed for drawing to a pixmap
 *      klin, Sat Aug 15 10:31:50 1992, patchlevel 4
 *                                      Changed <At/..> to <X11/At/..>.
 */
static char SCCSid[] = "@(#) Plotter V6.0  92/08/15  Text.c";

/*

Copyright 1992 by University of Paderborn
Copyright 1990,1991 by the Massachusetts Institute of Technology

All rights reserved.

Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of the firms, institutes
or employers of the authors not be used in advertising or publicity
pertaining to distribution of the software without specific, written
prior permission.

THE AUTHORS AND THEIR FIRMS, INSTITUTES OR EMPLOYERS DISCLAIM ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE AUTHORS AND THEIR FIRMS,
INSTITUTES OR EMPLOYERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.

*/

#include <X11/At/Text.h>

#include <ctype.h>

#define New(type) (type *)malloc(sizeof(type))

#define    sym_forall           34
#define    sym_exists           36
#define    sym_ni               39
#define    sym_ast              42
#define    sym_cong             64
#define    sym_therefore        92
#define    sym_perp             94
#define    sym_similar          126
#define    sym_prime            162
#define    sym_lte              163
#define    sym_infty            165
#define    sym_function         166
#define    sym_clubs            167
#define    sym_diamonds         168
#define    sym_hearts           169
#define    sym_spades           170
#define    sym_leftrightarrow   171
#define    sym_leftarrow        172
#define    sym_uparrow          173
#define    sym_rightarrow       174
#define    sym_downarrow        175
#define    sym_degr             176
#define    sym_pm               177
#define    sym_dblprime         178
#define    sym_gte              179
#define    sym_times            180
#define    sym_propto           181
#define    sym_partial          182
#define    sym_bullet           183
#define    sym_div              184
#define    sym_neq              185
#define    sym_eqv              186
#define    sym_approx           187
#define    sym_ldots            188
#define    sym_aleph            192
#define    sym_iset             193
#define    sym_rset             194
#define    sym_weierstrass      195
#define    sym_otimes           196
#define    sym_oplus            197
#define    sym_emptyset         198
#define    sym_inter            199
#define    sym_union            200
#define    sym_prsupset         201
#define    sym_supset           202
#define    sym_notsubset        203
#define    sym_prsubset         204
#define    sym_subset           205
#define    sym_in               206
#define    sym_notin            207
#define    sym_angle            208
#define    sym_nabla            209
#define    sym_registered       210
#define    sym_copyright        211
#define    sym_trademark        212
#define    sym_cdot             215
#define    sym_not              216
#define    sym_and              217
#define    sym_or               218
#define    sym_dbleftrightarrow 219
#define    sym_dbleftarrow      220
#define    sym_dbuparrow        221
#define    sym_dbrightarrow     222
#define    sym_dbdownarrow      223


#define sym_alpha       97
#define sym_beta        98
#define sym_chi 99
#define sym_delta       100
#define sym_epsilon     101
#define sym_phi 102
#define sym_gamma       103
#define sym_eta 104
#define sym_iota        105
#define sym_kappa       107
#define sym_lambda      108
#define sym_mu  109
#define sym_nu  110
#define sym_omicron     111
#define sym_pi  112
#define sym_theta       113
#define sym_rho 114
#define sym_sigma       115
#define sym_tau 116
#define sym_upsilon     117
#define sym_omega       119
#define sym_xi  120
#define sym_psi 121
#define sym_zeta        122

#define sym_Alpha       65
#define sym_Beta        66
#define sym_Chi 67
#define sym_Delta       68
#define sym_Epsilon     69
#define sym_Phi 70
#define sym_Gamma       71
#define sym_Eta 72
#define sym_Iota        73
#define sym_Kappa       75
#define sym_Lambda      76
#define sym_Mu  77
#define sym_Nu  78
#define sym_Omicron     79
#define sym_Pi  80
#define sym_Theta       81
#define sym_Rho 82
#define sym_Sigma       83
#define sym_Tau 84
#define sym_Upsilon     85
#define sym_Omega       87
#define sym_Xi  88
#define sym_Psi 89
#define sym_Zeta        90

#define sym_theta1      74
#define sym_sigma1      86
#define sym_phi1        106
#define sym_omega1      118
#define sym_Upsilon1 161


#define    env_bold             1       /* 'b' */
#define    env_ita              2       /* 'i' */
#define    env_greek            3       /* 'g' */
#define    env_roman            4       /* 'r' - change to roman font */
#define    env_bigger           5
#define    env_smaller          6
#define    env_super            7       /* '+' */
#define    env_sub              8       /* '-' */
/* special, handle later */
#define    env_vec              9       /* vector above variable */
#define    env_over             10      /* over - fractions */
#define    env_sqrt             11      /* sqrt - square root */
#define    env_int              12      /* int - integration */
#define    env_sum              13      /* sum - summation */
#define    env_prod             14      /* prod - pi sign for product */
#define    env_num              15      /* sub-environments */
#define    env_denom            16
#define    env_from             17
#define    env_to               18

#define cmd_space       1
#define cmd_nl          2
#define cmd_vector      3
#define cmd_sqrt        4
#define cmd_integral    5
#define cmd_summation   6
#define cmd_product     7
#define cmd_fraction    8


#define ENVIRONMENT 1
#define SYMBOL      2
#define NEWLINE     3
#define END         4
#define STRING      5
#define END_OF_STRING 6
#define START_OF_STRING 7


typedef struct {
    char s[100];
    unsigned short sp;
} char_stack;

static void init_char_stack(cs)
char_stack *cs;
{
    cs->sp = 0;
    cs->s[0] = '\0';
}

static char pop_char_stack(cs)
char_stack *cs;
{
    cs->sp--;
    return cs->s[cs->sp];
}

static void push_char_stack(cs,c)
char_stack *cs;
char c;
{
    cs->s[cs->sp++] = c;
}



static char otherside(opener)
char opener;
{
   switch (opener) {
    case '(':
      return(')');
    case '{':
      return('}');
    case '[':
      return(']');
    case '<':
      return('>');
   }
   /*NOTREACHED*/
   return 0;
}

static int openparen(c)
char c;
{
    return (c=='(')?1:(c=='[')?1:(c=='{')?1:(c=='<')?1:0;
}


/*
   text points to beginning of text string.  return value is
   length of string, up to but not including the passed terminator
   or the default terminators \0 \n @.
   Turns "@@" into "@".
   Turns "@ " into " ".
   Turns "@)" into ")", and similarly for "@]", "@}", and "@>"
   0 is a valid return value.
*/
static int text_length(text,terminator)
     char *text;
     char terminator;
{
   int len=0;

   while (1) {
      while (*text!='@' && *text!='\n' && *text!=terminator && *text) {
	 text++;
	 len++;
      }

      if (*text == '@') {
	  if ((*(text+1) == '@') || (*(text+1) == ' ') ||
	      (*(text+1) == ')') || (*(text+1) == ']') ||
	      (*(text+1) == '}') || (*(text+1) == '>')) {
	      *text = *(text+1);
	      bcopy(text+2,text+1,strlen(text+1));
	      text++;
	      len++;
	  }
	  else return(len);
      }
      else return (len);
  }
}


/*
 * on entry: *c == '@'
 * read the next token (up to an openparen) and compare
 * it against environment names.  If a match, return the
 * length of the matched string and put the environment
 * type in the env parameter.
 * If no openparen, or bad env name, return 0;
 */
static int getenvironmentname(c,terminator,env)
char *c;
char terminator;
int *env;
{
    int len;
    char save;

    *env = -1;
    c++;

    /*
     * scan forward until the next open delimiter
     */
    for(len=0;
	(c[len]!='\0')&&(c[len]!='\n')&&
	(c[len]!=terminator)&&!openparen(c[len]);
	len++);
    if ((c[len] == '\0')  || (c[len] == '\n') || (c[len] == terminator))
	return 0;

    save = c[len];
    c[len] = '\0';

#define COMPARE(str,sym) if (!(strcasecmp(c,str))) {*env=(int)sym; break;}

    switch(*c) {
    case 'a': case 'A':
	break;
    case 'b': case 'B':
	COMPARE("bigger",env_bigger);
	COMPARE("b", env_bold);
	break;
    case 'c': case 'C':
/*      COMPARE("c", env_c);
	COMPARE("center", env_center); */
	break;
    case 'd': case 'D':
	COMPARE("denom", env_denom);
	break;
    case 'e': case 'E':
	break;
    case 'f': case 'F':
/*      COMPARE("f", env_fix); */
	COMPARE("from", env_from);
	break;
    case 'g': case 'G':
	COMPARE("g", env_greek);
	break;
    case 'h': case 'H':
	break;
    case 'i': case 'I':
	COMPARE("i", env_ita);
	COMPARE("int", env_int);
	break;
    case 'l': case 'L':
	break;
    case 'm': case 'M':
	break;
    case 'n': case 'N':
	COMPARE("num", env_num);
	break;
    case 'o': case 'O':
	COMPARE("over", env_over);
	break;
    case 'p': case 'P':
	COMPARE("prod", env_prod);
	break;
    case 'r': case 'R':
	COMPARE("r", env_roman);
	break;
    case 's': case 'S':
	COMPARE("smaller",env_smaller);
	COMPARE("sqrt", env_sqrt);
	COMPARE("sum", env_sum);
	break;
    case 't': case 'T':
	COMPARE("to", env_to);
	break;
    case 'u': case 'U':
/*      COMPARE("u", env_u); */
	break;
    case 'v': case 'V':
	COMPARE("vec", env_vec);
	break;
    case '^':
	break;
    case '>':
/*      COMPARE(">", env_right);*/
	break;
    case '+':
	COMPARE("+", env_super);
	break;
    case '-':
	COMPARE("-", env_sub);
	break;
    }

#undef COMPARE

    c[len] = save;
    if (*env == -1) return 0;      /* if no match found */
    else return len;
}

/*
 * looks for a symbol name.
 * if it finds a valid one, returns its length, otherwise returns 0.
 * if it finds a valid name, return symbol in sym.
 * on entry: *c == '@';
 */
static int getsymbolname(c,sym)
char *c;
int *sym;
{
    int len;
    char save;

    c++;              /* move past the '@' */
    *sym = -1;

    /* all the other symbols require non-alphanumerics after their names to
     * delimit them.  So figure out how long the name is, then compare
     * it with all the symbol names.
     */
    for(len=0; isalnum(c[len]); len++);

    if (len == 0) return 0;

    save = c[len];
    c[len] = '\0';
#define COMPARE(str,symbol) if (!(strcasecmp(c, str))) \
			     {*sym = (int)symbol; break;}
#define UCOMPARE(str,symbol) if (!(strcmp(c, str))) \
			     {*sym = (int)symbol; break;}

    switch(*c) {
    case 'a': case 'A':
	COMPARE("aleph", sym_aleph);
	UCOMPARE("alpha", sym_alpha);
	UCOMPARE("Alpha", sym_Alpha);
	COMPARE("and", sym_and);
	COMPARE("angle", sym_angle);
	COMPARE("approx", sym_approx);
	COMPARE("ast", sym_ast);
	break;
    case 'b': case 'B':
	UCOMPARE("beta", sym_beta);
	UCOMPARE("Beta", sym_Beta);
	COMPARE("bullet", sym_bullet);
	break;
    case 'c': case 'C':
	COMPARE("cdot", sym_cdot);
	UCOMPARE("chi",sym_chi);
	UCOMPARE("Chi",sym_Chi);
	COMPARE("clubs",sym_clubs);
	COMPARE("cong", sym_cong);
	COMPARE("copyright",sym_copyright);
	break;
    case 'd': case 'D':
	COMPARE("dbdownarrow", sym_dbdownarrow);
	COMPARE("dbrightarrow", sym_dbrightarrow);
	COMPARE("dbleftarrow", sym_dbleftarrow);
	COMPARE("dbleftrightarrow", sym_dbleftrightarrow);
	COMPARE("dblprime", sym_dblprime);
	COMPARE("dbuparrow", sym_dbuparrow);
	COMPARE("degr", sym_degr);
	UCOMPARE("delta", sym_delta);
	UCOMPARE("Delta", sym_Delta);
	COMPARE("diamonds", sym_diamonds);
	COMPARE("div", sym_div);
	COMPARE("downarrow", sym_downarrow);
	break;
    case 'e': case 'E':
	COMPARE("emptyset", sym_emptyset);
	UCOMPARE("epsilon", sym_epsilon);
	UCOMPARE("Epsilon", sym_Epsilon);
	COMPARE("eqv", sym_eqv);
	UCOMPARE("eta", sym_eta);
	UCOMPARE("Eta", sym_Eta);
	COMPARE("exists", sym_exists);
	break;
    case 'f': case 'F':
	COMPARE("forall", sym_forall);
	COMPARE("function", sym_function);
	break;
    case 'g': case 'G':
	UCOMPARE("gamma", sym_gamma);
	UCOMPARE("Gamma", sym_Gamma);
	COMPARE("gte", sym_gte);
	break;
    case 'h': case 'H':
	COMPARE("hearts", sym_hearts);
	break;
    case 'i': case 'I':
	COMPARE("in", sym_in);
	COMPARE("infty", sym_infty);
	COMPARE("inter", sym_inter);
	UCOMPARE("iota", sym_iota);
	UCOMPARE("Iota", sym_Iota);
	COMPARE("iset", sym_iset);
	break;
    case 'j': case 'J':
	break;
    case 'k': case 'K':
	UCOMPARE("kappa", sym_kappa);
	UCOMPARE("Kappa", sym_Kappa);
	break;
    case 'l': case 'L':
	UCOMPARE("lambda", sym_lambda);
	UCOMPARE("Lambda", sym_Lambda);
	COMPARE("ldots", sym_ldots);
	COMPARE("leftarrow", sym_leftarrow);
	COMPARE("leftrightarrow", sym_leftrightarrow);
	COMPARE("lte", sym_lte);
	break;
    case 'm': case 'M':
	UCOMPARE("mu", sym_mu);
	UCOMPARE("Mu", sym_Mu);
	break;
    case 'n': case 'N':
	COMPARE("nabla", sym_nabla);
	COMPARE("neq", sym_neq);
	COMPARE("ni", sym_ni);
	COMPARE("not",sym_not);
	COMPARE("notin", sym_notin);
	COMPARE("notsubset", sym_notsubset);
	UCOMPARE("nu", sym_nu);
	UCOMPARE("Nu", sym_Nu);
	break;
    case 'o': case 'O':
	UCOMPARE("omega",sym_omega);
	UCOMPARE("Omega",sym_Omega);
	UCOMPARE("omega1",sym_omega1);
	UCOMPARE("omicron",sym_omicron);
	UCOMPARE("Omicron",sym_Omicron);
	COMPARE("oplus", sym_oplus);
	COMPARE("or", sym_or);
	COMPARE("otimes", sym_otimes);
	break;
    case 'p': case 'P':
	COMPARE("partial", sym_partial);
	COMPARE("perp", sym_perp);
	UCOMPARE("phi", sym_phi);
	UCOMPARE("Phi", sym_Phi);
	UCOMPARE("phi1", sym_phi1);
	UCOMPARE("pi", sym_pi);
	UCOMPARE("Pi", sym_Pi);
	COMPARE("pm", sym_pm);
	COMPARE("prime", sym_prime);
	COMPARE("propto", sym_propto);
	COMPARE("prsubset", sym_prsubset);
	COMPARE("prsupset", sym_prsupset);
	UCOMPARE("psi", sym_psi);
	UCOMPARE("Psi", sym_Psi);
	break;
    case 'r': case 'R':
	COMPARE("registered", sym_registered);
	UCOMPARE("rho", sym_rho);
	UCOMPARE("Rho", sym_Rho);
	COMPARE("rightarrow", sym_rightarrow);
	COMPARE("rset", sym_rset);
	break;
    case 's': case 'S':
	UCOMPARE("sigma", sym_sigma);
	UCOMPARE("Sigma", sym_Sigma);
	UCOMPARE("sigma1", sym_sigma1);
	COMPARE("similar", sym_similar);
	COMPARE("spades", sym_spades);
	COMPARE("subset", sym_subset);
	COMPARE("supset", sym_supset);
	break;
    case 't': case 'T':
	UCOMPARE("tau", sym_tau);
	UCOMPARE("Tau", sym_Tau);
	COMPARE("therefore", sym_therefore);
	UCOMPARE("theta", sym_theta);
	UCOMPARE("Theta", sym_Theta);
	UCOMPARE("theta1", sym_theta1);
	COMPARE("times", sym_times);
	COMPARE("trademark", sym_trademark);
	break;
    case 'u': case 'U':
	COMPARE("union", sym_union);
	COMPARE("uparrow", sym_uparrow);
	UCOMPARE("upsilon", sym_upsilon)
	UCOMPARE("Upsilon", sym_Upsilon)
	COMPARE("upsilon1", sym_Upsilon1)
	break;
    case 'v': case 'V':
	break;
    case 'w': case 'W':
	COMPARE("weierstrass", sym_weierstrass);
	break;
    case 'x': case 'X':
	UCOMPARE("xi", sym_xi);
	UCOMPARE("Xi", sym_Xi);
	break;
    case 'y': case 'Y':
	break;
    case 'z': case 'Z':
	UCOMPARE("zeta", sym_zeta);
	UCOMPARE("Zeta", sym_Zeta);
	break;
    }

#undef COMPARE
#undef UCOMPARE

    c[len] = save;
    if (*sym == -1) return 0;
    else return len;
}



static AtTextToken *Parse(string)
char *string;
{
   AtTextToken *firsttok, *tok;
   int len;
   char terminator;
   char *c;
   char_stack terminators;

   init_char_stack(&terminators);
   firsttok= New(AtTextToken);
   firsttok->type = START_OF_STRING;
   firsttok->baseline = 0;
   tok = New(AtTextToken);
   firsttok->next = tok;
   c = string;
   terminator = '\0';

   if (c)
     while (*c) {
       if (*c == '\n') {
	 tok->type = NEWLINE;
	 c++;
       }
       else if (*c == terminator) { /* if this is the end of an env */
	 tok->type = END;
	 terminator = pop_char_stack(&terminators);
	 c++;
       }
       else if (len=text_length(c,terminator)) { /* if there is a text block*/
	 /* this case catches occurences of "@@", "@ ", "@)", etc. */
	 tok->type = STRING;
	 tok->str = c;
	 tok->code = len;
	 c += len;
       }
       else if (*c=='@') { /* if this is the beginning of an env or symbol */
	 int env,sym;
	 if (len = getenvironmentname(c,terminator,&env)) {
	   tok->type = ENVIRONMENT;
	   tok->code = env;
	   push_char_stack(&terminators,terminator);
	   terminator = otherside(*(c+1+len));
	   c += len+2;  /* jump over @, name, and opener */
	 }
	 else if (len = getsymbolname(c,&sym)) {
	   tok->type = SYMBOL;
	   tok->code = (unsigned short) sym;
	   c += len + 1;             /* jump over @, and name */
	   while (isspace(*c)) c++;  /* jump over whitespace after name*/
	 }
	 else { /* error, so from here to next \n, \0, terminator or '@' */
	   tok->type = STRING;
	   tok->str = c;
	   for(len=1;
	       (c[len]!=terminator) && (c[len]!='@') &&
	       (c[len]!='\n') && (c[len]!='\0');
	       len++);
	   tok->code = len;
	   c += len;
	 }
       }

       tok->next = New(AtTextToken);
       tok=tok->next;
     }

   tok->type = END_OF_STRING;

   return(firsttok);
}


/*
 * on entry, t points at at the beginning of an env.
 * this procedure calculate a bounding box for that env, recursing
 * where necessary.  It puts the metrics in the text token.
 * returns the first text token following the end of the environment
 * so after a recursive call the parent call can continue from the
 * right place.
 */
static AtTextToken *compute_env_metrics(t)
AtTextToken *t;
{
    AtTextToken *tok, *next;
    short width = 0, ascent = 0, descent = 0;

    tok = t->next;
    while ((tok->type != END) && (tok->type != END_OF_STRING)) {
	if (tok->type == NEWLINE) {
	    tok = tok->next;
	    continue;
	}

	if (tok->type == ENVIRONMENT) {
	    next = compute_env_metrics(tok);
	    width += tok->width;
	    if (t->baseline - tok->baseline + tok->ascent >  ascent)
		ascent = t->baseline - tok->baseline + tok->ascent;
	    if (tok->baseline - t->baseline + tok->descent > descent)
		descent = tok->baseline - t->baseline + tok->descent;
	    tok = next;
	    continue;
	}

	width += tok->width;
	if (tok->ascent > ascent) ascent = tok->ascent;
	if (tok->descent > descent) descent = tok->descent;
	tok = tok->next;
    }
    t->width = width;
    t->ascent = ascent;
    t->descent = descent;

    if (tok->type == END) tok = tok->next;
    return tok;
}


static void Format(token,ff,sz,st)
AtTextToken *token;
AtFontFamily *ff;
int sz,st;
{
    AtTextToken *t = token;
    Display *dpy;
    AtFontFamily *family = ff;
    AtFontFamily *symbolfamily;
    int face = st;
    int size = sz;
    int base = 0;
    AtFontFamily *familystack[50];
    int facestack[50];
    int sizestack[50];
    int basestack[50];
    int sp = 0;
#define pushenv()  familystack[sp] = family;\
		   facestack[sp] = face;\
		   sizestack[sp] = size;\
		   basestack[sp] = base;\
		   sp++

#define popenv()   sp--;\
		   family = familystack[sp];\
		   face = facestack[sp];\
		   size = sizestack[sp];\
		   base = basestack[sp]

    XCharStruct overall;
    int dir, asc,des;
    unsigned char sym;


    dpy = AtFontFamilyGetDisplay(ff);
    symbolfamily = AtFontFamilyGet(dpy,"symbol");

    while (t->type != END_OF_STRING) {
	switch (t->type) {
	case NEWLINE:
	    break;
	case STRING:
	    t->font = AtFontFetch(family,face,size);
	    XTextExtents(t->font,t->str,t->code,&dir,&asc,&des,&overall);
	    t->ascent = asc;
	    t->descent = des;
	    t->width = overall.width;
	    t->baseline = base;
	    break;
	case SYMBOL:
	    t->font = AtFontFetch(symbolfamily,face,size);
	    sym = t->code;
	    XTextExtents(t->font,(char*)&sym,1,&dir,&asc,&des,&overall);
	    t->ascent = asc;
	    t->descent = des;
	    t->width =
		t->font->per_char[t->code-t->font->min_char_or_byte2].width;
	    t->baseline = base;
	    break;
	case ENVIRONMENT:
	    pushenv();
	    t->baseline = base;
	    switch (t->code) {
	    case env_bold:
		face |= AtFontBOLD;
		break;
	    case env_ita:
		face |= AtFontITALIC;
		break;
	    case env_greek:
		family = symbolfamily;
		break;
	    case env_roman:
		family = ff;
		face = AtFontPLAIN;
		break;
	    case env_bigger:
		size = AtFontBigger(size);
		break;
	    case env_smaller:
		size = AtFontSmaller(size);
		break;
	    case env_super:
		size = AtFontSmaller(size);
		base = base-AtFontPixelSize(ff,size)/2;
		t->baseline = base;
		break;
	    case env_sub:
		size = AtFontSmaller(size);
		base = base+AtFontPixelSize(ff,size)/2;
		t->baseline = base;
		break;
	    }
	    break;
	case END:
	    popenv();
	    break;
	}

	t = t->next;
    }
    compute_env_metrics(token);
}


static void Draw(d, w, gc, t, x, y)
Display *d;
Drawable w;
GC gc;
AtTextToken *t;
short x,y;
{
    unsigned char c;
    static GC localgc = None;

    if (localgc == None)
	localgc = XCreateGC(d,w,0L,NULL);

    XCopyGC(d, gc, GCFunction | GCPlaneMask | GCForeground | GCBackground |
	    GCFont | GCSubwindowMode | GCClipXOrigin | GCClipYOrigin |
	    GCClipMask | GCFillStyle | GCTile, localgc);


    while(t->type != END_OF_STRING) {
	switch (t->type) {
	case SYMBOL:
	    c = t->code;
	    XSetFont(d, localgc, t->font->fid);
	    XDrawString(d, w, localgc, x, y+t->baseline, (char *) &c, 1);
	    x += t->width;
	    break;

	case STRING:
	    XSetFont(d, localgc, t->font->fid);
	    XDrawString(d, w, localgc, x, y+t->baseline, t->str, t->code);
	    x += t->width;
	    break;

	case START_OF_STRING:
	case ENVIRONMENT:
	case NEWLINE:
	case END:
	    break;
	}
	t = t->next;
    }
}

static void Rotate(dpy, win, d, gc, t)
Display *dpy;
Window win;
Drawable d;
GC gc;
AtText *t;
{
    Pixmap pixmap;
    XImage *image;
    XWindowAttributes wa;
    GC localgc;
    char *data;
    int i,j;
    unsigned long foreground;
    unsigned long background;
    int w,h;  /* width, height */

    if (dpy != AtFontFamilyGetDisplay(t->family)) {
	fprintf(stderr, "Warning: attempt to rotate text on a Display different \n       from the text's font family.\n");
	return;
    }

    /*
     * the height and width of the rotated text.
     * when actually rotating the text, we'll have to reverse these sometimes.
     */
    w = t->width;
    h = t->ascent + t->descent ;

    if ((w==0) || (h==0)) return; /* if there's nothing there, don't bother */

    XGetWindowAttributes(dpy, win, &wa); /* we need the depth and the visual */
    /* this only works for a window, not a drawable.  Fix it. */

    localgc = XCreateGC(dpy, d, 0L, NULL);
    XCopyGC(dpy, gc, GCFunction | GCPlaneMask | GCForeground | GCBackground |
	    GCSubwindowMode | GCFillStyle | GCTile,  localgc);

    /*
     * create a pixmap the size of the unrotated text
     * clear the pixmap to the background color
     * draw the text into it unrotated
     * get the text as an unrotated image
     * Since everything is unrotated, use w and h backwards
     */
    pixmap = XCreatePixmap(dpy, d, h, w, wa.depth);
    XFillRectangle(dpy, pixmap, localgc, 0, 0, h, w);
    XSetForeground(dpy, localgc, foreground);
    Draw(dpy, pixmap, localgc, t->parsed, 0, t->parsed->ascent);
    image = XGetImage(dpy, pixmap, 0, 0, h, w, AllPlanes, XYPixmap);

    /*
     * allocate enough memory for the rotated image
     * create the image.
     */
    data = (char *) malloc(wa.depth * h * (w+7)/8);
    t->rotated_image = XCreateImage(dpy, wa.visual, wa.depth, XYPixmap, 0, data,
				    w, h, 8, (w+7)/8);

    /*
     * rotate the image
     */
    for(i=0; i < h; i++)
	for(j=0; j < w; j++)
	    XPutPixel(t->rotated_image, j, h-i-1, XGetPixel(image, i, j));

    t->rotated_depth = wa.depth;

    XDestroyImage(image);
    XFreePixmap(dpy, pixmap);
    XFreeGC(dpy, localgc);
}


AtText* AtTextCreate(str, ff, size, style)
char *str;
AtFontFamily *ff;
int size, style;
{
    AtText *text;
    char *newstr;

    if (str)
      newstr = (char *) strcpy((char *) malloc(strlen(str)+1), str);
    else
      newstr = (char *) NULL;

    text = New(AtText);

    text->family = ff;
    if (size < AtFontSMALLEST || size > AtFontBIGGEST) {
#ifdef DEBUG
	 fprintf(stderr, "AtTextCreate for \"%s\" had bad size %d\n",
		 str, size);
#endif
	 size = AtFontNORMAL;
    }
    if (style < AtFontPLAIN || style > AtFontBOLDITALIC) {
#ifdef DEBUG
	 fprintf(stderr, "AtTextCreate for \"%s\" had bad style %d\n",
		 str, style);
#endif
	 style = AtFontPLAIN;
    }
    text->size = size;
    text->style = style;
    text->str = newstr;
    text->parsed = Parse(newstr);
    Format(text->parsed, ff, size, style);
    text->rotated = NULL;
    text->rotated_image = NULL;
    text->width = text->parsed->width;
    text->ascent = text->parsed->ascent;
    text->descent = text->parsed->descent;
    text->psformat = NULL;
    return text;
}

void AtTextDestroy(t)
AtText *t;
{
    AtTextToken *tok, *tmp;

    if( !t)
	return;
    tok = t->parsed;
    while (tok->type != END_OF_STRING) {
	tmp = tok;
	tok = tok->next;
	free(tmp);
    }
    free(tok);

    free(t->str);

    if (t->rotated && (t->rotated_image != NULL))
	XDestroyImage(t->rotated_image);

    if (t->psformat) free(t->psformat);  /* it was calloc'ed in one chunk */
    free(t);
    *SCCSid = *SCCSid;        /* Keep gcc quiet */
}

void AtTextReformat(t, ff, size, style)
AtText *t;
AtFontFamily *ff;
int size, style;
{
    /* if Format() in the future may discard some of the useless TextTokens,
     * then this call will have to re-parse(t->raw) before re-formatting.
     */
    if (size < AtFontSMALLEST || size > AtFontBIGGEST) {
#ifdef DEBUG
	 fprintf(stderr, "AtTextReformat for \"%s\" had bad size %d\n",
		 t->str, size);
#endif
	 size = AtFontNORMAL;
    }
    if (style < AtFontPLAIN || style > AtFontBOLDITALIC) {
#ifdef DEBUG
	 fprintf(stderr, "AtTextReformat for \"%s\" had bad style %d\n",
		 t->str, style);
#endif
	 style = AtFontPLAIN;
    }

    Format(t->parsed, ff, size, style);
    t->width = t->parsed->width;
    t->ascent = t->parsed->ascent;
    t->descent = t->parsed->descent;

    if (t->rotated) {
	short tmp;
	if (t->rotated_image != NULL) XDestroyImage(t->rotated_image);
	t->rotated_image = NULL;
	tmp = t->width;
	t->width = t->ascent + t->descent;
	t->ascent = tmp;
	t->descent = 0;
    }
}

void AtTextRotate(t)
AtText *t;
{
    short tmp;

    if (t->rotated == FALSE) {
	t->rotated = TRUE;
	tmp = t->width;
	t->width = t->ascent + t->descent;
	t->ascent = tmp;
	t->descent = 0;
    }
}


void AtTextDraw(dpy, win, d, gc, t, x, y)
Display *dpy;
Window win;
Drawable d;
GC gc;
AtText *t;
int x,y;
{
    if (dpy != AtFontFamilyGetDisplay(t->family)) {
	fprintf(stderr, "Warning: attempt to draw text on a Display different \n       from the text's font family.\n");
	return;
    }

    if (t->rotated) {
	Window junkw;
	int junki;
	unsigned int junk;
	unsigned int depth;

	if (t->rotated_image == NULL)
	    Rotate(dpy, win, d, gc, t);
	else {
	    XGetGeometry(dpy, d, &junkw, &junki, &junki, &junk, &junk, &junk, &depth);
	    if (depth != t->rotated_depth) {
		fprintf(stderr, "Warning: attempt to draw rotated text in a window\n         of a different depth than than on which it was rotated.\n");
		return;
	    }
	}

	XPutImage(dpy, d, gc, t->rotated_image, 0, 0, x, y-t->ascent, t->width, t->ascent);
    }
    else
	Draw(dpy, d, gc, t->parsed, x, y);
}

void AtTextDrawJustified(dpy, win, d, gc, t, hjust, vjust, x, y, w, h)
Display *dpy;
Window win;
Drawable d;
GC gc;
AtText *t;
int hjust, vjust;
int x,y,w,h;
{
    switch (hjust) {
    case AtTextJUSTIFY_LEFT:
	break;
    case AtTextJUSTIFY_CENTER:
	x += (w - t->width)/2;
	break;
    case AtTextJUSTIFY_RIGHT:
	x += (w - t->width);
	break;
    }

    switch (vjust) {
    case AtTextJUSTIFY_TOP:
	y += t->ascent;
	break;
    case AtTextJUSTIFY_CENTER:
	y += t->ascent + (h - (t->ascent + t->descent))/2;
	break;
    case AtTextJUSTIFY_BOTTOM:
	y += h - t->descent;
	break;
    }

    AtTextDraw(dpy, win, d, gc, t, x, y);
}
