
/*
 * hc.c
 *
 * Stand-alone FRACTINT help compiler.  Compile in the COMPACT memory model.
 *
 * See HC.DOC for source file syntax.
 *
 *
 * Revision History:
 *
 *   02-26-91 EAN     Initial version.
 *
 *   03-21-91 EAN     Modified for automatic paragraph formatting.
 *		      Added several new commands:
 *			 Format[+/-]  Enable/disable paragraph formatting
 *			 Doc[+/-]     Enable/disable output to document.
 *			 Online[+/-]  Enable/disable output to online help.
 *			 Label=       Defines a label. Replaces ~(...)
 *			 FF	      Forces a form-feed.  Replaces ~~
 *			 FormatExclude=val Exclude lines past val from
 *				      formatting.  If before any topic sets
 *				      global default, otherwise set local.
 *			 FormatExclude= Set to global default.
 *			 FormatExclude=n Disable exclusion. (global or local)
 *			 FormatExclude[+/-] Enable/disable format exclusion.
 *			 Center[+/-]  Enable/disable centering of text.
 *			 \ before nl  Forces the end of a paragraph
 *		      Support for commands embedded in text with new
 *		      ~(...) format.
 *		      Support for multiple commands on a line separated by
 *		      commas.
 *		      Support for implict links; explicit links must now
 *		      start with an equal sign.
 *   04-03-91 EAN     Added "include" command (works like #include)
 *   04-10-91 EAN     Added support for "data" topics.
 *		      Added Comment/EndComment commands for multi-line
 *			 comments.
 *		      Added CompressSpaces[+/-] command.
 *		      Added DocContents command for document printing.
 *		      Added BinInc command which includes a binary file
 *			 in a data topic.
 *		      Fixed tables to flow down instead of across the page.
 *			 Makes no allowances for page breaks within tables.
 *   11-03-94 TIW     Increased buffer size.
 *
 */


#define HC_C

#define INCLUDE_COMMON  /* tell helpcom.h to include common code */


#include <stdio.h>
#ifndef XFRACT
#include <io.h>
#include <stdarg.h>
#else
#include <varargs.h>
#define strupr strlwr
#endif
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#ifdef __TURBOC__
#   include <dir.h>
#   define FNSPLIT fnsplit
#else
#   define MAXFILE _MAX_FNAME
#   define MAXEXT  _MAX_EXT
#   define FNSPLIT _splitpath
#endif


#include <assert.h>
#include "helpcom.h"


/*
 * When defined, SHOW_ERROR_LINE will cause the line number in HC.C where
 * errors/warnings/messages are generated to be displayed at the start of
 * the line.
 *
 * Used when debugging HC.  Also useful for finding the line (in HC.C) that
 * generated a error or warning.
 */

#ifndef XFRACT
#define SHOW_ERROR_LINE
#endif


#define DEFAULT_SRC_FNAME "help.src"
#define DEFAULT_HLP_FNAME "fractint.hlp"
#define DEFAULT_EXE_FNAME "fractint.exe"
#define DEFAULT_DOC_FNAME "fractint.doc"

#define TEMP_FNAME	  "HC.$$$"
#define SWAP_FNAME	  "HCSWAP.$$$"

#define MAX_ERRORS	  (25)	 /* stop after this many errors */
#define MAX_WARNINGS	  (25)	 /* stop after this many warnings */
				 /* 0 = never stop */

#define INDEX_LABEL	  "HELP_INDEX"
#define DOCCONTENTS_TITLE "DocContent"



/* #define BUFFER_SIZE   (24*1024) */
#define BUFFER_SIZE   (30*1024)


typedef struct
   {
   int	    type;	     /* 0 = name is topic title, 1 = name is label, */
			     /*   2 = "special topic"; name is NULL and */
			     /*   topic_num/topic_off is valid */
   int	    topic_num;	     /* topic number to link to */
   unsigned topic_off;	     /* offset into topic to link to */
   int	    doc_page;	     /* document page # to link to */
   char    *name;	     /* name of label or title of topic to link to */
   char    *srcfile;	     /* .SRC file link appears in */
   int	    srcline;	     /* .SRC file line # link appears in */
   } LINK;


typedef struct
   {
   unsigned offset;     /* offset from start of topic text */
   unsigned length;     /* length of page (in chars) */
   int	    margin;     /* if > 0 then page starts in_para and text */
		        /* should be indented by this much */
   } PAGE;


/* values for TOPIC.flags */

#define TF_IN_DOC  (1)	     /* 1 if topic is part of the printed document */
#define TF_DATA    (2)	     /* 1 if it is a "data" topic */


typedef struct
   {
   unsigned  flags;	     /* see #defines for TF_??? */
   int	     doc_page;	     /* page number in document where topic starts */
   unsigned  title_len;      /* length of title */
   char     *title;	     /* title for this topic */
   int	     num_page;	     /* number of pages */
   PAGE     *page;	     /* list of pages */
   unsigned  text_len;	     /* lenth of topic text */
   long      text;	     /* topic text (all pages) */
   long      offset;	     /* offset to topic from start of file */
   } TOPIC;


typedef struct
   {
   char    *name;	     /* its name */
   int	    topic_num;	     /* topic number */
   unsigned topic_off;	     /* offset of label in the topic's text */
   int	    doc_page;
   } LABEL;


/* values for CONTENT.flags */

#define CF_NEW_PAGE  (1)     /* true if section starts on a new page */


#define MAX_CONTENT_TOPIC (10)


typedef struct
   {
   unsigned  flags;
   char     *id;
   char     *name;
   int	     doc_page;
   unsigned  page_num_pos;
   int	     num_topic;
   char      is_label[MAX_CONTENT_TOPIC];
   char     *topic_name[MAX_CONTENT_TOPIC];
   int	     topic_num[MAX_CONTENT_TOPIC];
   char     *srcfile;
   int	     srcline;
   } CONTENT;


struct help_sig_info
   {
   unsigned long sig;
   int		 version;
   unsigned long base;
   } ;


int	 num_topic	  = 0;	  /* topics */
TOPIC   *topic;

int	 num_label	  = 0;	  /* labels */
LABEL   *label;

int	 num_plabel	  = 0;	  /* private labels */
LABEL   *plabel;

int	 num_link	  = 0;	  /* all links */
LINK    *a_link		  = 0;

int	 num_contents	  = 0;	  /* the table-of-contents */
CONTENT *contents;

int	 quiet_mode	  = 0;	  /* true if "/Q" option used */

int	 max_pages	  = 0;	  /* max. pages in any topic */
int	 max_links	  = 0;	  /* max. links on any page */
int	 num_doc_pages	  = 0;	  /* total number of pages in document */

FILE    *srcfile;		  /* .SRC file */
int	 srcline	  = 0;	  /* .SRC line number (used for errors) */
int	 srccol 	  = 0;	  /* .SRC column. */

int	 version	  = -1;   /* help file version */

int	 errors 	  = 0,	  /* number of errors reported */
	 warnings	  = 0;	  /* number of warnings reported */

char	 src_fname[81]	  = "";   /* command-line .SRC filename */
char	 hdr_fname[81]	  = "";   /* .H filename */
char	 hlp_fname[81]	  = "";   /* .HLP filename */
char    *src_cfname	  = NULL; /* current .SRC filename */

int	 format_exclude   = 0;	  /* disable formatting at this col, 0 to */
				  /*    never disable formatting */
FILE    *swapfile;
long	 swappos;

char    *buffer;		  /* alloc'ed as BUFFER_SIZE bytes */
char    *curr;			  /* current position in the buffer */
char	 cmd[128];		  /* holds the current command */
int	 compress_spaces;
int	 xonline;
int	 xdoc;

#define  MAX_INCLUDE_STACK (5)	  /* allow 5 nested includes */

struct
   {
   char *fname;
   FILE *file;
   int	 line;
   int	 col;
   } include_stack[MAX_INCLUDE_STACK];
int include_stack_top = -1;


#define CHK_BUFFER(off) { if ((unsigned)(curr+(off)) - (unsigned)buffer >= (BUFFER_SIZE-1024)) fatal(0,"Buffer overflowed -- Help topic too large."); }

#ifdef __WATCOMC__
#define putw( x1, x2 )  fprintf( x2, "%c%c", x1&0xFF, x1>>8 );
#endif

#ifdef XFRACT
#define putw( x1, x2 )  fwrite( &(x1), 1, sizeof(int), x2);
#endif

/*
 * error/warning/message reporting functions.
 */


void report_errors(void)
   {
   printf("\n");
   printf("Compiler Status:\n");
   printf("%8d Error%c\n",	 errors,   (errors==1)	 ? ' ' : 's');
   printf("%8d Warning%c\n",	 warnings, (warnings==1) ? ' ' : 's');
   }


void print_msg(char *type, int lnum, char *format, va_list arg)
   {
   if (type != NULL)
      {
      printf("   %s", type);
      if (lnum>0)
	 printf(" %s %d", src_cfname, lnum);
      printf(": ");
      }
   vprintf(format, arg);
   printf("\n");
   }


#ifndef XFRACT
void fatal(int diff, char *format, ...)
#else
void fatal(va_alist)
    va_dcl
#endif
   {
   va_list arg;

#ifndef XFRACT
   va_start(arg, format);
#else
   int diff;
   char *format;
   va_start(arg);
   diff = va_arg(arg,int);
   format = va_arg(arg,char *);
#endif

   print_msg("Fatal", srcline-diff, format, arg);
   va_end(arg);

   if ( errors || warnings )
      report_errors();

   exit( errors + 1 );
   }


#ifndef XFRACT
void error(int diff, char *format, ...)
#else
void error(va_alist)
    va_dcl
#endif
   {
   va_list arg;

#ifndef XFRACT
   va_start(arg, format);
#else
   int diff;
   char *format;
   va_start(arg);
   diff = va_arg(arg,int);
   format = va_arg(arg,char *);
#endif
   print_msg("Error", srcline-diff, format, arg);
   va_end(arg);

   if (++errors >= MAX_ERRORS && MAX_ERRORS > 0)
      fatal(0,"Too many errors!");
   }


#ifndef XFRACT
void warn(int diff, char *format, ...)
#else
void warn(va_alist)
   va_dcl
#endif
   {
   va_list arg;
#ifndef XFRACT
   va_start(arg, format);
#else
   int diff;
   char *format;
   va_start(arg);
   diff = va_arg(arg, int);
   format = va_arg(arg, char *);
#endif
   print_msg("Warning", srcline-diff, format, arg);
   va_end(arg);

   if (++warnings >= MAX_WARNINGS && MAX_WARNINGS > 0)
      fatal(0,"Too many warnings!");
   }


#ifndef XFRACT
void notice(char *format, ...)
#else
void notice(va_alist)
    va_dcl
#endif
   {
   va_list arg;
#ifndef XFRACT
   va_start(arg, format);
#else
   char *format;

   va_start(arg);
   format = va_arg(arg,char *);
#endif
   print_msg("Note", srcline, format, arg);
   va_end(arg);
   }


#ifndef XFRACT
void msg(char *format, ...)
#else
void msg(va_alist)
va_dcl
#endif
   {
   va_list arg;
#ifdef XFRACT
   char *format;
#endif

   if (quiet_mode)
      return;
#ifndef XFRACT
   va_start(arg, format);
#else
   va_start(arg);
   format = va_arg(arg,char *);
#endif
   print_msg(NULL, 0, format, arg);
   va_end(arg);
   }


#ifdef SHOW_ERROR_LINE
#   define fatal  (printf("[%04d] ", __LINE__), fatal)
#   define error  (printf("[%04d] ", __LINE__), error)
#   define warn   (printf("[%04d] ", __LINE__), warn)
#   define notice (printf("[%04d] ", __LINE__), notice)
#   define msg	  (printf((quiet_mode)?"":"[%04d] ", __LINE__), msg)
#endif


/*
 * store-topic-text-to-disk stuff.
 */


void alloc_topic_text(TOPIC *t, unsigned size)
   {
   t->text_len = size;
   t->text = swappos;
   swappos += size;
   fseek(swapfile, t->text, SEEK_SET);
   fwrite(buffer, 1, t->text_len, swapfile);
   }


char *get_topic_text(TOPIC *t)
   {
   fseek(swapfile, t->text, SEEK_SET);
   fread(buffer, 1, t->text_len, swapfile);
   return (buffer);
   }


void release_topic_text(TOPIC *t, int save)
   {
   if ( save )
      {
      fseek(swapfile, t->text, SEEK_SET);
      fwrite(buffer, 1, t->text_len, swapfile);
      }
   }


/*
 * memory-allocation functions.
 */


#define new(item)    (item *)newx(sizeof(item))
#define delete(item) free(item)


VOIDPTR newx(unsigned size)
   {
   VOIDPTR ptr;

   ptr = malloc(size);

   if (ptr == NULL)
      fatal(0,"Out of memory!");

   return (ptr);
   }


VOIDPTR renewx(VOIDPTR ptr, unsigned size)
   {
   ptr = realloc(ptr, size);

   if (ptr == NULL)
      fatal(0,"Out of memory!");

   return (ptr);
   }


char *dupstr(char *s, unsigned len)
   {
   char *ptr;

   if (len == 0)
      len = strlen(s) + 1;

   ptr = newx(len);

   memcpy(ptr, s, len);

   return (ptr);
   }


#define LINK_ALLOC_SIZE (16)


int add_link(LINK *l)
   {
   if (num_link == 0)
      a_link = newx( sizeof(LINK)*LINK_ALLOC_SIZE );

   else if (num_link%LINK_ALLOC_SIZE == 0)
      a_link = renewx(a_link, sizeof(LINK) * (num_link+LINK_ALLOC_SIZE) );

   a_link[num_link] = *l;

   return( num_link++ );
   }


#define PAGE_ALLOC_SIZE (4)


int add_page(TOPIC *t, PAGE *p)
   {
   if (t->num_page == 0)
      t->page = newx( sizeof(PAGE)*PAGE_ALLOC_SIZE );

   else if (t->num_page%PAGE_ALLOC_SIZE == 0)
      t->page = renewx(t->page, sizeof(PAGE) * (t->num_page+PAGE_ALLOC_SIZE) );

   t->page[t->num_page] = *p;

   return ( t->num_page++ );
   }


#define TOPIC_ALLOC_SIZE (16)


int add_topic(TOPIC *t)
   {
   if (num_topic == 0)
      topic = newx( sizeof(TOPIC)*TOPIC_ALLOC_SIZE );

   else if (num_topic%TOPIC_ALLOC_SIZE == 0)
      topic = renewx(topic, sizeof(TOPIC) * (num_topic+TOPIC_ALLOC_SIZE) );

   topic[num_topic] = *t;

   return ( num_topic++ );
   }


#define LABEL_ALLOC_SIZE (16)


int add_label(LABEL *l)
   {
   if (l->name[0] == '@')    /* if it's a private label... */
      {
      if (num_plabel == 0)
	 plabel = newx( sizeof(LABEL)*LABEL_ALLOC_SIZE );

      else if (num_plabel%LABEL_ALLOC_SIZE == 0)
	 plabel = renewx(plabel, sizeof(LABEL) * (num_plabel+LABEL_ALLOC_SIZE) );

      plabel[num_plabel] = *l;

      return ( num_plabel++ );
      }
   else
      {
      if (num_label == 0)
	 label = newx( sizeof(LABEL)*LABEL_ALLOC_SIZE );

      else if (num_label%LABEL_ALLOC_SIZE == 0)
	 label = renewx(label, sizeof(LABEL) * (num_label+LABEL_ALLOC_SIZE) );

      label[num_label] = *l;

      return ( num_label++ );
      }
   }


#define CONTENTS_ALLOC_SIZE (16)


int add_content(CONTENT *c)
   {
   if (num_contents == 0)
      contents = newx( sizeof(CONTENT)*CONTENTS_ALLOC_SIZE );

   else if (num_contents%CONTENTS_ALLOC_SIZE == 0)
      contents = renewx(contents, sizeof(CONTENT) * (num_contents+CONTENTS_ALLOC_SIZE) );

   contents[num_contents] = *c;

   return ( num_contents++ );
   }


/*
 * read_char() stuff
 */


#define READ_CHAR_BUFF_SIZE (32)


int  read_char_buff[READ_CHAR_BUFF_SIZE];
int  read_char_buff_pos = -1;
int  read_char_sp       = 0;


void unread_char(int ch)
   /*
    * Will not handle new-lines or tabs correctly!
    */
   {
   if (read_char_buff_pos+1 >= READ_CHAR_BUFF_SIZE)
      fatal(0,"Compiler Error -- Read char buffer overflow!");

   read_char_buff[++read_char_buff_pos] = ch;

   --srccol;
   }


void unread_string(char *s)
   {
   int p = strlen(s);

   while (p-- > 0)
      unread_char(s[p]);
   }


int eos(void)	 /* end-of-source ? */
   {
   return ( !((read_char_sp==0) && (read_char_buff_pos==0) && feof(srcfile)) );
   }


int _read_char(void)
   {
   int ch;

   if (srcline <= 0)
      {
      srcline = 1;
      srccol = 0;
      }

   if (read_char_buff_pos >= 0)
      {
      ++srccol;
      return ( read_char_buff[read_char_buff_pos--] );
      }

   if (read_char_sp > 0)
      {
      --read_char_sp;
      return (' ');
      }

   if ( feof(srcfile) )
      return (-1);

   while (1)
      {
      ch = getc(srcfile);

      switch (ch)
	 {
	 case '\t':    /* expand a tab */
	    {
	    int diff = ( ( (srccol/8) + 1 ) * 8 ) - srccol;

	    srccol += diff;
	    read_char_sp += diff;
	    break;
	    }

	 case ' ':
	    ++srccol;
	    ++read_char_sp;
	    break;

	 case '\n':
	    read_char_sp = 0;   /* delete spaces before a \n */
	    srccol = 0;
	    ++srcline;
	    return ('\n');

	 case -1:	        /* EOF */
	    if (read_char_sp > 0)
	       {
	       --read_char_sp;
	       return (' ');
	       }
	    return (-1);

	 default:
	    if (read_char_sp > 0)
	       {
	       ungetc(ch, srcfile);
	       --read_char_sp;
	       return (' ');
	       }

	    ++srccol;
	    return (ch);

	 } /* switch */
      }
   }


int read_char(void)
   {
   int ch;

   ch = _read_char();

   while (ch == ';' && srccol==1)    /* skip over comments */
      {
      ch = _read_char();

      while (ch!='\n' && ch!=-1 )
	 ch = _read_char();

      ch = _read_char();
      }

   if (ch == '\\')   /* process an escape code */
      {
      ch = _read_char();

      if (ch >= '0' && ch <= '9')
	 {
	 char buff[4];
	 int  ctr;

	 for (ctr=0; ; ctr++)
	    {
	    if ( ch<'0' || ch>'9' || ch==-1 || ctr>=3 )
	       {
	       unread_char(ch);
	       break;
	       }
	    buff[ctr] = ch;
	    ch = _read_char();
	    }
	 buff[ctr] = '\0';
	 ch = atoi(buff);
	 }

#ifdef XFRACT
   /* Convert graphics arrows into keyboard chars */
       if (ch>=24 && ch<=27) {
	   ch = "KJHL"[ch-24];
       }
#endif
      ch |= 0x100;
      }

   if ( (ch & 0xFF) == 0 )
      {
      error(0,"Null character (\'\\0\') not allowed!");
      ch = 0x1FF; /* since we've had an error the file will not be written; */
		  /*   the value we return doesn't really matter */
      }

   return(ch);
   }


/*
 * misc. search functions.
 */


LABEL *find_label(char *name)
   {
   int	  l;
   LABEL *lp;

   if (*name == '@')
      {
      for (l=0, lp=plabel; l<num_plabel; l++, lp++)
	 if ( strcmp(name, lp->name) == 0 )
	    return (lp);
      }
   else
      {
      for (l=0, lp=label; l<num_label; l++, lp++)
	 if ( strcmp(name, lp->name) == 0 )
	    return (lp);
      }

   return (NULL);
   }


int find_topic_title(char *title)
   {
   int t;
   int len;

   while (*title == ' ')
      ++title;

   len = strlen(title) - 1;
   while ( title[len] == ' ' && len > 0 )
      --len;

   ++len;

   if ( len > 2 && title[0] == '\"' && title[len-1] == '\"' )
      {
      ++title;
      len -= 2;
      }

   for (t=0; t<num_topic; t++)
      if ( strlen(topic[t].title) == len &&
	   strnicmp(title, topic[t].title, len) == 0 )
	 return (t);

   return (-1);   /* not found */
   }


/*
 * .SRC file parser stuff
 */


int validate_label_name(char *name)
   {
   if ( !isalpha(*name) && *name!='@' && *name!='_' )
      return (0);  /* invalid */

   while (*(++name) != '\0')
      if ( !isalpha(*name) && !isdigit(*name) && *name!='_' )
	 return(0);  /* invalid */

   return (1);  /* valid */
   }


char *read_until(char *buff, int len, char *stop_chars)
   {
   int ch;

   while ( --len > 0 )
      {
      ch = read_char();

      if ( ch == -1 )
	 {
	 *buff++ = '\0';
	 break;
	 }

      if ( (ch&0xFF) <= MAX_CMD )
	 *buff++ = CMD_LITERAL;

      *buff++ = ch;

      if ( (ch&0x100)==0 && strchr(stop_chars, ch) != NULL )
	 break;
      }

   return ( buff-1 );
   }


void skip_over(char *skip)
   {
   int ch;

   while (1)
      {
      ch = read_char();

      if ( ch == -1 )
	 break;

      else if ( (ch&0x100) == 0 && strchr(skip, ch) == NULL )
	 {
	 unread_char(ch);
	 break;
	 }
      }
   }


char *pchar(int ch)
   {
   static char buff[16];

   if ( ch >= 0x20 && ch <= 0x7E )
      sprintf(buff, "\'%c\'", ch);
   else
      sprintf(buff, "\'\\x%02X\'", ch&0xFF);

   return (buff);
   }


void put_spaces(int how_many)
   {
   if (how_many > 2 && compress_spaces)
      {
      if (how_many > 255)
	 {
	 error(0,"Too many spaces (over 255).");
	 how_many = 255;
	 }

      *curr++ = CMD_SPACE;
      *curr++ = (BYTE)how_many;
      }
   else
      {
      while (how_many-- > 0)
	 *curr++ = ' ';
      }
   }


int get_next_item(void)   /* used by parse_contents() */
   {
   int	 last;
   char *ptr;

   skip_over(" \t\n");
   ptr = read_until(cmd, 128, ",}");
   last = (*ptr == '}');
   --ptr;
   while ( ptr >= cmd && strchr(" \t\n",*ptr) )   /* strip trailing spaces */
      --ptr;
   *(++ptr) = '\0';

   return (last);
   }


void process_contents(void)
   {
   CONTENT c;
   char   *ptr;
   int	   indent;
   int	   ch;
   TOPIC   t;

   t.flags     = 0;
   t.title_len = strlen(DOCCONTENTS_TITLE)+1;
   t.title     = dupstr(DOCCONTENTS_TITLE, t.title_len);
   t.doc_page  = -1;
   t.num_page  = 0;

   curr = buffer;

   c.flags = 0;
   c.id = dupstr("",1);
   c.name = dupstr("",1);
   c.doc_page = -1;
   c.page_num_pos = 0;
   c.num_topic = 1;
   c.is_label[0] = 0;
   c.topic_name[0] = dupstr(DOCCONTENTS_TITLE,0);
   c.srcline = -1;
   add_content(&c);

   while (1)
      {
      ch = read_char();

      if (ch == '{')   /* process a CONTENT entry */
	 {
	 int last;

	 c.flags = 0;
	 c.num_topic = 0;
	 c.doc_page = -1;
	 c.srcfile = src_cfname;
	 c.srcline = srcline;

	 if ( get_next_item() )
	    {
	    error(0,"Unexpected end of DocContent entry.");
	    continue;
	    }
	 c.id = dupstr(cmd,0);

	 if ( get_next_item() )
	    {
	    error(0,"Unexpected end of DocContent entry.");
	    continue;
	    }
	 indent = atoi(cmd);

	 last = get_next_item();

	 if ( cmd[0] == '\"' )
	    {
	    ptr = cmd+1;
	    if (ptr[strlen(ptr)-1] == '\"')
	       ptr[strlen(ptr)-1] = '\0';
	    else
	       warn(0,"Missing ending quote.");

	    c.is_label[c.num_topic] = 0;
	    c.topic_name[c.num_topic] = dupstr(ptr,0);
	    ++c.num_topic;
	    c.name = dupstr(ptr,0);
	    }
	 else
	    c.name = dupstr(cmd,0);

	 /* now, make the entry in the buffer */

	 sprintf(curr, "%-5s %*.0s%s", c.id, indent*2, "", c.name);
	 ptr = curr + strlen(curr);
	 while ( (ptr-curr) < PAGE_WIDTH-10 )
	    *ptr++ = '.';
	 c.page_num_pos = (unsigned) ( (ptr-3) - buffer );
	 curr = ptr;

	 while (!last)
	    {
	    last = get_next_item();

	    if ( stricmp(cmd, "FF") == 0 )
	       {
	       if ( c.flags & CF_NEW_PAGE )
		  warn(0,"FF already present in this entry.");
	       c.flags |= CF_NEW_PAGE;
	       continue;
	       }

	    if (cmd[0] == '\"')
	       {
	       ptr = cmd+1;
	       if (ptr[strlen(ptr)-1] == '\"')
		  ptr[strlen(ptr)-1] = '\0';
	       else
		  warn(0,"Missing ending quote.");

	       c.is_label[c.num_topic] = 0;
	       c.topic_name[c.num_topic] = dupstr(ptr,0);
	       }
	    else
	       {
	       c.is_label[c.num_topic] = 1;
	       c.topic_name[c.num_topic] = dupstr(cmd,0);
	       }

	    if ( ++c.num_topic >= MAX_CONTENT_TOPIC )
	       {
	       error(0,"Too many topics in DocContent entry.");
	       break;
	       }
	    }

	 add_content(&c);
	 }

      else if (ch == '~')   /* end at any command */
	 {
	 unread_char(ch);
	 break;
	 }

      else
	 *curr++ = ch;

      CHK_BUFFER(0);
      }

   alloc_topic_text(&t, (unsigned) (curr - buffer) );
   add_topic(&t);
   }


int parse_link(void)   /* returns length of link or 0 on error */
   {
   char *ptr;
   char *end;
   int	 bad = 0;
   int	 len;
   LINK  l;
   int	 lnum;
   int	 err_off;

   l.srcfile  = src_cfname;
   l.srcline  = srcline;
   l.doc_page = -1;

   end = read_until(cmd, 128, "}\n");   /* get the entire hot-link */

   if (*end == '\0')
      {
      error(0,"Unexpected EOF in hot-link.");
      return (0);
      }

   if (*end == '\n')
      {
      err_off = 1;
      warn(1,"Hot-link has no closing curly-brace (\'}\').");
      }
   else
      err_off = 0;

   *end = '\0';

   if (cmd[0] == '=')   /* it's an "explicit" link to a label or "special" */
      {
      ptr = strchr(cmd, ' ');

      if (ptr == NULL)
	 ptr = end;
      else
	 *ptr++ = '\0';

      len = (int) (end - ptr);

      if ( cmd[1] == '-' )
	 {
	 l.type      = 2;	   /* type 2 = "special" */
	 l.topic_num = atoi(cmd+1);
	 l.topic_off = 0;
	 l.name      = NULL;
	 }
      else
	 {
	 l.type = 1;	       /* type 1 = to a label */
	 if ((int)strlen(cmd) > 32)
	    warn(err_off, "Label is long.");
	 if (cmd[1] == '\0')
	    {
	    error(err_off, "Explicit hot-link has no Label.");
	    bad = 1;
	    }
	 else
	    l.name = dupstr(cmd+1,0);
	 }
      if (len == 0)
	 warn(err_off, "Explicit hot-link has no title.");
      }
   else
      {
      ptr = cmd;
      l.type = 0;   /* type 0 = topic title */
      len = (int) (end - ptr);
      if (len == 0)
	 {
	 error(err_off, "Implicit hot-link has no title.");
	 bad = 1;
	 }
      l.name = dupstr(ptr,len+1);
      l.name[len] = '\0';
      }

   if ( !bad )
      {
      CHK_BUFFER(1+3*sizeof(int)+len+1)
      lnum = add_link(&l);
      *curr++ = CMD_LINK;
      setint(curr,lnum);
      curr += 3*sizeof(int);
      memcpy(curr, ptr, len);
      curr += len;
      *curr++ = CMD_LINK;
      return (len);
      }
   else
      return (0);
   }


#define MAX_TABLE_SIZE (100)


int create_table(void)
   {
   char  *ptr;
   int	  width;
   int	  cols;
   int	  start_off;
   int	  first_link;
   int	  rows;
   int	  r, c;
   int	  ch;
   int	  done;
   int	  len;
   int	  lnum;
   int	  count;
   char  *title[MAX_TABLE_SIZE];
   char  *table_start;

   ptr = strchr(cmd, '=');

   if (ptr == NULL)
      return (0);   /* should never happen! */

   ptr++;

   len = sscanf(ptr, " %d %d %d", &width, &cols, &start_off);

   if (len < 3)
      {
      error(1,"Too few arguments to Table.");
      return (0);
      }

   if (width<=0 || width > 78 || cols<=0 || start_off<0 || start_off > 78)
      {
      error(1,"Argument out of range.");
      return (0);
      }

   done = 0;

   first_link = num_link;
   table_start = curr;
   count = 0;

   /* first, read all the links in the table */

   do
      {

      do
	 ch = read_char();
      while ( ch=='\n' || ch == ' ' );

      if (done)
	 break;

      switch (ch)
	 {
	 case -1:
	    error(0,"Unexpected EOF in a Table.");
	    return(0);

	 case '{':
	    if (count >= MAX_TABLE_SIZE)
	       fatal(0,"Table is too large.");
	    len = parse_link();
	    curr = table_start;   /* reset to the start... */
            title[count] = dupstr(curr+3*sizeof(int)+1, len+1);
	    if (len >= width)
	       {
	       warn(1,"Link is too long; truncating.");
	       len = width-1;
	       }
	    title[count][len] = '\0';
	    ++count;
	    break;

	 case '~':
	    {
	    int imbedded;

	    ch = read_char();

	    if (ch=='(')
	       imbedded = 1;
	    else
	       {
	       imbedded = 0;
	       unread_char(ch);
	       }

	    ptr = read_until(cmd, 128, ")\n,");

	    ch = *ptr;
	    *ptr = '\0';

	    if  ( stricmp(cmd, "EndTable") == 0 )
	       done = 1;
	    else
	       {
	       error(1,"Unexpected command in table \"%s\"", cmd);
	       warn(1,"Command will be ignored.");
	       }

	    if (ch == ',')
	       {
	       if (imbedded)
		  unread_char('(');
	       unread_char('~');
	       }
	    }
	    break;

	 default:
	    error(0,"Unexpected character %s.", pchar(ch));
	    break;
	 }
      }
   while (!done);

   /* now, put all the links into the buffer... */

   rows = 1 + ( count / cols );

   for (r=0; r<rows; r++)
      {
      put_spaces(start_off);
      for (c=0; c<cols; c++)
	 {
	 lnum = c*rows + r;

	 if ( first_link+lnum >= num_link )
	    break;

	 len = strlen(title[lnum]);
	 *curr++ = CMD_LINK;
         setint(curr,first_link+lnum);
         curr += 3*sizeof(int);
	 memcpy(curr, title[lnum], len);
	 curr += len;
	 *curr++ = CMD_LINK;

	 delete(title[lnum]);

	 if ( c < cols-1 )
	    put_spaces( width-len );
	 }
      *curr++ = '\n';
      }

   return (1);
   }


void process_comment(void)
   {
   int ch;

   while ( 1 )
      {
      ch = read_char();

      if (ch == '~')
	 {
	 int   imbedded;
	 char *ptr;

	 ch = read_char();

	 if (ch=='(')
	    imbedded = 1;
	 else
	    {
	    imbedded = 0;
	    unread_char(ch);
	    }

	 ptr = read_until(cmd, 128, ")\n,");

	 ch = *ptr;
	 *ptr = '\0';

	 if  ( stricmp(cmd, "EndComment") == 0 )
	    {
	    if (ch == ',')
	       {
	       if (imbedded)
		  unread_char('(');
	       unread_char('~');
	       }
	    break;
	    }
	 }

      else if ( ch == -1 )
	 {
	 error(0,"Unexpected EOF in Comment");
	 break;
	 }
      }
   }


void process_bininc(void)
   {
   int  handle;
   long len;

   if ( (handle=open(cmd+7, O_RDONLY|O_BINARY)) == -1 )
      {
      error(0,"Unable to open \"%s\"", cmd+7);
      return ;
      }

   len = filelength(handle);

   if ( len >= BUFFER_SIZE )
      {
      error(0,"File \"%s\" is too large to BinInc (%dK).", cmd+7, (int)(len>>10));
      close(handle);
      return ;
      }

   /*
    * Since we know len is less than BUFFER_SIZE (and therefore less then
    * 64K) we can treat it as an unsigned.
    */

   CHK_BUFFER((unsigned)len);

   read(handle, curr, (unsigned)len);

   curr += (unsigned)len;

   close(handle);
   }


void start_topic(TOPIC *t, char *title, int title_len)
   {
   t->flags = 0;
   t->title_len = title_len;
   t->title = dupstr(title, title_len+1);
   t->title[title_len] = '\0';
   t->doc_page = -1;
   t->num_page = 0;
   curr = buffer;
   }


void end_topic(TOPIC *t)
   {
   alloc_topic_text(t, (unsigned) (curr - buffer) );
   add_topic(t);
   }


int end_of_sentence(char *ptr)  /* true if ptr is at the end of a sentence */
   {
   if ( *ptr == ')')
      --ptr;

   if ( *ptr == '\"')
      --ptr;

   return ( *ptr=='.' || *ptr=='?' || *ptr=='!' );
   }


void add_blank_for_split(void)	 /* add space at curr for merging two lines */
   {
   if ( !is_hyphen(curr-1) )   /* no spaces if it's a hyphen */
      {
      if ( end_of_sentence(curr-1) )
	 *curr++ = ' ';  /* two spaces at end of a sentence */
      *curr++ = ' ';
      }
   }


void put_a_char(int ch, TOPIC *t)
   {
   if (ch == '{' && !(t->flags & TF_DATA) )   /* is it a hot-link? */
      parse_link();
   else
      {
      if ( (ch&0xFF) <= MAX_CMD)
	 *curr++ = CMD_LITERAL;
      *curr++ = ch;
      }
   }


enum STATES   /* states for FSM's */
   {
   S_Start,		    /* initial state, between paragraphs	   */
   S_StartFirstLine,	    /* spaces at start of first line		   */
   S_FirstLine, 	    /* text on the first line			   */
   S_FirstLineSpaces,	    /* spaces on the first line 		   */
   S_StartSecondLine,	    /* spaces at start of second line		   */
   S_Line,		    /* text on lines after the first		   */
   S_LineSpaces,	    /* spaces on lines after the first		   */
   S_StartLine, 	    /* spaces at start of lines after second	   */
   S_FormatDisabled,	    /* format automatically disabled for this line */
   S_FormatDisabledSpaces,  /* spaces in line which format is disabled	   */
   S_Spaces
   } ;


void check_command_length(int eoff, int len)
   {
   if (strlen(cmd) != len)
      error(eoff, "Invalid text after a command \"%s\"", cmd+len);
   }


void read_src(char *fname)
   {
   int	  ch;
   char  *ptr;
   TOPIC  t;
   LABEL  lbl;
   char  *margin_pos = NULL;
   int	  in_topic   = 0,
	  formatting = 1,
	  state      = S_Start,
	  num_spaces = 0,
	  margin     = 0,
	  in_para    = 0,
	  centering  = 0,
	  lformat_exclude = format_exclude,
	  again;

   xonline = xdoc = 0;

   src_cfname = fname;

   if ( (srcfile = fopen(fname, "rt")) == NULL )
      fatal(0,"Unable to open \"%s\"", fname);

   msg("Compiling: %s", fname);

   in_topic = 0;

   curr = buffer;

   while ( 1 )
      {

      ch = read_char();

      if ( ch == -1 )   /* EOF? */
	 {
	 if ( include_stack_top >= 0)
	    {
	    fclose(srcfile);
	    src_cfname = include_stack[include_stack_top].fname;
	    srcfile = include_stack[include_stack_top].file;
	    srcline = include_stack[include_stack_top].line;
	    srccol  = include_stack[include_stack_top].col;
	    --include_stack_top;
	    continue;
	    }
	 else
	    {
	    if (in_topic)  /* if we're in a topic, finish it */
	       end_topic(&t);
	    if (num_topic == 0)
	       warn(0,".SRC file has no topics.");
	    break;
	    }
	 }

      if (ch == '~')   /* is is a command? */
	 {
	 int imbedded;
	 int eoff;
	 int done;

	 ch = read_char();
	 if (ch == '(')
	    {
	    imbedded = 1;
	    eoff = 0;
	    }
	 else
	    {
	    imbedded = 0;
	    eoff=0;
	    unread_char(ch);
	    }

	 done = 0;

	 while ( !done )
	    {
	    do
	       ch = read_char();
	    while (ch == ' ');
	    unread_char(ch);

	    if (imbedded)
	       ptr = read_until(cmd, 128, ")\n,");
	    else
	       ptr = read_until(cmd, 128, "\n,");

	    done = 1;

	    if ( *ptr == '\0' )
	       {
	       error(0,"Unexpected EOF in command.");
	       break;
	       }

	    if (*ptr == '\n')
	       ++eoff;

	    if ( imbedded && *ptr == '\n' )
	       error(eoff,"Imbedded command has no closing parend (\')\')");

	    done = (*ptr != ',');   /* we done if it's not a comma */

	    if ( *ptr != '\n' && *ptr != ')' && *ptr != ',' )
	       {
	       error(0,"Command line too long.");
	       break;
	       }

	    *ptr = '\0';


	    /* commands allowed anytime... */

	    if ( strnicmp(cmd, "Topic=", 6) == 0 )
	       {
	       if (in_topic)  /* if we're in a topic, finish it */
		  end_topic(&t);
	       else
		  in_topic = 1;

	       if (cmd[6] == '\0')
		  warn(eoff,"Topic has no title.");

	       else if ((int)strlen(cmd+6) > 70)
		  error(eoff,"Topic title is too long.");

	       else if ((int)strlen(cmd+6) > 60)
		  warn(eoff,"Topic title is long.");

	       if ( find_topic_title(cmd+6) != -1 )
		  error(eoff,"Topic title already exists.");

	       start_topic(&t, cmd+6, (unsigned)(ptr-(cmd+6)));
	       formatting = 1;
	       centering = 0;
	       state = S_Start;
	       in_para = 0;
	       num_spaces = 0;
	       xonline = xdoc = 0;
	       lformat_exclude = format_exclude;
	       compress_spaces = 1;
	       continue;
	       }

	    else if ( strnicmp(cmd, "Data=", 5) == 0 )
	       {
	       if (in_topic)  /* if we're in a topic, finish it */
		  end_topic(&t);
	       else
		  in_topic = 1;

	       if (cmd[5] == '\0')
		  warn(eoff,"Data topic has no label.");

	       if ( !validate_label_name(cmd+5) )
		  {
		  error(eoff,"Label \"%s\" contains illegal characters.", cmd+5);
		  continue;
		  }

	       if ( find_label(cmd+5) != NULL )
		  {
		  error(eoff,"Label \"%s\" already exists", cmd+5);
		  continue;
		  }

	       if ( cmd[5] == '@' )
		  warn(eoff, "Data topic has a local label.");

	       start_topic(&t, "", 0);
	       t.flags |= TF_DATA;

	       if ((int)strlen(cmd+5) > 32)
		  warn(eoff,"Label name is long.");

	       lbl.name      = dupstr(cmd+5, 0);
	       lbl.topic_num = num_topic;
	       lbl.topic_off = 0;
	       lbl.doc_page  = -1;
	       add_label(&lbl);

	       formatting = 0;
	       centering = 0;
	       state = S_Start;
	       in_para = 0;
	       num_spaces = 0;
	       xonline = xdoc = 0;
	       lformat_exclude = format_exclude;
	       compress_spaces = 0;
	       continue;
	       }

	    else if ( strnicmp(cmd, "DocContents", 11) == 0 )
	       {
	       check_command_length(eoff, 11);
	       if (in_topic)  /* if we're in a topic, finish it */
		  end_topic(&t);
	       if (!done)
		  {
		  if (imbedded)
		     unread_char('(');
		  unread_char('~');
		  done = 1;
		  }
	       compress_spaces = 1;
	       process_contents();
	       in_topic = 0;
	       continue;
	       }

	    else if ( stricmp(cmd, "Comment") == 0 )
	       {
	       process_comment();
	       continue;
	       }

	    else if ( strnicmp(cmd, "FormatExclude", 13) == 0 )
	       {
	       if (cmd[13] == '-')
		  {
		  check_command_length(eoff, 14);
		  if ( in_topic )
		     {
		     if (lformat_exclude > 0)
		        lformat_exclude = -lformat_exclude;
		     else
		        warn(eoff,"\"FormatExclude-\" is already in effect.");
		     }
		  else
		     {
		     if (format_exclude > 0)
		        format_exclude = -format_exclude;
		     else
		        warn(eoff,"\"FormatExclude-\" is already in effect.");
		     }
		  }
	       else if (cmd[13] == '+')
		  {
		  check_command_length(eoff,14);
		  if ( in_topic )
		     {
		     if (lformat_exclude < 0)
		        lformat_exclude = -lformat_exclude;
		     else
		        warn(eoff,"\"FormatExclude+\" is already in effect.");
		     }
		  else
		     {
		     if (format_exclude < 0)
		        format_exclude = -format_exclude;
		     else
		        warn(eoff,"\"FormatExclude+\" is already in effect.");
		     }
		  }
	       else if (cmd[13] == '=')
		  {
		  if (cmd[14] == 'n' || cmd[14] == 'N')
		     {
		     check_command_length(eoff,15);
		     if (in_topic)
		        lformat_exclude = 0;
		     else
		       format_exclude = 0;
		     }
		  else if (cmd[14] == '\0')
		     lformat_exclude = format_exclude;
		  else
		     {
		     int n = ( ( (in_topic) ? lformat_exclude : format_exclude) < 0 ) ? -1 : 1;

		     lformat_exclude = atoi(cmd+14);

		     if ( lformat_exclude <= 0 )
		        {
		        error(eoff,"Invalid argument to FormatExclude=");
		        lformat_exclude = 0;
		        }

		     lformat_exclude *= n;

		     if ( !in_topic )
		        format_exclude = lformat_exclude;
		     }
		  }
	       else
		  error(eoff,"Invalid format for FormatExclude");

	       continue;
	       }

	    else if ( strnicmp(cmd, "Include ", 8) == 0 )
	       {
	       if (include_stack_top >= MAX_INCLUDE_STACK-1)
		  error(eoff, "Too many nested Includes.");
	       else
		  {
		  ++include_stack_top;
		  include_stack[include_stack_top].fname = src_cfname;
		  include_stack[include_stack_top].file = srcfile;
		  include_stack[include_stack_top].line = srcline;
		  include_stack[include_stack_top].col  = srccol;
		  strupr(cmd+8);
		  if ( (srcfile = fopen(cmd+8, "rt")) == NULL )
		     {
		     error(eoff, "Unable to open \"%s\"", cmd+8);
		     srcfile = include_stack[include_stack_top--].file;
		     }
		  src_cfname = dupstr(cmd+8,0);  /* never deallocate! */
		  srcline = 1;
		  srccol = 0;
		  }

	       continue;
	       }


	    /* commands allowed only before all topics... */

	    if ( !in_topic )
	       {
	       if ( strnicmp(cmd, "HdrFile=", 8) == 0 )
		  {
		  if (hdr_fname[0] != '\0')
		     warn(eoff,"Header Filename has already been defined.");
		  strcpy(hdr_fname, cmd+8);
		  strupr(hdr_fname);
		  }

	       else if ( strnicmp(cmd, "HlpFile=", 8) == 0 )
		  {
		  if (hlp_fname[0] != '\0')
		     warn(eoff,"Help Filename has already been defined.");
		  strcpy(hlp_fname, cmd+8);
		  strupr(hlp_fname);
		  }

	       else if ( strnicmp(cmd, "Version=", 8) == 0 )
		  {
		  if (version != -1)   /* an unlikely value */
		     warn(eoff,"Help version has already been defined");
		  version = atoi(cmd+8);
		  }

	       else
		  error(eoff,"Bad or unexpected command \"%s\"", cmd);

	       continue;
	       }


	    /* commands allowed only in a topic... */

	    else
	       {
	       if (strnicmp(cmd, "FF", 2) == 0 )
		  {
		  check_command_length(eoff,2);
		  if ( in_para )
		     *curr++ = '\n';  /* finish off current paragraph */
		  *curr++ = CMD_FF;
		  state = S_Start;
		  in_para = 0;
		  num_spaces = 0;
		  }

	       else if (strnicmp(cmd, "DocFF", 5) == 0 )
		  {
		  check_command_length(eoff,5);
		  if ( in_para )
		     *curr++ = '\n';  /* finish off current paragraph */
		  if (!xonline)
		     *curr++ = CMD_XONLINE;
		  *curr++ = CMD_FF;
		  if (!xonline)
		     *curr++ = CMD_XONLINE;
		  state = S_Start;
		  in_para = 0;
		  num_spaces = 0;
		  }

	       else if (strnicmp(cmd, "OnlineFF", 8) == 0 )
		  {
		  check_command_length(eoff,8);
		  if ( in_para )
		     *curr++ = '\n';  /* finish off current paragraph */
		  if (!xdoc)
		     *curr++ = CMD_XDOC;
		  *curr++ = CMD_FF;
		  if (!xdoc)
		     *curr++ = CMD_XDOC;
		  state = S_Start;
		  in_para = 0;
		  num_spaces = 0;
		  }

	       else if ( strnicmp(cmd, "Label=", 6) == 0 )
		  {
		  if ((int)strlen(cmd+6) <= 0)
		     error(eoff,"Label has no name.");

		  else if ( !validate_label_name(cmd+6) )
		     error(eoff,"Label \"%s\" contains illegal characters.", cmd+6);

		  else if ( find_label(cmd+6) != NULL )
		     error(eoff,"Label \"%s\" already exists", cmd+6);

		  else
		     {
		     if ((int)strlen(cmd+6) > 32)
		        warn(eoff,"Label name is long.");

		    if ( (t.flags & TF_DATA) && cmd[6] == '@' )
		       warn(eoff, "Data topic has a local label.");

		     lbl.name	   = dupstr(cmd+6, 0);
		     lbl.topic_num = num_topic;
		     lbl.topic_off = (unsigned)(curr - buffer);
		     lbl.doc_page  = -1;
		     add_label(&lbl);
		     }
		  }

	       else if ( strnicmp(cmd, "Table=", 6) == 0 )
		  {
		  if ( in_para )
		     {
		     *curr++ = '\n';  /* finish off current paragraph */
		     in_para = 0;
		     num_spaces = 0;
		     state = S_Start;
		     }

		  if (!done)
		     {
		     if (imbedded)
		        unread_char('(');
		     unread_char('~');
		     done = 1;
		     }

		  create_table();
		  }

	       else if ( strnicmp(cmd, "FormatExclude", 12) == 0 )
		  {
		  if (cmd[13] == '-')
		     {
		     check_command_length(eoff,14);
		     if (lformat_exclude > 0)
		        lformat_exclude = -lformat_exclude;
		     else
		        warn(0,"\"FormatExclude-\" is already in effect.");
		     }
		  else if (cmd[13] == '+')
		     {
		     check_command_length(eoff,14);
		     if (lformat_exclude < 0)
		        lformat_exclude = -lformat_exclude;
		     else
		        warn(0,"\"FormatExclude+\" is already in effect.");
		     }
		  else
		     error(eoff,"Unexpected or invalid argument to FormatExclude.");
		  }

	       else if ( strnicmp(cmd, "Format", 6) == 0 )
		  {
		  if (cmd[6] == '+')
		     {
		     check_command_length(eoff,7);
		     if ( !formatting )
		        {
		        formatting = 1;
		        in_para = 0;
		        num_spaces = 0;
		        state = S_Start;
		        }
		     else
		        warn(eoff,"\"Format+\" is already in effect.");
		     }
		  else if (cmd[6] == '-')
		     {
		     check_command_length(eoff,7);
		     if ( formatting )
		        {
		        if ( in_para )
			   *curr++ = '\n';  /* finish off current paragraph */
		        state = S_Start;
		        in_para = 0;
		        formatting = 0;
		        num_spaces = 0;
		        state = S_Start;
		        }
		     else
		        warn(eoff,"\"Format-\" is already in effect.");
		     }
		  else
		     error(eoff,"Invalid argument to Format.");
		  }

	       else if ( strnicmp(cmd, "Online", 6) == 0 )
		  {
		  if (cmd[6] == '+')
		     {
		     check_command_length(eoff,7);

		     if ( xonline )
		        {
		        *curr++ = CMD_XONLINE;
		        xonline = 0;
		        }
		     else
		        warn(eoff,"\"Online+\" already in effect.");
		     }
		  else if (cmd[6] == '-')
		     {
		     check_command_length(eoff,7);
		     if ( !xonline )
		        {
		        *curr++ = CMD_XONLINE;
		        xonline = 1;
		        }
		     else
		        warn(eoff,"\"Online-\" already in effect.");
		     }
		  else
		     error(eoff,"Invalid argument to Online.");
		  }

	       else if ( strnicmp(cmd, "Doc", 3) == 0 )
		  {
		  if (cmd[3] == '+')
		     {
		     check_command_length(eoff,4);
		     if ( xdoc )
		        {
		        *curr++ = CMD_XDOC;
		        xdoc = 0;
		        }
		     else
		        warn(eoff,"\"Doc+\" already in effect.");
		     }
		  else if (cmd[3] == '-')
		     {
		     check_command_length(eoff,4);
		     if ( !xdoc )
		        {
		        *curr++ = CMD_XDOC;
		        xdoc = 1;
		        }
		     else
		        warn(eoff,"\"Doc-\" already in effect.");
		     }
		  else
		     error(eoff,"Invalid argument to Doc.");
		  }

	       else if ( strnicmp(cmd, "Center", 6) == 0 )
		  {
		  if (cmd[6] == '+')
		     {
		     check_command_length(eoff,7);
		     if ( !centering )
		        {
		        centering = 1;
		        if ( in_para )
			   {
			   *curr++ = '\n';
			   in_para = 0;
			   }
		        state = S_Start;  /* for centering FSM */
		        }
		     else
		        warn(eoff,"\"Center+\" already in effect.");
		     }
		  else if (cmd[6] == '-')
		     {
		     check_command_length(eoff,7);
		     if ( centering )
		        {
		        centering = 0;
		        state = S_Start;  /* for centering FSM */
		        }
		     else
		        warn(eoff,"\"Center-\" already in effect.");
		     }
		  else
		     error(eoff,"Invalid argument to Center.");
		  }

	       else if ( strnicmp(cmd, "CompressSpaces", 14) == 0 )
		  {
		  check_command_length(eoff,15);

		  if ( cmd[14] == '+' )
		     {
		     if ( compress_spaces )
		        warn(eoff,"\"CompressSpaces+\" is already in effect.");
		     else
		        compress_spaces = 1;
		     }
		  else if ( cmd[14] == '-' )
		     {
		     if ( !compress_spaces )
		        warn(eoff,"\"CompressSpaces-\" is already in effect.");
		     else
		        compress_spaces = 0;
		     }
		  else
		     error(eoff,"Invalid argument to CompressSpaces.");
		  }

	       else if ( strnicmp("BinInc ", cmd, 7) == 0 )
		  {
		  if ( !(t.flags & TF_DATA) )
		     error(eoff,"BinInc allowed only in Data topics.");
		  else
		     process_bininc();
		  }

	       else
		  error(eoff,"Bad or unexpected command \"%s\".", cmd);
	       } /* else */

	    } /* while (!done) */

	 continue;
	 }

      if ( !in_topic )
	 {
	 cmd[0] = ch;
	 ptr = read_until(cmd+1, 127, "\n~");
	 if (*ptr == '~')
	    unread_char('~');
	 *ptr = '\0';
	 error(0,"Text outside of any topic \"%s\".", cmd);
	 continue;
	 }

      if ( centering )
	 {
	 do
	    {
	    again = 0;	 /* default */

	    switch (state)
	       {
	       case S_Start:
		  if (ch == ' ')
		     ; /* do nothing */
		  else if ( (ch&0xFF) == '\n' )
		     *curr++ = ch;  /* no need to center blank lines. */
		  else
		     {
		     *curr++ = CMD_CENTER;
		     state = S_Line;
		     again = 1;
		     }
		  break;

	       case S_Line:
		  put_a_char(ch, &t);
		  if ( (ch&0xFF) == '\n')
		     state = S_Start;
		  break;
	       } /* switch */
	    }
	 while (again);
	 }

      else if ( formatting )
	 {
	 int again;

	 do
	    {
	    again = 0;	 /* default */

	    switch (state)
	       {
	       case S_Start:
		  if ( (ch&0xFF) == '\n' )
		     *curr++ = ch;
		  else
		     {
		     state = S_StartFirstLine;
		     num_spaces = 0;
		     again = 1;
		     }
		  break;

	       case S_StartFirstLine:
		  if ( ch == ' ')
		     ++num_spaces;

		  else
		     {
		     if (lformat_exclude > 0 && num_spaces >= lformat_exclude )
		        {
		        put_spaces(num_spaces);
		        num_spaces = 0;
		        state = S_FormatDisabled;
		        again = 1;
		        }
		     else
		        {
		        *curr++ = CMD_PARA;
		        *curr++ = (char)num_spaces;
		        *curr++ = (char)num_spaces;
		        margin_pos = curr - 1;
		        state = S_FirstLine;
		        again = 1;
		        in_para = 1;
		        }
		     }
		  break;

	       case S_FirstLine:
		  if (ch == '\n')
		     {
		     state = S_StartSecondLine;
		     num_spaces = 0;
		     }
		  else if (ch == ('\n'|0x100) )   /* force end of para ? */
		     {
		     *curr++ = '\n';
		     in_para = 0;
		     state = S_Start;
		     }
		  else if ( ch == ' ' )
		     {
		     state = S_FirstLineSpaces;
		     num_spaces = 1;
		     }
		  else
		     put_a_char(ch, &t);
		  break;

	       case S_FirstLineSpaces:
		  if (ch == ' ')
		     ++num_spaces;
		  else
		     {
		     put_spaces(num_spaces);
		     state = S_FirstLine;
		     again = 1;
		     }
		  break;

	       case S_StartSecondLine:
		  if ( ch == ' ')
		     ++num_spaces;
		  else if ((ch&0xFF) == '\n') /* a blank line means end of a para */
		     {
		     *curr++ = '\n';   /* end the para */
		     *curr++ = '\n';   /* for the blank line */
		     in_para = 0;
		     state = S_Start;
		     }
		  else
		     {
		     if (lformat_exclude > 0 && num_spaces >= lformat_exclude )
		        {
		        *curr++ = '\n';
		        in_para = 0;
		        put_spaces(num_spaces);
		        num_spaces = 0;
		        state = S_FormatDisabled;
		        again = 1;
		        }
		     else
		        {
		        add_blank_for_split();
		        margin = num_spaces;
		        *margin_pos = (char)num_spaces;
		        state = S_Line;
		        again = 1;
		        }
		     }
		  break;

	       case S_Line:   /* all lines after the first */
		  if (ch == '\n')
		     {
		     state = S_StartLine;
		     num_spaces = 0;
		     }
		  else if (ch == ('\n' | 0x100) )   /* force end of para ? */
		     {
		     *curr++ = '\n';
		     in_para = 0;
		     state = S_Start;
		     }
		  else if ( ch == ' ' )
		     {
		     state = S_LineSpaces;
		     num_spaces = 1;
		     }
		  else
		     put_a_char(ch, &t);
		  break;

	       case S_LineSpaces:
		  if (ch == ' ')
		     ++num_spaces;
		  else
		     {
		     put_spaces(num_spaces);
		     state = S_Line;
		     again = 1;
		     }
		  break;

	       case S_StartLine:   /* for all lines after the second */
		  if ( ch == ' ')
		     ++num_spaces;
		  else if ((ch&0xFF) == '\n') /* a blank line means end of a para */
		     {
		     *curr++ = '\n';   /* end the para */
		     *curr++ = '\n';   /* for the blank line */
		     in_para = 0;
		     state = S_Start;
		     }
		  else
		     {
		     if ( num_spaces != margin )
		        {
		        *curr++ = '\n';
		        in_para = 0;
		        state = S_StartFirstLine;  /* with current num_spaces */
		        again = 1;
		        }
		     else
		        {
		        add_blank_for_split();
		        state = S_Line;
		        again = 1;
		        }
		     }
		  break;

	       case S_FormatDisabled:
		  if ( ch == ' ' )
		     {
		     state = S_FormatDisabledSpaces;
		     num_spaces = 1;
		     }
		  else
		     {
		     if ( (ch&0xFF) == '\n' )
		        state = S_Start;
		     put_a_char(ch, &t);
		     }
		  break;

	       case S_FormatDisabledSpaces:
		  if ( ch == ' ' )
		     ++num_spaces;
		  else
		     {
		     put_spaces(num_spaces);
		     num_spaces = 0;    /* is this needed? */
		     state = S_FormatDisabled;
		     again = 1;
		     }
		  break;

	       } /* switch (state) */
	    }
	 while (again);
	 }

      else
	 {
	 do
	    {
	    again = 0;	 /* default */

	    switch (state)
	       {
	       case S_Start:
		  if ( ch == ' ' )
		     {
		     state = S_Spaces;
		     num_spaces = 1;
		     }
		  else
		     put_a_char(ch, &t);
		  break;

	       case S_Spaces:
		  if (ch == ' ')
		     ++num_spaces;
		  else
		     {
		     put_spaces(num_spaces);
		     num_spaces = 0;	 /* is this needed? */
		     state = S_Start;
		     again = 1;
		     }
		  break;
	       } /* switch */
	    }
	 while (again);
	 }

      CHK_BUFFER(0)
      } /* while ( 1 ) */

   fclose(srcfile);

   srcline = -1;
   }


/*
 * stuff to resolve hot-link references.
 */


void make_hot_links(void)
   /*
    * calculate topic_num/topic_off for each link.
    */
   {
   LINK    *l;
   LABEL   *lbl;
   int	    lctr;
   int	    t;
   CONTENT *c;
   int	    ctr;

   msg("Making hot-links.");

   /*
    * Calculate topic_num for all entries in DocContents.  Also set
    * "TF_IN_DOC" flag for all topics included in the document.
    */

   for (lctr=0, c=contents; lctr<num_contents; lctr++, c++)
      {
      for (ctr=0; ctr<c->num_topic; ctr++)
	 {
	 if ( c->is_label[ctr] )
	    {
	    lbl = find_label(c->topic_name[ctr]);
	    if (lbl == NULL)
	       {
	       src_cfname = c->srcfile;
	       srcline = c->srcline;
	       error(0,"Cannot find DocContent label \"%s\".", c->topic_name[ctr]);
	       srcline = -1;
	       }
	    else
	       {
	       if ( topic[lbl->topic_num].flags & TF_DATA )
		  {
		  src_cfname = c->srcfile;
		  srcline = c->srcline;
		  error(0,"Label \"%s\" is a data-only topic.", c->topic_name[ctr]);
		  srcline = -1;
		  }
	       else
		  {
		  c->topic_num[ctr] = lbl->topic_num;
		  if ( topic[lbl->topic_num].flags & TF_IN_DOC )
		     warn(0,"Topic \"%s\" appears in document more than once.",
			  topic[lbl->topic_num].title);
		  else
		     topic[lbl->topic_num].flags |= TF_IN_DOC;
		  }
	       }

	    }
	 else
	    {
	    t = find_topic_title(c->topic_name[ctr]);

	    if (t == -1)
	       {
	       src_cfname = c->srcfile;
	       srcline = c->srcline;
	       error(0,"Cannot find DocContent topic \"%s\".", c->topic_name[ctr]);
	       srcline = -1;  /* back to reality */
	       }
	    else
	       {
	       c->topic_num[ctr] = t;
	       if ( topic[t].flags & TF_IN_DOC )
		  warn(0,"Topic \"%s\" appears in document more than once.",
		       topic[t].title);
	       else
		  topic[t].flags |= TF_IN_DOC;
	       }
	    }
	 }
      }

   /*
    * Find topic_num and topic_off for all hot-links.  Also flag all hot-
    * links which will (probably) appear in the document.
    */

   for (lctr=0, l=a_link; lctr<num_link; l++, lctr++)
      {
      switch ( l->type )
	 {
	 case 0:      /* name is the title of the topic */
	    t = find_topic_title(l->name);
	    if (t == -1)
	       {
	       src_cfname = l->srcfile;
	       srcline = l->srcline; /* pretend we are still in the source... */
	       error(0,"Cannot find implicit hot-link \"%s\".", l->name);
	       srcline = -1;  /* back to reality */
	       }
	    else
	       {
	       l->topic_num = t;
	       l->topic_off = 0;
	       l->doc_page = (topic[t].flags & TF_IN_DOC) ? 0 : -1;
	       }
	    break;

	 case 1:  /* name is the name of a label */
	    lbl = find_label(l->name);
	    if (lbl == NULL)
	       {
	       src_cfname = l->srcfile;
	       srcline = l->srcline; /* pretend again */
	       error(0,"Cannot find explicit hot-link \"%s\".", l->name);
	       srcline = -1;
	       }
	    else
	       {
	       if ( topic[lbl->topic_num].flags & TF_DATA )
		  {
		  src_cfname = l->srcfile;
		  srcline = l->srcline;
		  error(0,"Label \"%s\" is a data-only topic.", l->name);
		  srcline = -1;
		  }
	       else
		  {
		  l->topic_num = lbl->topic_num;
		  l->topic_off = lbl->topic_off;
		  l->doc_page  = (topic[lbl->topic_num].flags & TF_IN_DOC) ? 0 : -1;
		  }
	       }
	    break;

	 case 2:   /* it's a "special" link; topic_off already has the value */
	    break;
	 }
      }

   }


/*
 * online help pagination stuff
 */


void add_page_break(TOPIC *t, int margin, char *text, char *start, char *curr, int num_links)
   {
   PAGE p;

   p.offset = (unsigned) (start - text);
   p.length = (unsigned) (curr - start);
   p.margin = margin;
   add_page(t, &p);

   if (max_links < num_links)
      max_links = num_links;
   }


void paginate_online(void)    /* paginate the text for on-line help */
   {		       /* also calculates max_pages and max_links */
   int	     lnum;
   char     *start;
   char     *curr;
   char     *text;
   TOPIC    *t;
   int	     tctr;
   unsigned  len;
   int	     skip_blanks;
   int	     num_links;
   int	     col;
   int	     tok;
   int	     size,
	     width;
   int	     start_margin;

   msg("Paginating online help.");

   for (t=topic, tctr=0; tctr<num_topic; t++, tctr++)
      {
      if ( t->flags & TF_DATA )
	 continue;    /* don't paginate data topics */

      text = get_topic_text(t);
      curr = text;
      len  = t->text_len;

      start = curr;
      skip_blanks = 0;
      lnum = 0;
      num_links = 0;
      col = 0;
      start_margin = -1;

      while (len > 0)
	 {
	 tok = find_token_length(ONLINE, curr, len, &size, &width);

	 switch ( tok )
	    {
	    case TOK_PARA:
	       {
	       int indent,
		   margin;

	       ++curr;

	       indent = *curr++;
	       margin = *curr++;

	       len -= 3;

	       col = indent;

	       while (1)
		  {
		  tok = find_token_length(ONLINE, curr, len, &size, &width);

		  if (tok == TOK_DONE || tok == TOK_NL || tok == TOK_FF )
		     break;

		  if ( tok == TOK_PARA )
		     {
		     col = 0;   /* fake a nl */
		     ++lnum;
		     break;
		     }

		  if (tok == TOK_XONLINE || tok == TOK_XDOC )
		     {
		     curr += size;
		     len -= size;
		     continue;
		     }

		  /* now tok is TOK_SPACE or TOK_LINK or TOK_WORD */

		  if (col+width > SCREEN_WIDTH)
		     {	        /* go to next line... */
		     if ( ++lnum >= SCREEN_DEPTH )
		        {	    /* go to next page... */
		        add_page_break(t, start_margin, text, start, curr, num_links);
		        start = curr + ( (tok == TOK_SPACE) ? size : 0 );
		        start_margin = margin;
		        lnum = 0;
		        num_links = 0;
		        }
		     if ( tok == TOK_SPACE )
		        width = 0;   /* skip spaces at start of a line */

		     col = margin;
		     }

		  col += width;
		  curr += size;
		  len -= size;
		  }

	       skip_blanks = 0;
	       width = size = 0;
	       break;
	       }

	    case TOK_NL:
	       if (skip_blanks && col == 0)
		  {
		  start += size;
		  break;
		  }
	       ++lnum;
	       if ( lnum >= SCREEN_DEPTH || (col == 0 && lnum==SCREEN_DEPTH-1) )
		  {
		  add_page_break(t, start_margin, text, start, curr, num_links);
		  start = curr + size;
		  start_margin = -1;
		  lnum = 0;
		  num_links = 0;
		  skip_blanks = 1;
		  }
	       col = 0;
	       break;

	    case TOK_FF:
	       col = 0;
	       if (skip_blanks)
		  {
		  start += size;
		  break;
		  }
	       add_page_break(t, start_margin, text, start, curr, num_links);
	       start_margin = -1;
	       start = curr + size;
	       lnum = 0;
	       num_links = 0;
	       break;

	    case TOK_DONE:
	    case TOK_XONLINE:   /* skip */
	    case TOK_XDOC:      /* ignore */
	    case TOK_CENTER:    /* ignore */
	       break;

	    case TOK_LINK:
	       ++num_links;

	       /* fall-through */

	    default:    /* TOK_SPACE, TOK_LINK, TOK_WORD */
	       skip_blanks = 0;
	       break;

	    } /* switch */

	 curr += size;
	 len  -= size;
	 col  += width;
	 } /* while */

      if (!skip_blanks)
	 add_page_break(t, start_margin, text, start, curr, num_links);

      if (max_pages < t->num_page)
	 max_pages = t->num_page;

      release_topic_text(t, 0);
      } /* for */
   }


/*
 * paginate document stuff
 */


#define CNUM	       0
#define TNUM	       1
#define LINK_DEST_WARN 2


typedef struct
   {
   int	    cnum,  /* must match above #defines so pd_get_info() will work */
	    tnum,
	    link_dest_warn;

   char far *start;
   CONTENT  *c;
   LABEL    *lbl;

   } PAGINATE_DOC_INFO;


LABEL *find_next_label_by_topic(int t)
   {
   LABEL *temp, *g, *p;
   int	  ctr;

   g = p = NULL;

   for (temp=label, ctr=0; ctr<num_label; ctr++, temp++)
      if ( temp->topic_num == t && temp->doc_page == -1 )
	 {
	 g = temp;
	 break;
	 }
      else if (temp->topic_num > t)
	 break;

   for (temp=plabel, ctr=0; ctr<num_plabel; ctr++, temp++)
      if ( temp->topic_num == t && temp->doc_page == -1 )
	 {
	 p = temp;
	 break;
	 }
      else if (temp->topic_num > t)
	 break;

   if ( p == NULL )
      return (g);

   else if ( g == NULL )
      return (p);

   else
      return ( (g->topic_off < p->topic_off) ? g : p );
   }


void set_hot_link_doc_page(void)
   /*
    * Find doc_page for all hot-links.
    */
   {
   LINK  *l;
   LABEL *lbl;
   int	  lctr;
   int	  t;

   for (lctr=0, l=a_link; lctr<num_link; l++, lctr++)
      {
      switch ( l->type )
	 {
	 case 0:      /* name is the title of the topic */
	    t = find_topic_title(l->name);
	    if (t == -1)
	       {
	       src_cfname = l->srcfile;
	       srcline = l->srcline; /* pretend we are still in the source... */
	       error(0,"Cannot find implicit hot-link \"%s\".", l->name);
	       srcline = -1;  /* back to reality */
	       }
	    else
	       l->doc_page = topic[t].doc_page;
	    break;

	 case 1:  /* name is the name of a label */
	    lbl = find_label(l->name);
	    if (lbl == NULL)
	       {
	       src_cfname = l->srcfile;
	       srcline = l->srcline; /* pretend again */
	       error(0,"Cannot find explicit hot-link \"%s\".", l->name);
	       srcline = -1;
	       }
	    else
	       l->doc_page = lbl->doc_page;
	    break;

	 case 2:   /* special topics don't appear in the document */
	    break;
	 }
      }
   }


void set_content_doc_page(void)
   /*
    * insert page #'s in the DocContents
    */
   {
   CONTENT *c;
   TOPIC   *t;
   char    *base;
   int	    tnum;
   int	    ctr;
   char     buf[4];
   int	    len;

   tnum = find_topic_title(DOCCONTENTS_TITLE);
   assert(tnum>=0);
   t = &topic[tnum];

   base = get_topic_text(t);

   for (ctr=1, c=contents+1; ctr<num_contents; ctr++, c++)
      {
      assert(c->doc_page>=1);
      sprintf(buf, "%d", c->doc_page);
      len = strlen(buf);
      assert(len<=3);
      memcpy(base+c->page_num_pos+(3-len), buf, len);
      }

   release_topic_text(t, 1);
   }


int pd_get_info(int cmd, PD_INFO *pd, int *info)
   {		 /* this funtion also used by print_document() */
   CONTENT *c;

   switch (cmd)
      {
      case PD_GET_CONTENT:
	 if ( ++info[CNUM] >= num_contents )
	    return (0);
	 c = &contents[info[CNUM]];
	 info[TNUM] = -1;
	 pd->id       = c->id;
	 pd->title    = c->name;
	 pd->new_page = (c->flags & CF_NEW_PAGE) ? 1 : 0;
	 return (1);

      case PD_GET_TOPIC:
	 c = &contents[info[CNUM]];
	 if ( ++info[TNUM] >= c->num_topic )
	    return (0);
	 pd->curr = get_topic_text( &topic[c->topic_num[info[TNUM]]] );
	 pd->len = topic[c->topic_num[info[TNUM]]].text_len;
	 return (1);

      case PD_GET_LINK_PAGE:
         if ( a_link[getint(pd->s)].doc_page == -1 )
	    {
	    if ( info[LINK_DEST_WARN] )
	       {
               src_cfname = a_link[getint(pd->s)].srcfile;
               srcline    = a_link[getint(pd->s)].srcline;
	       warn(0,"Hot-link destination is not in the document.");
	       srcline = -1;
	       }
	    return (0);
	    }
         pd->i = a_link[getint(pd->s)].doc_page;
	 return (1);

      case PD_RELEASE_TOPIC:
	 c = &contents[info[CNUM]];
	 release_topic_text(&topic[c->topic_num[info[TNUM]]], 0);
	 return (1);

      default:
	 return (0);
      }
   }


int paginate_doc_output(int cmd, PD_INFO *pd, PAGINATE_DOC_INFO *info)
   {
   switch (cmd)
      {
      case PD_FOOTING:
      case PD_PRINT:
      case PD_PRINTN:
      case PD_PRINT_SEC:
	 return (1);

      case PD_HEADING:
	 ++num_doc_pages;
	 return (1);

      case PD_START_SECTION:
	 info->c = &contents[info->cnum];
	 return (1);

      case PD_START_TOPIC:
	 info->start = pd->curr;
	 info->lbl = find_next_label_by_topic(info->c->topic_num[info->tnum]);
	 return (1);

      case PD_SET_SECTION_PAGE:
	 info->c->doc_page = pd->pnum;
	 return (1);

      case PD_SET_TOPIC_PAGE:
	 topic[info->c->topic_num[info->tnum]].doc_page = pd->pnum;
	 return (1);

      case PD_PERIODIC:
	 while ( info->lbl != NULL && (unsigned)(pd->curr - info->start) >= info->lbl->topic_off)
	    {
	    info->lbl->doc_page = pd->pnum;
	    info->lbl = find_next_label_by_topic(info->c->topic_num[info->tnum]);
	    }
	 return (1);

      default:
	 return (0);
      }
   }


void paginate_document(void)
   {
   PAGINATE_DOC_INFO info;

   if (num_contents == 0)
      return ;

   msg("Paginating document.");

   info.cnum = info.tnum = -1;
   info.link_dest_warn = 1;

   process_document((PD_FUNC)pd_get_info, (PD_FUNC)paginate_doc_output, &info);

   set_hot_link_doc_page();
   set_content_doc_page();
   }


/*
 * label sorting stuff
 */


int fcmp_LABEL(VOIDCONSTPTR a, VOIDCONSTPTR b)
   {
   char *an = ((LABEL *)a)->name,
        *bn = ((LABEL *)b)->name;
   int	 diff;

   /* compare the names, making sure that the index goes first */

   if ( (diff=strcmp(an,bn)) == 0 )
      return (0);

   if ( strcmp(an, INDEX_LABEL) == 0 )
      return (-1);

   if ( strcmp(bn, INDEX_LABEL) == 0 )
      return (1);

   return ( diff );
   }


void sort_labels(void)
   {
   qsort(label,  num_label,  sizeof(LABEL), fcmp_LABEL);
   qsort(plabel, num_plabel, sizeof(LABEL), fcmp_LABEL);
   }


/*
 * file write stuff.
 */


int compare_files(FILE *f1, FILE *f2) /* returns TRUE if different */
   {
   if ( filelength(fileno(f1)) != filelength(fileno(f2)) )
      return (1);   /* different if sizes are not the same */

   while ( !feof(f1) && !feof(f2) )
      if ( getc(f1) != getc(f2) )
	 return (1);

   return ( ( feof(f1) && feof(f2) ) ? 0 : 1);
   }


void _write_hdr(char *fname, FILE *file)
   {
   int ctr;
   char nfile[MAXFILE],
        next[MAXEXT];

   FNSPLIT(fname, NULL, NULL, nfile, next);
   fprintf(file, "\n/*\n * %s%s\n", nfile, next);
   FNSPLIT(src_fname, NULL, NULL, nfile, next);
   fprintf(file, " *\n * Contains #defines for help.\n *\n");
   fprintf(file, " * Generated by HC from: %s%s\n *\n */\n\n\n", nfile, next);

   fprintf(file, "/* current help file version */\n");
   fprintf(file, "\n");
   fprintf(file, "#define %-32s %3d\n", "HELP_VERSION", version);
   fprintf(file, "\n\n");

   fprintf(file, "/* labels */\n\n");

   for (ctr=0; ctr<num_label; ctr++)
      if (label[ctr].name[0] != '@')  /* if it's not a local label... */
	 {
	 fprintf(file, "#define %-32s %3d", label[ctr].name, ctr);
	 if ( strcmp(label[ctr].name, INDEX_LABEL) == 0 )
	    fprintf(file, "        /* index */");
	 fprintf(file, "\n");
	 }

   fprintf(file, "\n\n");
   }


void write_hdr(char *fname)
   {
   FILE *temp,
        *hdr;

   hdr = fopen(fname, "rt");

   if (hdr == NULL)
      {         /* if no prev. hdr file generate a new one */
      hdr = fopen(fname, "wt");
      if (hdr == NULL)
	 fatal(0,"Cannot create \"%s\".", fname);
      msg("Writing: %s", fname);
      _write_hdr(fname, hdr);
      fclose(hdr);
      notice("FRACTINT must be re-compiled.");
      return ;
      }

   msg("Comparing: %s", fname);

   temp = fopen(TEMP_FNAME, "wt");

   if (temp == NULL)
      fatal(0,"Cannot create temporary file: \"%s\".", TEMP_FNAME);

   _write_hdr(fname, temp);

   fclose(temp);
   temp = fopen(TEMP_FNAME, "rt");

   if (temp == NULL)
      fatal(0,"Cannot open temporary file: \"%s\".", TEMP_FNAME);

   if ( compare_files(temp, hdr) )   /* if they are different... */
      {
      msg("Updating: %s", fname);
      fclose(temp);
      fclose(hdr);
      unlink(fname);		   /* delete the old hdr file */
      rename(TEMP_FNAME, fname);   /* rename the temp to the hdr file */
      notice("FRACTINT must be re-compiled.");
      }
   else
      {   /* if they are the same leave the original alone. */
      fclose(temp);
      fclose(hdr);
      unlink(TEMP_FNAME);      /* delete the temp */
      }
   }


void calc_offsets(void)    /* calc file offset to each topic */
   {
   int	    t;
   TOPIC   *tp;
   long     offset;
   CONTENT *cp;
   int	    c;

   /* NOTE: offsets do NOT include 6 bytes for signature & version! */

   offset = sizeof(int) +           /* max_pages */
            sizeof(int) +           /* max_links */
            sizeof(int) +           /* num_topic */
            sizeof(int) +           /* num_label */
            sizeof(int) +           /* num_contents */
            sizeof(int) +           /* num_doc_pages */
            num_topic*sizeof(long) +/* offsets to each topic */
            num_label*2*sizeof(int);/* topic_num/topic_off for all public labels */

   for (c=0, cp=contents; c<num_contents; c++, cp++)
      offset += sizeof(int) +       /* flags */
	        1 +		    /* id length */
	        strlen(cp->id) +    /* id text */
	        1 +		    /* name length */
	        strlen(cp->name) +  /* name text */
	        1 +		    /* number of topics */
                cp->num_topic*sizeof(int);    /* topic numbers */

   for (t=0, tp=topic; t<num_topic; t++, tp++)
      {
      tp->offset = offset;
      offset += (long)sizeof(int) + /* topic flags */
                sizeof(int) +       /* number of pages */
                tp->num_page*3*sizeof(int) +   /* page offset, length & starting margin */
	        1 +		    /* length of title */
	        tp->title_len +     /* title */
                sizeof(int) +       /* length of text */
	        tp->text_len;	    /* text */
      }

   }


void insert_real_link_info(char *curr, unsigned len)
   /*
    * Replaces link indexes in the help text with topic_num, topic_off and
    * doc_page info.
    */
   {
   int	     size;
   int	     tok;
   LINK     *l;

   while (len > 0)
      {
      tok = find_token_length(0, curr, len, &size, NULL);

      if ( tok == TOK_LINK )
	 {
         l = &a_link[ getint(curr+1) ];
         setint(curr+1,l->topic_num);
         setint(curr+1+sizeof(int),l->topic_off);
         setint(curr+1+2*sizeof(int),l->doc_page);
	 }

      len -= size;
      curr += size;
      }
   }


void _write_help(FILE *file)
   {
   int			 t, p, l, c;
   char 	        *text;
   TOPIC	        *tp;
   CONTENT	        *cp;
   struct help_sig_info  hs;

   /* write the signature and version */

   hs.sig = HELP_SIG; /* Edit line 17 of helpcom.h if this is a syntax error */
   hs.version = version;

   fwrite(&hs, sizeof(long)+sizeof(int), 1, file);

   /* write max_pages & max_links */

   putw(max_pages, file);
   putw(max_links, file);

   /* write num_topic, num_label and num_contents */

   putw(num_topic, file);
   putw(num_label, file);
   putw(num_contents, file);

   /* write num_doc_page */

   putw(num_doc_pages, file);

   /* write the offsets to each topic */

   for (t=0; t<num_topic; t++) 
      fwrite(&topic[t].offset, sizeof(long), 1, file);

   /* write all public labels */

   for (l=0; l<num_label; l++)
      {
      putw(label[l].topic_num, file);
      putw(label[l].topic_off, file);
      }

   /* write contents */

   for (c=0, cp=contents; c<num_contents; c++, cp++)
      {
      putw(cp->flags, file);

      t = strlen(cp->id);
      putc((BYTE)t, file);
      fwrite(cp->id, 1, t, file);

      t = strlen(cp->name);
      putc((BYTE)t, file);
      fwrite(cp->name, 1, t, file);

      putc((BYTE)cp->num_topic, file);
      fwrite(cp->topic_num, sizeof(int), cp->num_topic, file);
      }

   /* write topics */

   for (t=0, tp=topic; t<num_topic; t++, tp++)
      {
      /* write the topics flags */

      putw(tp->flags, file);

      /* write offset, length and starting margin for each page */

      putw(tp->num_page, file);
      for (p=0; p<tp->num_page; p++)
	 {
	 putw(tp->page[p].offset, file);
	 putw(tp->page[p].length, file);
	 putw(tp->page[p].margin, file);
	 }

      /* write the help title */

      putc((BYTE)tp->title_len, file);
      fwrite(tp->title, 1, tp->title_len, file);

      /* insert hot-link info & write the help text */

      text = get_topic_text(tp);

      if ( !(tp->flags & TF_DATA) )   /* don't process data topics... */
	 insert_real_link_info(text, tp->text_len);

      putw(tp->text_len, file);
      fwrite(text, 1, tp->text_len, file);

      release_topic_text(tp, 0);  /* don't save the text even though	    */
				  /* insert_real_link_info() modified it    */
				  /* because we don't access the info after */
				  /* this.				    */

      }
   }


void write_help(char *fname)
   {
   FILE *hlp;

   hlp = fopen(fname, "wb");

   if (hlp == NULL)
      fatal(0,"Cannot create .HLP file: \"%s\".", fname);

   msg("Writing: %s", fname);

   _write_help(hlp);

   fclose(hlp);
   }


/*
 * print document stuff.
 */


typedef struct
   {

   /*
    * Note: Don't move these first three or pd_get_info will work not
    *	    correctly.
    */

   int	    cnum;
   int	    tnum;
   int	    link_dest_warn;   /* = 0 */

   FILE    *file;
   int	    margin;
   int	    start_of_line;
   int	    spaces;
   } PRINT_DOC_INFO;


void printerc(PRINT_DOC_INFO *info, int c, int n)
   {
   while ( n-- > 0 )
      {
      if (c==' ')
	 ++info->spaces;

      else if (c=='\n' || c=='\f')
	 {
	 info->start_of_line = 1;
	 info->spaces = 0;   /* strip spaces before a new-line */
	 putc(c, info->file);
	 }

      else
	 {
	 if (info->start_of_line)
	    {
	    info->spaces += info->margin;
	    info->start_of_line = 0;
	    }

	 while (info->spaces > 0)
	    {
	    fputc(' ', info->file);
	    --info->spaces;
	    }

	 fputc(c, info->file);
	 }
      }
   }


void printers(PRINT_DOC_INFO *info, char far *s, int n)
   {
   if (n > 0)
      {
      while ( n-- > 0 )
	 printerc(info, *s++, 1);
      }
   else
      {
      while ( *s != '\0' )
	 printerc(info, *s++, 1);
      }
   }


int print_doc_output(int cmd, PD_INFO *pd, PRINT_DOC_INFO *info)
   {
   switch (cmd)
      {
      case PD_HEADING:
	 {
	 char buff[20];

	 info->margin = 0;
	 printers(info, "\n                     Fractint Version xx.xx                     Page ", 0);
	 sprintf(buff, "%d\n\n", pd->pnum);
	 printers(info, buff, 0);
	 info->margin = PAGE_INDENT;
	 return (1);
	 }

      case PD_FOOTING:
	 info->margin = 0;
	 printerc(info, '\f', 1);
	 info->margin = PAGE_INDENT;
	 return (1);

      case PD_PRINT:
	 printers(info, pd->s, pd->i);
	 return (1);

      case PD_PRINTN:
	 printerc(info, *pd->s, pd->i);
	 return (1);

      case PD_PRINT_SEC:
	 info->margin = TITLE_INDENT;
	 if (pd->id[0] != '\0')
	    {
	    printers(info, pd->id, 0);
	    printerc(info, ' ', 1);
	    }
	 printers(info, pd->title, 0);
	 printerc(info, '\n', 1);
	 info->margin = PAGE_INDENT;
	 return (1);

      case PD_START_SECTION:
      case PD_START_TOPIC:
      case PD_SET_SECTION_PAGE:
      case PD_SET_TOPIC_PAGE:
      case PD_PERIODIC:
	 return (1);

      default:
	 return (0);
      }
   }


void print_document(char *fname)
   {
   PRINT_DOC_INFO info;

   if (num_contents == 0)
      fatal(0,".SRC has no DocContents.");

   msg("Printing to: %s", fname);

   info.cnum = info.tnum = -1;
   info.link_dest_warn = 0;

   if ( (info.file = fopen(fname, "wt")) == NULL )
      fatal(0,"Couldn't create \"%s\"", fname);

   info.margin = PAGE_INDENT;
   info.start_of_line = 1;
   info.spaces = 0;

   process_document((PD_FUNC)pd_get_info, (PD_FUNC)print_doc_output, &info);

   fclose(info.file);
   }


/*
 * compiler status and memory usage report stuff.
 */


void report_memory(void)
   {
   long string = 0,   /* bytes in strings */
        text   = 0,   /* bytes in topic text (stored on disk) */
        data   = 0,   /* bytes in active data structure */
        dead   = 0;   /* bytes in unused data structure */
   int  ctr, ctr2;

   for (ctr=0; ctr<num_topic; ctr++)
      {
      data   += sizeof(TOPIC);
      string += topic[ctr].title_len;
      text   += topic[ctr].text_len;
      data   += topic[ctr].num_page * sizeof(PAGE);

      dead   += (PAGE_ALLOC_SIZE-(topic[ctr].num_page%PAGE_ALLOC_SIZE)) * sizeof(PAGE);
      }

   for (ctr=0; ctr<num_link; ctr++)
      {
      data += sizeof(LINK);
      string += strlen(a_link[ctr].name);
      }

   if (num_link > 0)
      dead += (LINK_ALLOC_SIZE-(num_link%LINK_ALLOC_SIZE)) * sizeof(LINK);

   for (ctr=0; ctr<num_label; ctr++)
      {
      data   += sizeof(LABEL);
      string += strlen(label[ctr].name) + 1;
      }

   if (num_label > 0)
      dead += (LABEL_ALLOC_SIZE-(num_label%LABEL_ALLOC_SIZE)) * sizeof(LABEL);

   for (ctr=0; ctr<num_plabel; ctr++)
      {
      data   += sizeof(LABEL);
      string += strlen(plabel[ctr].name) + 1;
      }

   if (num_plabel > 0)
      dead += (LABEL_ALLOC_SIZE-(num_plabel%LABEL_ALLOC_SIZE)) * sizeof(LABEL);

   for (ctr=0; ctr<num_contents; ctr++)
      {
      int t;

      t = ( MAX_CONTENT_TOPIC - contents[ctr].num_topic ) *
	  ( sizeof(contents[0].is_label[0])   +
	    sizeof(contents[0].topic_name[0]) +
	    sizeof(contents[0].topic_num[0])	 );
      data += sizeof(CONTENT) - t;
      dead += t;
      string += strlen(contents[ctr].id) + 1;
      string += strlen(contents[ctr].name) + 1;
      for (ctr2=0; ctr2<contents[ctr].num_topic; ctr2++)
	 string += strlen(contents[ctr].topic_name[ctr2]) + 1;
      }

   dead += (CONTENTS_ALLOC_SIZE-(num_contents%CONTENTS_ALLOC_SIZE)) * sizeof(CONTENT);

   printf("\n");
   printf("Memory Usage:\n");
   printf("%8ld Bytes in buffers.\n", (long)BUFFER_SIZE);
   printf("%8ld Bytes in strings.\n", string);
   printf("%8ld Bytes in data.\n", data);
   printf("%8ld Bytes in dead space.\n", dead);
   printf("--------\n");
   printf("%8ld Bytes total.\n", (long)BUFFER_SIZE+string+data+dead);
   printf("\n");
   printf("Disk Usage:\n");
   printf("%8ld Bytes in topic text.\n", text);
   }


void report_stats(void)
   {
   int  pages = 0;
   int	    t;

   for (t=0; t<num_topic; t++)
      pages += topic[t].num_page;

   printf("\n");
   printf("Statistics:\n");
   printf("%8d Topics\n", num_topic);
   printf("%8d Links\n", num_link);
   printf("%8d Labels\n", num_label);
   printf("%8d Private labels\n", num_plabel);
   printf("%8d Table of contents (DocContent) entries\n", num_contents);
   printf("%8d Online help pages\n", pages);
   printf("%8d Document pages\n", num_doc_pages);
   }


/*
 * add/delete help from .EXE functions.
 */


void add_hlp_to_exe(char *hlp_fname, char *exe_fname)
   {
   int		        exe,   /* handles */
		        hlp;
   long 	        len,
		        count;
   int		        size;
   struct help_sig_info hs;

   if ( (exe=open(exe_fname, O_RDWR|O_BINARY)) == -1 )
      fatal(0,"Unable to open \"%s\"", exe_fname);

   if ( (hlp=open(hlp_fname, O_RDONLY|O_BINARY)) == -1 )
      fatal(0,"Unable to open \"%s\"", hlp_fname);

   msg("Appending %s to %s", hlp_fname, exe_fname);

   /* first, check and see if any help is currently installed */

   lseek(exe, filelength(exe) - sizeof(struct help_sig_info), SEEK_SET);

   read(exe, (char *)&hs, 10);

   if ( hs.sig == HELP_SIG )
      warn(0,"Overwriting previous help. (Version=%d)", hs.version);
   else
      hs.base = filelength(exe);

   /* now, let's see if their help file is for real (and get the version) */

   read(hlp, (char *)&hs, sizeof(long)+sizeof(int));

   if (hs.sig != HELP_SIG )
      fatal(0,"Help signature not found in %s", hlp_fname);

   msg("Help file %s Version=%d", hlp_fname, hs.version);

   /* append the help stuff, overwriting old help (if any) */

   lseek(exe, hs.base, SEEK_SET);

   len = filelength(hlp) - sizeof(long) - sizeof(int); /* adjust for the file signature & version */

   for (count=0; count<len; )
      {
      size = (int) min((long)BUFFER_SIZE, len-count);
      read(hlp, buffer, size);
      write(exe, buffer, size);
      count += size;
      }

   /* add on the signature, version and offset */

   write(exe, (char *)&hs, 10);

   chsize(exe, lseek(exe,0L,SEEK_CUR));/* truncate if old help was longer */

   close(exe);
   close(hlp);
   }


void delete_hlp_from_exe(char *exe_fname)
   {
   int	 exe;   /* file handle */
   struct help_sig_info hs;

   if ( (exe=open(exe_fname, O_RDWR|O_BINARY)) == -1 )
      fatal(0,"Unable to open \"%s\"", exe_fname);

   msg("Deleting help from %s", exe_fname);

   /* see if any help is currently installed */

#ifndef XFRACT
   lseek(exe, filelength(exe) - 10, SEEK_SET);
   read(exe, (char *)&hs, 10);
#else
   lseek(exe, filelength(exe) - 12, SEEK_SET);
   read(exe, (char *)&hs, 12);
#endif

   if ( hs.sig == HELP_SIG )
      {
      chsize(exe, hs.base);   /* truncate at the start of the help */
      close(exe);
      }
   else
      {
      close(exe);
      fatal(0,"No help found in %s", exe_fname);
      }
   }


/*
 * command-line parser, etc.
 */


#define MODE_COMPILE 1
#define MODE_PRINT   2
#define MODE_APPEND  3
#define MODE_DELETE  4


int main(int argc, char *argv[])
   {
   int	  show_stats = 0,
	  show_mem   = 0;
   int	  mode	     = 0;

   char **arg;
   char   fname1[81],
	  fname2[81];
   char   swappath[81];

   fname1[0] = fname2[0] = swappath[0] = 0;

   printf("HC - FRACTINT Help Compiler.\n\n");

   buffer = malloc(BUFFER_SIZE);

   if (buffer == NULL)
      fatal(0,"Not enough memory to allocate buffer.");

   for (arg= &argv[1]; argc>1; argc--, arg++)
      {
      switch ( (*arg)[0] )
	 {
	 case '/':
	 case '-':
	    switch ( (*arg)[1] )
	       {
	       case 'c':
		  if (mode == 0)
		     mode = MODE_COMPILE;
		  else
		     fatal(0,"Cannot have /c with /a, /d or /p");
		  break;

	       case 'a':
		  if (mode == 0)
		     mode = MODE_APPEND;
		  else
		     fatal(0,"Cannot have /a with /c, /d or /p");
		  break;

	       case 'd':
		  if (mode == 0)
		     mode = MODE_DELETE;
		  else
		     fatal(0,"Cannot have /d with /c, /a or /p");
		  break;

	       case 'p':
		  if (mode == 0)
		     mode = MODE_PRINT;
		  else
		     fatal(0,"Cannot have /p with /c, /a or /d");
		  break;

	       case 'm':
		  if (mode == MODE_COMPILE)
		     show_mem = 1;
		  else
		     fatal(0,"/m switch allowed only when compiling (/c)");
		  break;

	       case 's':
		  if (mode == MODE_COMPILE)
		     show_stats = 1;
		  else
		     fatal(0,"/s switch allowed only when compiling (/c)");
		  break;

	       case 'r':
		  if (mode == MODE_COMPILE || mode == MODE_PRINT)
		     strcpy(swappath, (*arg)+2);
		  else
		     fatal(0,"/r switch allowed when compiling (/c) or printing (/p)");
		  break;

	       case 'q':
		  quiet_mode = 1;
		  break;

	       default:
		  fatal(0,"Bad command-line switch /%c", (*arg)[1]);
		  break;
	       }
	    break;

	 default:   /* assume it is a fname */
	    if (fname1[0] == '\0')
	       strcpy(fname1, *arg);
	    else if (fname2[0] == '\0')
	       strcpy(fname2, *arg);
	    else
	       fatal(0,"Unexpected command-line argument \"%s\"", *arg);
	    break;
	 } /* switch */
      } /* for */

   strupr(fname1);
   strupr(fname2);
   strupr(swappath);

   switch (mode)
      {
      case 0:
         printf( "To compile a .SRC file:\n");
         printf( "      HC /c [/s] [/m] [/r[path]] [src_file]\n");
         printf( "         /s       = report statistics.\n");
         printf( "         /m       = report memory usage.\n");
         printf( "         /r[path] = set swap file path.\n");
         printf( "         src_file = .SRC file.  Default is \"%s\"\n", DEFAULT_SRC_FNAME);
         printf( "To print a .SRC file:\n");
         printf( "      HC /p [/r[path]] [src_file] [out_file]\n");
         printf( "         /r[path] = set swap file path.\n");
         printf( "         src_file = .SRC file.  Default is \"%s\"\n", DEFAULT_SRC_FNAME);
         printf( "         out_file = Filename to print to. Default is \"%s\"\n",
         DEFAULT_DOC_FNAME);
         printf( "To append a .HLP file to an .EXE file:\n");
         printf( "      HC /a [hlp_file] [exe_file]\n");
         printf( "         hlp_file = .HLP file.  Default is \"%s\"\n", DEFAULT_HLP_FNAME);
         printf( "         exe_file = .EXE file.  Default is \"%s\"\n", DEFAULT_EXE_FNAME);
         printf( "To delete help info from an .EXE file:\n");
         printf( "      HC /d [exe_file]\n");
         printf( "         exe_file = .EXE file.  Default is \"%s\"\n", DEFAULT_EXE_FNAME);
         printf( "\n");
         printf( "Use \"/q\" for quiet mode. (No status messages.)\n");
	 break;

      case MODE_COMPILE:
	 if (fname2[0] != '\0')
	    fatal(0,"Unexpected command-line argument \"%s\"", fname2);

	 strcpy(src_fname, (fname1[0]=='\0') ? DEFAULT_SRC_FNAME : fname1);

	 strcat(swappath, SWAP_FNAME);

	 if ( (swapfile=fopen(swappath, "w+b")) == NULL )
	    fatal(0,"Cannot create swap file \"%s\"", swappath);
	 swappos = 0;

	 read_src(src_fname);

	 if (hdr_fname[0] == '\0')
	    error(0,"No .H file defined.  (Use \"~HdrFile=\")");
	 if (hlp_fname[0] == '\0')
	    error(0,"No .HLP file defined.  (Use \"~HlpFile=\")");
	 if (version == -1)
	    warn(0,"No help version has been defined.  (Use \"~Version=\")");

	 /* order of these is very important... */

	 make_hot_links();  /* do even if errors since it may report */
			    /* more... */

	 if ( !errors )     paginate_online();
	 if ( !errors )     paginate_document();
	 if ( !errors )     calc_offsets();
	 if ( !errors )     sort_labels();
	 if ( !errors )     write_hdr(hdr_fname);
	 if ( !errors )     write_help(hlp_fname);

	 if ( show_stats )
	    report_stats();

	 if ( show_mem )
	    report_memory();

	 if ( errors || warnings )
	    report_errors();

	 fclose(swapfile);
	 remove(swappath);

	 break;

      case MODE_PRINT:
	 strcpy(src_fname, (fname1[0]=='\0') ? DEFAULT_SRC_FNAME : fname1);

	 strcat(swappath, SWAP_FNAME);

	 if ( (swapfile=fopen(swappath, "w+b")) == NULL )
	    fatal(0,"Cannot create swap file \"%s\"", swappath);
	 swappos = 0;

	 read_src(src_fname);

	 make_hot_links();

	 if ( !errors )     paginate_document();
	 if ( !errors )     print_document( (fname2[0]=='\0') ? DEFAULT_DOC_FNAME : fname2 );

	 if ( errors || warnings )
	    report_errors();

	 fclose(swapfile);
	 remove(swappath);

	 break;

      case MODE_APPEND:
	 add_hlp_to_exe( (fname1[0]=='\0') ? DEFAULT_HLP_FNAME : fname1,
			 (fname2[0]=='\0') ? DEFAULT_EXE_FNAME : fname2);
	 break;

      case MODE_DELETE:
	 if (fname2[0] != '\0')
	    fatal(0,"Unexpected argument \"%s\"", fname2);
	 delete_hlp_from_exe((fname1[0]=='\0') ? DEFAULT_EXE_FNAME : fname1);
	 break;
      }

   free(buffer);

   return ( errors );   /* return the number of errors */
   }


