/***************************************************************************
 *                                                                         *
 *   GALRTF.C                                                              *
 *                                                                         *
 *   Copyright (c) 1988-1997 Galacticomm, Inc.    All Rights Reserved.     *
 *                                                                         *
 *   This module contains functions for handling Rich Text Format (RTF).   *
 *                                                                         *
 *   The reader is based on a sample RTF reader published in Microsoft     *
 *   Application Note GC0165.                                              *
 *                                                                         *
 *                                                - J. Alvrus  7/17/95     *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "galrtf.h"

#define FILREV "$Revision: 14 $"

struct kwdtab kwdtab[]={           /* keyword parsing table                */
/*    keyword       dftval    usedft    kwdtyp    idx                      */
     {"'",          0,        FALSE,    KWTYPSP,  SPECHEX},
     {"*",          0,        FALSE,    KWTYPSP,  SPECSKPD},
     {"\\",         0,        FALSE,    KWTYPCH,  '\\'},
     {"\x0a",       0,        FALSE,    KWTYPCH,  '\r'},
     {"\x0d",       0,        FALSE,    KWTYPCH,  '\r'},
     {"_",          0,        FALSE,    KWTYPCH,  '-'},
     {"aftncn",     0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"aftnsep",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"aftnsepc",   0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"annotation", 0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"atnauthor",  0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"atnicn",     0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"atnid",      0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"atnref",     0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"atntime",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"atrfend",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"atrfstart",  0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"author",     0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"bin",        0,        FALSE,    KWTYPSP,  SPECBIN},
     {"bkmkend",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"bkmkstart",  0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"bullet",     0,        FALSE,    KWTYPCH,  '-'},
     {"buptim",     0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"caps",       1,        FALSE,    KWTYPPF,  CHPCAPS},
     {"colortbl",   0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"comment",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"creatim",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"datafield",  0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"deftab",     720,      FALSE,    KWTYPSP,  SPECDFTB},
     {"do",         0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"doccomm",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"dptxbxtext", 0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"emdash",     0,        FALSE,    KWTYPCH,  '-'},
     {"emspace",    0,        FALSE,    KWTYPCH,  ' '},
     {"endash",     0,        FALSE,    KWTYPCH,  '-'},
     {"enspace",    0,        FALSE,    KWTYPCH,  ' '},
     {"falt",       0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"fi",         0,        FALSE,    KWTYPPW,  PAPFI},
     {"field",      0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"file",       0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"filetbl",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"fldinst",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"fldrslt",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"fontemb",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"fontfile",   0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"fonttbl",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"footer",     0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"footerf",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"footerl",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"footerr",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"footnote",   0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"ftncn",      0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"ftnsep",     0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"ftnsepc",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"header",     0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"headerf",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"headerl",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"headerr",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"info",       0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"keycode",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"keywords",   0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"ldblquote",  0,        FALSE,    KWTYPCH,  '"'},
     {"li",         0,        FALSE,    KWTYPPW,  PAPLI},
     {"line",       0,        FALSE,    KWTYPCH,  '\r'},
     {"lquote",     0,        FALSE,    KWTYPCH,  '\''},
     {"margl",      1800,     FALSE,    KWTYPSP,  SPECMRGL},
     {"marglsxn",   0,        FALSE,    KWTYPPW,  SCPMRGL},
     {"margr",      1800,     FALSE,    KWTYPSP,  SPECMRGR},
     {"margrsxn",   0,        FALSE,    KWTYPPW,  SCPMRGR},
     {"nextfile",   0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"objalias",   0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"objclass",   0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"objdata",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"object",     0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"objname",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"objsect",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"objtime",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"operator",   0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"paperw",     12240,    FALSE,    KWTYPSP,  SPECPGW},
     {"par",        0,        FALSE,    KWTYPCH,  '\r'},
     {"pard",       0,        FALSE,    KWTYPSP,  SPECPAPD},
     {"pgwsxn",     0,        FALSE,    KWTYPPW,  SCPPGW},
     {"pict",       0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"plain",      0,        FALSE,    KWTYPSP,  SPECCHPD},
     {"pn",         0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"pnseclvl",   0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"pntext",     0,        FALSE,    KWTYPDS,  DESTBODY},
     {"pntxta",     0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"pntxtb",     0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"printim",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"private1",   0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"qc",         JUSTC,    TRUE,     KWTYPPB,  PAPJUST},
     {"qj",         JUSTF,    TRUE,     KWTYPPB,  PAPJUST},
     {"ql",         JUSTL,    TRUE,     KWTYPPB,  PAPJUST},
     {"rdblquote",  0,        FALSE,    KWTYPCH,  '"'},
     {"result",     0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"revtbl",     0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"revtim",     0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"ri",         0,        FALSE,    KWTYPPW,  PAPRI},
     {"rquote",     0,        FALSE,    KWTYPCH,  '\''},
     {"rxe",        0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"scaps",      1,        FALSE,    KWTYPPF,  CHPCAPS},
     {"sect",       0,        FALSE,    KWTYPCH,  '\r'},
     {"sectd",      0,        FALSE,    KWTYPSP,  SPECSCPD},
     {"stylesheet", 0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"subject",    0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"tab",        0,        FALSE,    KWTYPCH,  '\t'},
     {"tb",         0,        FALSE,    KWTYPSP,  SPECTABB},
     {"tc",         0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"template",   0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"title",      0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"tqc",        TTCENTER, TRUE,     KWTYPPT,  0},
     {"tqdec",      TTDEC,    TRUE,     KWTYPPT,  0},
     {"tqr",        TTRIGHT,  TRUE,     KWTYPPT,  0},
     {"tx",         0,        FALSE,    KWTYPSP,  SPECTAB},
     {"txe",        0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"xe",         0,        FALSE,    KWTYPDS,  DESTSKIP},
     {"{",          0,        FALSE,    KWTYPCH,  '{'},
     {"}",          0,        FALSE,    KWTYPCH,  '}'},
     {"~",          0,        FALSE,    KWTYPCH,  ' '}
};

UINT _kwtabsz=(sizeof(kwdtab)/sizeof(struct kwdtab));/* kwd table size */

#define MAXLINE 79                 /* max output line length               */

/* RTF input state variables */
const CHAR *srcptr;                /* RTF reader global source pointer     */
INT rtfrgrp,                       /* RTF reader current group (stack ptr) */
    rtfris;                        /* RTF reader internal state            */
LONG rtfnbin;                      /* # bytes to read in binary mode       */
GBOOL skipunk;                     /* skip if next destination unknown     */
struct tabsetup temptab;           /* temporary tab building struct        */
UINT rtfrksz=0;                    /* RTF reader stack size                */
struct rtfrstt rtfrstt,            /* RTF reader current state             */
              *rtfrstk=NULL;       /* RTF reader state stack               */
struct rtfrglob rtfrglob;          /* RTF reader global info               */

/* RTF reader output function hooks */
INT (*rtfputc)(INT ch);            /* output character function pointer    */
VOID (*finout)(VOID);              /* finish output function pointer       */
VOID (*grpbeg)(VOID);              /* new group beginning function pointer */
VOID (*grpend)(VOID);              /* group ending function pointer        */

/* ASCII converter global variables */
CHAR *dstbuf,                      /* conversion destination buffer        */
     *dstptr;                      /* conversion destination pointer       */
UINT dstsiz;                       /* conversion destination size          */
jmp_buf lenxcp;                    /* destination buffer overflow exception*/
GBOOL newpara,                     /* new paragraph flag                   */
     newline;                      /* new line flag                        */
INT startc,                        /* position of first character on line  */
    lastc,                         /* position of last character on line   */
    linepos;                       /* current position in linebuf          */
CHAR linebuf[MAXLINE+1];           /* temporary buffer for forming lines   */

static GBOOL isquoted(CHAR *s);

VOID
initrtf(                           /* initialize RTF handler               */
UINT stacksiz)                     /*   number of elements to stack        */
{
     if (rtfrstk == NULL) {
          rtfrksz=stacksiz;
          rtfrstk=(struct rtfrstt *)alcmem(sizeof(struct rtfrstt)*rtfrksz);
     }
}

GBOOL
isrtf(                             /* is this RTF?                         */
const CHAR *s)                     /*   string to check                    */
{
     return(sameto(RTFIDSTR,(CHAR *)s));
}

INT                                /*   returns result code                */
rtf2asc(                           /* convert RTF text to ASCII            */
const CHAR *rtfsrc,                /*   RTF source buffer                  */
CHAR *ascdst,                      /*   ASCII destination buffer           */
UINT bufsiz)                       /*   destination buffer size            */
{
     if (setjmp(lenxcp)) {         /* destination buffer overflow          */
          return(RTFOK);
     }
     dstbuf=dstptr=ascdst;
     dstsiz=bufsiz;
     newpara=TRUE;
     linepos=0;
     rtfputc=putasc;
     finout=ascfin;
     grpbeg=NULL;
     grpend=NULL;
     return(rtfread(rtfsrc));
}

VOID
ascfin(VOID)                       /* finish ASCII output                  */
{
     addline(linebuf,linepos,'\0',TRUE);
}

INT                                /*   returns result of operation        */
putasc(                            /* output character to ASCII destination*/
INT ch)                            /*   character to output                */
{
     INT n;

     if (ch == '\0') {             /* just in case                         */
          return(RTFOK);
     }
     if (rtfrstt.flag&CHPCAPS) {
          ch=toupper(ch);
     }
     if (newpara) {
          startc=(rtfrstt.word[PAPLI]+rtfrstt.word[PAPFI])/TWIPERC;
          if (startc < 0) {
               startc=0;
          }
          lastc=(rtfrstt.word[SCPPGW]-rtfrstt.word[SCPMRGL]
                -rtfrstt.word[SCPMRGR]-rtfrstt.word[PAPRI])/TWIPERC-1;
          if (lastc > MAXLINE) {
               lastc=MAXLINE;
          }
          if (lastc < startc) {
               startc=0;
               lastc=MAXLINE;
          }
          newpara=FALSE;
     }
     else if (newline) {
          startc=rtfrstt.word[PAPLI]/TWIPERC;
          if (startc < 0) {
               startc=0;
          }
          newline=FALSE;
     }
     if (ch == '\r') {
          addline(linebuf,linepos,'\r',TRUE);
          newpara=TRUE;
          linepos=0;
          return(RTFOK);
     }
     if (linelen(linebuf,linepos) > lastc) {
          if (isspace(ch)) {
               addline(linebuf,linepos,'\n',FALSE);
               newline=TRUE;
               linepos=0;
               return(RTFOK);
          }
          for (n=linepos ; n > 0 && !isspace(linebuf[n-1]) ; --n) {
          }
          ASSERT(n >= 0);
          if (n == 0) {
               n=linepos;
          }
          addline(linebuf,n,'\n',FALSE);
          newline=TRUE;
          if (n == linepos) {
               linepos=0;
          }
          else {
               linepos=linepos-n;
               movmem(&linebuf[n],linebuf,linepos);
          }
     }
     linebuf[linepos++]=ch;
     if (linepos == 3 && startc == 0 && isquoted(linebuf)) {
          lastc=MAXLINE;           /* allow full length for quoted lines   */
     }
     return(RTFOK);
}

static GBOOL
isquoted(                          /* does line start with quote symbol?   */
CHAR *s)                           /*   pointer to start of line           */
{
     if (isalnum(*s)) {
          ++s;
          if (isalnum(*s)) {
               ++s;
          }
     }
     return(strchr("<>{}[]|:",*s) != NULL);
}

UINT                               /*   returns length in characters       */
linelen(                           /* compute length of line (expand tabs) */
CHAR *s,                           /*   line buffer (not nul terminated)   */
UINT n)                            /*   # characters in line buffer        */
{
     CHAR tmpdst[2*MAXLINE+1];

     #ifdef DEBUG
          setmem(tmpdst,sizeof(tmpdst),0);
     #endif
     return(strlen(formline(tmpdst,s,n,TRUE)));
}

VOID
addline(                           /* add completed line to output buffer  */
CHAR *s,                           /*   line buffer (not nul terminated)   */
UINT n,                            /*   # characters in line buffer        */
CHAR termc,                        /*   line terminator character          */
GBOOL lastline)                    /*   last line in paragraph?            */
{
     UINT lenleft;
     CHAR tmpbuf[2*MAXLINE+1];

     ASSERT(lastc <= sizeof(tmpbuf));
     formline(tmpbuf,s,n,lastline);
     unpad(tmpbuf);
     lenleft=dstsiz-(UINT)(dstptr-dstbuf);
     if (strlen(tmpbuf)+1 >= lenleft) {
          movmem(tmpbuf,dstptr,lenleft-1);
          dstptr[lenleft-1]='\0';
          longjmp(lenxcp,TRUE);
     }
     dstptr=stpcpy(dstptr,tmpbuf);
     *dstptr++=termc;
}

CHAR *                             /*   returns a copy of destination      */
formline(                          /* form completed line                  */
CHAR *dst,                         /*   destination buffer                 */
CHAR *src,                         /*   source buffer (not nul terminated) */
UINT n,                            /*   # characters in line buffer        */
GBOOL lastline)                    /*   last line in paragraph?            */
{                                  /*   NOTE: dst must be 2*MAXLINE+1 long */
     INT i,just,leadsp,curtt,curtp,charpos,eoprev,sothis,tabi,nattab;
     GBOOL gotdec;

     ASSERT(lastc <= MAXLINE+1);
     just=rtfrstt.byte[PAPJUST];
     if ((just == JUSTR || just == JUSTC) && strchr(src,'\t') != NULL) {
          just=JUSTL;
     }
     for (charpos=0 ; charpos < startc ; ++charpos) {
          dst[charpos]=' ';
     }
     switch (just) {
     case JUSTR:
     case JUSTC:
          leadsp=(lastc-startc)-n;
          if (just == JUSTC) {
               leadsp=leadsp/2;
          }
          for (charpos=0 ; charpos < leadsp ; ++charpos) {
               dst[charpos]=' ';
          }
          movmem(src,&dst[charpos],n);
          charpos+=n;
          break;
     case JUSTF:
     case JUSTL:
          curtt=TTNONE;
          sothis=charpos;
          for (i=0 ; i < n ; ++i) {
               if (charpos >= 2*MAXLINE) {
                    charpos=2*MAXLINE;
                    break;
               }
               if (src[i] == '\t') {
                    eoprev=charpos;
                    if ((tabi=nexttab(charpos)) != -1) {
                         curtt=rtfrstt.tab[tabi].type;
                         curtp=rtfrstt.tab[tabi].pos/TWIPERC;
                    }
                    else {
                         curtt=TTLEFT;
                         curtp=(rtfrglob.dfttab
                               *(1+TWIPERC*charpos/rtfrglob.dfttab))/TWIPERC;
                    }
                    if (curtp > 2*MAXLINE-1) {
                         curtp=2*MAXLINE-1;
                    }
                    while (charpos < curtp) {
                         dst[charpos++]=' ';
                    }
                    if (curtt == TTBAR) {
                         dst[charpos++]=BARCHAR;
                    }
                    else if (curtt == TTCENTER) {
                         nattab=0;
                    }
                    else if (curtt == TTDEC) {
                         gotdec=FALSE;
                    }
                    sothis=charpos;
               }
               else {
                    switch (curtt) {
                    case TTNONE:
                    case TTLEFT:
                    case TTBAR:
                         dst[charpos++]=src[i];
                         break;
                    case TTCENTER:
                         if ((nattab&1) && dst[eoprev] == ' ') {
                              dst[charpos]=src[i];
                              movmem(&dst[eoprev+1],&dst[eoprev],
                                     charpos-eoprev);
                              --sothis;
                         }
                         else {
                              dst[charpos++]=src[i];
                         }
                         ++nattab;
                         break;
                    case TTRIGHT:
                         if (dst[eoprev] == ' ') {
                              dst[charpos]=src[i];
                              movmem(&dst[eoprev+1],&dst[eoprev],
                                     charpos-eoprev);
                              --sothis;
                         }
                         else {
                              dst[charpos++]=src[i];
                         }
                         break;
                    case TTDEC:
                         if (src[i] == '.') {
                              gotdec=TRUE;
                         }
                         if (!gotdec && dst[eoprev] == ' ') {
                              dst[charpos]=src[i];
                              movmem(&dst[eoprev+1],&dst[eoprev],
                                     charpos-eoprev);
                              --sothis;
                         }
                         else {
                              dst[charpos++]=src[i];
                         }
                         break;
                    }
               }
          }
          if (just == JUSTF && !lastline) {
               dst[charpos]='\0';
               applyjust(&dst[sothis],lastc-sothis);
               charpos=lastc;
          }
          break;
     default:
          ASSERT(FALSE);
     }
     dst[charpos]='\0';
     return(dst);
}

INT                                /*   returns index in tab array (or -1) */
nexttab(                           /* figure out next tab                  */
INT charpos)                       /*   after this character position      */
{
     INT i;

     for (i=0 ; i < MAXTABS && rtfrstt.tab[i].type != TTNONE ; ++i) {
          if (rtfrstt.tab[i].pos/TWIPERC > charpos) {
               return(i);
          }
     }
     return(-1);
}

VOID
applyjust(                         /* apply full justification to a string */
CHAR *strbuf,                      /*   string buffer                      */
UINT justlen)                      /*   length after justification         */
{
     GBOOL changed;
     CHAR *sp,*ep;

     if (strchr(strbuf,' ') == NULL) {
          return;
     }
     while (strlen(strbuf) < justlen) {
          changed=FALSE;
          sp=strbuf;
          ep=strbuf+strlen(strbuf)-1;
          while (sp < ep) {
               while (ep > sp && *ep == ' ') {
                    --ep;
               }
               while (ep > sp && *ep != ' ') {
                    --ep;
               }
               if (*ep == ' ' && ep > sp) {
                    movmem(ep,ep+1,strlen(ep)+1);
                    changed=TRUE;
               }
               if (strlen(strbuf) == justlen) {
                    return;
               }
               while (sp < ep && *sp == ' ') {
                    ++sp;
               }
               while (sp < ep && *sp != ' ') {
                    ++sp;
               }
               if (*sp == ' ' && sp < ep) {
                    movmem(sp,sp+1,strlen(sp)+1);
                    changed=TRUE;
               }
               if (strlen(strbuf) == justlen || !changed) {
                    return;
               }
          }
     }
}

GBOOL                              /*   was able to insert?                */
rtfinasc(                          /* insert ASCII into RTF                */
GBOOL endflg,                      /*   insert at end (vs. at beginning)   */
const CHAR *srcbuf,                /*   buffer containing ASCII text       */
CHAR *dstbuf,                      /*   buffer containing formatted text   */
UINT dstsiz)                       /*   destination buffer size            */
{                                  /*   (ASCII is truncated to fit)        */
     UINT inilen,bufavl;
     CHAR *soavl,*borest,*tmpbuf;

     if (!isrtf(dstbuf)) {
          return(FALSE);
     }
     inilen=strlen(dstbuf);
     bufavl=dstsiz-inilen-1;
     if (bufavl < AFMTLEN+1) {
          return(TRUE);
     }
     if ((tmpbuf=malloc(bufavl+1)) == NULL) {
          return(FALSE);
     }
     strcpy(tmpbuf,AFMTSTR);
     asc2rtf(srcbuf,tmpbuf+AFMTLEN,bufavl-AFMTLEN+1);
     if (endflg) {
          stlcpy(dstbuf+inilen-1,tmpbuf,bufavl+1);
          stlcat(dstbuf,"}",dstsiz);
     }
     else {
          soavl=rtfbotxt(dstbuf);
          borest=soavl+bufavl;
          movmem(soavl,borest,strlen(soavl)+1);
          movmem(tmpbuf,soavl,strlen(tmpbuf));
          movmem(borest,soavl+strlen(tmpbuf),strlen(borest)+1);
     }
     free(tmpbuf);
     return(TRUE);
}

CHAR *                             /*   copy of pointer to destination     */
asc2rtf(                           /* "convert" ASCII to RTF (only chars)  */
const CHAR *ascsrc,                /*   buffer containing ASCII text       */
CHAR *rtfdst,                      /*   buffer to put RTF                  */
UINT dstsiz)                       /*   size of destination buffer         */
{                                  /*   (single-character EOLs assumed)    */
     INT totlen,linlen;
     CHAR *cp,*op,ch,cbuf[2];
     const CHAR *ip;

     if (dstsiz < 5) {
          *rtfdst='\0';
          return(rtfdst);
     }
     op=stpcpy(rtfdst,"\r\n");
     dstsiz-=2;                    /* allow room for final \r\n            */
     linlen=0;
     ip=ascsrc;
     cbuf[1]='\0';
     while ((ch=*ip++) != '\0') {
          if (ch == '\t') {
               cp="\\tab ";
          }
          else if (ch == '\r' || ch == '\n') {
               cp="\\par ";
          }
          else if (ch == '\\') {
               cp="\\\\";
          }
          else if (ch == '{') {
               cp="\\{";
          }
          else if (ch == '}') {
               cp="\\}";
          }
          else {
               cbuf[0]=ch;
               cp=cbuf;
          }
          totlen=strlen(rtfdst)+strlen(cp);
          if (totlen >= dstsiz) {
               break;
          }
          linlen+=strlen(cp);
          if (linlen > 255) {
               if (totlen >= dstsiz+2) {
                    break;
               }
               op=stpcpy(op,"\r\n");
               linlen=0;
          }
          op=stpcpy(op,cp);
     }
     *(stpcpy(op,"\r\n"))='\0';
     return(rtfdst);
}

CHAR *                             /*   pointer to start of text           */
rtfbotxt(                          /* find start of text                   */
const CHAR *rtfsrc)                /*   in this RTF text                   */
{
     CHAR *cp;

     cp=(CHAR *)rtfsrc+sizeof(RTFIDSTR)-1;
     while (isdigit(*cp)) {
          ++cp;
     }
     return(cp);
}

INT                                /*   returns result code                */
striprtf(                          /* super-simple RTFormat stripper       */
const CHAR *rtfsrc,                /*   RTF source buffer                  */
CHAR *ascdst,                      /*   ASCII destination buffer           */
UINT bufsiz)                       /*   destination buffer size            */
{
     if (setjmp(lenxcp)) {         /* destination buffer overflow          */
          return(RTFOK);
     }
     dstbuf=dstptr=ascdst;
     dstsiz=bufsiz;
     newpara=TRUE;
     linepos=0;
     rtfputc=putstp;
     finout=stpfin;
     grpbeg=NULL;
     grpend=NULL;
     return(rtfread(rtfsrc));
}

VOID
stpfin(VOID)                       /* finish RTF stripper output           */
{
     *dstptr='\0';
}

INT                                /*   returns result of operation        */
putstp(                            /* output char to stripped destination  */
INT ch)                            /*   character to output                */
{
     if ((UINT)(dstptr-dstbuf) >= dstsiz-1) {
          dstbuf[dstsiz-1]='\0';
          longjmp(lenxcp,TRUE);
     }
     if (ch == '\t') {
          ch=' ';
     }
     else if (ch == '\n') {
          ch='\r';
     }
     *dstptr++=ch;
     return(RTFOK);
}

INT                                /*   returns result code                */
rtfread(                           /* read RTF                             */
const CHAR *rtfsrc)                /*   RTF source buffer                  */
{
     INT ch;
     INT rc;

     if ((rc=initrtfr(rtfsrc)) != RTFOK) {
          return(rc);
     }
     while ((ch=*srcptr++) != '\0') {
          if (rtfrgrp < 0) {
               return(RTFUNDFL);
          }
          if (rtfris == RISBIN) {
               if ((rc=parsech(ch)) != RTFOK) {
                    return(rc);
               }
          }
          else {
               switch (ch) {
               case '{':
                    if ((rc=pushrtf()) != RTFOK) {
                         return(rc);
                    }
                    break;
               case '}':
                    if ((rc=poprtf()) != RTFOK) {
                         return(rc);
                    }
                    break;
               case '\\':
                    if ((rc=xrtfkwd()) != RTFOK) {
                         return(rc);
                    }
                    break;
               case 0x0d:
               case 0x0a:          /* cr and lf are noise characters       */
                    break;
               default:
                    ASSERT(rtfris == RISNORM);
                    if ((rc=parsech(ch)) != RTFOK) {
                         return(rc);
                    }
                    break;
               }
          }
     }
     if (finout != NULL) {
          (*finout)();
     }
     if (rtfrgrp < -1) {
          return(RTFUNDFL);
     }
     if (rtfrgrp >= 0) {
          return(RTFEOF);
     }
     return(RTFOK);
}

INT                                /*   returns result code                */
initrtfr(                          /* initialize RTF reader                */
const CHAR *rtfsrc)                /*   RTF source buffer pointer          */
{
     if (!isrtf(rtfsrc)) {
          return(RTFNOTRTF);
     }
     srcptr=rtfsrc+sizeof(RTFIDSTR)-1;  /* strip off {\rtfX                */
     while (isdigit(*srcptr)) {
          ++srcptr;
     }
     rtfrgrp=0;
     rtfnbin=0;
     skipunk=FALSE;
     rtfris=RISNORM;
     setmem(&rtfrglob,sizeof(struct rtfrglob),0);
     rtfrglob.dftpgw=kwdtab[findrk("paperw")].dftval;
     rtfrglob.dftmrgl=kwdtab[findrk("margl")].dftval;
     rtfrglob.dftmrgr=kwdtab[findrk("margr")].dftval;
     rtfrglob.dfttab=kwdtab[findrk("deftab")].dftval;
     setmem(&rtfrstt,sizeof(struct rtfrstt),0);
     rtfrstt.word[SCPPGW]=rtfrglob.dftpgw;
     rtfrstt.word[SCPMRGL]=rtfrglob.dftmrgl;
     rtfrstt.word[SCPMRGR]=rtfrglob.dftmrgr;
     setmem(&temptab,sizeof(struct tabsetup),0);
     return(RTFOK);
}

INT                                /*   returns result of operation        */
pushrtf(VOID)                      /* save current RTF parser state        */
{

     if (rtfrgrp < rtfrksz) {
          rtfrstk[rtfrgrp++]=rtfrstt;
          rtfris=RISNORM;
          if (grpbeg != NULL) {
               (*grpbeg)();
          }
          return(RTFOK);
     }
     return(RTFOVRFL);
}

INT                                /*   returns result of operation        */
poprtf(VOID)                       /* restore saved RTF parser state       */
{

     if (rtfrgrp < 0) {
          return(RTFUNDFL);
     }
     if (grpend != NULL) {
          (*grpend)();
     }
     --rtfrgrp;
     if (rtfrgrp >= 0) {
          rtfrstt=rtfrstk[rtfrgrp];
     }
     return(RTFOK);
}

INT                                /*   returns result of operation        */
xrtfkwd(VOID)                      /* extract RTF keyword and parameter    */
{
     LONG param;
     GBOOL parmflg;
     GBOOL parmneg;
     CHAR ch,*pch;
     CHAR kwdbuf[30];
     CHAR parmbuf[20];

     param=0L;
     parmflg=FALSE;
     parmneg=FALSE;
     *kwdbuf='\0';
     *parmbuf='\0';
     if ((ch=*srcptr++) == '\0') {
          return(RTFEOF);
     }
     if (!isalpha(ch)) {
          kwdbuf[0]=ch;
          kwdbuf[1]='\0';
          return(hrtfkwd(kwdbuf,0L,parmflg));
     }
     for (pch=kwdbuf ; isalpha(ch) ; ch=*srcptr++) {
          *pch++=ch;
     }
     *pch='\0';
     if (ch == '-') {
          parmneg=TRUE;
          if ((ch=*srcptr++) == '\0') {
               return(RTFEOF);
          }
     }
     if (isdigit(ch)) {
          parmflg=TRUE;
          for (pch=parmbuf ; isdigit(ch) ; ch=*srcptr++) {
               *pch++=ch;
          }
          *pch='\0';
          param=atol(parmbuf);
          if (parmneg) {
               param=-param;
          }
     }
     if (ch != ' ') {
          --srcptr;
     }
     return(hrtfkwd(kwdbuf,param,parmflg));
}

INT                                /*   returns result of operation        */
parsech(                           /* route character to appropriate dest  */
INT ch)                            /*   character to output                */
{
     if (rtfris == RISBIN && --rtfnbin <= 0) {
          rtfris=RISNORM;
     }
     if (rtfrstt.flag&SKIPFLG) {
          return(RTFOK);
     }
     return((*rtfputc)(ch));
}

INT                                /*   returns result of operation        */
hrtfkwd(                           /* find and handle RTF keyword          */
const CHAR *keyword,               /*   keyword                            */
LONG param,                        /*   parameter                          */
GBOOL parmflg)                     /*   was there a param (param is valid)?*/
{
     INT kwdidx;
     struct kwdtab *kwdptr;

     kwdidx=findrk(keyword);
     if (kwdidx == KWTABSZ) {
          if (skipunk) {
               rtfrstt.flag|=SKIPFLG;
          }
          skipunk=FALSE;
          return(RTFOK);
     }
     skipunk=FALSE;
     kwdptr=&kwdtab[kwdidx];
     if (kwdptr->usedft || !parmflg) {
          param=kwdptr->dftval;
     }
     switch (kwdptr->kwdtyp) {
     case KWTYPPF:
     case KWTYPPB:
     case KWTYPPW:
     case KWTYPPT:
          return(rtfpchg(kwdptr->idx,param,kwdptr->kwdtyp));
     case KWTYPCH:
          return(parsech(kwdptr->idx));
     case KWTYPDS:
          return(rtfdchg(kwdptr->idx));
     case KWTYPSP:
          return(rtfspec(kwdptr->idx,param));
     default:
          ASSERT(FALSE);
     }
     return(RTFEOF);               /* bogus return, should never happen    */
}

INT                                /*   index of keyword in table          */
findrk(                            /* find RTF keyword in table            */
const CHAR *keyword)               /*   keyword                            */
{
     INT lo,md,hi,cond;

     lo=0;
     hi=KWTABSZ-1;
     while (lo <= hi) {
          md=lo+(hi-lo)/2;
          if ((cond=strcmp(keyword,kwdtab[md].keyword)) < 0) {
               if (md == lo) {
                    break;
               }
               hi=md-1;
          }
          else if (cond > 0) {
               if (md == hi) {
                    break;
               }
               lo=md+1;
          }
          else {
               return(md);
          }
     }
     return(KWTABSZ);
}

INT                                /*   returns result of operation        */
rtfpchg(                           /* change a property                    */
INT propidx,                       /*   index of property in arrays        */
LONG propval,                      /*   value to set property to           */
INT proptyp)                       /*   type of property                   */
{
     if (rtfrstt.flag&SKIPFLG) {
          return(RTFOK);
     }
     switch (proptyp) {
     case KWTYPPF:
          if (propval) {
               rtfrstt.flag|=propidx;
          }
          else {
               rtfrstt.flag&=~propidx;
          }
          break;
     case KWTYPPB:
          rtfrstt.byte[propidx]=(signed char)propval;
          break;
     case KWTYPPW:
          rtfrstt.word[propidx]=(int)propval;
          break;
     case KWTYPPT:
          temptab.type=(unsigned char)propval;
          break;
     default:
          ASSERT(FALSE);
     }
     return(RTFOK);
}

INT                                /*   returns result of operation        */
rtfdchg(                           /* change destination                   */
INT dest)                          /*   new destination                    */
{
     if (rtfrstt.flag&SKIPFLG) {
          return(RTFOK);
     }
     switch (dest) {
     case DESTBODY:
          rtfrstt.flag&=~SKIPFLG;
          break;
     case DESTSKIP:
     default:
          rtfrstt.flag|=SKIPFLG;
          break;
     }
     return(RTFOK);
}

INT                                /*   returns result of operation        */
rtfspec(                           /* evaluate a special keyword           */
INT action,                        /*   action required                    */
LONG value)                        /*   keyword value                      */
{
     if ((rtfrstt.flag&SKIPFLG)
      && action != SPECBIN && action != SPECHEX) {
          return(RTFOK);
     }
     switch (action) {
     case SPECBIN:
          rtfris=RISBIN;
          rtfnbin=value;
          break;
     case SPECHEX:
          return(hdlhex());
     case SPECSKPD:
          skipunk=TRUE;
          break;
     case SPECDFTB:
          if (rtfrgrp == 0 && !(rtfrglob.flags&RGFTBS)) {
               rtfrglob.flags|=RGFTBS;
               rtfrglob.dfttab=(int)value;
          }
          break;
     case SPECMRGL:
          if (rtfrgrp == 0 && !(rtfrglob.flags&RGFMLS)) {
               rtfrglob.flags|=RGFMLS;
               rtfrglob.dftmrgl=(int)value;
          }
          break;
     case SPECMRGR:
          if (rtfrgrp == 0 && !(rtfrglob.flags&RGFMRS)) {
               rtfrglob.flags|=RGFMRS;
               rtfrglob.dftmrgr=(int)value;
          }
          break;
     case SPECPGW:
          if (rtfrgrp == 0 && !(rtfrglob.flags&RGFPWS)) {
               rtfrglob.flags|=RGFPWS;
               rtfrglob.dftpgw=(int)value;
          }
          break;
     case SPECPAPD:
          rtfrstt.byte[PAPJUST]=JUSTL;
          rtfrstt.word[PAPFI]=0;
          rtfrstt.word[PAPLI]=0;
          rtfrstt.word[PAPRI]=0;
          setmem(rtfrstt.tab,MAXTABS*sizeof(struct tabsetup),0);
          break;
     case SPECCHPD:
          rtfrstt.flag&=~CHPCAPS;
          break;
     case SPECSCPD:
          rtfrstt.word[SCPPGW]=rtfrglob.dftpgw;
          rtfrstt.word[SCPMRGL]=rtfrglob.dftmrgl;
          rtfrstt.word[SCPMRGR]=rtfrglob.dftmrgr;
          break;
     case SPECTAB:
          if (temptab.type == TTNONE) {
               temptab.type=TTLEFT;
          }
          settab((int)value);
          break;
     case SPECTABB:
          temptab.type=TTBAR;
          settab((int)value);
          break;
     default:
          ASSERT(FALSE);
     }
     return(RTFOK);
}

INT                                /*   returns result of operation        */
hdlhex(VOID)                       /* handle hex data keyword              */
{
     CHAR ch,cl;

     ch=*srcptr++;
     cl=*srcptr++;
     if (!isxdigit(ch) || !isxdigit(cl)) {
          return(RTFINVHX);
     }
     return(parsech((hex2bin(ch)<<4)+hex2bin(cl)));
}

INT                                /*   returns numeric equivalent         */
hex2bin(                           /* convert a hex character to binary    */
CHAR c)                            /*   hex character to convert           */
{
     ASSERT(isxdigit(c));
     if (isdigit(c)) {
          return(c-'0');
     }
     return((toupper(c)-'A')+10);
}

VOID
settab(                            /* add new tab to array of tabs         */
INT tabpos)                        /*   with this position                 */
{
     INT i;

     if ((i=lasttab()) < MAXTABS) {
          temptab.pos=tabpos;
          rtfrstt.tab[i]=temptab;
          sorttabs(i+1);
     }
     setmem(&temptab,sizeof(struct tabsetup),0);
}

INT                                /*   returns index of open slot         */
lasttab(VOID)                      /* find open slot in tab array          */
{
     INT i;

     for (i=0 ; i < MAXTABS ; ++i) {
          if (rtfrstt.tab[i].type == TTNONE) {
               return(i);
          }
     }
     return(MAXTABS);
}

VOID
sorttabs(                          /* sort tabs to keep in ascending order */
INT numtabs)                       /*   number of tabs in array            */
{
     INT i,j;
     struct tabsetup swap;

     for (i=0 ; i < numtabs-1 ; ++i) {
          for (j=i+1 ; j < numtabs ; ++j) {
               if (rtfrstt.tab[j].pos < rtfrstt.tab[i].pos) {
                    swap=rtfrstt.tab[j];
                    rtfrstt.tab[j]=rtfrstt.tab[i];
                    rtfrstt.tab[i]=swap;
               }
          }
     }
}
