/*
 * CDE - Common Desktop Environment
 *
 * Copyright (c) 1993-2012, The Open Group. All rights reserved.
 *
 * These libraries and programs are free software; you can
 * redistribute them and/or modify them under the terms of the GNU
 * Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * These libraries and programs are distributed in the hope that
 * they will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with these libraries and programs; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301 USA
 */
#if DOC
/*===================================================================
$FILEBEG$:   PrintTopics.c
$COMPONENT$: dthelpprint
$PROJECT$:   Cde1
$SYSTEM$:    HPUX 9.0; AIX 3.2; SunOS 5.3
$REVISION$:  $TOG: PrintTopics.c /main/12 1998/04/01 17:26:51 mgreess $
$COPYRIGHT$:
   (c) Copyright 1996 Digital Equipment Corporation.
   (c) Copyright 1993,1994,1996 Hewlett-Packard Company.
   (c) Copyright 1993,1994,1996 International Business Machines Corp.
   (c) Copyright 1993,1994,1996 Sun Microsystems, Inc.
   (c) Copyright 1993,1994,1996 Novell, Inc. 
   (c) Copyright 1996 FUJITSU LIMITED.
   (c) Copyright 1996 Hitachi.
==$END$==============================================================*/
#endif

#ifndef _XOPEN_SOURCE
# define _XOPEN_SOURCE 600
#endif

#ifdef HAVE_CONFIG_H
#include <cde_config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <sys/stat.h>    /* for stat() */
#if defined(sun)
#include <widec.h>
#else
#include <wchar.h>
#endif
#include <locale.h>
#if !defined(sun)
#include <langinfo.h>
#endif

#include <iconv.h>
#include <errno.h>

#include <time.h>


#include "HelpPrintI.h"  /* helpprint */

#include "DtI/HelpTermP.h"	/* from libDtHelp */
#include "Dt/CanvasP.h"	/* from libDtHelp */
#include "StringFuncsI.h" /* from libDtHelp */
#include "DtI/bufioI.h" /* from libDtHelp; required for AccessI.h */
#include "DtI/FileUtilsI.h" /* from libDtHelp */
#include "DtI/Access.h" /* from libDtHelp */
#include "DtI/AccessI.h" /* from libDtHelp */
#include "GenUtilsP.h" /* from libDtHelp */
#include "HelpXlate.h" /* from libDtHelp */
#include "ObsoleteP.h" /* from libDtHelp */

// lib/DtHelp/FormatTerm.c
int _DtHelpTermCreateCanvas(int maxColumns, _DtCvHandle *ret_canvas);
int _DtHelpTermGetTopicData(_DtCvHandle canvasHandle,
                            _DtHelpVolumeHdl volHandle,
                            char *locationId,
                            char ***helpList,
                            DtHelpHyperLines **hyperList);


extern char * _DtHelpCeGetVolumeLocale(VolumeHandle helpVolumeHandle);
extern const char * _DtHelpCeGetVolumeCharSet(VolumeHandle helpVolumeHandle);

/*======== platform adjustments ==============*/
#ifdef sun
#define wcswidth(s,n) wscol(s)
#define wcslen(s) wslen(s)
#endif

/*======== boundary values ==============*/
#define MAXSECTS		50	   /* max number of section nesting */

#define MAXVOLTITLEWIDTH 50
#define MAXTOPICTITLEWIDTH 50
#define MAXPAGENUMWIDTH 3
#define MAXSECTNUMWIDTH 8

/*======== helper values ===============*/
#define EOS           '\0'
#define EMPTY_STR     s_EmptyStr

#define PTSET       3               /* message set */


/*======== helper variables ===============*/
static char s_EmptyStr[1] = { EOS };

/* To do:
	* check roman 8/Latin 1
	* check PAGER env variable
	* do character wrap
*/

/*======== data structs ==============*/

/* These data structs manage the table of contents (Toc)
   of a help volume.  The Toc uses two organizational mechanisms:
   a linear table of entries, which gives sequential order
   and is used to generate the TOC; and a binary tree of entries
   that is sorted alphabetically by location id, and is used to
   speed lookup of the page number of a locationId for use in
   generating the index. */

#if DOC
===================================================================
$PTYPEBEG$:  TocEntry
$1LINER$:  All data for a single toc entry
$DESCRIPT$:
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/
/*$DEF$*/
typedef struct TocEntry
{
   struct TocEntry * nextEntry;		/* next entry in the linear list */
   struct TocEntry * leftLoc;		/* left location of sorted binary tree */
   struct TocEntry * rightLoc;		/* right location of sorted binary tree */
   char * locationId;			/* locationId of the topic */
   int    pageNumber;			/* page the topic located on */
   int    level;			/* nesting level */
   int    sectNums[MAXSECTS];		/* section numbers */
   char * sectStr;                      /* alternate section string */
} TocEntry;
/*$END$*/

#if DOC
===================================================================
$PTYPEBEG$:  Toc
$1LINER$:  Manages the toc entries in a volume
$DESCRIPT$:
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/
/*$DEF$*/
typedef struct Toc
{
   TocEntry * linearEntries;		/* ptr to head of linear list of entries */
   TocEntry * lastLinearEntry;		/* ptr to tail of linear list of entries */
   TocEntry * sortedEntries;		/* ptr to top of sorted btree of entries */
} Toc;
/*$END$*/

#if DOC
===================================================================
$PTYPEBEG$:  HeadFootFormat
$1LINER$:  All data related to the header/footer formatting
$DESCRIPT$:
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/
/*$DEF$*/
typedef struct HeadFootFormat
{
   char *  formattedEvenHeader; /* formatted even-page header when printing volume */
   char *  formattedOddHeader;  /* formatted odd-page header when printing volume */
   char *  formattedEvenFooter; /* formatted even-page footer when printing volume */
   char *  formattedOddFooter;  /* formatted odd-page footer when printing volume */
   int     evenHeaderLineCnt;   /* num lines in even-page header when printing volume */
   int     oddHeaderLineCnt;    /* num lines in odd-page header when printing volume */
   int     evenFooterLineCnt;   /* num lines in even-page footer when printing volume */
   int     oddFooterLineCnt;    /* num lines in odd-page footer when printing volume */
} HeadFootFormat;
/*$END$*/

#if DOC
===================================================================
$PTYPEBEG$:  HeadFootFormatArgs
$1LINER$:  Arguments used in head/foot formatting
$DESCRIPT$:
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/
/*$DEF$*/
typedef struct HeadFootFormatArgs
{
   char *   volumeTitle;
   int      volumeTitleColsWidth;
   char *   topicTitle;
   int      topicTitleColsWidth;
   char *   volumeDate;
   int      volumeDateColsWidth;
   char *   todaysDate;
   int      todaysDateColsWidth;
   int      sectNumColsWidth;
   int      pageNumColsWidth;
}
HeadFootFormatArgs;
/*$END$*/

#if DOC
===================================================================
$PTYPEBEG$:  PrintState
$1LINER$:  All data related to the on-going printing operation
$DESCRIPT$:
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/
/*$DEF$*/
typedef struct PrintState
{
   char *   currentLocId;		/* current loc Id in volume */
   char *   outputFromLocId;		/* start output at this location */
   HeadFootFormatArgs hffArgs;          /* arguments used in head/foot formatting */
   HeadFootFormat tocHFF;		/* HF formatting info for TOC */
   HeadFootFormat bodyHFF;		/* HF formatting info for body */
   HeadFootFormat indexHFF;		/* HF formatting info for index */
   Boolean  inhibitOutput;		/* inhibit output */
   int      curPageNumber;		/* page number of current output */
   int      curLineNumber;		/* line number of current page of output */
   VolumeHandle volHandle;		/* volume handle of volume being printed */
   CanvasHandle canvasHandle;		/*canvas handle of terminal format canvas*/
   int      sectNums[MAXSECTS];		/* current section number */
   char *   sectStr;			/* current section string value */
   int      level;			/* level of section nesting */
} PrintState;
/*$END$*/

/*======== static variables ===============*/

#if DOC
===================================================================
$PTYPEBEG$:  SymValue
$1LINER$:  Maps a symbolic string to a printf-format string.
$DESCRIPT$:
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/
/*$DEF$*/
typedef struct SymValue
{
   char *  symbol;
   char *  argref;
} SymValue;

/* List of symbols that can be used in header/footer strings
   and the printf args that are used to access the data. */
/* NOTE: ***IT IS CRITICAL THAT THE SYMBOL STRING HAVE THE 
   SAME OR LONGER LENGTH THAN THE ARGUMENT STRING. ***/
/* NOTE: ***ALWAYS UPDATE THE UNUSED_ARGREF AND THE sprintf() CALL 
            IN PrintHeadFootStr() WHEN ADDING NEW SYMBOLS***/
/* NOTE: The replacement values use a trick to get around the limitations
   of printf(), which only allows args numbered 1 to 9.  The strategy
   is to do two printfs, and to use the %% construct in the first string
   to generate a % construct in the second string. */
SymValue g_HeadFootSymsList[] = 
{
  { "$VOLUMEFILL", "%%2$*1$c" },  /* filler for fixed sized volume name */
  { "$TOPICFILL", "%%4$*3$c" },   /* filler for fixed sized current topic title */
  { "$PAGENUMFILL", "%%6$*5$c" }, /* filler for fixed sized page number */
  { "$SECTNUMFILL", "%%8$*7$c" }, /* filler for fixed sized section number or name */
  { "$TODAY", "%1$s" },       /* today's date */
  { "$VOLUME", "%2$s" },      /* volume name */
  { "$TOPIC", "%3$s" },       /* current topic title */
  { "$PAGENUM", "%4$d" },     /* page number */
  { "$VOLDATE", "%5$s" },     /* date on the help volume file */
  { "$SECTNUM", "%6$s" },     /* section number or name */
  { "$LMARGIN", "%8$*7$c" },      /* left margin blanks; args 7 & 8 */
  { NULL, NULL }
};
#define  UNUSED_ARGREF   "%%9$n"    /* arg to hold the num of unused args */
/*$END$*/


/*======== functions ==============*/

#if DOC
===================================================================
$PFUNBEG$:  TocNextEntry()
$1LINER$:  Get next entry in the toc
$DESCRIPT$:
$ARGS$:
$RETURNS$:
!= NULL:   next entry
NULL:      no more entires
========================================================$SKIP$=====*/
#endif /*DOC*/

static
TocEntry * TocNextEntry(
  Toc *      toc,
  TocEntry * entry)
{       /*$CODE$*/
  if (NULL == entry) return toc->linearEntries;
  return entry->nextEntry;
}       /*$END$*/


#if DOC
===================================================================
$PFUNBEG$:  TocFindSortedEntry()
$1LINER$:  Finds an entry using the sorted btree of entries
$DESCRIPT$:
$RETURNS$:
$ARGS$:
0:   ok
-1:  entry not found
========================================================$SKIP$=====*/
#endif /*DOC*/

static
int TocFindSortedEntry(
  Toc *        toc,
  char *       locationId,
  TocEntry * * ret_parent,
  TocEntry * * ret_entry)
{       /*$CODE$*/
   TocEntry * prev;
   TocEntry * cur;
   TocEntry * tmp;

   if (NULL == ret_parent) ret_parent = &tmp;
   if (NULL == ret_entry) ret_entry = &tmp;

   *ret_parent = NULL;
   *ret_entry = NULL;

   prev = toc->sortedEntries;
   cur = prev;
   while ( NULL != cur )
   {
      int cmp;
      cmp = _DtHelpCeStrCaseCmp(locationId,cur->locationId);
      if ( cmp < 0 )
         prev = cur, cur = cur->leftLoc;
      else if ( cmp > 0 )
         prev = cur, cur = cur->rightLoc;
      else
      {
         *ret_parent = prev, *ret_entry = cur;
         return 0;                            /* RETURN: found */
      }
   }
   *ret_parent = prev;
   return -1;                                 /* RETURN: not found */
}       /*$END$*/


#if DOC
===================================================================
$PFUNBEG$:  TocNewEntry()
$1LINER$:  Add a Toc entry to the Toc
$DESCRIPT$:
$RETURNS$:
$ARGS$:
0:   ok
-1:  memory allocation error
========================================================$SKIP$=====*/
#endif /*DOC*/

static
int TocNewEntry(
  Toc *      toc,
  char *     locationId,
  int        pageNumber,
  int        level,
  int *      sectNums,
  char *     sectStr)
{       /*$CODE$*/
   TocEntry * new;

   new = calloc(1,sizeof(TocEntry));
   if (NULL == new) return -1;        /* RETURN: mem alloc err */

   /* init contents */
   new->locationId = strdup(locationId);
   new->pageNumber = pageNumber;
   new->level = level;
   new->sectStr = sectStr;
   memcpy(new->sectNums, sectNums, sizeof(new->sectNums));

   if (NULL == new->locationId)
      { free(new); return -1; }       /* RETURN: mem alloc err */

   /*** insert into toc ***/

   /* if first in list */
   if (NULL == toc->linearEntries)
   {
      toc->linearEntries = new;
      toc->lastLinearEntry = new;
      toc->sortedEntries = new;
   }
   else /* not first in list */
   {
      TocEntry * parent, * entry;

      if ( TocFindSortedEntry(toc,locationId,&parent,&entry) == 0 )
         return -2;                        /* RETURN: duplicate entry */
      /* insert into the btree (not balanced) */
      if( _DtHelpCeStrCaseCmp(locationId,parent->locationId) < 0 )
         parent->leftLoc = new;
      else
         parent->rightLoc = new;
      /* insert into the linear list */
      toc->lastLinearEntry->nextEntry = new;
      toc->lastLinearEntry = new;
   }
   return 0;        /* RETURN: ok */
}       /*$END$*/


#if DOC
===================================================================
$PFUNBEG$:  IconvFile()
$1LINER$:  Compares cur env and volume and iconv file if necessary 
$DESCRIPT$:
$RETURNS$:
0:  no conversion needed or not possible to determine if needed
1:  conversion needed & successful
-1: conversion needed and failed
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/

static
int IconvFile(
      _DtHPrOptions * options,
      VolumeHandle helpVolumeHandle,
      char * * srcFile)
{       /*$CODE$*/
#define CUR_LOCALE    0
#define CUR_CODESET   1
#define VOL_LOCALE    2
#define VOL_CODESET   3
#define FROM_CODESET  4
#define TO_CODESET    5
#define NUMSTRS       6

   int    ret;
   int    i;
   char * loc[NUMSTRS];
   char * destFile = NULL;
   char * codeset;
   char   buf[1000];

   for (i=0; i<NUMSTRS; i++) loc[i] = NULL;
   
   /* get the normalized current codeset */
   _DtHelpCeGetLcCtype(&loc[CUR_LOCALE], NULL, &loc[CUR_CODESET]);

   /* get the normalized volume codeset */
   loc[VOL_LOCALE] = _DtHelpCeGetVolumeLocale(helpVolumeHandle);

   /* codeset begins after the '.'; find it */
   codeset = NULL;
   if (    loc[VOL_LOCALE]
        && _DtHelpCeStrchr(loc[VOL_LOCALE], ".", 1, &codeset) == 0)
   {
       codeset++;
   }
   loc[VOL_CODESET] = (NULL != codeset ? strdup(codeset) : NULL);

   /* if either locale is NULL or if they are the same string
      then don't iconv the file */
   if (   NULL == loc[CUR_CODESET]
       || NULL == loc[VOL_CODESET]
       || strcmp(loc[CUR_CODESET],loc[VOL_CODESET]) == 0 )
   {
       ret = 0;                   /* RETURN:  no iconv needed/possible */
       goto cleanup;
   }

   /* get the source codeset */
   loc[FROM_CODESET] = strdup(loc[VOL_LOCALE]);

   /* get the target codeset */
   loc[TO_CODESET] = strdup(loc[CUR_LOCALE]);

   /* construct the command line */
   destFile = _DtHPrCreateTmpFile(TMPFILE_PREFIX,TMPFILE_SUFFIX);
   if (NULL == destFile)
   {
       ret = -1;                   /* error */
       goto cleanup;
   }

   sprintf(buf,options->iconvCmdAndArgs,
                 loc[FROM_CODESET],loc[TO_CODESET],*srcFile, destFile);

   /* do the conversion */
   if(options->debugHelpPrint) printf("%s\n",buf);
   ret = system(buf);
   ret = (ret == 0 ? 1 : -1);    /* 1: success; -1: failure */

   /* if successful conversion, change the src file */
   if (ret >= 0)
   {
      unlink(*srcFile);
      free(*srcFile);
      *srcFile = destFile;
   }
   else
   {
      unlink(destFile);
   }

cleanup:
   /* free memory */
   for (i=0; i<NUMSTRS; i++) if (loc[i]) free(loc[i]);

   return ret;
} /* count lines */

#define ICONV_INBUF_TYPE	ICONV_CONST char **

#define WORKSIZE 1024*10	/* 10k */

/*
 * _i18nwork1[] is used to convert the passed string with CD iconv.
 * in _converter_().
 *
 */
static void           *_i18nwork1 = NULL;
static unsigned long  _i18nsize1 = 0;
static int            shouldAlloc1 = ~0;


static void _converter_( iconv_t CD,
			void *from, unsigned long from_len,
			void **to )
{
    char          *InBuf;
    size_t        InBytesLeft;
    char          *OutBuf = NULL;
    size_t        OutBytesLeft = 0;
    size_t        _OutBytesLeft = 0;
    size_t        iconv_ret;
    size_t        converted_num = 0;
    unsigned long to_len;


    *to = NULL;
    to_len = 0;

    if ( shouldAlloc1 ) {
        /* Obtain work area */
        _i18nwork1 = (size_t *)malloc( WORKSIZE );
        if ( !_i18nwork1 ) {
            _i18nwork1 = NULL;
            return;
        }
        _i18nsize1 = WORKSIZE; 
        shouldAlloc1 = 0;
    }

    InBuf        = (char *)from;
    InBytesLeft  = from_len;
    OutBytesLeft = _i18nsize1;
    OutBuf = (char *)_i18nwork1;

    /*
     * Need to place iconv state to the initial one by
     * setting inbuf to NULL of iconv().
     */
    iconv( CD, (ICONV_INBUF_TYPE)NULL, 0, NULL, 0 );
    while( 1 ) {
	/*
	 * InBuf
	 *  v
	 * +----------------------------+
	 * | |                        | |
	 * +----------------------------+
	 *  <-------------------------->
	 *          InBytesLeft
	 *
	 *             |
	 *             | iconv()
	 *             V
	 * (_i18nwork1)
	 * OutBuf
	 *  v
	 * +----------------------------+
	 * | |                        | |
	 * +----------------------------+
	 *  <-------------------------->
	 *          InBytesLeft
	 */

	iconv_ret = iconv( CD, (ICONV_INBUF_TYPE)&InBuf, &InBytesLeft,
                               &OutBuf, &OutBytesLeft );
	if ( iconv_ret == 0 ) {
	    /* iconv done
	     *                             InBuf
	     *                               v
	     * +----------------------------+
	     * |XXXXXXXXXXXXXXXXXXXXXXXXXXXX|
	     * +----------------------------+
	     *                               
	     *                               InBytesLeft=0
	     *
	     * (_i18nwork1)
	     *  |               OutBuf
	     *  V                 v
	     * +----------------------------+
	     * |XXXXXXXXXXXXXXXXX| |      | |
	     * +----------------------------+
	     *  <---------------> <-------->
	     *   converted_num    OutBytesLeft
	     */
	    converted_num = (unsigned long)((char *)OutBuf-(char *)_i18nwork1);
	    *to = (void *)_i18nwork1;
	    to_len = (unsigned long)converted_num;
	    break;
	} else {
	    if ( errno == E2BIG ) {
		/* Overflow. still data is left.
		 *               InBuf
		 *                 v
		 * +----------------------------+
		 * |XXXXXXXXXXXXXX| |         | |
		 * +----------------------------+
		 *                 <----------->
		 *                  InBytesLeft
		 *
		 * (_i18nwork1)
		 *  |                         OutBuf
		 *  V                          v
		 * +----------------------------+
		 * |XXXXXXXXXXXXXXXXXXXXXXXXXXX |
		 * +----------------------------+
		 *  <-------------------------> 
		 *          converted_num      OutBytesLeft=?
		 */
		void *_p;

		/* Check how many converted already. */
		converted_num =
			(unsigned long)((char *)OutBuf - (char *)_i18nwork1);
		_i18nsize1 += WORKSIZE;
		_p = realloc( _i18nwork1, _i18nsize1 );
		if ( !_p ) {
		    *to = NULL;
		    to_len = 0;
		    free( _i18nwork1 );
		    _i18nwork1 = NULL;
		    _i18nsize1 = 0;
		    shouldAlloc1 = ~0;
		    break;
		} else {
		    _i18nwork1 = _p;
		    OutBuf = (char *)((char*)_i18nwork1 + converted_num);
		    OutBytesLeft += WORKSIZE;
		}  
	    } else {
		*to = NULL;
		to_len = 0;
		break;
	    }
	}
    }

    /*
     * Null terminate
     */

    if ( *to != NULL ) {
        if ( _i18nsize1 >= to_len + 1 ) {
            ((char *)_i18nwork1)[to_len] = '\0';
        } else {
	    void *_p;

            _i18nsize1++;
            _p = realloc( _i18nwork1, _i18nsize1 );
	    if ( !_p ) {
	        *to = NULL;
	        to_len = 0;
	        free( _i18nwork1 );
	        _i18nwork1 = NULL;
	        _i18nsize1 = 0;
	        shouldAlloc1 = ~0;
	    } else {
	        _i18nwork1 = _p;
	        ((char *)_i18nwork1)[to_len] = '\0';
	    }  
       }
    }
}

/*
 * Iconv for buffer
 */
static
int IconvBuffer(
      VolumeHandle helpVolumeHandle,
      char *src,
      char **dest )
{       /*$CODE$*/
#define CUR_LOCALE    0
#define CUR_CODESET   1
#define VOL_LOCALE    2
#define VOL_CODESET   3
#define FROM_CODESET  4
#define TO_CODESET    5
#define NUMSTRS       6

   int    ret;
   int    i;
   char * loc[NUMSTRS];
   char * codeset;
   char   buf[1000];
   static int isFirst = ~0;
   static iconv_t CD = (iconv_t)-1;

   for (i=0; i<NUMSTRS; i++) loc[i] = NULL;
   
   /* get the normalized current codeset */
   _DtHelpCeGetLcCtype(&loc[CUR_LOCALE], NULL, &loc[CUR_CODESET]);

   /* get the normalized volume codeset */
   loc[VOL_LOCALE] = _DtHelpCeGetVolumeLocale(helpVolumeHandle);

   /* codeset begins after the '.'; find it */
   codeset = NULL;
   if (    loc[VOL_LOCALE]
        && _DtHelpCeStrchr(loc[VOL_LOCALE], ".", 1, &codeset) == 0)
   {
       codeset++;
   }
   loc[VOL_CODESET] = (NULL != codeset ? strdup(codeset) : NULL);

   /* if either locale is NULL or if they are the same string
      then don't iconv the file */
   if (   NULL == loc[CUR_CODESET]
       || NULL == loc[VOL_CODESET]
       || strcmp(loc[CUR_CODESET],loc[VOL_CODESET]) == 0 )
   {
       ret = 0;                   /* RETURN:  no iconv needed/possible */
       goto cleanup;
   }

   /* get the source codeset */
   loc[FROM_CODESET] = strdup(loc[VOL_LOCALE]);

   /* get the target codeset */
   loc[TO_CODESET] = strdup(loc[CUR_LOCALE]);

   if ( isFirst ) {
      CD = iconv_open( loc[TO_CODESET], loc[FROM_CODESET] );
      isFirst = 0;
   }
   if ( CD == (iconv_t)-1 ) {
      ret = 0;
   } else {
      ret = 1;
       _converter_( CD, (void *)src, (unsigned long)strlen( src ),
		(void **)dest );
       if ( *dest == NULL )
           ret = 0;
   }
cleanup:
   /* free memory */
   for (i=0; i<NUMSTRS; i++) if (loc[i]) free(loc[i]);

   return ret;
} /* count lines */



#if DOC
===================================================================
$PFUNBEG$:  OpenTmpFile()
$1LINER$:  Opens temporary file
$DESCRIPT$:
opens a temporary file
$RETURNS$:
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/

static
FILE * OpenTmpFile(
      _DtHPrOptions * options,
      char * * ret_tmpFile)
{       /*$CODE$*/
   FILE * fp;

   /* Put in $HOME/.dt/tmp so that if the printer operation is running
      of a remote system, it can get to the (local) temp file. 
      This would not be possible if the file were put in /tmp */
   *ret_tmpFile = _DtHPrCreateTmpFile(TMPFILE_PREFIX,TMPFILE_SUFFIX);
   if (NULL == *ret_tmpFile) return NULL;     /* RETURN: error */

   fp = fopen(*ret_tmpFile,"w");
   if (NULL == fp)
   {
      fprintf(stderr,_DTGETMESSAGE(PTSET,6,
                           "%s Error: unable to open temporary file %s\n"),
                           options->programName, *ret_tmpFile);
   }

   return fp;
} /* open tmp file */


#if DOC
===================================================================
$PFUNBEG$:  CountLines()
$1LINER$:  counters number of CRs in a string
$DESCRIPT$:
counters number of CRs in a string
$RETURNS$:
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/

static
int CountLines(
      char * str)
{       /*$CODE$*/
   char * substr;
   int    lineCount = 0;
   static char * newLine = "\n";

   substr = str;
   while( _DtHelpCeStrchr(substr,newLine,MB_CUR_MAX,&substr) == 0 )
   {
      lineCount++;
      substr++;
   }
   /* return line count to caller */
   return lineCount;
} /* count lines */



#if DOC
===================================================================
$PFUNBEG$:  AvailContentLines()
$1LINER$:  count number of lines available between top of page & footer
$DESCRIPT$:
Count number of lines available between top of page & footer
$RETURNS$:
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/

int AvailContentLines(
  _DtHPrOptions * options,
  PrintState *    state,
  HeadFootFormat * hff)
{      /*$DEF$*/
    return options->rowsTextHeight - 
                 ( (state->curPageNumber % 2) == 0 
                     ? hff->evenFooterLineCnt 
                     : hff->oddFooterLineCnt);  
}      /*$END$*/



#if DOC
===================================================================
$PFUNBEG$:  SectNumStr()
$1LINER$:  generates string ver of section number
$DESCRIPT$:
 generates string ver of section number
$RETURNS$:
$ARGS$:
buf:       must be at least 4 * MAXSECTS chars long
========================================================$SKIP$=====*/
#endif /*DOC*/

static
char * SectNumStr(
  int *    sectNums,
  char *   sectStr,
  char *          buf)
{       /*$CODE$*/
    char partial[5];

     /* recall: if sectNums[0] == 0, then no section number is defined
        and none should be printed; try sectStr as alternate. */

    /* generate the section number */
    buf[0] = EOS;
    if (sectNums && sectNums[0] != 0 )
    {
       int sect;
       for ( sect = 1; sectNums[sect] != 0; sect++ )
       {
          sprintf(partial, "%d.", sectNums[sect]);
          strcat(buf,partial);
       } 
    }
    else  /* if no section number, take the section string, if avail */
    {
       if (sectStr) strcpy(buf,sectStr);
       else buf[0] = EOS;
    }
    return buf;
}       /*$END$*/



#if DOC
===================================================================
$PFUNBEG$:  OutputBlankSpaces()
$1LINER$:  Outputs blank Spaces
$DESCRIPT$:
Outputs blank Spaces
$RETURNS$:
$ARGS$:
topicsFP:  pointer to output stream.  If NULL, do nothing
========================================================$SKIP$=====*/
#endif /*DOC*/

static
void OutputBlankSpaces(
  FILE *  topicsFP,
  int     spaceCount)
{       /*$CODE$*/
     if (NULL == topicsFP || spaceCount < 0) return;
     while (spaceCount--) fprintf(topicsFP," "); 
} /* $END$ */


#if DOC
===================================================================
$PFUNBEG$:  OutputBlankLines()
$1LINER$:  Outputs blank lines
$DESCRIPT$:
Outputs blank lines
$RETURNS$:
$ARGS$:
topicsFP:  pointer to output stream.  If NULL, do nothing
========================================================$SKIP$=====*/
#endif /*DOC*/

static
void OutputBlankLines(
  FILE *  topicsFP,
  PrintState * state,
  int     lineCount)
{       /*$CODE$*/
     if (lineCount < 0) return;			/* RETURN */
     state->curLineNumber += lineCount;
     if (NULL == topicsFP) return;		/* RETURN */
     while (lineCount--) fprintf(topicsFP,"\n"); 
} /* $END$ */


#if DOC
===================================================================
$PFUNBEG$:  DoStrColsWidth()
$1LINER$:  Calculates number of cols used by a string; truncate if needed
$DESCRIPT$:
Calculates the number of cols used by the string and truncates
the string to the specified maxWidth if this is exceeded.
$NOTE$:
$RETURNS$:
The number of columns this string requires for printing.
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/

static
int DoStrColsWidth(
      char *   str,
      int      maxWidth,
      Boolean  truncateStr)
{       /*$CODE$*/
   int len;
   wchar_t * wcstr;
   int wclen;
   int width;

   /* alloc memory for the wchar_t string */
   len = strlen(str);
   wcstr = malloc(sizeof(wchar_t) * (len+1));

   /* convert str to wchar_t and get width in chars */
   mbstowcs(wcstr,str,len+1);
   wclen = wcslen(wcstr);

   /* get col width of the string and truncate if necessary */
   while (    (width = wcswidth(wcstr,wclen+1)) > maxWidth 
           && truncateStr == True)
      wcstr[--wclen] = EOS;
   wcstombs(str,wcstr,len+1);

   free(wcstr);

   return wclen;
}       /*$END$*/



#if DOC
===================================================================
$PFUNBEG$:  GenHeadFootFormatArgs()
$1LINER$:  Generates args used by a header/footer format string
$DESCRIPT$:
Generates args used by a header/footer format string
$NOTE$:
$RETURNS$:
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/

static
void GenHeadFootFormatArgs(
  _DtHPrOptions * options,
  PrintState *    state,
  Boolean         updateTopicTitle,
  char *          sectNumStr,
  Boolean         updatePageNum)
{       /*$CODE$*/
   char * str;
   int    width;
   time_t date = 0;
   struct tm * pTm;
   char   buf[100];

    /* get the volume title and its size */
    if (NULL == state->hffArgs.volumeTitle)
    {
       /* get the volume title */
       str = NULL;
       _DtHelpCeGetVolumeTitle(state->canvasHandle,state->volHandle,&str);
       if (NULL == str) str = strdup("");

       width = DoStrColsWidth(str,MAXVOLTITLEWIDTH,True);

       /* put into state data */
       state->hffArgs.volumeTitle = str;
       state->hffArgs.volumeTitleColsWidth = width;
    }

    /* get the volume's date */
    if (NULL == state->hffArgs.volumeDate)
    {
       char * locDocId = NULL;
       char * locDateStamp = NULL;
       long long_date = 0;

       /* locDocId & locDateStamp will point to private memory; do not modify */
       _DtHelpCeGetDocStamp(state->volHandle,&locDocId, &locDateStamp);
       if (    NULL != locDateStamp
            && sscanf(locDateStamp, "%ld", &long_date) != 1 )
          locDateStamp = NULL;     /* make invalid */
       else
	 date = (time_t)long_date;

       /* if no vol date, try getting from the help volume file */
       if ( NULL == locDateStamp )
       {
          struct stat stats;
          date = 0;
          if ( stat(options->helpVolume,&stats) == 0 )
             date = stats.st_mtime;
       }

       /* convert the int into a string */
       pTm = localtime(&date);
       strftime(buf,sizeof(buf),"%x",pTm);

       width = DoStrColsWidth(buf,0,False);

       /* put into state data */
       state->hffArgs.volumeDate = strdup(buf);
       state->hffArgs.volumeDateColsWidth = width;
       
       free(locDocId);
       free(locDateStamp);
    }

    /* get today's date */
    if (NULL == state->hffArgs.todaysDate)
    {
       /* convert the int into a string */
       date = time(NULL);
       pTm = localtime(&date);
       strftime(buf,sizeof(buf),"%x",pTm);

       width = DoStrColsWidth(buf,0,False);

       /* put into state data */
       state->hffArgs.todaysDate = strdup(buf);
       state->hffArgs.todaysDateColsWidth = width;
    }

    /* get the topic title and its size */
    if (updateTopicTitle)
    {
       /* get the topic title */
       str = NULL;
       _DtHelpCeGetTopicTitle(state->canvasHandle, state->volHandle,
                              state->currentLocId, &str);
       if (NULL == str) str = strdup("");

       width = DoStrColsWidth(str,MAXTOPICTITLEWIDTH,True);

       /* put into state data */
       if (state->hffArgs.topicTitle) free(state->hffArgs.topicTitle);
       state->hffArgs.topicTitle = str;
       state->hffArgs.topicTitleColsWidth = width;
    }

    /* get the size of the section number */
    if (sectNumStr)
    {
       width = DoStrColsWidth(sectNumStr,0,False);
       state->hffArgs.sectNumColsWidth = width;
    }

    /* get the size of the page number */
    if (updatePageNum)
    {
       int num;
       for ( width = 1, num = state->curPageNumber;
             (num >= 0) && ((num / 10) > 0);
             num /= 10, width++ )
       { /* do nothing */ }
       state->hffArgs.pageNumColsWidth = width;
    }
}       /*$END$*/


#if DOC
===================================================================
$PFUNBEG$:  GenHeadFootFormatStr()
$1LINER$:  Generates a printf-ready format string for the header/footer
$DESCRIPT$:
Generates a printf-ready format string for the header/footer
$NOTE$:
This function generates a string that uses the "%n$" prefix 
supported by printf(3S) to allow a footer/header string to 
ref any argument out of order.  This functionality requires 
that a ref to *every* argument be part of the format string.  
Because not all args may be part of the user-specified format 
string, this function appends the unused strings to the end 
of the format string for purposes of sprintf(), then truncates 
the unused portion before output.
$RETURNS$:
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/

static
void GenHeadFootFormatStr(
  char *          specStr,
  char * *        io_formattedStr,
  int *           io_lineCnt)
{       /*$CODE$*/
    char * lowLevelFormatStr;
    SymValue * sym;
    char * substr;
    char   unusedSyms[60];

    if (NULL == specStr) 
    {
       *io_formattedStr = NULL;
       *io_lineCnt = 0;
       return;                        /* RETURN */
    }

    /*** create the formatted string ***/

    /* Make a working copy of the string; I assume that the
       values of 'argref' string always be shorter than 'symbol'. */
    lowLevelFormatStr = malloc(strlen(specStr) + sizeof(unusedSyms) + 10);
    if (NULL == lowLevelFormatStr) return;
    strcpy(lowLevelFormatStr,specStr);

    /* replace the symbolic names with printf argument refs */
    strcpy(unusedSyms,UNUSED_ARGREF);
    for ( sym = g_HeadFootSymsList; sym->symbol != NULL; sym++ )
    {
       Boolean unused = True;

       /* look for the symbol string */
       while ( (substr = strstr(lowLevelFormatStr,sym->symbol)) != NULL )
       {  /* and replace it with the argref */
          unused = False;
          strcpy(substr, sym->argref);
          strcpy(substr + strlen(sym->argref), substr + strlen(sym->symbol));
       }
       /* if unused, add to unused list */
       if (unused) strcat(unusedSyms,sym->argref);
    }

    /* append unused syms to end of format str */
    strcat(lowLevelFormatStr,unusedSyms);

    /* store in caller's location */
    *io_formattedStr = lowLevelFormatStr;
    *io_lineCnt = CountLines(lowLevelFormatStr);
}       /*$END$*/


#if DOC
===================================================================
$PFUNBEG$:  GenAllHeadFootFormatStrs()
$1LINER$:  Generates printf-ready format strings for all headers/footers
$DESCRIPT$:
Generates printf-ready format strings for all headers/footers
$NOTE$:
$RETURNS$:
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/

static
void GenAllHeadFootFormatStrs(
  _DtHPrOptions * options,
  PrintState *    state)
{       /*$CODE$*/
   /* Toc */
   GenHeadFootFormatStr(options->tocHF.evenHeader,
            &state->tocHFF.formattedEvenHeader, &state->tocHFF.evenHeaderLineCnt);
   GenHeadFootFormatStr(options->tocHF.oddHeader,
            &state->tocHFF.formattedOddHeader, &state->tocHFF.oddHeaderLineCnt);
   GenHeadFootFormatStr(options->tocHF.evenFooter,
            &state->tocHFF.formattedEvenFooter, &state->tocHFF.evenFooterLineCnt);
   GenHeadFootFormatStr(options->tocHF.oddFooter,
            &state->tocHFF.formattedOddFooter, &state->tocHFF.oddFooterLineCnt);
   /* Body */
   GenHeadFootFormatStr(options->bodyHF.evenHeader,
            &state->bodyHFF.formattedEvenHeader, &state->bodyHFF.evenHeaderLineCnt);
   GenHeadFootFormatStr(options->bodyHF.oddHeader,
            &state->bodyHFF.formattedOddHeader, &state->bodyHFF.oddHeaderLineCnt);
   GenHeadFootFormatStr(options->bodyHF.evenFooter,
            &state->bodyHFF.formattedEvenFooter, &state->bodyHFF.evenFooterLineCnt);
   GenHeadFootFormatStr(options->bodyHF.oddFooter,
            &state->bodyHFF.formattedOddFooter, &state->bodyHFF.oddFooterLineCnt);
   /* Index */
   GenHeadFootFormatStr(options->indexHF.evenHeader,
            &state->indexHFF.formattedEvenHeader, &state->indexHFF.evenHeaderLineCnt);
   GenHeadFootFormatStr(options->indexHF.oddHeader,
            &state->indexHFF.formattedOddHeader, &state->indexHFF.oddHeaderLineCnt);
   GenHeadFootFormatStr(options->indexHF.evenFooter,
            &state->indexHFF.formattedEvenFooter, &state->indexHFF.evenFooterLineCnt);
   GenHeadFootFormatStr(options->indexHF.oddFooter,
            &state->indexHFF.formattedOddFooter, &state->indexHFF.oddFooterLineCnt);
}       /*$END$*/



#if DOC
===================================================================
$PFUNBEG$:  PrintHeadFootStr()
$1LINER$:  Formats and prints the header/footer string
$DESCRIPT$:
Takes a format string, replaces the place holders with actual values,
and prints the output to topicsFP.
$NOTE$:
This function uses the "%n$" prefix supported by printf(3S)
to allow a footer/header string to ref any argument out of
order.  This functionality requires that a ref to *every*
argument be part of the format string.  Because not all args
may be part of the user-specified format string, this function
appends the unused strings to the end of the format string
for purposes of sprintf(), then truncates the unused portion
before output.
$RETURNS$:
number of lines in the header or footer
$ARGS$:
topicsFP:  pointer to output stream.  If NULL, increment
           page and reset line number), but do not output;
           If not NULL, request new page as well
========================================================$SKIP$=====*/
#endif /*DOC*/

static
int PrintHeadFootStr(
  _DtHPrOptions * options,
  FILE *          topicsFP,
  PrintState *    state,
  char *          formattedStr,
  int             lineCnt)
{       /*$CODE$*/
    char * newLine = "\n";
    int    lastValid = 0;
    char   sectNumStr[MAXSECTS * 4 + 5];	/* $SECTNUM */
    char   buf[3000];

    if (NULL == formattedStr) return 0;        /* RETURN */
    if (NULL == topicsFP) return lineCnt;      /* RETURN */

    /*** generate dynamic data ***/

    /* get the section number */
    SectNumStr(state->sectNums,state->sectStr,sectNumStr);

    /* update args; FIX: impove efficiency by processing topic title only when needed */
    GenHeadFootFormatArgs(options,state,True,sectNumStr,True);

    /* guidelines on string size and construction: 
       The objective is to allow one set of headers & footers to apply
       to many different volumes and topics.  This is made possible by
       allowing for fixed length strings.  To get fixed length strings,
       the header/footer spec should include not only the string but
       also the fill for that string.

       The fill size is calculated based on the following widths:
         volTitle   : 50 printing chars    e.g. "title        "
         topicTitle : 50 printing chars    e.g. "title        "
         sectNumStr :  8 printing chars    e.g. "  3.10.4"
         pageNum    :  3 printing chars    e.g. "  3"
         volDate    : constant by locale--no fill needed e.g. "Mon, Jul 4, 1988"
         dateStr    : constant by locale--no fill needed e.g. "Mon, Jul 4, 1988"
    */

    /*** generate the str ***/
    /* IMPT: the order of these arguments MUST match the argument numbers
       given in the definition of the g_HeadFootSymsList variable. */
    /* print the first set */
    sprintf(buf, formattedStr,
               state->hffArgs.todaysDate,state->hffArgs.volumeTitle,
               state->hffArgs.topicTitle, (int) state->curPageNumber,
               state->hffArgs.volumeDate, sectNumStr,
               (int) options->colsAdjLeftMargin,(int) ' ' );

    /* move the format string for the second set into new memory */
    formattedStr = strdup(buf);
    if(NULL == formattedStr) return 0;            /* RETURN */

    /* print the second set */
    sprintf(buf, formattedStr,
        MAXVOLTITLEWIDTH - state->hffArgs.volumeTitleColsWidth, (int) ' ',
        MAXTOPICTITLEWIDTH - state->hffArgs.topicTitleColsWidth, (int) ' ',
        MAXPAGENUMWIDTH - state->hffArgs.pageNumColsWidth, (int) ' ',
        MAXSECTNUMWIDTH - state->hffArgs.sectNumColsWidth, (int) ' ',
        &lastValid);
    buf[lastValid] = EOS;	/* truncate unused args */
    free(formattedStr);

    /*** output the str ***/
    fprintf(topicsFP, "%s", buf);

    return lineCnt;
}       /*$END$*/



#if DOC
===================================================================
$PFUNBEG$:  PrintFooter()
$1LINER$:  Print footer and use right form for odd/even pages
$DESCRIPT$:
Print footer and use right form for odd/even pages
$RETURNS$:
$ARGS$:
topicsFP:  pointer to output stream.  If NULL, increment
           lines), but do not output;
========================================================$SKIP$=====*/
#endif /*DOC*/

static
void PrintFooter(
  _DtHPrOptions * options,
  FILE *          topicsFP,
  PrintState *    state,
  _DtHPrHeadFoot * headFootInfo,
  HeadFootFormat * headFootFormatting)
{       /*$CODE$*/
   /* fill to bottom of page */
   OutputBlankLines(topicsFP,state,
        (options->rowsTextHeight - state->curLineNumber) - 
              ( (state->curPageNumber % 2) == 0 
                  ? headFootFormatting->evenFooterLineCnt 
                  : headFootFormatting->oddFooterLineCnt));  

   if ( (state->curPageNumber % 2) == 0)  /* Even page */
       state->curLineNumber += PrintHeadFootStr(options,topicsFP,state,
                                 headFootFormatting->formattedEvenFooter,
                                 headFootFormatting->evenFooterLineCnt);
   else  /* odd page */
       state->curLineNumber += PrintHeadFootStr(options,topicsFP,state,
                                 headFootFormatting->formattedOddFooter,
                                 headFootFormatting->oddFooterLineCnt);
}  /*$END$*/



#if DOC
===================================================================
$PFUNBEG$:  PrintHeader()
$1LINER$:  Print footer and use right form for odd/even pages
$DESCRIPT$:
Print footer and use right form for odd/even pages
$RETURNS$:
$ARGS$:
topicsFP:  pointer to output stream.  If NULL, increment
           lines), but do not output;
========================================================$SKIP$=====*/
#endif /*DOC*/

static
void PrintHeader(
  _DtHPrOptions * options,
  FILE *          topicsFP,
  PrintState *    state,
  _DtHPrHeadFoot * headFootInfo,
  HeadFootFormat * headFootFormatting)
{       /*$CODE$*/

   if ( (state->curPageNumber % 2) == 0)  /* Even page */
       state->curLineNumber += PrintHeadFootStr(options,topicsFP,state,
                                 headFootFormatting->formattedEvenHeader,
                                 headFootFormatting->evenHeaderLineCnt);
   else
       state->curLineNumber += PrintHeadFootStr(options,topicsFP,state,
                                 headFootFormatting->formattedOddHeader,
                                 headFootFormatting->oddHeaderLineCnt);
}  /*$END$*/


#if DOC
===================================================================
$PFUNBEG$:  NewPage()
$1LINER$:  Outputs paper form feed, increments page count, resets line cnt
$DESCRIPT$:
Outputs paper form feed, increments page count, resets line cnt
$RETURNS$:
$ARGS$:
topicsFP:  pointer to output stream.  If NULL, increment
           page and reset line number), but do not output;
           If not NULL, request new page as well
========================================================$SKIP$=====*/
#endif /*DOC*/

static
void NewPage(
  _DtHPrOptions * options,
  FILE *          topicsFP,
  PrintState *    state,
  Boolean         advancePage)
{       /*$CODE$*/
    /* start new page: form feed */
    if (topicsFP) 
    {
       if (advancePage) fprintf(topicsFP,"\n");

       /* print top margin but don't add it to line count because the rowsTextHeight
          value is calculated post-topMargin */
       OutputBlankLines(topicsFP,state,options->rowsAdjTopMargin);
    }

    /* adjust page and line numbers */
    if (advancePage) state->curPageNumber++;
    state->curLineNumber = 1;
}       /*$END$*/


#if DOC
===================================================================
$PFUNBEG$:  ProcessOneTopic()
$1LINER$:  Recovers and formats help text for one topic
$DESCRIPT$:
Recovers and formats help text for one topic
$RETURNS$:
 0:  The number of lines output.
-2:  could not get topic information
$ARGS$:
topicsFP:  pointer to output stream.  If NULL, process topic
           (e.g. count lines and inc page numbers), but do not output;
           If not NULL, output lines and page headers
========================================================$SKIP$=====*/
#endif /*DOC*/

static
int ProcessOneTopic(
  _DtHPrOptions * options,
  FILE *          topicsFP,
  PrintState *    state,
  Boolean         printHeaderFooter)
{       /*$CODE$*/
   char * * helpList = NULL;
   char * * ptrToLst;
   int      lineCount;
   int      availLines;
   char sectNumStr[MAXSECTS * 4 + 5];

   /* retrieve the text (but not the hyperlinks) from the volume */
   if ( _DtHelpTermGetTopicData(state->canvasHandle, state->volHandle,
                       state->currentLocId, &helpList, NULL) != 0 )
   {
      fprintf(stderr,_DTGETMESSAGE(PTSET,5,
                           "%s Error: unable to get topic information:\n"
                           "volume %s, locationId %s\n"),
                     options->programName, options->helpVolume, options->locationId);
      return -2;          		/* RETURN error */
   }

   /* output topic section number */
   /* this operates on the assumption that topic title is first line */
   ptrToLst = helpList;
   SectNumStr(state->sectNums,state->sectStr,sectNumStr);
   if (sectNumStr[0] != EOS)  
   {
      /* NOTE: if allow the sect num string to be resource-defined,
         then count the number of \n chars in it; don't assume how many. */
      if (topicsFP) fprintf(topicsFP,"\n");
      OutputBlankSpaces(topicsFP,options->colsAdjLeftMargin);
      if (topicsFP) fprintf(topicsFP,"%s ", sectNumStr);
      state->curLineNumber++;

      /* and put the title (must be on first two lines) on the same line */
      if (   (*helpList && (*helpList)[0] != EOS)
          || (*(++helpList) && (*helpList)[0] != EOS) )
      {
        if (topicsFP) {
	   char *_p;
           int ret;

           ret = IconvBuffer( state->volHandle, *helpList, &_p );
	   if ( ret ) {
               fprintf(topicsFP,"%s\n", _p); /* output title */
           } else {
               fprintf(topicsFP,"%s\n", *helpList); /* output title */
           }
	}
         state->curLineNumber += CountLines(*helpList) + 1; /* 1=the known \n */
         helpList++;
      }
   }

   /* calc number of available lines from top of page to footer */
   availLines = AvailContentLines(options,state,&state->bodyHFF);

   /* cycle through the lines; add new pages where necessary */
   for ( ; *helpList != NULL; helpList++ )
   {
      /* NOTE: it's impt to calc the final line before outputting it,
         as the line may contain embedded newlines */
      int linesCnt = CountLines(*helpList) + 1;     /* 1=the known \n */ 

      /* calc what line that will leave us on */
      state->curLineNumber += linesCnt;
 
      /* if at the end of a page, print footer, eject, and print header */
      if (state->curLineNumber >= availLines)
      {
         /* output any filler blank lines */
         OutputBlankLines(topicsFP,state,
                   availLines - (state->curLineNumber - linesCnt) );
         if (printHeaderFooter) 
            PrintFooter(options,topicsFP,state,&options->bodyHF,&state->bodyHFF);
         NewPage(options,topicsFP,state,True);
         if (printHeaderFooter) 
            PrintHeader(options,topicsFP,state,&options->bodyHF,&state->bodyHFF);

         /* recalc the line we're on */
         state->curLineNumber += linesCnt;

         /* calc number of available lines from top of page to footer */
         availLines = AvailContentLines(options,state,&state->bodyHFF);
      }

      /* output the lines */
      if (topicsFP) 
      {
         OutputBlankSpaces(topicsFP,options->colsAdjLeftMargin);
	{
	    char *_p;
	    int ret;

	    ret = IconvBuffer( state->volHandle, *helpList, &_p );
	    if ( ret ) {
               fprintf(topicsFP,"%s\n", _p );
	    } else {
               fprintf(topicsFP,"%s\n",*helpList);
	    }
	}
      }
   }

   /* free the memory of helpList */
   _DtHelpFreeTopicData(ptrToLst,NULL);

   return 0;
}       /*$END$*/



#if DOC
===================================================================
$PFUNBEG$:  ProcessSubTopics()
$1LINER$:  Recovers and formats help text for current & sub topics
$DESCRIPT$:
     ond none should be printed. */

Subsections are numbered according to the legal method (e.g. 1.5.4.3)

Recovers and formats help text for current & sub topics
$RETURNS$:
 0:  success
-1:  could not create or open a temp file
-2:  could not get help topic info
INHIBIT_OUTPUT:  stop outputting children; we printed all we need to
$ARGS$:
level:   may range from 1 to (MAXSECTS-1)
========================================================$SKIP$=====*/
#endif /*DOC*/
static
int ProcessSubTopics(
  _DtHPrOptions * options,
  FILE *          topicsFP,
  Toc *           toc,
  int             level,
  PrintState *    state)
{       /*$CODE$*/
   char * * children = NULL;
   int      ret = 0;
   int      firstSubSectNum;
   int      subSectNumIndex;
   FILE *   curFP;

#define INHIBIT_OUTPUT (-3)
   /* remember, don't turn inhibitOutput on if there's no match */
   if ( _DtHelpCeStrCaseCmp(state->outputFromLocId,state->currentLocId) == 0 )
   {
      int curPos;

      /* toggle the flag and set ret value */
      state->inhibitOutput = False;
      ret = INHIBIT_OUTPUT;     /* stop printing after this topic & its children */

      curPos = state->curLineNumber;  /* save off the current line position */

      /* start new page but don't eject; output header;
         output right number of blank lines to get to where topic starts */
      NewPage(options,topicsFP,state,False);
      PrintHeader(options,topicsFP,state,&options->bodyHF,&state->bodyHFF);
      OutputBlankLines(topicsFP,state,curPos - state->curLineNumber);
   }

   /* init file ptr */
   curFP = (state->inhibitOutput ? (FILE *) NULL : topicsFP);

   /* init according to level */
   if(level == 0)   /* the top topic? */
   {  /* put top topic at same level as children */
      firstSubSectNum = 2;
      subSectNumIndex = 1;
      state->level = 1;
      /* no prior instance of ProcessSubTopics() set this */
      state->sectNums[subSectNumIndex] = 1;  
   }
   else /* not the top topic */
   {
      firstSubSectNum = 1;
      subSectNumIndex = level;
      state->level = level;
   }

   /* add this topic to the toc */
   TocNewEntry(toc,state->currentLocId,state->curPageNumber,
                           state->level,state->sectNums,state->sectStr);

   /* retrieve and possibly output the current topic */
   ProcessOneTopic(options,curFP,state,True);

   /* output the sub topics */
   if (_DtHelpCeGetTopicChildren(state->volHandle,
                            state->currentLocId,&children) > 0)
   {
      char * * topic;
      Boolean  initSubSect = True;

      /* cycle through the topics and output each one */
      for (topic = children; *topic != NULL && (*topic)[0]; topic++ )
      {
         /* start level 0 & 1 topics on a new page */
         if ( subSectNumIndex < 2 )
         {
            PrintFooter(options,curFP,state,&options->bodyHF,&state->bodyHFF);
            NewPage(options,curFP,state,True);
            PrintHeader(options,curFP,state,&options->bodyHF,&state->bodyHFF);
         }

         /* avoid orphans (i.e. require more than 4 lines left on page) */
         if ( (AvailContentLines(options,state,&state->bodyHFF) 
                                               - state->curLineNumber) < 4 )
         {
             PrintFooter(options,curFP,state,&options->bodyHF,&state->bodyHFF);
             NewPage(options,curFP,state,True);
             PrintHeader(options,curFP,state,&options->bodyHF,&state->bodyHFF);
         }

         /* init new sub sect, if haven't yet done so */
         if (initSubSect)
         {
            /* start new subsection; -1: make up for ++ later */
            state->sectNums[subSectNumIndex] = firstSubSectNum - 1;
            initSubSect = False;
         }

         /* make this the current topic */
         state->currentLocId = *topic;
         /* use subSectNumIndex, not level, so that the top topic and its
            immediate children are at the same level */
         /* inc the sect num before the call so that the
            sect used below in the INHIBIT_OUTPUT wrapup is correct. */
         /* pass in topicsFP, not curFP */
         state->sectNums[subSectNumIndex]++;     /* next subsection */
         /* even though ProcessSubTopics() can return 'INHIBIT_OUTPUT',
            don't stop processing, as this would cause the Toc to
            be incomplete. */
         ProcessSubTopics(options,topicsFP,toc, subSectNumIndex+1,state);
         /* ProcessSubTopics(options,topicsFP,toc, state->level+1,state); */
      }
   }

   /* if this is the last topic to be output, then finish up */
   if (ret == INHIBIT_OUTPUT)
   {
      /* save off line num */
      int curLine = state->curLineNumber;

      /* output footer */
      PrintFooter(options,curFP,state,&options->bodyHF,&state->bodyHFF);
      state->inhibitOutput = True;	/* inhibit again */

      /* restore line num */
      state->curLineNumber = curLine;
   }

   /* if processing subtopics, reset subsection number */
   if(subSectNumIndex > 1) state->sectNums[subSectNumIndex] = 0;
   state->level = level;	/* state->level was modified by the FOR loop */

   free(children);
   return ret;
}


#if DOC
===================================================================
$PFUNBEG$:  ProcessFrontMatter()
$1LINER$:  Process the help volume to generate a file of front matter
$DESCRIPT$:
Process the help volume to generate a file of front matter
$RETURNS$:
 0:  success
-1:  could not create or open a temp file
-2:  could not get help topic info
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/

static
int ProcessFrontMatter(
  _DtHPrOptions * options,
  char * *        ret_resultsFile,
   PrintState *   state)
{       /*$CODE$*/
   FILE *  fp;
   int     ret;

   /* open file */
   fp = OpenTmpFile(options,ret_resultsFile);
   if (NULL == fp) return -1;               /* RETURN: error */

   /*** process text ***/
   /* setup the state */
   state->inhibitOutput = False;
   state->curPageNumber = 1;
   state->curLineNumber = 1;
   state->sectNums[0] = 0;      /* inhibit section num printing */
   state->sectStr = NULL;       /* inhibit section string printing */

   OutputBlankLines(fp,state, options->rowsTextHeight / 3);

   /* NOTE: the code below causes the memory allocated for currentLocId
      to be lost.  I didn't fix this to save a few bytes of code space. */

   /* generate volume title */
   state->currentLocId = strdup("_TITLE");
   state->outputFromLocId = state->currentLocId;
   ret = ProcessOneTopic(options,fp,state,False);
   /*free(state->currentLocId);*/

   NewPage(options,fp,state,True);

   /* generate abstract */
   state->currentLocId = strdup("_ABSTRACT");
   state->outputFromLocId = state->currentLocId;
   ret = ProcessOneTopic(options,fp,state,False);
   /*free(state->currentLocId);*/ 

   /* Make a space between abstract and copyright */
   if ( AvailContentLines(options,state,&state->indexHFF) > 3 )
      OutputBlankLines(fp,state, 3);
   else
      NewPage(options,fp,state,True);

   /* generate copyright */
   state->currentLocId = strdup("_COPYRIGHT");
   state->outputFromLocId = state->currentLocId;
   ret = ProcessOneTopic(options,fp,state,False);
   /*free(state->currentLocId);*/
   /*state->currentLocId = NULL;*/

   if (fp) fclose(fp);

   return ret;
}       /*$END$*/



#if DOC
===================================================================
$PFUNBEG$:  ProcessIndex()
$1LINER$:  Process the TOC data to generate an index file
$DESCRIPT$:
Process the TOC data to generate an index file
$WARNING$:
This function uses state->pageNumber+1 as the first page
of the index.
$RETURNS$:
 0:  success
-1:  could not create or open a temp file
-2:  could not get help topic info
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/

static
int ProcessIndex(
  _DtHPrOptions * options,
  char * *        ret_resultsFile,
   PrintState *   state,
   Toc *          toc)
{       /*$CODE$*/
   FILE *   fp;
   int      ret;
   int      availLines;
   char * * indexEntriesList;
   char *   entryStr;
   int      entriesCnt;

   *ret_resultsFile = NULL;

   /* Get the index.  Recall that the array and strings pointed to by
      the indexEntriesList is owned by the open volume */
   entriesCnt = _DtHelpCeGetKeywordList(state->volHandle,&indexEntriesList);
   if (entriesCnt <= 0) return 0;

   /* open file */
   fp = OpenTmpFile(options,ret_resultsFile);
   if (NULL == fp) return -1;               /* RETURN: error */

   /*** process index ***/

   /* setup the state */
   state->curPageNumber++;         /* inc page number from prev. value */
   state->inhibitOutput = False;
   state->curLineNumber = 1;
   state->sectNums[0] = 0;      /* inhibit section num printing */
   state->sectStr = _DTGETMESSAGE(PTSET,10,"Index");  /* $SECTNUM name */

   /* add an index entry to the TOC */
   TocNewEntry(toc,_DTGETMESSAGE(PTSET,30,"__GENERATED-INDEX"),
                 state->curPageNumber, 1,state->sectNums,state->sectStr);

   /* start new page (but don't eject) and output header */
   NewPage(options,fp,state,False);
   PrintHeader(options,fp,state,&options->indexHF,&state->indexHFF);

   /* calc number of available lines from top of page to footer */
   availLines = AvailContentLines(options,state,&state->indexHFF);

   /*** loop through the entries ***/
   for ( entryStr = *indexEntriesList++;
         NULL != entryStr;
         entryStr = *indexEntriesList++ )
   {
      char * * topicIdsList;
      int      topicCnt;
      int      i;
      TocEntry * tocEntry;

      /* get the topics associated with this entry */
      /* topicIdsList is set but not allocated & need not be freed */
      topicCnt = _DtHelpCeFindKeyword(state->volHandle,
                               entryStr,&topicIdsList);
      if (topicCnt <= 0) continue;             /* CONTINUE */

      /*** if only one topic, just put page number after the entry ***/
      if (topicCnt == 1)
      {
         /* find the toc entry of the topic */
         ret = TocFindSortedEntry(toc, *topicIdsList, NULL, &tocEntry);
         if (ret < 0) continue;                 /* CONTINUE */

         /* output the entry */
         OutputBlankSpaces(fp,options->colsAdjLeftMargin);
         {
             char *_p;
             int ret;

             ret = IconvBuffer( state->volHandle, entryStr, &_p );
	     if ( ret ) {
                 fprintf(fp, _DTGETMESSAGE(PTSET,20,"%s, %d\n"), 
                         _p, tocEntry->pageNumber); 
             } else {
                 fprintf(fp, _DTGETMESSAGE(PTSET,20,"%s, %d\n"), 
                         entryStr, tocEntry->pageNumber); 
             }
         }
         state->curLineNumber++;

         /* if at the end of a page, print footer, eject, and print header */
         if (state->curLineNumber >= availLines)
         {
             PrintFooter(options,fp,state,&options->indexHF,&state->indexHFF);
             NewPage(options,fp,state,True);
             PrintHeader(options,fp,state,&options->indexHF,&state->indexHFF);
             /* calc number of available lines from top of page to footer */
             availLines = AvailContentLines(options,state,&state->indexHFF);
         }

         continue;                              /* CONTINUE */
      }

      /*** if more than one topic, list topic title & page number under the entry ***/

      /* do we need a new page to start this entry? */
      if ( (availLines - state->curLineNumber) < 3 )
      {
          PrintFooter(options,fp,state,&options->indexHF,&state->indexHFF);
          NewPage(options,fp,state,True);
          PrintHeader(options,fp,state,&options->indexHF,&state->indexHFF);
          /* calc number of available lines from top of page to footer */
          availLines = AvailContentLines(options,state,&state->indexHFF);
      }

      OutputBlankSpaces(fp,options->colsAdjLeftMargin);
      {
          char *_p;
          int  ret;

          ret = IconvBuffer( state->volHandle, entryStr, &_p );
          if ( ret ) {
              fprintf(fp,"%s\n", _p ); 
          } else {
              fprintf(fp,"%s\n", entryStr); 
          }
      }
      state->curLineNumber++;

      /* for all topics in an index entry */
      for ( i=0; 
            i<topicCnt; 
            i++, topicIdsList++ )
      {
         char * topicTitle;

         if (NULL == *topicIdsList) continue;   /* CONTINUE */

         /* find the toc entry of the topic */
         ret = TocFindSortedEntry(toc, *topicIdsList, NULL, &tocEntry);
         if (ret < 0) continue;                 /* CONTINUE */

         /* get the topic title */
         _DtHelpCeGetTopicTitle(state->canvasHandle, state->volHandle,
                              tocEntry->locationId, &topicTitle);
         if (NULL == topicTitle) 
            topicTitle = _DTGETMESSAGE(PTSET,31,EMPTY_STR);

         /* output the entry */
         OutputBlankSpaces(fp,options->colsAdjLeftMargin);
         {
             char *_p;
             int ret;

             ret = IconvBuffer( state->volHandle, topicTitle, &_p );
             if ( ret ) {
                 fprintf(fp,_DTGETMESSAGE(PTSET,21,"     %s, %d\n"), 
                      _p, tocEntry->pageNumber); 
             } else {
                 fprintf(fp,_DTGETMESSAGE(PTSET,21,"     %s, %d\n"), 
                      topicTitle, tocEntry->pageNumber); 
             }
         }
         state->curLineNumber++;

         /* if at the end of a page, print footer, eject, and print header */
         if (state->curLineNumber >= availLines)
         {
             PrintFooter(options,fp,state,&options->indexHF,&state->indexHFF);
             NewPage(options,fp,state,True);
             PrintHeader(options,fp,state,&options->indexHF,&state->indexHFF);
             /* calc number of available lines from top of page to footer */
             availLines = AvailContentLines(options,state,&state->indexHFF);
         }
      }  /* for all topics in an index entry */

   }  /* for all index entries */

   /* output footer */
   PrintFooter(options,fp,state,&options->indexHF,&state->indexHFF);

   if (fp) fclose(fp);

   return 0;
}       /*$END$*/



#if DOC
===================================================================
$PFUNBEG$:  ProcessToc()
$1LINER$:  Process the TOC data to generate a toc file
$DESCRIPT$:
Process the TOC data to generate a toc file
$RETURNS$:
 0:  success
-1:  could not create or open a temp file
-2:  could not get help topic info
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/

static
int ProcessToc(
  _DtHPrOptions * options,
  char * *        ret_resultsFile,
   PrintState *   state,
   Toc *          toc)
{       /*$CODE$*/
   FILE *  fp;
   int     availLines;
   TocEntry * entry;

   /* open file */
   fp = OpenTmpFile(options,ret_resultsFile);
   if (NULL == fp) return -1;               /* RETURN: error */

   /*** process toc ***/

   /* setup the state */
   state->inhibitOutput = False;
   state->curPageNumber = 1;
   state->curLineNumber = 1;
   state->sectNums[0] = 0;      /* inhibit section num printing */
   state->sectStr = _DTGETMESSAGE(PTSET,11,"Table of Contents"); /* $SECTNUM name */

   /* start new page and output header */
   NewPage(options,fp,state,False);
   PrintHeader(options,fp,state,&options->tocHF,&state->tocHFF);

   /* calc number of available lines from top of page to footer */
   availLines = AvailContentLines(options,state,&state->tocHFF);

   /* walk through the toc; output the label, title string, and page num */
   for ( entry = TocNextEntry(toc,NULL);
         NULL != entry;
         entry = TocNextEntry(toc,entry) )
   {
       char   sectNumStr[MAXSECTS * 4 + 5];	/* $SECTNUM */
       char * title = NULL;
       wchar_t * wctitle;
       int    lhsWidth;
       int    titlelen;
       int    fillerChar;
       int    blanksCnt;

       /* get the data to print */
       SectNumStr(entry->sectNums,entry->sectStr,sectNumStr);
       _DtHelpCeGetTopicTitle(state->canvasHandle, state->volHandle,
                              entry->locationId, &title);
       if (NULL == title) title = EMPTY_STR;

       /* set max length of title and calc num blanks needed */
       lhsWidth = strlen(sectNumStr) + 1;   /* 1=blank after sect num */
       titlelen = strlen(title);
       wctitle = malloc(sizeof(wchar_t) * (titlelen+1));
       /* truncate the title using wchar_t */
       if(wctitle)
       {
          /* convert title to wchar_t and get num chars */
          mbstowcs(wctitle,title,titlelen+1);
          titlelen = wcslen(wctitle);
          /* truncate the title to fit on the line */
          if (titlelen > options->colsTextWidth - lhsWidth)
          {
             wctitle[options->colsTextWidth - lhsWidth - 1] = EOS;
             titlelen = wcslen(wctitle);
          }
          wcstombs(title,wctitle,titlelen+1);
          free(wctitle);
       }
       /* truncate the title assuming single-byte */
       else if (titlelen > options->colsTextWidth - lhsWidth)
       { 
          title[options->colsTextWidth - lhsWidth - 1] = EOS;
          titlelen = strlen(title);
       }
       lhsWidth += titlelen;

       /* add a blank line before a major section */
       if ( 0 == entry->sectNums[2] )
       {
          fprintf(fp,"\n");
          state->curLineNumber++;

          /* do we need a new page to start this section? */
          if ( (availLines - state->curLineNumber) < 3 )
          {
              PrintFooter(options,fp,state,&options->tocHF,&state->tocHFF);
              NewPage(options,fp,state,True);
              PrintHeader(options,fp,state,&options->tocHF,&state->tocHFF);
              /* calc number of available lines from top of page to footer */
              availLines = AvailContentLines(options,state,&state->tocHFF);
          }
       }

       /* output the beginning of the line */
       if ( sectNumStr[0] == EOS )      /* no section number */
       {
          /* space between title and page num */
          /* 1: no sect num so no space betw sect num & title */
          /* -3: reserve 3 spaces for the page number */
          blanksCnt = ((options->colsTextWidth - lhsWidth) - 3) + 1;

          {
              char *_p;
              int ret;

              ret = IconvBuffer( state->volHandle, title, &_p );
              if ( ret ) {
                  fprintf(fp,"%*c%s", options->colsAdjLeftMargin, ' ', _p );
              } else {
                  fprintf(fp,"%*c%s", options->colsAdjLeftMargin, ' ', title);
              }
          }
       }

       else     /* valid section number */
       {
          /* space between title and page num */
          /* -3: reserve 3 spaces for the page number */
          blanksCnt = (options->colsTextWidth - lhsWidth) - 3;

          {
              char *_p;
              int ret;

              ret = IconvBuffer( state->volHandle, title, &_p );
              if ( ret ) {
                fprintf(fp,"%*c%s %s",
                      options->colsAdjLeftMargin, ' ', sectNumStr, _p );
              } else {
                fprintf(fp,"%*c%s %s",
                      options->colsAdjLeftMargin, ' ', sectNumStr, title);
              }
          }
       }

       /* output the filler and page number */
       if ( (blanksCnt % 2) != 0) { fputc(' ',fp); blanksCnt--; }
       for( ;blanksCnt > 0; blanksCnt -= 2) fprintf(fp," .");
       fprintf(fp,"%3d\n", entry->pageNumber);

       state->curLineNumber++;
 
       /* if at the end of a page, print footer, eject, and print header */
       if (state->curLineNumber >= availLines)
       {
           PrintFooter(options,fp,state,&options->tocHF,&state->tocHFF);
           NewPage(options,fp,state,True);
           PrintHeader(options,fp,state,&options->tocHF,&state->tocHFF);
           /* calc number of available lines from top of page to footer */
           availLines = AvailContentLines(options,state,&state->tocHFF);
       }
   }  /* for all entries */

   /* output footer */
   PrintFooter(options,fp,state,&options->tocHF,&state->tocHFF);

   if (fp) fclose(fp);

   return 0;
}       /*$END$*/


#if DOC
===================================================================
$PFUNBEG$:  ProcessTopics()
$1LINER$:  Process the help volume to generate a topics file
$DESCRIPT$:
Process the TOC data to generate a toc file
$RETURNS$:
 0:  success
-1:  could not create or open a temp file
-2:  could not get help topic info
$ARGS$:
outputFromLocId:  output of topics occurs for this topic and all its
                  subtopics.  This topic Id is strcasecmp()ed against the
                  current Id to determine a match.  If NULL, the volume
                  top topic is used.
inhibitTopicsOutput:    if true, output only starts when outputFromLocId is found

outputFromLocId  inhibitOutput   action
---------------  -------------   -------------------------------------
NULL              True            no output is generated
NULL              False           output begins with the top topic
!= NULL           True            output begins with outputFromLocId
                                    and stops after last subtopic of
                                    outputFromLocId
!= NULL           False           output begins with the top topic
                                    and stops after last subtopic of
                                    outputFromLocId
========================================================$SKIP$=====*/
#endif /*DOC*/

static
int ProcessTopics(
  _DtHPrOptions * options,
  char * *        ret_resultsFile,
   PrintState *   state,
   Toc *          toc,
  Boolean         processSubTopics,
  char *          outputFromLocId,
  Boolean         inhibitTopicsOutput)
{       /*$CODE$*/
   FILE *  fp;
   int     ret;

   /* don't print a single topic with no locId */
   if (    processSubTopics == False
        && (outputFromLocId == NULL || *outputFromLocId == EOS) )
       return 0;                                /* RETURN: ok */

   /* if no output is desired, set ptr to NULL */
   if ( NULL == outputFromLocId && inhibitTopicsOutput )
       fp = NULL;
   else
   {  /* output is desired...open file */
      fp = OpenTmpFile(options,ret_resultsFile);
      if (NULL == fp) return -1;               /* RETURN: error */
   }

   /*** process text ***/

   /* if processing subtopics, start processing at the top */
   if ( processSubTopics )
   {
      int    offset;

      /* get the top topic of the volume */
      ret = _DtHelpCeGetTopTopicId(state->volHandle, &state->currentLocId);
      if (ret != True) state->currentLocId = strdup("_HOMETOPIC");
   }
   else
   {   /* otherwise, process only where needed */
       state->currentLocId = strdup(outputFromLocId);
   }

   /* set the other state values */
   if (   NULL != outputFromLocId
       && (ret = _DtHelpCeIsTopTopic(state->volHandle,outputFromLocId)) != 0 )
      state->outputFromLocId = outputFromLocId;
   else
      state->outputFromLocId = state->currentLocId;
   state->inhibitOutput = inhibitTopicsOutput;
   state->curPageNumber = 1;
   state->curLineNumber = 1;

   /* output topics but don't inhibit */
   if ( processSubTopics )
   {
      state->sectNums[0] = 1;      /* support section num printing */
      /* 0: level of the top topic; note tha this is diff from the
         level of the top topic in an SDL volume, which is 1.  This
         is merely for convenience, to make addressing into the
         sectNums array directly rather than subtracting 1 or 2 to
         get the array index from the level. */
      ret = ProcessSubTopics(options,fp,toc,0,state);
   }
   else
   {
      /* start new page but don't eject; output header;
         output right number of blank lines to get to where topic starts */

      state->sectNums[0] = 0;      /* inhibit section num printing */
      NewPage(options,fp,state,False);        /* for top margin */
      PrintHeader(options,fp,state,&options->bodyHF,&state->bodyHFF);

      ret = ProcessOneTopic(options,fp,state,True);

      /* output footer */
      PrintFooter(options,fp,state,&options->bodyHF,&state->bodyHFF);
   }

   /* if the file was opened, close & check if anything written to it */
   /* if empty, pretend the file wasn't created. */
   if (fp)
   {
      Boolean empty = False;
      long int pos;
      pos = ftell(fp);
      if (pos <= 0) empty = True;
      fclose(fp);
      if (empty)
      {
         unlink(*ret_resultsFile);
         free(*ret_resultsFile);
         *ret_resultsFile = NULL;
      }
   }

   return ret;
}       /*$END$*/


#if DOC
===================================================================
$PFUNBEG$:  MakePageBreakFile()
$1LINER$:  Creates a file with only a page break char in it
$DESCRIPT$:
Creates a file with only a page break char in it.
If fails, file ptr is NULL
$RETURNS$:
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/

static
void MakePageBreakFile(
  _DtHPrOptions * options,
  char * *        ret_resultsFile)
{       /*$CODE$*/
   FILE * fp;

   fp = OpenTmpFile(options,ret_resultsFile);
   if (NULL == fp) 
   { 
      free(*ret_resultsFile); 
      *ret_resultsFile = NULL; 
      return; 
   }
   fprintf(fp,"\n");
   fclose(fp);
}       /*$END$*/


#if DOC
===================================================================
$PFUNBEG$:  DoHelpTopicsProcessing()
$1LINER$:  Passes through entire volume and generates toc and topic output
$DESCRIPT$:
Passes through entire volume and generates toc and topic output
$RETURNS$:
 0:  success
-1:  could not create or open a temp file
-2:  could not get help topic info
-3:  could not alloc memory
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/

static
int DoHelpTopicsProcessing(
  _DtHPrOptions * options,
  char * *        ret_resultsFile, 
  Boolean         processToc,
  Boolean         processIndex,
  Boolean         processFrontMatter,
  Boolean         processSubTopics,
  char *          outputFromLocId,
  Boolean         inhibitTopicsOutput)
{       /*$CODE$*/
#define FM     0
#define TOC    1
#define TOPICS 2
#define INDEX  3
#define NUMPARTS  4
   
   PrintState  state;
   Toc         toc;
   int         ret = 0;
   int         i;
   char *      buf;
   char *      start;
   char *      next;
   char *      pgbrkFile = NULL;
   char *      partFiles[NUMPARTS];
   Boolean     validFile = False;

   /* init larger vars to 0 */
   memset(&toc,0,sizeof(Toc));
   memset(&state,0,sizeof(state));
   memset(partFiles,0,sizeof(partFiles));

   /* create terminal-based canvas for text retrieval */
   ret = _DtHelpTermCreateCanvas(options->colsTextWidth,&state.canvasHandle);
   if ( ret != 0 ) return ret;             /* RETURN: error */

   /* open volume and get state.volHandle */
   ret = _DtHelpCeOpenVolume(state.canvasHandle, 
                             options->helpVolume, &state.volHandle);
   if ( ret != 0 ) return ret;             /* RETURN: error */

   /* do some prepatory format string generation */
   GenAllHeadFootFormatStrs(options,&state);

   /* after this point, if we get an error, we don't return--we do the best we can */
   /* so don't monitor the return values. */

   /*** process a help volume; maybe create a topics file ***/
   /* always do this, as all other processing (except FM) needs the data */
   ret=ProcessTopics(options,&partFiles[TOPICS],&state,&toc,
               processSubTopics,outputFromLocId,inhibitTopicsOutput);

   /*** process toc to create an index and add an index entry to toc ***/
   /* Do this right after processing topics so that page number info is right */
   if (processIndex)
      ret=ProcessIndex(options,&partFiles[INDEX],&state,&toc);

   /*** process volume to create front matter ***/
   if (processFrontMatter)
      ret=ProcessFrontMatter(options,&partFiles[FM],&state);

   /*** process toc to create a toc; do this last to get all entries ***/
   if (processToc)
      ret=ProcessToc(options,&partFiles[TOC],&state,&toc);

   /*** create the final output file ***/

   MakePageBreakFile(options,&pgbrkFile);

   /* create a temporary file in $HOME/.dt/tmp to hold everything */
   /* Put in $HOME/.dt/tmp so that if the printer operation is running
      of a remote system, it can get to the (local) temp file. 
      This would not be possible if the file were put in /tmp */
   *ret_resultsFile = _DtHPrCreateTmpFile(TMPFILE_PREFIX,TMPFILE_SUFFIX);
   if (NULL == *ret_resultsFile) { ret = -1; goto cleanup; } /* RETURN: error */

   buf = malloc(3000 * sizeof(char));
   if (NULL == buf) { ret = -3; goto cleanup; }      /* RETURN: error */

   /* cat together the part files that got created in the array order */
   /* separate them with a file containing only a page break */
   strcpy(buf,"cat ");
   start = buf + strlen(buf);
   next = start;
   for (i=0; i<NUMPARTS; i++)
   {
     if (partFiles[i]) 
     { 
        /* if at least one file in cat list, separate from it with a pgbrk */
        if (start != next && pgbrkFile)
        {
           sprintf(next,"%s ", pgbrkFile);
           next += strlen(next);
        }
        sprintf(next,"%s ", partFiles[i]);
        next += strlen(next);
        validFile = True;
     }
   }
   /* only do the operation if there are valid files */
   if (validFile)
   {
      int rv;
      sprintf(next,"> %s", *ret_resultsFile);
      if(options->debugHelpPrint) printf("%s\n",buf);
      rv = system(buf);
   }
   free(buf);
   ret = 0;

   /* if needed, iconv file and change filenames */

   /* close the volume when done */
   _DtHelpCeCloseVolume(state.canvasHandle,state.volHandle);

   /*** cleanup ***/
cleanup:
   /* delete all part files */
   for (i=0; i<NUMPARTS; i++)
     if (partFiles[i]) 
     { 
        unlink(partFiles[i]); 
        free(partFiles[i]);
     }

   if(pgbrkFile) { 
     unlink(pgbrkFile);
   }

   /* NOTE: should free Toc here if interested in no leaks */

   return ret;
}       /*$END$*/


#if DOC
===================================================================
$PFUNBEG$:  GenerateTopicsFile()
$1LINER$:  Recovers and formats help topic text and then sends to printer
$DESCRIPT$:
Recovers and formats help topic text and then sends to printer
$RETURNS$:
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/

static
int GenerateTopicsFile(
  Display *       dpy,
  _DtHPrOptions * options,
  char * *        ret_resultsFile)
{       /*$CODE$*/
   int ret = 0;
   Boolean allTopics = (options->allTopics != NULL);
   Boolean subTopics = (options->subTopics != NULL);
   Boolean toc = (options->toc != NULL);
   Boolean index = (options->index != NULL);
   Boolean frontMatter = (options->frontMatter != NULL);
   Boolean oneTopic = (options->oneTopic != NULL);
   Boolean dfltOneTopic;
   char *  locationId = options->locationId;

   /* one topic is selected if other options aren't selected */
   dfltOneTopic = !(allTopics || subTopics || toc || index);

   /* This is sort of a kludge, but since we don't require the -One flag,
      this keeps the _Hometopic from being printed along with 
      the front matter, when only front matter was specified */
   if (frontMatter && !oneTopic && dfltOneTopic)
      locationId = NULL;   /* prevent printing the hometopic */

   /* to generate a toc or index, we need to process all topics; if
      processing all or subtopics wasn't requested, request it;
      and set a locationId that will never match (an empty string) */
   if ( (toc || index) && !allTopics ) 
   { 
      if (!subTopics) locationId = ""; 
      subTopics = True;
   }

   /* check the flags and do right thing */
   if (oneTopic || dfltOneTopic)
      /* False: no subtopics--output only locationId; False: don't inhibit output */
      ret = DoHelpTopicsProcessing(
                   options, ret_resultsFile, 
                   False, False, frontMatter,
                   False, locationId, False);
   else if (allTopics)
      /* start at root and don't inhibit output */
      ret = DoHelpTopicsProcessing(
                   options, ret_resultsFile, 
                   True, True, True,
                   True, NULL, False);
   else
      ret = DoHelpTopicsProcessing(
                   options, ret_resultsFile, 
                   toc, index, frontMatter,
                   subTopics, locationId,True);

   return ret;
}       /*$END$*/



#if DOC
===================================================================
$FUNBEG$:  _DtHPrPrintHelpTopic()
$1LINER$:  Recovers and formats help topic text and then sends to printer
$DESCRIPT$:
Recovers and formats help topic text and then sends to printer
$RETURNS$:
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/

int _DtHPrPrintHelpTopic(
   Display *      dpy,
  _DtHPrOptions * options)
{       /*$CODE$*/
   char * printCommand;
   char   cmdFormat[100];
   char   prOffsetArg[30];
   char * tmpFile;
   int    status;
   char * path;


    if ( NULL == options->helpVolume )
    {
         fprintf(stderr, _DTGETMESSAGE(PTSET,1,
                             "%s: Error: helpType is topic, "
                             "but no helpVolume specified.\n"), 
                             options->programName);

         return 1;                      /* RETURN error */
    }

   /* try to locate the volume */
   path = _DtHelpFileLocate(DtHelpVOLUME_TYPE, options->helpVolume,
                               _DtHelpFileSuffixList,False,R_OK);
   if (path == NULL)
   {
         fprintf(stderr, _DTGETMESSAGE(PTSET,2,
                             "%s Error: unable to locate help volume '%s'\n"),
                             options->programName, options->helpVolume);

         return 1;                      /* RETURN error */
   }
   options->helpVolume = path;  /* don't free old helpVolume: owned by Xrm */

   /* generate a file containing the help topic */
   status = GenerateTopicsFile(dpy,options,&tmpFile);
   if (status != 0)
   {
         fprintf(stderr, _DTGETMESSAGE(PTSET,3,
                             "%s Error: problem processing help volume '%s'\n"),
                             options->programName, options->helpVolume);

         return 1;                      /* RETURN error */
   }

   /* Alloc max shell command line len */
   printCommand = malloc(MAX_COMMAND_LENGTH*sizeof(char));
   if (printCommand == NULL)
   {
         fprintf(stderr, _DTGETMESSAGE(PTSET,4,
                             "%s Error: memory allocation failed\n"),
                             options->programName);

         return 1;                      /* RETURN error */
   }
   

   /** generate the file **/
   sprintf(printCommand,"cat %s",tmpFile);

   status = _DtHPrGenFileOrPrint(options,"Help Topic(s)",printCommand);

   unlink(tmpFile);

   return(status);
} /*$END$*/



#if 0  /* This is left-over code that used to deal with printing
          raster images.  We don't need it for printing text-only.
	  I left it here for future reference. */
#if DOC
===================================================================
$FUNBEG$:  _DtHPrPrintHelpTopicWithXvp()
$1LINER$:  Recovers and formats help topic text and then sends to printer
$DESCRIPT$:
Recovers and formats help topic text and then sends to printer
$STATUS$:  
Code left over from VUE 3.0 help print
Has not been updated.
$RETURNS$:
$ARGS$:
========================================================$SKIP$=====*/
#endif /*DOC*/

int _DtHPrPrintHelpTopicWithXvp(
   Display *    dpy,
   VolumeHandle volHandle,
   char *       locationId)
{       /*$CODE$*/
  extern _DtHelpDisplayAreaStruct *Spoof();
  GC             gc;
  GC             pageNumberGC;
  int            screen;
  Window         main_window;
  Display       *dpy;
  XFontStruct   *defaultFont;
  XFontStruct   *pageNumberFont;
  _DtHelpDisplayAreaStruct *pDAS;
  char *lpDocName;

#ifdef RASTER_PRINT
   char         *displayNameX;
   Display      *dpyX;
   Window       main_windowX;
   GC           gcX;
   GC           invert_gcX;
   int          widthX;
   int          heightX;
   XFontStruct  *defaultFontX;
#endif

#define MY_WIDTH        400
#define MY_HEIGHT       400
#define DEFAULT_FONT "-agfa-courier-normal-r-normal-93950-0-120-300-300-m-0-hp-roman8"

#ifdef RASTER_PRINT
   /* set color to bitonal */
   value.addr = RN_bitonal;
   value.size = strlen(RN_BITONAL) + 1;
   XrmPutResource(&XvpDB, STAR_RN_helpColorUse, *str_type, &value);
#endif

#ifdef RASTER_PRINT
   /********************/
   /* Get display name */
   /********************/

   strcpy(resource_name, name_prefix);
   strcat(resource_name, RN_display);
   strcpy(resource_class, class_prefix);
   strcat(resource_class, RC_display);
   if (XrmGetResource(appDB, resource_name,
                      resource_class,
                      str_type, &value) == True)
      displayNameX = value.addr;
   else displayNameX = EMPTY_STR;
#endif

   strcpy(resource_name, name_prefix);
   strcat(resource_name, RN_jobTitle);
   strcpy(resource_class, class_prefix);
   strcat(resource_class, RC_jobTitle);
   if (XrmGetResource(appDB, resource_name, 
		      resource_class, 
		      str_type, &value) == True)
      lpDocName = (char *)value.addr;
   else
      lpDocName = DFLT_JOB_TITLE_STR;
   
   status = XvpEscape(dpy, STARTDOC, strlen(lpDocName), lpDocName, NULL);
   screen = DefaultScreen(dpy);
   
  /*	Calculate application page size		*/

   _DtHPrCalculatePageSize(dpy, &x, &y, &width, &height);

   /* create a window; x, y, width, and height are recorded */
   main_window = XCreateSimpleWindow(dpy, XRootWindow(dpy, screen),
		x, y,
		width,
		height,
		10, 0, XWhitePixel (dpy, screen));
  		/* 0, 0, MY_WIDTH, MY_HEIGHT, 10, 0, XWhitePixel (dpy, screen)); */

  XSelectInput (dpy, main_window, ExposureMask);
  XMapWindow (dpy, main_window);
  XvpEscape(dpy, SET_IMAGE_RESOLUTION, 4, 100, NULL);

  defaultFont = XLoadQueryFont (dpy, DEFAULT_FONT);
  if (defaultFont == NULL)
    {
	printf ("Unable to get a font\n");
	exit (1);
    }

  gc = DefaultGC(dpy, screen);
  XSetForeground (dpy, gc, XBlackPixel (dpy, screen));
  XSetBackground (dpy, gc, XWhitePixel (dpy, screen));
  XSetFont(dpy, gc, defaultFont->fid);
  XSetLineAttributes(dpy, gc, 2, LineSolid, CapButt, JoinMiter);
 
#ifndef RASTER_PRINT
  pageNumberFont = 
     XLoadQueryFont (dpy, 
	     "-agfa-univers-normal-r-normal-94021-0-100-300-300-p-0-iso8859-1");
  if (pageNumberFont == NULL)
    {
	printf ("Unable to get a font\n");
	exit (1);
    }

  pageNumberGC = XCreateGC(dpy, RootWindow(dpy, screen), NULL, NULL);
  XSetForeground (dpy, pageNumberGC, XBlackPixel (dpy, screen));
  XSetBackground (dpy, pageNumberGC, XWhitePixel (dpy, screen));
  XSetFont(dpy, pageNumberGC, pageNumberFont->fid);
  XSetLineAttributes(dpy, pageNumberGC, 2, LineSolid, CapButt, JoinMiter);
#endif

#ifdef RASTER_PRINT
  widthX = width/3; /* hard coded for 300 dpi printers */
  heightX = height/3;
  status = CreateRealXObjects(displayNameX, widthX, heightX, 
			      &dpyX, &main_windowX, &gcX,
			      &invert_gcX, &defaultFontX);
  dpyX->db = dpy->db;	/* carry over data base */
  
  pDAS = Spoof (dpyX, gcX, invert_gcX, defaultFontX, widthX, heightX, lineLeading);
#else
  pDAS = Spoof (dpy, gc, gc, defaultFont, width, height, lineLeading);
#endif
  if (pDAS == NULL)
  {
     fprintf(stderr, "%s: Internal error.\n");
     exit(1);
  }

  if (helpType == DtHELP_TYPE_TOPIC)
#ifdef RASTER_PRINT
     status = printTopicAndChildrenX(helpVolume, locationId, allTopics, pDAS, 
				    dpy, main_window, gc, 
				    dpyX, main_windowX, gcX,
				    widthX, heightX);
#else
     status = printTopicAndChildren(helpVolume, locationId, allTopics, pDAS, 
				    dpy, main_window, gc, pageNumberGC );
#endif
     /*
     pDAS = (_DtHelpDisplayAreaStruct *) SetUp (pDAS, dpy, main_window, gc, defaultFont,
     width,
     height,
     helpVolume,
     locationId);
     */
     else if (helpType == DtHELP_TYPE_DYNAMIC_STRING)
     {
#ifdef RASTER_PRINT
     XImage	*image;
     
     pDAS = (_DtHelpDisplayAreaStruct *) SetUpDynamicString (pDAS, dpy, gc, 
							 defaultFont,
							 width,
							 height,
							 stringData);
     RenderLinesX (dpyX, main_windowX, pDAS, widthX, heightX, &image);
     XvpPutImage(dpy, main_window, gcX, image, 0, 0, 0, 0, 
		 (image->width)*3, (image->height*3) );
     status = XvpEscape(dpy, NEWFRAME, NULL, NULL, NULL);
     if (status > 0) ;
  else if (status < 0)
     fprintf (stdout, "XvpEscape(NEWFRAME) not implemented.\n");
  else fprintf(stdout, "XvpEscape(NEWFRAME) not successful.\n");
#else
	pDAS = (_DtHelpDisplayAreaStruct *) SetUpDynamicString (pDAS, dpy, gc, 
							    defaultFont,
							    width,
							    height,
							    stringData);
	RenderLines (dpy, main_window, pDAS);
#endif
     }
     else 
     {
	fprintf(stderr, "%s Error: Illegal helpType %d.\n",
		argv[0], helpType);
	exit(1);
     }
  
  if (status == -1)
  {
     fprintf (stdout, "Error occurred in XvpEscape(STARTDOC)\n");
     exit(1);
  }

  status = XvpEscape(dpy, ENDDOC, NULL, NULL, NULL);
  if (status == -1)
  {
     fprintf (stdout, "Error occurred in XvpEscape(ENDDOC)\n");
     exit(1);
  }
  
  exit(0);
} /*$END$*/
#endif  /* #if 0 at function start */
