/*		Character grid hypertext object
**		===============================
*/

#include <HTUtils.h>
#include <HTString.h>
#include <HTFont.h>
#include <HTAccess.h>
#include <HTAnchor.h>
#include <HTParse.h>
#include <HTTP.h>
#include <HTAlert.h>
#include <HTCJK.h>
#include <UCDefs.h>
#include <UCAux.h>

#include <assert.h>
#ifndef VMS
#ifdef SYSLOG_REQUESTED_URLS
#include <syslog.h>
#endif /* SYSLOG_REQUESTED_URLS */
#endif /* !VMS */

#include <GridText.h>
#include <LYCurses.h>
#include <LYUtils.h>
#include <LYStrings.h>
#include <LYStructs.h>
#include <LYGlobalDefs.h>
#include <LYGetFile.h>
#include <LYSignal.h>
#include <LYMail.h>
#include <LYList.h>
#include <LYCharSets.h>
#include <LYCharUtils.h>	/* LYUCTranslateBack... */
#include <UCMap.h>
#ifdef EXP_CHARTRANS_AUTOSWITCH
#include <UCAuto.h>
#endif /* EXP_CHARTRANS_AUTOSWITCH */

#include <LYexit.h>
#include <LYLeaks.h>

#undef DEBUG_APPCH

#ifdef USE_COLOR_STYLE
#include <AttrList.h>
#include <LYHash.h>

unsigned int cached_styles[CACHEH][CACHEW];

#endif

#ifdef USE_COLOR_STYLE_UNUSED
void LynxClearScreenCache NOARGS
{
    int i,j;

    CTRACE(tfp, "flushing cached screen styles\n");
    for (i=0;i<CACHEH;i++)
	for (j=0;j<CACHEW;j++)
	    cached_styles[i][j]=s_a;
}
#endif /* USE_COLOR_STYLE */

struct _HTStream {			/* only know it as object */
    CONST HTStreamClass *       isa;
    /* ... */
};

#define TITLE_LINES  1
#define IS_UTF_EXTRA(ch) (text->T.output_utf8 && \
			  ((unsigned char)(ch)&0xc0) == 0x80)

extern BOOL HTPassHighCtrlRaw;
extern HTkcode kanji_code;
extern HTCJKlang HTCJK;

/*	Exports
*/
PUBLIC HText * HTMainText = NULL;		/* Equivalent of main window */
PUBLIC HTParentAnchor * HTMainAnchor = NULL;	/* Anchor for HTMainText */

PUBLIC char * HTAppName = "Lynx";		/* Application name */
PUBLIC char * HTAppVersion = LYNX_VERSION;	/* Application version */

PUBLIC int HTFormNumber = 0;
PUBLIC int HTFormFields = 0;
PUBLIC char * HTCurSelectGroup = NULL;		/* Form select group name */
PRIVATE int HTCurSelectGroupCharset = -1;	/* ... and name's charset */
PUBLIC int HTCurSelectGroupType = F_RADIO_TYPE;	/* Group type */
PUBLIC char * HTCurSelectGroupSize = NULL;	/* Length of select */
PRIVATE char * HTCurSelectedOptionValue = NULL;	/* Select choice */

PUBLIC char * checked_box = "[X]";
PUBLIC char * unchecked_box = "[ ]";
PUBLIC char * checked_radio = "(*)";
PUBLIC char * unchecked_radio = "( )";

PUBLIC BOOLEAN underline_on = OFF;
PUBLIC BOOLEAN bold_on      = OFF;

#if defined(USE_COLOR_STYLE)
#define MAX_STYLES_ON_LINE 64

typedef struct _stylechange {
	int	horizpos;	/* horizontal position of this change */
	int	style;		/* which style to change to */
	int	direction;	/* on or off */
	int	previous;	/* previous style */
} HTStyleChange;
#endif

typedef struct _line {
	struct _line	*next;
	struct _line	*prev;
	unsigned	offset;		/* Implicit initial spaces */
	unsigned	size;		/* Number of characters */
	BOOL	split_after;		/* Can we split after? */
	BOOL	bullet;			/* Do we bullet? */
#if defined(USE_COLOR_STYLE)
	HTStyleChange	styles[MAX_STYLES_ON_LINE];
	int	numstyles;
#endif
	char	data[1];		/* Space for terminator at least! */
} HTLine;

#define LINE_SIZE(l) (sizeof(HTLine)+(l))	/* Allow for terminator */

typedef struct _TextAnchor {
	struct _TextAnchor *	next;
	int			number;		/* For user interface */
	int			start;		/* Characters */
	int			line_pos;	/* Position in text */
	int			extent; 	/* Characters */
	int			line_num;	/* Place in document */
	char *			hightext;	/* The link text */
	char *			hightext2;	/* A second line*/
	int			hightext2offset;/* offset from left */
	int			link_type;	/* Normal, internal, or form? */
	FormInfo *		input_field;	/* Info for form links */
	BOOL			show_anchor;	/* Show the anchor? */
	BOOL			inUnderline;	/* context is underlined */
	HTChildAnchor *		anchor;
} TextAnchor;

typedef struct _HTTabID {
	char *			name;		/* ID value of TAB */
	int			column;		/* Zero-based column value */
} HTTabID;


/*	Notes on struct _Htext:
**	next_line is valid if state is false.
**	top_of_screen line means the line at the top of the screen
**			or just under the title if there is one.
*/
struct _HText {
	HTParentAnchor *	node_anchor;
	HTLine * 		last_line;
	int			Lines;		/* Number of them */
	int			chars;		/* Number of them */
	TextAnchor *		first_anchor;	/* Singly linked list */
	TextAnchor *		last_anchor;
	HTList *		forms;		/* also linked internally */
	int			last_anchor_number;	/* user number */
	BOOL			source;		/* Is the text source? */
	BOOL			toolbar;	/* Toolbar set? */
	HTList *		tabs;		/* TAB IDs */
	HTList *		hidden_links;	/* Content-less links ... */
	int			hiddenlinkflag; /*  ... and how to treat them */
	BOOL			no_cache;	/* Always refresh? */
	char			LastChar;	/* For absorbing white space */
	BOOL			IgnoreExcess;	/* Ignore chars at wrap point */

/* For Internal use: */
	HTStyle *		style;			/* Current style */
	int			display_on_the_fly;	/* Lines left */
	int			top_of_screen;		/* Line number */
	HTLine *		top_of_screen_line;	/* Top */
	HTLine *		next_line;		/* Bottom + 1 */
	unsigned		permissible_split;	/* in last line */
	BOOL			in_line_1;		/* of paragraph */
	BOOL			stale;			/* Must refresh */
	BOOL			page_has_target; /* has target on screen */

	HTkcode 		kcode;			/* Kanji code? */
	enum grid_state       { S_text, S_esc, S_dollar, S_paren,
				S_nonascii_text, S_dollar_paren,
				S_jisx0201_text }
				state;			/* Escape sequence? */
	int			kanji_buf;		/* Lead multibyte */
	int			in_sjis;		/* SJIS flag */
	int			halted; 		/* emergency halt */

	BOOL			have_8bit_chars;   /* Any non-ASCII chars? */
	LYUCcharset *		UCI;		   /* node_anchor UCInfo */
	int			UCLYhndl;	   /* charset we are fed */
	UCTransParams		T;

	HTStream *		target; 		/* Output stream */
	HTStreamClass		targetClass;		/* Output routines */
};

PRIVATE void HText_AddHiddenLink PARAMS((HText *text, TextAnchor *textanchor));

/*
 *  Boring static variable used for moving cursor across
 */
#define UNDERSCORES(n) \
 ((n) >= MAX_LINE ? underscore_string : &underscore_string[(MAX_LINE-1)] - (n))

/*
 *	Memory leak fixed.
 *	05-29-94 Lynx 2-3-1 Garrett Arch Blythe
 *	Changed to arrays.
 */
PRIVATE char underscore_string[MAX_LINE + 1];
PUBLIC char star_string[MAX_LINE + 1];

PRIVATE int ctrl_chars_on_this_line = 0; /* num of ctrl chars in current line */

PRIVATE HTStyle default_style =
	{ 0,  "(Unstyled)", "",
	(HTFont)0, 1, HT_BLACK,		0, 0,
	0, 0, 0, HT_LEFT,		1, 0,	0,
	NO, NO, 0, 0,			0 };



PRIVATE HTList * loaded_texts = NULL;	 /* A list of all those in memory */
PUBLIC  HTList * search_queries = NULL;  /* isindex and whereis queries   */
PRIVATE void free_all_texts NOARGS;
PRIVATE int HText_TrueLineSize PARAMS((
	HTLine *	line,
	HText *		text,
	BOOL		IgnoreSpaces));

#ifndef VMS			/* VMS has a better way - right? - kw */
#define CHECK_FREE_MEM
#endif

#ifdef CHECK_FREE_MEM

/*
 *  text->halted = 1: have set fake 'Z' and output a message
 *		   2: next time when HText_appendCharacter is called
 *		      it will append *** MEMORY EXHAUSTED ***, then set
 *		      to 3.
 *		   3: normal text output will be suppressed (but not anchors,
 *		      form fields etc.)
 */
PRIVATE void HText_halt NOARGS
{
    if (HTFormNumber > 0)
	HText_DisableCurrentForm();
    if (!HTMainText)
	return;
    if (HTMainText->halted < 2)
	HTMainText->halted = 2;
}

#define MIN_NEEDED_MEM 5000

/*
 *  Check whether factor*min(bytes,MIN_NEEDED_MEM) is available,
 *  or bytes if factor is 0.
 *  MIN_NEEDED_MEM and factor together represent a security margin,
 *  to take account of all the memory allocations where we don't check
 *  and of buffers which may be emptied before HTCheckForInterupt()
 *  is (maybe) called and other things happening, with some chance of
 *  success.
 *  This just tries to malloc() the to-be-checked-for amount of memory,
 *  which might make the situation worse depending how allocation works.
 *  There should be a better way... - kw
 */
PRIVATE BOOL mem_is_avail ARGS2(
    size_t,	factor,
    size_t,	bytes)
{
    void *p;
    if (bytes < MIN_NEEDED_MEM && factor > 0)
	bytes = MIN_NEEDED_MEM;
    if (factor == 0)
	factor = 1;
    p = malloc(factor * bytes);
    if (p) {
	FREE(p);
	return YES;
    } else {
	return NO;
    }
}

/*
 *  Replacement for calloc which checks for "enough" free memory
 *  (with some security margins) and tries various recovery actions
 *  if deemed necessary. - kw
 */
PRIVATE void * LY_check_calloc ARGS2(
    size_t,	nmemb,
    size_t,	size)
{
    int i, n;
    if (mem_is_avail(4, nmemb * size)) {
	return (calloc(nmemb, size));
    }
    n = HTList_count(loaded_texts);
    for (i = n - 1; i > 0; i--) {
	HText * t = HTList_objectAt(loaded_texts, i);
	if (t == HTMainText)
	    t = NULL;		/* shouldn't happen */
	{
	CTRACE(tfp, "\r *** Emergency freeing document %d/%d for '%s'%s!\n",
		    i + 1, n,
		    ((t && t->node_anchor &&
		      t->node_anchor->address) ?
		     t->node_anchor->address : "unknown anchor"),
		    ((t && t->node_anchor &&
		      t->node_anchor->post_data) ?
		     " with POST data" : ""));
	}
	HTList_removeObjectAt(loaded_texts, i);
	HText_free(t);
	if (mem_is_avail(4, nmemb * size)) {
	    return (calloc(nmemb, size));
	}
    }
    LYFakeZap(YES);
    if (!HTMainText || HTMainText->halted <= 1) {
	if (!mem_is_avail(2, nmemb * size)) {
	    HText_halt();
	    if (mem_is_avail(0, 700)) {
		HTAlert("Memory exhausted, display interrupted!");
	    }
	} else {
	    if ((!HTMainText || HTMainText->halted == 0) &&
		mem_is_avail(0, 700)) {
		HTAlert("Memory exhausted, will interrupt transfer!");
		if (HTMainText)
		    HTMainText->halted = 1;
	    }
	}
    }
    return (calloc(nmemb, size));
}

#define LY_CALLOC LY_check_calloc

#else  /* CHECK_FREE_MEM */

  /* using the regular calloc */
#define LY_CALLOC calloc

#endif /* CHECK_FREE_MEM */

PRIVATE void HText_getChartransInfo ARGS1(
	HText *,	me)
{
    me->UCLYhndl = HTAnchor_getUCLYhndl(me->node_anchor, UCT_STAGE_HTEXT);
    if (me->UCLYhndl < 0) {
	int chndl = current_char_set;
	HTAnchor_setUCInfoStage(me->node_anchor, chndl,
				UCT_STAGE_HTEXT, UCT_SETBY_STRUCTURED);
	me->UCLYhndl = HTAnchor_getUCLYhndl(me->node_anchor,
					    UCT_STAGE_HTEXT);
    }
    me->UCI = HTAnchor_getUCInfoStage(me->node_anchor, UCT_STAGE_HTEXT);
}

PRIVATE void PerFormInfo_free ARGS1(
    PerFormInfo *,	form)
{
    if (form) {
	FREE(form->accept_cs);
	FREE(form->thisacceptcs);
	FREE(form);
    }
}

PRIVATE void FormList_delete ARGS1(
    HTList *,		forms)
{
    HTList *cur = forms;
    PerFormInfo *form;
    while ((form = (PerFormInfo *)HTList_nextObject(cur)) != NULL)
	PerFormInfo_free(form);
    HTList_delete(forms);
}

/*			Creation Method
**			---------------
*/
PUBLIC HText *	HText_new ARGS1(
	HTParentAnchor *,	anchor)
{
#if defined(VMS) && defined(VAXC) && !defined(__DECC)
#include <lib$routines.h>
    int status, VMType=3, VMTotal;
#endif /* VMS && VAXC && !__DECC */
    HTLine * line = NULL;
    HText * self = (HText *) calloc(1, sizeof(*self));
    if (!self)
	return self;

#if defined(VMS) && defined (VAXC) && !defined(__DECC)
    status = lib$stat_vm(&VMType, &VMTotal);
    CTRACE(tfp, "GridText: VMTotal = %d\n", VMTotal);
#endif /* VMS && VAXC && !__DECC */

    if (!loaded_texts)	{
	loaded_texts = HTList_new();
	atexit(free_all_texts);
    }

    /*
     *  Links between anchors & documents are a 1-1 relationship. If
     *  an anchor is already linked to a document we didn't call
     *  HTuncache_current_document(), e.g., for the showinfo, options,
     *  download, print, etc., temporary file URLs, so we'll check now
     *  and free it before reloading. - Dick Wesseling (ftu@fi.ruu.nl)
     */
    if (anchor->document) {
	HTList_removeObject(loaded_texts, anchor->document);
	CTRACE(tfp, "GridText: Auto-uncaching\n") ;
	((HText *)anchor->document)->node_anchor = NULL;
	HText_free((HText *)anchor->document);
	anchor->document = NULL;
    }

    HTList_addObject(loaded_texts, self);
#if defined(VMS) && defined(VAXC) && !defined(__DECC)
    while (HTList_count(loaded_texts) > HTCacheSize &&
	   VMTotal > HTVirtualMemorySize)
#else
    if (HTList_count(loaded_texts) > HTCacheSize)
#endif /* VMS && VAXC && !__DECC */
    {
	CTRACE(tfp, "GridText: Freeing off cached doc.\n");
	HText_free((HText *)HTList_removeFirstObject(loaded_texts));
#if defined(VMS) && defined (VAXC) && !defined(__DECC)
	status = lib$stat_vm(&VMType, &VMTotal);
	CTRACE(tfp, "GridText: VMTotal reduced to %d\n", VMTotal);
#endif /* VMS && VAXC && !__DECC */
    }

    line = self->last_line = (HTLine *)calloc(1, LINE_SIZE(MAX_LINE));
    if (line == NULL)
	outofmem(__FILE__, "HText_New");
    line->next = line->prev = line;
    line->offset = line->size = 0;
#ifdef USE_COLOR_STYLE
    line->numstyles = 0;
#endif
    self->Lines = self->chars = 0;
    self->first_anchor = self->last_anchor = NULL;
    self->style = &default_style;
    self->top_of_screen = 0;
    self->node_anchor = anchor;
    self->last_anchor_number = 0;	/* Numbering of them for references */
    self->stale = YES;
    self->toolbar = NO;
    self->tabs = NULL;
    /*
     *  If we are going to render the List Page, always merge in hidden
     *  links to get the numbering consistent if form fields are numbered
     *  and show up as hidden links in the list of links. - kw
     */
    if (anchor->address && !strcmp(anchor->address, LYlist_temp_url()))
	self->hiddenlinkflag = HIDDENLINKS_MERGE;
    else
	self->hiddenlinkflag = LYHiddenLinks;
    self->hidden_links = NULL;
    self->no_cache = ((anchor->no_cache || anchor->post_data) ?
							  YES : NO);
    self->LastChar = '\0';
    self->IgnoreExcess = FALSE;

    if (HTOutputFormat == WWW_SOURCE)
	self->source = YES;
    else
	self->source = NO;
    HTAnchor_setDocument(anchor, (HyperDoc *)self);
    HTFormNumber = 0;  /* no forms started yet */
    HTMainText = self;
    HTMainAnchor = anchor;
    self->display_on_the_fly = 0;
    self->kcode = NOKANJI;
    self->state = S_text;
    self->kanji_buf = '\0';
    self->in_sjis = 0;
    self->have_8bit_chars = NO;
    HText_getChartransInfo(self);
    UCSetTransParams(&self->T,
		     self->UCLYhndl, self->UCI,
		     current_char_set,
		     &LYCharSet_UC[current_char_set]);

    /*
     *  Check the kcode setting if the anchor has a charset element. - FM
     */
    if (anchor->charset)
	HText_setKcode(self, anchor->charset,
		       HTAnchor_getUCInfoStage(anchor, UCT_STAGE_HTEXT));

    /*
     *	Memory leak fixed.
     *  05-29-94 Lynx 2-3-1 Garrett Arch Blythe
     *	Check to see if our underline and star_string need initialization
     *		if the underline is not filled with dots.
     */
    if (underscore_string[0] != '.') {
	/*
	 *  Create an array of dots for the UNDERSCORES macro. - FM
	 */
	memset(underscore_string, '.', (MAX_LINE-1));
	underscore_string[(MAX_LINE-1)] = '\0';
	underscore_string[MAX_LINE] = '\0';
	/*
	 *  Create an array of underscores for the STARS macro. - FM
	 */
	memset(star_string, '_', (MAX_LINE-1));
	star_string[(MAX_LINE-1)] = '\0';
	star_string[MAX_LINE] = '\0';
    }

    underline_on = FALSE; /* reset */
    bold_on = FALSE;

#ifdef DISP_PARTIAL
    /*
     * By this function we create HText object and set new Lines counter
     * so we may start displaying the document while downloading. - LP
     */
    if (display_partial)
	 NumOfLines_partial = 0;  /* enable HTDisplayPartial() */
#endif

    CTRACE(tfp, "GridText: start HText_new\n");

    return self;
}

/*			Creation Method 2
**			---------------
**
**      Stream is assumed open and left open.
*/
PUBLIC HText *  HText_new2 ARGS2(
	HTParentAnchor *,	anchor,
	HTStream *,		stream)

{
    HText * this = HText_new(anchor);

    if (stream) {
	this->target = stream;
	this->targetClass = *stream->isa;	/* copy action procedures */
    }
    return this;
}

/*	Free Entire Text
**	----------------
*/
PUBLIC void HText_free ARGS1(
	HText *,	self)
{
    if (!self)
	return;

    HTAnchor_setDocument(self->node_anchor, (HyperDoc *)0);

    while (YES) {	/* Free off line array */
	HTLine * l = self->last_line;
	if (l) {
	    l->next->prev = l->prev;
	    l->prev->next = l->next;	/* Unlink l */
	    self->last_line = l->prev;
	    if (l != self->last_line) {
		FREE(l);
	    } else {
		free(l);
	    }
	}
	if (l == self->last_line) {	/* empty */
	    l = NULL;
	    break;
	}
    };

    while (self->first_anchor) {		/* Free off anchor array */
	TextAnchor * l = self->first_anchor;
	self->first_anchor = l->next;

	if (l->link_type == INPUT_ANCHOR && l->input_field) {
	    /*
	     *  Free form fields.
	     */
	    if (l->input_field->type == F_OPTION_LIST_TYPE &&
		l->input_field->select_list != NULL) {
		/*
		 *  Free off option lists if present.
		 *  It should always be present for F_OPTION_LIST_TYPE
		 *  unless we had invalid markup which prevented
		 *  HText_setLastOptionValue from finishing its job
		 *  and left the input field in an insane state. - kw
		 */
		OptionType *optptr = l->input_field->select_list;
		OptionType *tmp;
		while (optptr) {
		    tmp = optptr;
		    optptr = tmp->next;
		    FREE(tmp->name);
		    FREE(tmp->cp_submit_value);
		    FREE(tmp);
		}
		l->input_field->select_list = NULL;
		/*
		 *  Don't free the value field on option
		 *  lists since it points to a option value
		 *  same for orig value.
		 */
		l->input_field->value = NULL;
		l->input_field->orig_value = NULL;
		l->input_field->cp_submit_value = NULL;
		l->input_field->orig_submit_value = NULL;
	    } else {
		FREE(l->input_field->value);
		FREE(l->input_field->orig_value);
		FREE(l->input_field->cp_submit_value);
		FREE(l->input_field->orig_submit_value);
	    }
	    FREE(l->input_field->name);
	    FREE(l->input_field->submit_action);
	    FREE(l->input_field->submit_enctype);
	    FREE(l->input_field->submit_title);

	    FREE(l->input_field->accept_cs);

	    FREE(l->input_field);
	}

	FREE(l->hightext);
	FREE(l->hightext2);

	FREE(l);
    }
    FormList_delete(self->forms);

    /*
     *  Free the tabs list. - FM
     */
    if (self->tabs) {
	HTTabID * Tab = NULL;
	HTList * cur = self->tabs;

	while (NULL != (Tab = (HTTabID *)HTList_nextObject(cur))) {
	    FREE(Tab->name);
	    FREE(Tab);
	}
	HTList_delete(self->tabs);
	self->tabs = NULL;
    }

    /*
     *  Free the hidden links list. - FM
     */
    if (self->hidden_links) {
	char * href = NULL;
	HTList * cur = self->hidden_links;

	while (NULL != (href = (char *)HTList_nextObject(cur)))
	    FREE(href);
	HTList_delete(self->hidden_links);
	self->hidden_links = NULL;
    }

    /*
     *  Invoke HTAnchor_delete() to free the node_anchor
     *  if it is not a destination of other links. - FM
     */
    if (self->node_anchor) {
	HTAnchor_resetUCInfoStage(self->node_anchor, -1, UCT_STAGE_STRUCTURED,
				  UCT_SETBY_NONE);
	HTAnchor_resetUCInfoStage(self->node_anchor, -1, UCT_STAGE_HTEXT,
				  UCT_SETBY_NONE);
	if (HTAnchor_delete(self->node_anchor))
	    /*
	     *  Make sure HTMainAnchor won't point
	     *  to an invalid structure. - KW
	     */
	    HTMainAnchor = NULL;
    }

    FREE(self);
}

/*		Display Methods
**		---------------
*/


/*	Output a line
**	-------------
*/
PRIVATE int display_line ARGS2(
	HTLine *,	line,
	HText *,	text)
{
    register int i, j;
    char buffer[7];
    char *data;
    size_t utf_extra = 0;
#ifdef USE_COLOR_STYLE
    int current_style = 0;
#endif
    char LastDisplayChar = ' ';

    /*
     *  Set up the multibyte character buffer,
     *  and clear the line to which we will be
     *  writing.
     */
    buffer[0] = buffer[1] = buffer[2] = '\0';
    clrtoeol();

    /*
     *  Add offset, making sure that we do not
     *  go over the COLS limit on the display.
     */
    j = (int)line->offset;
    if (j > (int)LYcols - 1)
	j = (int)LYcols - 1;
#ifdef USE_SLANG
    SLsmg_forward (j);
    i = j;
#else
#ifdef USE_COLOR_STYLE
    if (line->size == 0)
	i = j;
    else
#endif
    for (i = 0; i < j; i++)
	addch (' ');
#endif /* USE_SLANG */

    /*
     *  Add the data, making sure that we do not
     *  go over the COLS limit on the display.
     */
    data = line->data;
    i++;
    while ((i < LYcols) && ((buffer[0] = *data) != '\0')) {
	data++;

#if defined(USE_COLOR_STYLE) || defined(SLSC)
#define CStyle line->styles[current_style]

	while (current_style < line->numstyles &&
	       i >= (int) (CStyle.horizpos + line->offset + 1))
	{
		LynxChangeStyle (CStyle.style,CStyle.direction,CStyle.previous);
		current_style++;
	}
#endif
	switch (buffer[0]) {

#ifndef USE_COLOR_STYLE
	    case LY_UNDERLINE_START_CHAR:
		if (dump_output_immediately && use_underscore) {
		    addch('_');
		    i++;
		} else {
		    start_underline();
		}
		break;

	    case LY_UNDERLINE_END_CHAR:
		if (dump_output_immediately && use_underscore) {
		    addch('_');
		    i++;
		} else {
		    stop_underline();
		}
		break;

	    case LY_BOLD_START_CHAR:
		start_bold();
		break;

	    case LY_BOLD_END_CHAR:
		stop_bold ();
		break;

#endif
	    case LY_SOFT_NEWLINE:
		if (!dump_output_immediately)
		    addch('+');
		break;

	    case LY_SOFT_HYPHEN:
		if (*data != '\0' ||
		    isspace((unsigned char)LastDisplayChar) ||
		    LastDisplayChar == '-') {
		    /*
		     *  Ignore the soft hyphen if it is not the last
		     *  character in the line.  Also ignore it if it
		     *  first character following the margin, or if it
		     *  is preceded by a white character (we loaded 'M'
		     *  into LastDisplayChar if it was a multibyte
		     *  character) or hyphen, though it should have
		     *  been excluded by HText_appendCharacter() or by
		     *  split_line() in those cases. - FM
		     */
		    break;
		} else {
		    /*
		     *  Make it a hard hyphen and fall through. - FM
		     */
		    buffer[0] = '-';
		    i++;
		}

	    default:
		i++;
		if (text->T.output_utf8 && !isascii(buffer[0])) {
		    if ((*buffer & 0xe0) == 0xc0) {
			utf_extra = 1;
		    } else if ((*buffer & 0xf0) == 0xe0) {
			utf_extra = 2;
		    } else if ((*buffer & 0xf8) == 0xf0) {
			utf_extra = 3;
		    } else if ((*buffer & 0xfc) == 0xf8) {
			utf_extra = 4;
		    } else if ((*buffer & 0xfe) == 0xfc) {
			utf_extra = 5;
		    } else {
			 /*
			  *  Garbage.
			  */
			utf_extra = 0;
		    }
		    if (strlen(data) < utf_extra) {
			/*
			 *  Shouldn't happen.
			 */
			utf_extra = 0;
		    }
		    LastDisplayChar = 'M';
		}
		if (utf_extra) {
		    strncpy(&buffer[1], data, utf_extra);
		    buffer[utf_extra+1] = '\0';
		    addstr(buffer);
		    buffer[1] = '\0';
		    data += utf_extra;
		    utf_extra = 0;
		} else if (HTCJK != NOCJK && !isascii(buffer[0])) {
		    /*
		     *  For CJK strings, by Masanobu Kimura.
		     */
		    buffer[1] = *data;
		    data++;
		    addstr(buffer);
		    buffer[1] = '\0';
		    /*
		     *  For now, load 'M' into LastDisplayChar,
		     *  but we should check whether it's white
		     *  and if so, use ' '.  I don't know if
		     *  there actually are white CJK characters,
		     *  and we're loading ' ' for multibyte
		     *  spacing characters in this code set,
		     *  but this will become an issue when
		     *  the development code set's multibyte
		     *  character handling is used. - FM
		     */
		    LastDisplayChar = 'M';
		} else {
		    addstr(buffer);
		    LastDisplayChar = buffer[0];
		}
	} /* end of switch */
    } /* end of while */

    /*
     *  Add the return.
     */
    addch('\n');

#ifndef USE_COLOR_STYLE
    stop_underline();
    stop_bold();
#else
    while (current_style < line->numstyles)
    {
	LynxChangeStyle (CStyle.style, CStyle.direction, CStyle.previous);
	current_style++;
    }
#undef CStyle
#endif
    return(0);
}

/*	Output the title line
**	---------------------
*/
PRIVATE void display_title ARGS1(
	HText *,	text)
{
    char *title = NULL;
    char percent[20];
    char *cp = NULL;
    unsigned char *tmp = NULL;
    int i = 0, j = 0;

    /*
     *  Make sure we have a text structure. - FM
     */
    if (!text)
	return;

    lynx_start_title_color ();
#ifdef USE_COLOR_STYLE
/* turn the TITLE style on */
    LynxChangeStyle(s_title, ABS_ON, 0);
#endif /* USE_COLOR_STYLE */

    /*
     *  Load the title field. - FM
     */
    StrAllocCopy(title,
		 (HTAnchor_title(text->node_anchor) ?
		  HTAnchor_title(text->node_anchor) : ""));

    /*
     *  There shouldn't be any \n in the title field,
     *  but if there is, lets kill it now.  Also trim
     *  any trailing spaces. - FM
     */
    if ((cp = strchr(title,'\n')) != NULL)
	*cp = '\0';
    i = (*title ? (strlen(title) - 1) : 0);
    while ((i >= 0) && title[i] == ' ')
	title[i--] = '\0';

    /*
     *  Generate the page indicator (percent) string.
     */
    if ((text->Lines + 1) > (display_lines)) {
	/*
	 *  In a small attempt to correct the number of pages counted....
	 *    GAB 07-14-94
	 *
	 *  In a bigger attempt (hope it holds up 8-)....
	 *    FM 02-08-95
	 */
	int total_pages =
		(((text->Lines + 1) + (display_lines - 1))/(display_lines));
	int start_of_last_page =
		((text->Lines + 1) < display_lines) ? 0 :
		((text->Lines + 1) - display_lines);

	sprintf(percent, " (p%d of %d)",
		((text->top_of_screen >= start_of_last_page) ?
						 total_pages :
		    ((text->top_of_screen + display_lines)/(display_lines))),
		total_pages);
    } else {
	percent[0] = '\0';	/* Null string */
    }

    /*
     *  Generate and display the title string, with page indicator
     *  if appropriate, preceded by the toolbar token if appropriate,
     *  and truncated if necessary. - FM & KW
     */
    if (HTCJK != NOCJK) {
	if (*title &&
	    (tmp = (unsigned char *)calloc(1, (strlen(title) + 1)))) {
	    if (kanji_code == EUC) {
		TO_EUC((unsigned char *)title, tmp);
	    } else if (kanji_code == SJIS) {
		TO_SJIS((unsigned char *)title, tmp);
	    } else {
		for (i = 0, j = 0; title[i]; i++) {
		    if (title[i] != '\033') {
			tmp[j++] = title[i];
		    }
		}
		tmp[j] = '\0';
	    }
	    StrAllocCopy(title, (CONST char *)tmp);
	    FREE(tmp);
	}
    }
    move(0, 0);
    clrtoeol();
    if (text->top_of_screen > 0 && HText_hasToolbar(text)) {
	addch('#');
    }
    i = (LYcols - 1) - strlen(percent) - strlen(title);
    if (i > 0) {
	move(0, i);
    } else {
	/*
	 *  Note that this truncation is not taking into
	 *  account the possibility that multibyte
	 *  characters might be present. - FM
	 */
	title[((LYcols - 2) - strlen(percent))] = '\0';
	move(0, 1);
    }
    addstr(title);
    if (percent[0] != '\0')
	addstr(percent);
    addch('\n');
    FREE(title);

#ifdef USE_COLOR_STYLE
/* turn the TITLE style off */
    LynxChangeStyle(s_title, ABS_OFF, 0);
#endif /* USE_COLOR_STYLE */
    lynx_stop_title_color ();

    return;
}

/*	Output a page
**	-------------
*/
PRIVATE void display_page ARGS3(
	HText *,	text,
	int,		line_number,
	char *,		target)
{
    HTLine * line = NULL;
    int i;
    char *cp, tmp[7];
    int last_screen;
    TextAnchor *Anchor_ptr = NULL;
    FormInfo *FormInfo_ptr;
    BOOL display_flag = FALSE;
    HTAnchor *link_dest;
    HTAnchor *link_dest_intl = NULL;
    static int last_nlinks = 0;
    static int charset_last_displayed = -1;

    lynx_mode = NORMAL_LYNX_MODE;

    if (text == NULL) {
	/*
	 *  Check whether to force a screen clear to enable scrollback,
	 *  or as a hack to fix a reverse clear screen problem for some
	 *  curses packages. - shf@access.digex.net & seldon@eskimo.com
	 */
	if (enable_scrollback) {
	    addch('*');
	    refresh();
	    clear();
	}
	addstr("\n\nError accessing document!\nNo data available!\n");
	refresh();
	nlinks = 0;  /* set number of links to 0 */
	return;
    }

    tmp[0] = tmp[1] = tmp[2] = '\0';
    text->page_has_target = NO;
    last_screen = text->Lines - (display_lines - 2);
    line = text->last_line->prev;

    /*
     *  Constrain the line number to be within the document.
     */
    if (text->Lines < (display_lines))
	line_number = 0;
    else if (line_number > text->Lines)
	line_number = last_screen;
    else if (line_number < 0)
	line_number = 0;

    for (i = 0, line = text->last_line->next;		/* Find line */
	 i < line_number && (line != text->last_line);
	 i++, line = line->next) {			/* Loop */
#ifndef VMS
	if (!LYNoCore) {
	    assert(line->next != NULL);
	} else if (line->next == NULL) {
	    if (enable_scrollback) {
		addch('*');
		refresh();
		clear();
	    }
	    addstr("\n\nError drawing page!\nBad HText structure!\n");
	    refresh();
	    nlinks = 0;  /* set number of links to 0 */
	    return;
	}
#else
	assert(line->next != NULL);
#endif /* !VMS */
    } /* Loop */

    if (LYlowest_eightbit[current_char_set] <= 255 &&
	(current_char_set != charset_last_displayed) &&
	/*
	 *  current_char_set has changed since last invocation,
	 *  and it's not just 7-bit.
	 *  Also we don't want to do this for -dump and -source etc.
	 */
	LYCursesON) {
	charset_last_displayed = current_char_set;
#ifdef EXP_CHARTRANS_AUTOSWITCH
#ifdef LINUX
	/*
	 *  Currently implemented only for LINUX
	 */
	stop_curses();
	UCChangeTerminalCodepage(current_char_set,
				 &LYCharSet_UC[current_char_set]);
	start_curses();
#endif /* LINUX */
#endif /* EXP_CHARTRANS_AUTOSWITCH */
    }

    /*
     *  Check whether to force a screen clear to enable scrollback,
     *  or as a hack to fix a reverse clear screen problem for some
     *  curses packages. - shf@access.digex.net & seldon@eskimo.com
     */
    if (enable_scrollback) {
	addch('*');
	refresh();
	clear();
    }

    text->top_of_screen = line_number;
    display_title(text);  /* will move cursor to top of screen */
    display_flag=TRUE;

    /*
     *  Output the page.
     */
    if (line) {
	char *data;
	int offset, HitOffset, LenNeeded;
	for (i = 0; i < (display_lines); i++)  {
	    /*
	     *  Verify and display each line.
	     */
#ifndef VMS
	    if (!LYNoCore) {
		assert(line != NULL);
	    } else if (line == NULL) {
		if (enable_scrollback) {
		    addch('*');
		    refresh();
		    clear();
		}
		addstr("\n\nError drawing page!\nBad HText structure!\n");
		refresh();
		nlinks = 0;  /* set number of links to 0 */
		return;
	    }
#else
	    assert(line != NULL);
#endif /* !VMS */
	    display_line(line, text);

#if defined(FANCY_CURSES) || defined(USE_SLANG)
	    /*
	     *  If the target is on this line, recursively
	     *  seek and emphasize it. - FM
	     */
	    data = (char *)line->data;
	    offset = (int)line->offset;
	    while ((target && *target) &&
		   (case_sensitive ?
		    (cp = LYno_attr_mbcs_strstr(data,
						target,
						text->T.output_utf8,
						&HitOffset,
						&LenNeeded)) != NULL :
		    (cp = LYno_attr_mbcs_case_strstr(data,
						     target,
						text->T.output_utf8,
						&HitOffset,
						&LenNeeded)) != NULL) &&
		   ((int)line->offset + LenNeeded) < LYcols) {
		int itmp = 0;
		int written = 0;
		int x_pos = offset + (int)(cp - data);
		int len = strlen(target);
		size_t utf_extra = 0;
		int y;

		text->page_has_target = YES;

		/*
		 *  Start the emphasis.
		 */
		LYstartTargetEmphasis();

		/*
		 *  Output the target characters.
		 */
		for (;
		     written < len && (tmp[0] = data[itmp]) != '\0';
		     itmp++)  {
		    if (IsSpecialAttrChar(tmp[0])) {
			/*
			 *  Ignore special characters.
			 */
			x_pos--;

		    } else if (cp == &data[itmp]) {
			/*
			 *  First printable character of target.
			 */
			move((i + 1), x_pos);
			if (text->T.output_utf8 && !isascii(tmp[0])) {
			    if ((*tmp & 0xe0) == 0xc0) {
				utf_extra = 1;
			    } else if ((*tmp & 0xf0) == 0xe0) {
				utf_extra = 2;
			    } else if ((*tmp & 0xf8) == 0xf0) {
				utf_extra = 3;
			    } else if ((*tmp & 0xfc) == 0xf8) {
				utf_extra = 4;
			    } else if ((*tmp & 0xfe) == 0xfc) {
				utf_extra = 5;
			    } else {
				/*
				 *  Garbage.
				 */
				utf_extra = 0;
			    }
			    if (strlen(&line->data[itmp+1]) < utf_extra) {
				/*
				 *  Shouldn't happen.
				 */
				utf_extra = 0;
			    }
			}
			if (utf_extra) {
			    strncpy(&tmp[1], &line->data[itmp+1], utf_extra);
			    tmp[utf_extra+1] = '\0';
			    itmp += utf_extra;
			    addstr(tmp);
			    tmp[1] = '\0';
			    written += (utf_extra + 1);
			    utf_extra = 0;
			} else if (HTCJK != NOCJK && !isascii(tmp[0])) {
			    /*
			     *  For CJK strings, by Masanobu Kimura.
			     */
			    tmp[1] = data[++itmp];
			    addstr(tmp);
			    tmp[1] = '\0';
			    written += 2;
			} else {
			    addstr(tmp);
			    written++;
			}

		    } else if (&data[itmp] > cp) {
			/*
			 *  Output all the other printable target chars.
			 */
			if (text->T.output_utf8 && !isascii(tmp[0])) {
			    if ((*tmp & 0xe0) == 0xc0) {
				utf_extra = 1;
			    } else if ((*tmp & 0xf0) == 0xe0) {
				utf_extra = 2;
			    } else if ((*tmp & 0xf8) == 0xf0) {
				utf_extra = 3;
			    } else if ((*tmp & 0xfc) == 0xf8) {
				utf_extra = 4;
			    } else if ((*tmp & 0xfe) == 0xfc) {
				utf_extra = 5;
			    } else {
				/*
				 *  Garbage.
				 */
				utf_extra = 0;
			    }
			    if (strlen(&line->data[itmp+1]) < utf_extra) {
				/*
				 *  Shouldn't happen.
				 */
				utf_extra = 0;
			    }
			}
			if (utf_extra) {
			    strncpy(&tmp[1], &line->data[itmp+1], utf_extra);
			    tmp[utf_extra+1] = '\0';
			    itmp += utf_extra;
			    addstr(tmp);
			    tmp[1] = '\0';
			    written += (utf_extra + 1);
			    utf_extra = 0;
			} else if (HTCJK != NOCJK && !isascii(tmp[0])) {
			    /*
			     *  For CJK strings, by Masanobu Kimura.
			     */
			    tmp[1] = data[++itmp];
			    addstr(tmp);
			    tmp[1] = '\0';
			    written += 2;
			} else {
			    addstr(tmp);
			    written++;
			}
		    }
		}

		/*
		 *  Stop the emphasis, and reset the offset and
		 *  data pointer for our current position in the
		 *  line. - FM
		 */
		LYstopTargetEmphasis();
		LYGetYX(y, offset);
		data = (char *)&data[itmp];

		/*
		 *  Adjust the cursor position, should we be at
		 *  the end of the line, or not have another hit
		 *  in it. - FM
		 */
		move((i + 2), 0);
	    } /* end while */
#endif /* FANCY CURSES || USE_SLANG */

	    /*
	     *  Stop if this is the last line.  Otherwise, make sure
	     *  display_flag is set and process the next line. - FM
	     */
	    if (line == text->last_line) {
		/*
		 *  Clear remaining lines of display.
		 */
		for (i++; i < (display_lines); i++) {
		    move((i + 1), 0);
		    clrtoeol();
		}
		break;
	    }
	    display_flag = TRUE;
	    line = line->next;
	} /* end of "Verify and display each line." loop */
    } /* end "Output the page." */

    text->next_line = line;	/* Line after screen */
    text->stale = NO;		/* Display is up-to-date */

    /*
     *  Add the anchors to Lynx structures.
     */
    nlinks = 0;
    for (Anchor_ptr=text->first_anchor;  Anchor_ptr != NULL &&
		Anchor_ptr->line_num <= line_number+(display_lines);
					    Anchor_ptr = Anchor_ptr->next) {

	if (Anchor_ptr->line_num >= line_number &&
		Anchor_ptr->line_num < line_number+(display_lines)) {
	    /*
	     *  Load normal hypertext anchors.
	     */
	    if (Anchor_ptr->show_anchor && Anchor_ptr->hightext &&
			strlen(Anchor_ptr->hightext) > 0 &&
			(Anchor_ptr->link_type & HYPERTEXT_ANCHOR)) {

		links[nlinks].hightext	= Anchor_ptr->hightext;
		links[nlinks].hightext2 = Anchor_ptr->hightext2;
		links[nlinks].hightext2_offset = Anchor_ptr->hightext2offset;
		links[nlinks].inUnderline = Anchor_ptr->inUnderline;

		links[nlinks].anchor_number = Anchor_ptr->number;
		links[nlinks].anchor_line_num = Anchor_ptr->line_num;

		link_dest = HTAnchor_followMainLink(
					     (HTAnchor *)Anchor_ptr->anchor);
		{
		    /*
		     *	Memory leak fixed 05-27-94
		     *	Garrett Arch Blythe
		     */
		    auto char *cp_AnchorAddress = NULL;
		    if (traversal)
			cp_AnchorAddress = stub_HTAnchor_address(link_dest);
		    else {
#ifndef DONT_TRACK_INTERNAL_LINKS
			if (Anchor_ptr->link_type == INTERNAL_LINK_ANCHOR) {
			    link_dest_intl = HTAnchor_followTypedLink(
				(HTAnchor *)Anchor_ptr->anchor, LINK_INTERNAL);
			    if (link_dest_intl && link_dest_intl != link_dest) {

				CTRACE(tfp,
				    "display_page: unexpected typed link to %s!\n",
					    link_dest_intl->parent->address);
				link_dest_intl = NULL;
			    }
			} else
			    link_dest_intl = NULL;
			if (link_dest_intl) {
			    char *cp2 = HTAnchor_address(link_dest_intl);
			    cp_AnchorAddress = cp2;
			} else
#endif
			    cp_AnchorAddress = HTAnchor_address(link_dest);
		    }
		    FREE(links[nlinks].lname);

		    if (cp_AnchorAddress != NULL)
			links[nlinks].lname = cp_AnchorAddress;
		    else
			StrAllocCopy(links[nlinks].lname, empty_string);
		}

		links[nlinks].lx = Anchor_ptr->line_pos;
		links[nlinks].ly = ((Anchor_ptr->line_num + 1) - line_number);
		if (link_dest_intl)
		    links[nlinks].type = WWW_INTERN_LINK_TYPE;
		else
		    links[nlinks].type = WWW_LINK_TYPE;
		links[nlinks].target = empty_string;
		links[nlinks].form = NULL;

		nlinks++;
		display_flag = TRUE;

	    } else if (Anchor_ptr->link_type == INPUT_ANCHOR
			&& Anchor_ptr->input_field->type != F_HIDDEN_TYPE) {
		/*
		 *  Handle form fields.
		 */
		lynx_mode = FORMS_LYNX_MODE;

		FormInfo_ptr = Anchor_ptr->input_field;

		links[nlinks].anchor_number = Anchor_ptr->number;
		links[nlinks].anchor_line_num = Anchor_ptr->line_num;

		links[nlinks].form = FormInfo_ptr;
		links[nlinks].lx = Anchor_ptr->line_pos;
		links[nlinks].ly = ((Anchor_ptr->line_num + 1) - line_number);
		links[nlinks].type = WWW_FORM_LINK_TYPE;
		links[nlinks].inUnderline = Anchor_ptr->inUnderline;
		links[nlinks].target = empty_string;
		StrAllocCopy(links[nlinks].lname, empty_string);

		if (FormInfo_ptr->type == F_RADIO_TYPE) {
		    if (FormInfo_ptr->num_value)
			links[nlinks].hightext = checked_radio;
		    else
			links[nlinks].hightext = unchecked_radio;

		} else if (FormInfo_ptr->type == F_CHECKBOX_TYPE) {
		    if (FormInfo_ptr->num_value)
			links[nlinks].hightext = checked_box;
		    else
			links[nlinks].hightext = unchecked_box;

		} else if (FormInfo_ptr->type == F_PASSWORD_TYPE) {
		    links[nlinks].hightext = STARS(strlen(FormInfo_ptr->value));

		} else {  /* TEXT type */
		    links[nlinks].hightext = FormInfo_ptr->value;
		}

		/*
		 *  Never a second line on form types.
		 */
		links[nlinks].hightext2 = NULL;
		links[nlinks].hightext2_offset = 0;

		nlinks++;
		/*
		 *  Bold the link after incrementing nlinks.
		 */
		highlight(OFF, (nlinks - 1), target);

		display_flag = TRUE;

	    } else {
		/*
		 *  Not showing anchor.
		 */
		if (Anchor_ptr->hightext && *Anchor_ptr->hightext)
		    CTRACE(tfp,
			    "\nGridText: Not showing link, hightext=%s\n",
			    Anchor_ptr->hightext);
	    }
	}

	if (Anchor_ptr == text->last_anchor)
	    /*
	     *  No more links in document. - FM
	     */
	    break;

	if (nlinks == MAXLINKS) {
	    /*
	     *  Links array is full.  If interactive, tell user
	     *  to use half-page or two-line scrolling. - FM
	     */
	    if (LYCursesON) {
		HTAlert(MAXLINKS_REACHED);
	    }
	    CTRACE(tfp, "\ndisplay_page: MAXLINKS reached.\n");
	    break;
	}
    } /* end of loop "Add the anchors to Lynx structures." */

    /*
     *  Free any un-reallocated links[] entries
     *  from the previous page draw. - FM
     */
    for (i = nlinks; i < last_nlinks; i++)
	FREE(links[i].lname);
    last_nlinks = nlinks;

    /*
     *  If Anchor_ptr is not NULL and is not pointing to the last
     *  anchor, then there are anchors farther down in the document,
     *  and we need to flag this for traversals.
     */
    more_links = FALSE;
    if (traversal && Anchor_ptr) {
	if (Anchor_ptr->next)
	    more_links = TRUE;
    }

    if (!display_flag) {
	/*
	 *  Nothing on the page.
	 */
	addstr("\n     Document is empty");
    }

    if (HTCJK != NOCJK || text->T.output_utf8) {
	/*
	 *  For non-multibyte curses.
	 */
	lynx_force_repaint();
    }
    refresh();

}


/*			Object Building methods
**			-----------------------
**
**	These are used by a parser to build the text in an object
*/
PUBLIC void HText_beginAppend ARGS1(
	HText *,	text)
{
    text->permissible_split = 0;
    text->in_line_1 = YES;

}


/*	Add a new line of text
**	----------------------
**
** On entry,
**
**	split	is zero for newline function, else number of characters
**		before split.
**	text->display_on_the_fly
**		may be set to indicate direct output of the finished line.
** On exit,
**		A new line has been made, justified according to the
**		current style. Text after the split (if split nonzero)
**		is taken over onto the next line.
**
**		If display_on_the_fly is set, then it is decremented and
**		the finished line is displayed.
*/
#define new_line(text) split_line(text, 0)

PRIVATE void split_line ARGS2(
	HText *,	text,
	unsigned,	split)
{
    HTStyle * style = text->style;
    HTLine * temp;
    int spare;
#if defined(USE_COLOR_STYLE)
    int inew;
#endif
    int indent = text->in_line_1 ?
	  text->style->indent1st : text->style->leftIndent;
    TextAnchor * a;
    int CurLine = text->Lines;
    int HeadTrim = 0;
    int SpecialAttrChars = 0;
    int TailTrim = 0;

    /*
     *  Make new line.
     */
    HTLine * previous = text->last_line;
    int ctrl_chars_on_previous_line = 0;
    char * cp;
    /* can't wrap in middle of multibyte sequences, so allocate 2 extra */
    HTLine * line = (HTLine *)LY_CALLOC(1, LINE_SIZE(MAX_LINE)+2);
    if (line == NULL)
	outofmem(__FILE__, "split_line_1");

    ctrl_chars_on_this_line = 0; /*reset since we are going to a new line*/
    text->LastChar = ' ';

#ifdef DEBUG_APPCH
    CTRACE(tfp,"GridText: split_line(%p,%d) called\n", text, split);
    CTRACE(tfp,"   bold_on=%d, underline_on=%d\n", bold_on, underline_on);
#endif

    text->Lines++;

    previous->next->prev = line;
    line->prev = previous;
    line->next = previous->next;
#if defined(USE_COLOR_STYLE)
#define LastStyle (previous->numstyles-1)
    line->numstyles = 0;
    inew = MAX_STYLES_ON_LINE - 1;
    spare = previous->numstyles;
    while (previous->numstyles && inew >= 0) {
	if (previous->numstyles >= 2 &&
	    previous->styles[LastStyle].style
	    == previous->styles[previous->numstyles-2].style &&
	    previous->styles[LastStyle].horizpos
	    == previous->styles[previous->numstyles-2].horizpos &&
	    ((previous->styles[LastStyle].direction == STACK_OFF &&
	      previous->styles[previous->numstyles-2].direction == STACK_ON) ||
	     (previous->styles[LastStyle].direction == ABS_OFF &&
	      previous->styles[previous->numstyles-2].direction == ABS_ON) ||
	     (previous->styles[LastStyle].direction == ABS_ON &&
	      previous->styles[previous->numstyles-2].direction == ABS_OFF)
		)) {
	    /*
	     *  Discard pairs of ON/OFF for the same color style, but only
	     *  if they appear at the same position. - kw
	     */
	    previous->numstyles -= 2;
	    if (spare > previous->numstyles)
		spare = previous->numstyles;
	} else if (spare > 0 && previous->styles[spare - 1].direction &&
		   previous->numstyles < MAX_STYLES_ON_LINE) {
	    /*
	     *  The index variable spare walks backwards through the
	     *  list of color style changes on the previous line, trying
	     *  to find an ON change which isn't followed by a
	     *  corresponding OFF.  When it finds one, the missing OFF
	     *  change is appended to the end, and an ON change is added
	     *  at the beginning of the current line.  The OFF change
	     *  appended to the previous line may get removed again in
	     *  the next iteration. - kw
	     */
	    line->styles[inew].horizpos = 0;
	    line->styles[inew].direction = ON;
	    line->styles[inew].style = previous->styles[spare - 1].style;
	    inew --;
	    line->numstyles ++;
	    previous->styles[previous->numstyles].style = line->styles[inew + 1].style;

	    previous->styles[previous->numstyles].direction = ABS_OFF;
	    previous->styles[previous->numstyles].horizpos = previous->size;
	    previous->numstyles++;
	    spare --;
	} else if (spare >= 2 &&
		   previous->styles[spare - 1].style == previous->styles[spare - 2].style &&
		   ((previous->styles[spare - 1].direction == STACK_OFF &&
		     previous->styles[spare - 2].direction == STACK_ON) ||
		    (previous->styles[spare - 1].direction == ABS_OFF &&
		     previous->styles[spare - 2].direction == ABS_ON) ||
		    (previous->styles[spare - 1].direction == STACK_ON &&
		     previous->styles[spare - 2].direction == STACK_OFF) ||
		    (previous->styles[spare - 1].direction == ABS_ON &&
		     previous->styles[spare - 2].direction == ABS_OFF)
		       )) {
	       /*
		*  Skip pairs of adjacent ON/OFF or OFF/ON changes.
		*/
	    spare -= 2;
	} else if (spare && !previous->styles[spare - 1].direction) {
	    /*
	     *  Found an OFF change not part of a matched pair.
	     *  Assume it is safer to leave whatever comes before
	     *  it on the previous line alone.  Setting spare to 0
	     *  ensures that it won't be used in a following
	     *  iteration. - kw
	     */
	    spare = 0;
	} else {
	    /*
	     *  Nothing applied, so we are done with the loop. - kw
	     */
	    break;
	}
    }
    if (previous->numstyles > 0 && previous->styles[LastStyle].direction) {

	CTRACE(tfp, "%s\n%s%s\n",
		    "........... Too many character styles on line:",
		    "........... ", previous->data);
    }
    if (line->numstyles > 0 && line->numstyles < MAX_STYLES_ON_LINE) {
	int n;
	inew ++;
	for (n = line->numstyles; n >= 0; n--)
		line->styles[n + inew] = line->styles[n];
    } else
	if (line->numstyles == 0)
	/* FIXME: RJP - shouldn't use 0xffffffff for largest integer */
	line->styles[0].horizpos = 0xffffffff;
    if (previous->numstyles == 0)
	previous->styles[0].horizpos = 0xffffffff;
#endif /*USE_COLOR_STYLE*/
    previous->next = line;
    text->last_line = line;
    line->size = 0;
    line->offset = 0;
    text->permissible_split = 0;  /* 12/13/93 */
    line->data[0] = '\0';

    /*
     *  If we are not splitting and need an underline char, add it now. - FM
     */
    if ((split < 1) &&
	!(dump_output_immediately && use_underscore) && underline_on) {
	line->data[line->size++] = LY_UNDERLINE_START_CHAR;
	line->data[line->size] = '\0';
	ctrl_chars_on_this_line++;
    }
    /*
     *  If we are not splitting and need a bold char, add it now. - FM
     */
    if ((split < 1) && bold_on) {
	line->data[line->size++] = LY_BOLD_START_CHAR;
	line->data[line->size] = '\0';
	ctrl_chars_on_this_line++;
    }

    /*
     *  Split at required point
     */
    if (split > 0) {	/* Delete space at "split" splitting line */
	char *p, *prevdata = previous->data, *linedata = line->data;
	unsigned plen;
	unsigned i;

	/*
	 *  Split the line. - FM
	 */
	prevdata[previous->size] = '\0';
	previous->size = split;

	/*
	 *  Trim any spaces or soft hyphens from the beginning
	 *  of our new line. - FM
	 */
	p = prevdata + split;
	while (*p == ' ' || *p == LY_SOFT_HYPHEN) {
	    p++;
	    HeadTrim++;
	}
	plen = strlen(p);

	/*
	 *  Add underline char if needed. - FM
	 */
	if (!(dump_output_immediately && use_underscore)) {
	    /*
	     * Make sure our global flag is correct. - FM
	     */
	    underline_on = NO;
	    if (split) {
		for (i = (split-1); i != 0; i--) {
		    if (prevdata[i] == LY_UNDERLINE_END_CHAR) {
			break;
		    }
		    if (prevdata[i] == LY_UNDERLINE_START_CHAR) {
			underline_on = YES;
			break;
		    }
		}
	    }
	    /*
	     *  Act on the global flag if set above. - FM
	     */
	    if (underline_on && *p != LY_UNDERLINE_END_CHAR) {
		linedata[line->size++] = LY_UNDERLINE_START_CHAR;
		linedata[line->size] = '\0';
		ctrl_chars_on_this_line++;
		SpecialAttrChars++;
	    }
	    if (plen) {
		for (i = (plen - 1); i != 0; i--) {
		    if (p[i] == LY_UNDERLINE_START_CHAR) {
			underline_on = YES;
			break;
		    }
		    if (p[i] == LY_UNDERLINE_END_CHAR) {
			underline_on = NO;
			break;
		    }
		}
		for (i = (plen - 1); i != 0; i--) {
		    if (p[i] == LY_UNDERLINE_START_CHAR ||
			p[i] == LY_UNDERLINE_END_CHAR) {
			ctrl_chars_on_this_line++;
		    }
		}
	    }
	}

	/*
	 *  Add bold char if needed, first making
	 *  sure that our global flag is correct. - FM
	 */
	bold_on = NO;
	if (split) {
	    for (i = (split - 1); i != 0; i--) {
		if (prevdata[i] == LY_BOLD_END_CHAR) {
		    break;
		}
		if (prevdata[i] == LY_BOLD_START_CHAR) {
		    bold_on = YES;
		    break;
		}
	    }
	}
	/*
	 *  Act on the global flag if set above. - FM
	 */
	if (bold_on && *p != LY_BOLD_END_CHAR) {
	    linedata[line->size++] = LY_BOLD_START_CHAR;
	    linedata[line->size] = '\0';
	    ctrl_chars_on_this_line++;
	    SpecialAttrChars++;;
	} else
	    bold_on = OFF;
	if (plen) {
	    for (i = (plen - 1); i != 0; i--) {
		if (p[i] == LY_BOLD_START_CHAR) {
		    bold_on = YES;
		    break;
		}
		if (p[i] == LY_BOLD_END_CHAR) {
		    bold_on = NO;
		    break;
		}
	    }
	    for (i = (plen - 1); i != 0; i--) {
		if (p[i] == LY_BOLD_START_CHAR ||
		    p[i] == LY_BOLD_END_CHAR ||
		    IS_UTF_EXTRA(p[i]) ||
		    p[i] == LY_SOFT_HYPHEN) {
		    ctrl_chars_on_this_line++;
		}
		if (p[i] == LY_SOFT_HYPHEN && text->permissible_split < i) {
		    text->permissible_split = i + 1;
		}
	    }
	}

	/*
	 *  Add the data to the new line. - FM
	 */
	strcat(linedata, p);
	line->size += plen;
    }

    /*
     *  Economize on space.
     */
    while ((previous->size > 0) &&
	(previous->data[previous->size-1] == ' ')) {
	/*
	 *  Strip trailers.
	 */
	previous->data[previous->size-1] = '\0';
	previous->size--;
	TailTrim++;
    }
    temp = (HTLine *)LY_CALLOC(1, LINE_SIZE(previous->size));
    if (temp == NULL)
	outofmem(__FILE__, "split_line_2");
    memcpy(temp, previous, LINE_SIZE(previous->size));
    FREE(previous);
    previous = temp;

    previous->prev->next = previous;	/* Link in new line */
    previous->next->prev = previous;	/* Could be same node of course */

    /*
     *  Terminate finished line for printing.
     */
    previous->data[previous->size] = '\0';

    /*
     *  Align left, right or center.
     */
    for (cp = previous->data; *cp; cp++) {
	if (*cp == LY_UNDERLINE_START_CHAR ||
	    *cp == LY_UNDERLINE_END_CHAR ||
	    *cp == LY_BOLD_START_CHAR ||
	    *cp == LY_BOLD_END_CHAR ||
	    IS_UTF_EXTRA(*cp) ||
	    *cp == LY_SOFT_HYPHEN)
	    ctrl_chars_on_previous_line++;
    }
    /* @@ first line indent */
    spare =  (LYcols-1) -
		(int)style->rightIndent - indent +
		ctrl_chars_on_previous_line - previous->size -
		((previous->size > 0) &&
		 (int)(previous->data[previous->size-1] ==
					    LY_SOFT_HYPHEN ?
							 1 : 0));

    switch (style->alignment) {
	case HT_CENTER :
	    previous->offset = previous->offset + indent + spare/2;
	    break;
	case HT_RIGHT :
	    previous->offset = previous->offset + indent + spare;
	    break;
	case HT_LEFT :
	case HT_JUSTIFY :		/* Not implemented */
	default:
	    previous->offset = previous->offset + indent;
	    break;
    } /* switch */

    text->chars = text->chars + previous->size + 1;	/* 1 for the line */
    text->in_line_1 = NO;		/* unless caller sets it otherwise */

    /*
     *  If we split the line, adjust the anchor
     *  structure values for the new line. - FM
     */
    if (split > 0) {
	for (a = text->first_anchor; a; a = a->next) {
	    if (a->line_num == CurLine) {
		if ((unsigned)a->line_pos >= split) {
		    a->start += (1 + SpecialAttrChars - HeadTrim - TailTrim);
		    a->line_pos -= (split - SpecialAttrChars + HeadTrim);
		    a->line_num = text->Lines;
		} else if ((a->link_type & HYPERTEXT_ANCHOR) &&
			   (unsigned)(a->line_pos + a->extent) >= split) {
		    a->extent -= (TailTrim + HeadTrim);
		    if (a->extent < 0) {
			a->extent = 0;
		    }
		}
	    }
	}
    }
} /* split_line */


/*	Allow vertical blank space
**	--------------------------
*/
PRIVATE void blank_lines ARGS2(
	HText *,	text,
	int,		newlines)
{
    BOOL IgnoreSpaces = FALSE;

    if (!HText_LastLineSize(text, IgnoreSpaces)) { /* No text on current line */
	HTLine * line = text->last_line->prev;
	while ((line != text->last_line) &&
	       (HText_TrueLineSize(line, text, IgnoreSpaces) == 0)) {
	    if (newlines == 0)
		break;
	    newlines--;		/* Don't bother: already blank */
	    line = line->prev;
	}
    } else {
	newlines++;			/* Need also to finish this line */
    }

    for (; newlines; newlines--) {
	new_line(text);
    }
    text->in_line_1 = YES;
}


/*	New paragraph in current style
**	------------------------------
** See also: setStyle.
*/
PUBLIC void HText_appendParagraph ARGS1(
	HText *,	text)
{
    int after = text->style->spaceAfter;
    int before = text->style->spaceBefore;
    blank_lines(text, ((after > before) ? after : before));
}


/*	Set Style
**	---------
**
**	Does not filter unnecessary style changes.
*/
PUBLIC void HText_setStyle ARGS2(
	HText *,	text,
	HTStyle *,	style)
{
    int after, before;

    if (!style)
	return; 			/* Safety */
    after = text->style->spaceAfter;
    before = style->spaceBefore;

    CTRACE(tfp, "GridText: Change to style %s\n", style->name);

    blank_lines (text, ((after > before) ? after : before));

    text->style = style;
}

/*	Append a character to the text object
**	-------------------------------------
*/
PUBLIC void HText_appendCharacter ARGS2(
	HText *,	text,
	char,		ch)
{
    HTLine * line;
    HTStyle * style;
    int indent;

#ifdef DEBUG_APPCH
    if (TRACE) {
	char * special = NULL;  /* make trace a little more readable */
	switch(ch) {
	case HT_NON_BREAK_SPACE:
		special = "HT_NON_BREAK_SPACE";
		break;
	case HT_EM_SPACE:
		special = "HT_EM_SPACE";
		break;
	case LY_UNDERLINE_START_CHAR:
		special = "LY_UNDERLINE_START_CHAR";
		break;
	case LY_UNDERLINE_END_CHAR:
		special = "LY_UNDERLINE_END_CHAR";
		break;
	case LY_BOLD_START_CHAR:
		special = "LY_BOLD_START_CHAR";
		break;
	case LY_BOLD_END_CHAR:
		special = "LY_BOLD_END_CHAR";
		break;
	case LY_SOFT_HYPHEN:
		special = "LY_SOFT_HYPHEN";
		break;
	case LY_SOFT_NEWLINE:
		special = "LY_SOFT_NEWLINE";
		break;
	default:
		special = NULL;
		break;
	}

	if (special != NULL) {
	    CTRACE(tfp, "add(%s %d special char) %d/%d\n", special, ch,
		   HTisDocumentSource(), HTOutputFormat != WWW_SOURCE);
	} else {
	    CTRACE(tfp, "add(%c) %d/%d\n", ch,
		   HTisDocumentSource(), HTOutputFormat != WWW_SOURCE);
	}
    } /* trace only */
#endif /* DEBUG_APPCH */

    /*
     *  Make sure we don't crash on NULLs.
     */
    if (!text)
	return;

    if (text->halted > 1) {
	/*
	 *  We should stop outputting more text, because low memory was
	 *  detected.  - kw
	 */
	if (text->halted == 2) {
	    /*
	     *  But if we haven't done so yet, first append a warning.
	     *  We should still have a few bytes left for that :).
	     *  We temporarily reset test->halted to 0 for this, since
	     *  this function will get called recursively. - kw
	     */
	    text->halted = 0;
	    text->kanji_buf = '\0';
	    HText_appendText(text, " *** MEMORY EXHAUSTED ***");
	}
	text->halted = 3;
	return;
    }
    /*
     *  Make sure we don't hang on escape sequences.
     */
    if (ch == '\033' && HTCJK == NOCJK)			/* decimal 27 */
	return;
#ifndef USE_SLANG
    /*
     *  Block 8-bit chars not allowed by the current display character
     *  set if they are below what LYlowest_eightbit indicates.
     *  Slang used its own replacements, so for USE_SLANG blocking here
     *  is not necessary to protect terminals from those characters.
     *  They should have been filtered out or translated by an earlier
     *  processing stage anyway. - kw
     */
    if ((unsigned char)ch >= 128 && HTCJK == NOCJK &&
	!text->T.transp && !text->T.output_utf8 &&
	(unsigned char)ch < LYlowest_eightbit[current_char_set])
	return;
#endif /* !USE_SLANG */
    if ((unsigned char)ch == 155 && HTCJK == NOCJK) {	/* octal 233 */
	if (!HTPassHighCtrlRaw &&
	    !text->T.transp && !text->T.output_utf8 &&
	    (155 < LYlowest_eightbit[current_char_set])) {
	    return;
	}
    }

    line = text->last_line;
    style = text->style;

    indent = text->in_line_1 ? (int)style->indent1st : (int)style->leftIndent;

    if (HTCJK != NOCJK) {
	switch(text->state) {
	    case S_text:
		if (ch == '\033') {
		    /*
		    **  Setting up for CJK escape sequence handling (based on
		    **  Takuya ASADA's (asada@three-a.co.jp) CJK Lynx). - FM
		    */
		    text->state = S_esc;
		    text->kanji_buf = '\0';
		    return;
		}
		break;

		case S_esc:
		/*
		 *  Expecting '$'or '(' following CJK ESC.
		 */
		if (ch == '$') {
		    text->state = S_dollar;
		    return;
		} else if (ch == '(') {
		    text->state = S_paren;
		    return;
		} else {
		    text->state = S_text;
		}

		case S_dollar:
		/*
		 *  Expecting '@', 'B', 'A' or '(' after CJK "ESC$".
		 */
		if (ch == '@' || ch == 'B' || ch=='A') {
		    text->state = S_nonascii_text;
		    return;
		} else if (ch == '(') {
		    text->state = S_dollar_paren;
		    return;
		} else {
		    text->state = S_text;
		}
		break;

		case S_dollar_paren:
		/*
		 * Expecting 'C' after CJK "ESC$(".
		 */
		if (ch == 'C') {
		    text->state = S_nonascii_text;
		    return;
		} else {
		    text->state = S_text;
		}
		break;

		case S_paren:
		/*
		 *  Expecting 'B', 'J', 'T' or 'I' after CJK "ESC(".
		 */
		if (ch == 'B' || ch == 'J' || ch == 'T')  {
		    /*
		     *  Can split here. - FM
		     */
		    text->permissible_split = text->last_line->size;
		    text->state = S_text;
		    return;
		} else if (ch == 'I')  {
		    text->state = S_jisx0201_text;
		    /*
		     *  Can split here. - FM
		     */
		    text->permissible_split = text->last_line->size;
		    return;
		} else {
		    text->state = S_text;
		}
		break;

		case S_nonascii_text:
		/*
		 *  Expecting CJK ESC after non-ASCII text.
		 */
		if (ch == '\033') {
		    text->state = S_esc;
		    text->kanji_buf = '\0';
		    return;
		} else {
		    ch |= 0200;
		}
		break;

		/*
		 *  JIS X0201 Kana in JIS support. - by ASATAKU
		 */
		case S_jisx0201_text:
		if (ch == '\033') {
		    text->state = S_esc;
		    text->kanji_buf = '\0';
		    return;
		} else {
		    text->kanji_buf = '\216';
		    ch |= 0200;
		}
		break;
	}

	if (!text->kanji_buf) {
	    if ((ch & 0200) != 0) {
		/*
		 *  JIS X0201 Kana in SJIS support. - by ASATAKU
		 */
		if ((text->kcode == SJIS) &&
		    ((unsigned char)ch >= 0xA1) &&
		    ((unsigned char)ch <= 0xDF)) {
		    unsigned char c = (unsigned char)ch;
		    unsigned char kb = (unsigned char)text->kanji_buf;
		    JISx0201TO0208_SJIS(c,
					(unsigned char *)&kb,
					(unsigned char *)&c);
		    ch = (char)c;
		    text->kanji_buf = kb;
		} else {
		    text->kanji_buf = ch;
		    /*
		     *  Can split here. - FM
		     */
		    text->permissible_split = text->last_line->size;
		    return;
		}
	    }
	} else {
	    goto check_IgnoreExcess;
	}
    } else if (ch == '\033') {
	return;
    }

    if (IsSpecialAttrChar(ch)) {
#ifndef USE_COLOR_STYLE
	if (line->size >= (MAX_LINE-1)) return;
	if (ch == LY_UNDERLINE_START_CHAR) {
	    line->data[line->size++] = LY_UNDERLINE_START_CHAR;
	    line->data[line->size] = '\0';
	    underline_on = ON;
	    if (!(dump_output_immediately && use_underscore))
		ctrl_chars_on_this_line++;
	    return;
	} else if (ch == LY_UNDERLINE_END_CHAR) {
	    line->data[line->size++] = LY_UNDERLINE_END_CHAR;
	    line->data[line->size] = '\0';
	    underline_on = OFF;
	    if (!(dump_output_immediately && use_underscore))
		ctrl_chars_on_this_line++;
	    return;
	} else if (ch == LY_BOLD_START_CHAR) {
	    line->data[line->size++] = LY_BOLD_START_CHAR;
	    line->data[line->size] = '\0';
	    bold_on = ON;
	    ctrl_chars_on_this_line++;
	    return;
	} else if (ch == LY_BOLD_END_CHAR) {
	    line->data[line->size++] = LY_BOLD_END_CHAR;
	    line->data[line->size] = '\0';
	    bold_on = OFF;
	    ctrl_chars_on_this_line++;
	    return;
	} else if (ch == LY_SOFT_NEWLINE) {
	    line->data[line->size++] = LY_SOFT_NEWLINE;
	    line->data[line->size] = '\0';
	    ctrl_chars_on_this_line++;
	    return;
	} else if (ch == LY_SOFT_HYPHEN) {
	    int i;

	    /*
	     *  Ignore the soft hyphen if it is the first character
	     *  on the line, or if it is preceded by a space or
	     *  hyphen. - FM
	     */
	    if (line->size < 1 || text->permissible_split >= line->size)
		return;

	    for (i = (text->permissible_split + 1); line->data[i]; i++) {
		if (!IsSpecialAttrChar((unsigned char)line->data[i]) &&
		    !isspace((unsigned char)line->data[i]) &&
		    (unsigned char)line->data[i] != '-' &&
		    (unsigned char)line->data[i] != HT_NON_BREAK_SPACE &&
		    (unsigned char)line->data[i] != HT_EM_SPACE) {
		    break;
		}
	    }
	    if (line->data[i] == '\0') {
		return;
	    }
	}
#else
	return;
#endif
    }

    if (IS_UTF_EXTRA(ch)) {
	line->data[line->size++] = ch;
	line->data[line->size] = '\0';
	ctrl_chars_on_this_line++;
	return;
    }

    /*
     *  New Line.
     */
    if (ch == '\n') {
	    new_line(text);
	    text->in_line_1 = YES;	/* First line of new paragraph */
	    /*
	     *  There are some pages written in
	     *  different kanji codes. - TA & kw
	     */
	    if (HTCJK == JAPANESE)
		text->kcode = NOKANJI;
	    return;
    }

    /*
     *  Convert EM_SPACE to a space here so that it doesn't get collapsed.
     */
    if (ch == HT_EM_SPACE)
	ch = ' ';

    /*
     *  I'm going to cheat here in a BIG way.  Since I know that all
     *  \r's will be trapped by HTML_put_character I'm going to use
     *  \r to mean go down a line but don't start a new paragraph.
     *  i.e. use the second line indenting.
     */
    if (ch == '\r') {
	new_line(text);
	text->in_line_1 = NO;
	/*
	 *  There are some pages written in
	 *  different kanji codes. - TA & kw
	 */
	if (HTCJK == JAPANESE)
	    text->kcode = NOKANJI;
	return;
    }


    /*
     *  Tabs.
     */
    if (ch == '\t') {
	CONST HTTabStop * Tab;
	int target;	/* Where to tab to */
	int here;

	if (line->size > 0 && line->data[line->size-1] == LY_SOFT_HYPHEN) {
	    /*
	     *  A tab shouldn't follow a soft hyphen, so
	     *  if one does, we'll dump the soft hyphen. - FM
	     */
	    line->data[--line->size] = '\0';
	    ctrl_chars_on_this_line--;
	}
	here = ((int)(line->size + line->offset) + indent)
		- ctrl_chars_on_this_line; /* Consider special chars GAB */
	if (style->tabs) {	/* Use tab table */
	    for (Tab = style->tabs;
		Tab->position <= here;
		Tab++) {
		if (!Tab->position) {
		    new_line(text);
		    return;
		}
	    }
	    target = Tab->position;
	} else if (text->in_line_1) {	/* Use 2nd indent */
	    if (here >= (int)style->leftIndent) {
		new_line(text); /* wrap */
		return;
	    } else {
		target = (int)style->leftIndent;
	    }
	} else {		/* Default tabs align with left indent mod 8 */
#ifdef DEFAULT_TABS_8
	    target = (((int)line->offset + (int)line->size + 8) & (-8))
			+ (int)style->leftIndent;
#else
	    new_line(text);
	    return;
#endif
	}

	if (target > (LYcols-1) - (int)style->rightIndent &&
	    HTOutputFormat != WWW_SOURCE) {
	    new_line(text);
	} else {
	    /*
	     *  Can split here. - FM
	     */
	    text->permissible_split = line->size;
	    if (line->size == 0) {
		line->offset = line->offset + target - here;
	    } else {
		for (; here<target; here++) {
		    /* Put character into line */
		    line->data[line->size++] = ' ';
		    line->data[line->size] = '\0';
		}
	    }
	}
	return;
    } /* if tab */
    else {
	/*
	 * If we're displaying document source, wrap long lines to keep all of
	 * the source visible.
	 */
	int target = (int)(line->offset + line->size);
	if ((target >= (LYcols-1) - style->rightIndent) &&
		HTisDocumentSource()) {
	    new_line(text);
	    line = text->last_line;
	    HText_appendCharacter (text, LY_SOFT_NEWLINE);
	}
    }

    if (ch == ' ') {
	/*
	 *  Can split here. - FM
	 */
	text->permissible_split = text->last_line->size;
	/*
	 *  There are some pages written in
	 *  different kanji codes. - TA
	 */
	if (HTCJK == JAPANESE)
	    text->kcode = NOKANJI;
    }

    /*
     *  Check if we should ignore characters at the wrap point.
     */
check_IgnoreExcess:
    if (text->IgnoreExcess &&
	((indent + (int)line->offset + (int)line->size) +
	(int)style->rightIndent - ctrl_chars_on_this_line) >= (LYcols-1))
	return;

    /*
     *  Check for end of line.
     */
    if (((indent + (int)line->offset + (int)line->size) +
	 (int)style->rightIndent - ctrl_chars_on_this_line +
	 ((line->size > 0) &&
	  (int)(line->data[line->size-1] ==
				LY_SOFT_HYPHEN ?
					     1 : 0))) >= (LYcols - 1)) {

	if (style->wordWrap && HTOutputFormat != WWW_SOURCE) {
	    split_line(text, text->permissible_split);
	    if (ch == ' ') return;	/* Ignore space causing split */

	}  else if (HTOutputFormat == WWW_SOURCE) {
		 /*
		  *  For source output we don't want to wrap this stuff
		  *  unless absolutely necessary. - LJM
		  *  !
		  *  If we don't wrap here we might get a segmentation fault.
		  *  but let's see what happens
		  */
		if ((int)line->size >= (int)(MAX_LINE-1))
		   new_line(text);  /* try not to linewrap */
	} else {
		/*
		 *  For normal stuff like pre let's go ahead and
		 *  wrap so the user can see all of the text.
		 */
		new_line(text);
	}
    } else if ((int)line->size >= (int)(MAX_LINE-1)) {
	/*
	 *  Never overrun memory if LYcols is set to a large value - KW
	 */
	new_line(text);
    }

    /*
     *  Insert normal characters.
     */
    if (ch == HT_NON_BREAK_SPACE) {
	ch = ' ';
    }

    if (ch & 0x80)
	text->have_8bit_chars = YES;

    {
	HTFont font = style->font;
	unsigned char hi, lo, tmp[2];

	line = text->last_line; /* May have changed */
	if (HTCJK != NOCJK && text->kanji_buf) {
	    hi = (unsigned char)text->kanji_buf, lo = (unsigned char)ch;
	    if (HTCJK == JAPANESE && text->kcode == NOKANJI) {
		if (IS_SJIS(hi, lo, text->in_sjis) && IS_EUC(hi, lo)) {
		    text->kcode = NOKANJI;
		} else if (IS_SJIS(hi, lo, text->in_sjis)) {
		    text->kcode = SJIS;
		} else if (IS_EUC(hi, lo)) {
		    text->kcode = EUC;
		}
	    }
	    if (HTCJK == JAPANESE &&
		(kanji_code == EUC) && (text->kcode == SJIS)) {
		SJIS_TO_EUC1(hi, lo, tmp);
		line->data[line->size++] = tmp[0];
		line->data[line->size++] = tmp[1];
	    } else if (HTCJK == JAPANESE &&
		       (kanji_code == EUC) && (text->kcode == EUC)) {
		JISx0201TO0208_EUC(hi, lo, &hi, &lo);
		line->data[line->size++] = hi;
		line->data[line->size++] = lo;
	    } else if (HTCJK == JAPANESE &&
		       (kanji_code == SJIS) && (text->kcode == EUC)) {
		EUC_TO_SJIS1(hi, lo, tmp);
		line->data[line->size++] = tmp[0];
		line->data[line->size++] = tmp[1];
	    } else {
		line->data[line->size++] = hi;
		line->data[line->size++] = lo;
	    }
	    text->kanji_buf = 0;
	} else if (HTCJK != NOCJK) {
	    line->data[line->size++] = (kanji_code != NOKANJI) ?
							    ch :
					  (font & HT_CAPITALS) ?
						   TOUPPER(ch) : ch;
	} else {
	    line->data[line->size++] =	/* Put character into line */
		font & HT_CAPITALS ? TOUPPER(ch) : ch;
	}
	line->data[line->size] = '\0';
	if (font & HT_DOUBLE)		/* Do again if doubled */
	    HText_appendCharacter(text, HT_NON_BREAK_SPACE);
	    /* NOT a permissible split */

	if (ch == LY_SOFT_HYPHEN) {
	    ctrl_chars_on_this_line++;
	    /*
	     *  Can split here. - FM
	     */
	    text->permissible_split = text->last_line->size;
	}
	if (ch == LY_SOFT_NEWLINE) {
	    ctrl_chars_on_this_line++;
	}
    }
}

#ifdef USE_COLOR_STYLE
/*  Insert a style change into the current line
**  -------------------------------------------
*/
PUBLIC void _internal_HTC ARGS3(HText *,text, int,style, int,dir)
{
 HTLine* line;
 static int last_style = -1;
 static int last_dir = -1;

 /* can't change style if we have no text to change style with */
 if (!text) return;

 line = text->last_line;

 if ((style != last_style || dir != last_dir) && line->numstyles < MAX_STYLES_ON_LINE)
 {
      line->styles[line->numstyles].horizpos = line->size;
      line->styles[line->numstyles].style = style;
      line->styles[line->numstyles].direction = dir;
      line->numstyles++;
 }
 last_style = style;
 last_dir = dir;
}
#endif



/*	Set LastChar element in the text object.
**	----------------------------------------
*/
PUBLIC void HText_setLastChar ARGS2(
	HText *,	text,
	char,		ch)
{
    if (!text)
	return;

    text->LastChar = ch;
}

/*	Get LastChar element in the text object.
**	----------------------------------------
*/
PUBLIC char HText_getLastChar ARGS1(
	HText *,	text)
{
    if (!text)
	return('\0');

    return((char)text->LastChar);
}

/*	Set IgnoreExcess element in the text object.
**	--------------------------------------------
*/
PUBLIC void HText_setIgnoreExcess ARGS2(
	HText *,	text,
	BOOL,		ignore)
{
    if (!text)
	return;

    text->IgnoreExcess = ignore;
}

/*		Anchor handling
**		---------------
*/

/*	Start an anchor field
*/
PUBLIC int HText_beginAnchor ARGS3(
	HText *,		text,
	BOOL,			underline,
	HTChildAnchor *,	anc)
{
    char marker[32]; 

    TextAnchor * a = (TextAnchor *) calloc(1, sizeof(*a));

    if (a == NULL)
	outofmem(__FILE__, "HText_beginAnchor");
    a->hightext  = NULL;
    a->hightext2 = NULL;
    a->start = text->chars + text->last_line->size;
    a->inUnderline = underline;

    a->line_num = text->Lines;
    a->line_pos = text->last_line->size;
    if (text->last_anchor) {
	text->last_anchor->next = a;
    } else {
	text->first_anchor = a;
    }
    a->next = 0;
    a->anchor = anc;
    a->extent = 0;
    a->link_type = HYPERTEXT_ANCHOR;
    text->last_anchor = a;

#ifndef DONT_TRACK_INTERNAL_LINKS
    if (HTAnchor_followTypedLink((HTAnchor*)anc, LINK_INTERNAL)) {
	a->number = ++(text->last_anchor_number);
	a->link_type = INTERNAL_LINK_ANCHOR;
    } else
#endif
	if (HTAnchor_followMainLink((HTAnchor*)anc)) {
	a->number = ++(text->last_anchor_number);
    } else {
	a->number = 0;
    }

    /*
     *  If we are doing link_numbering add the link number.
     */
    if ((a->number > 0) &&
	(keypad_mode == LINKS_ARE_NUMBERED ||
	 keypad_mode == LINKS_AND_FORM_FIELDS_ARE_NUMBERED)) {
	sprintf(marker,"[%d]", a->number);
	HText_appendText(text, marker);
	a->start = text->chars + text->last_line->size;
	a->line_num = text->Lines;
	a->line_pos = text->last_line->size;
    }

    return(a->number);
}


PUBLIC void HText_endAnchor ARGS2(
	HText *,	text,
	int,		number)
{
    TextAnchor *a;

    /*
     *  The number argument is set to 0 in HTML.c and
     *  LYCharUtils.c when we want to end the anchor
     *  for the immediately preceding HText_beginAnchor()
     *  call.  If it's greater than 0, we want to handle
     *  a particular anchor.  This allows us to set links
     *  for positions indicated by NAME or ID attributes,
     *  without needing to close any anchor with an HREF
     *  within which that link might be embedded. - FM
     */
    if (number <= 0 || number == text->last_anchor->number) {
	a = text->last_anchor;
    } else {
	for (a = text->first_anchor; a; a = a->next) {
	    if (a->number == number) {
		break;
	    }
	}
	if (a == NULL) {
	    /*
	     *  There's no anchor with that number,
	     *  so we'll default to the last anchor,
	     *  and cross our fingers. - FM
	     */
	    a = text->last_anchor;
	}
    }

    CTRACE(tfp, "HText_endAnchor: number:%d link_type:%d\n",
			a->number, a->link_type);
    if (a->link_type == INPUT_ANCHOR) {
	/*
	 *  Shouldn't happen, but put test here anyway to be safe. - LE
	 */

	CTRACE(tfp,
	   "HText_endAnchor: internal error: last anchor was input field!\n");
	return;
    }
    if (a->number) {
	/*
	 *  If it goes somewhere...
	 */
	int i, j, k, l;
	BOOL remove_numbers_on_empty =
	    ((keypad_mode == LINKS_ARE_NUMBERED ||
	      keypad_mode == LINKS_AND_FORM_FIELDS_ARE_NUMBERED) &&
	     (text->hiddenlinkflag != HIDDENLINKS_MERGE ||
	      (LYNoISMAPifUSEMAP &&
	       HTAnchor_isISMAPScript(
		   HTAnchor_followMainLink((HTAnchor *)a->anchor)))));
	HTLine *last = text->last_line;
	HTLine *prev = text->last_line->prev;
	int CurBlankExtent = 0;
	int BlankExtent = 0;

	/*
	 *  Check if the anchor content has only
	 *  white and special characters, starting
	 *  with the content on the last line. - FM
	 */
	a->extent += (text->chars + last->size) - a->start -
		     (text->Lines - a->line_num);
	if (a->extent > last->size) {
	    /*
	     *  The anchor extends over more than one line,
	     *  so set up to check the entire last line. - FM
	     */
	    i = last->size;
	} else {
	    /*
	     *  The anchor is restricted to the last line,
	     *  so check from the start of the anchor. - FM
	     */
	    i = a->extent;
	}
	j = (last->size - i);
	while (j < last->size) {
	    if (!IsSpecialAttrChar(last->data[j]) &&
		!isspace((unsigned char)last->data[j]) &&
		last->data[j] != HT_NON_BREAK_SPACE &&
		last->data[j] != HT_EM_SPACE)
		break;
	    i--;
	    j++;
	}
	if (i == 0) {
	    if (a->extent > last->size) {
		/*
		 *  The anchor starts on a preceding line, and
		 *  the last line has only white and special
		 *  characters, so declare the entire extent
		 *  of the last line as blank. - FM
		 */
		CurBlankExtent = BlankExtent = last->size;
	    } else {
		/*
		 *  The anchor starts on the last line, and
		 *  has only white or special characters, so
		 *  declare the anchor's extent as blank. - FM
		 */
		CurBlankExtent = BlankExtent = a->extent;
	    }
	}
	/*
	 *  While the anchor starts on a line preceding
	 *  the one we just checked, and the one we just
	 *  checked has only white and special characters,
	 *  check whether the anchor's content on the
	 *  immediately preceding line also has only
	 *  white and special characters. - FM
	 */
	while (i == 0 && a->extent > CurBlankExtent) {
	    j = prev->size - a->extent + CurBlankExtent;
	    if (j < 0) {
		/*
		 *  The anchor starts on a preceding line,
		 *  so check all of this line. - FM
		 */
		j = 0;
		i = prev->size;
	    } else {
		/*
		 *  The anchor starts on this line. - FM
		 */
		i = a->extent - CurBlankExtent;
	    }
	    while (j < prev->size) {
		if (!IsSpecialAttrChar(prev->data[j]) &&
		    !isspace((unsigned char)prev->data[j]) &&
		    prev->data[j] != HT_NON_BREAK_SPACE &&
		    prev->data[j] != HT_EM_SPACE)
		    break;
		i--;
		j++;
	    }
	    if (i == 0) {
		if (a->extent > (CurBlankExtent + prev->size)) {
		    /*
		     *  This line has only white and special
		     *  characters, so treat its entire extent
		     *  as blank, and decrement the pointer for
		     *  the line to be analyzed. - FM
		     */
		    CurBlankExtent += prev->size;
		    BlankExtent = CurBlankExtent;
		    prev = prev->prev;
		} else {
		    /*
		     *  The anchor starts on this line, and it
		     *  has only white or special characters, so
		     *  declare the anchor's extent as blank. - FM
		     */
		    BlankExtent = a->extent;
		    break;
		}
	    }
	}
	if (i == 0) {
	    /*
	     *  It's an invisible anchor probably from an ALT=""
	     *  or an ignored ISMAP attribute due to a companion
	     *  USEMAP. - FM
	     */
	    a->show_anchor = NO;

	    /*
	     *  If links are numbered, then try to get rid of the
	     *  numbered bracket and adjust the anchor count. - FM
	     *
	     * Well, let's do this only if -hiddenlinks=merged is not in
	     * effect, or if we can be reasonably sure that
	     * this is the result of an intentional non-generation of
	     * anchor text via NO_ISMAP_IF_USEMAP. In other cases it can
	     * actually be a feature that numbered links alert the viewer
	     * to the presence of a link which is otherwise not selectable -
	     * possibly caused by HTML errors. - kw
	     */
	    if (remove_numbers_on_empty) {
		HTLine *start;
		int NumSize = 0;
		TextAnchor *anc;

		/*
		 *  Set start->data[j] to the close-square-bracket,
		 *  or to the beginning of the line on which the
		 *  anchor start. - FM
		 */
		if (prev == last->prev) {
		    /*
		     *  The anchor starts on the last line. - FM
		     */
		    start = last;
		    j = (last->size - a->extent - 1);
		} else {
		    /*
		     *  The anchor starts on a previous line. - FM
		     */
		    start = prev;
		    prev = prev->prev;
		    j = (start->size - a->extent + CurBlankExtent - 1);
		}
		if (j < 0)
		    j = 0;
		i = j;

		/*
		 *  If start->data[j] is a close-square-bracket, verify
		 *  that it's the end of the numbered bracket, and if so,
		 *  strip the numbered bracket.  If start->data[j] is not
		 *  a close-square-bracket, check whether we had a wrap
		 *  and the close-square-bracket is at the end of the
		 *  previous line.  If so, strip the numbered bracket
		 *  from that line. - FM
		 */
		if (start->data[j] == ']') {
		    j--;
		    NumSize++;
		    while (j >= 0 && isdigit((unsigned char)start->data[j])) {
			j--;
			NumSize++;
		    }
		    while (j < 0) {
			j++;
			NumSize--;
		    }
		    if (start->data[j] == '[') {
			/*
			 *  The numbered bracket is entirely
			 *  on this line. - FM
			 */
			NumSize++;
			k = j + NumSize;
			while (k < start->size)
			    start->data[j++] = start->data[k++];
			if (start != last)
			    text->chars -= NumSize;
			for (anc = a; anc; anc = anc->next) {
			    anc->start -= NumSize;
			    anc->line_pos -= NumSize;
			}
			start->size = j;
			start->data[j++] = '\0';
			while (j < k)
			     start->data[j++] = '\0';
		    } else if (prev && prev->size > 1) {
			k = (i + 1);
			j = (prev->size - 1);
			while ((j >= 0) && IsSpecialAttrChar(prev->data[j]))
			    j--;
			i = (j + 1);
			while (j >= 0 &&
			       isdigit((unsigned char)prev->data[j])) {
			    j--;
			    NumSize++;
			}
			while (j < 0) {
			    j++;
			    NumSize--;
			}
			if (prev->data[j] == '[') {
			    /*
			     *  The numbered bracket started on the
			     *  previous line, and part of it was
			     *  wrapped to this line. - FM
			     */
			    NumSize++;
			    l = (i - j);
			    while (i < prev->size)
				start->data[j++] = start->data[i++];
			    text->chars -= l;
			    for (anc = a; anc; anc = anc->next) {
				anc->start -= l;
			    }
			    prev->size = j;
			    prev->data[j] = '\0';
			    while (j < i)
				start->data[j++] = '\0';
			    j = 0;
			    i = k;
			    while (k < start->size)
				start->data[j++] = start->data[k++];
			    if (start != last)
				text->chars -= i;
			    for (anc = a; anc; anc = anc->next) {
				anc->start -= i;
				anc->line_pos -= i;
			    }
			    start->size = j;
			    start->data[j++] = '\0';
			    while (j < k)
				start->data[j++] = '\0';
			} else {
			    /*
			     *  Shucks!  We didn't find the
			     *  numbered bracket. - FM
			     */
			    a->show_anchor = YES;
			}
		    } else {
			/*
			 *  Shucks!  We didn't find the
			 *  numbered bracket. - FM
			 */
			a->show_anchor = YES;
		    }
		} else if (prev && prev->size > 2) {
		    j = (prev->size - 1);
		    while ((j >= 0) && IsSpecialAttrChar(prev->data[j]))
			j--;
		    if (j < 0)
			j = 0;
		    i = (j + 1);
		    if ((j > 2) &&
			(prev->data[j] == ']' &&
			 isdigit((unsigned char)prev->data[j - 1]))) {
			j--;
			NumSize++;
			k = (j + 1);
			while (j >= 0 &&
			       isdigit((unsigned char)prev->data[j])) {
			    j--;
			    NumSize++;
			}
			while (j < 0) {
			    j++;
			    NumSize--;
			}
			if (prev->data[j] == '[') {
			    /*
			     *  The numbered bracket is all on the
			     *  previous line, and the anchor content
			     *  was wrapped to the last line. - FM
			     */
			    NumSize++;
			    k = j + NumSize;
			    while (k < prev->size)
				prev->data[j++] = prev->data[k++];
			    text->chars -= NumSize;
			    for (anc = a; anc; anc = anc->next) {
				anc->start -= NumSize;
			    }
			    prev->size = j;
			    prev->data[j++] = '\0';
			    while (j < k)
				start->data[j++] = '\0';
			} else {
			    /*
			     *  Shucks!  We didn't find the
			     *  numbered bracket. - FM
			     */
			    a->show_anchor = YES;
			}
		    } else {
			/*
			 *  Shucks!  We didn't find the
			 *  numbered bracket. - FM
			 */
			a->show_anchor = YES;
		    }
		} else {
		    /*
		     *  Shucks!  We didn't find the
		     *  numbered bracket. - FM
		     */
		    a->show_anchor = YES;
		}
	    }
	} else {
	    /*
	     *  The anchor's content is not restricted to only
	     *  white and special characters, so we'll show it
	     *  as a link. - FM
	     */
	    a->show_anchor = YES;
	}
	if (a->show_anchor == NO) {
	    /*
	     *  The anchor's content is restricted to white
	     *  and special characters, so set it's number
	     *  and extent to zero, decrement the visible
	     *  anchor number counter, and add this anchor
	     *  to the hidden links list. - FM
	     */
	    a->extent = 0;
	    if (text->hiddenlinkflag != HIDDENLINKS_MERGE) {
		a->number = 0;
		text->last_anchor_number--;
		HText_AddHiddenLink(text, a);
	    }
	} else {
	    /*
	     *  The anchor's content is not restricted to white
	     *  and special characters, so we'll display the
	     *  content, but shorten it's extent by any trailing
	     *  blank lines we've detected. - FM
	     */
	    a->extent -= ((BlankExtent < a->extent) ?
					BlankExtent : 0);
	}
    } else {
	/*
	 *  It's a named anchor without an HREF, so it
	 *  should be registered but not shown as a
	 *  link. - FM
	 */
	a->show_anchor = NO;
	a->extent = 0;
    }
}


PUBLIC void HText_appendText ARGS2(
	HText *,	text,
	CONST char *,	str)
{
    CONST char *p;

    if (str == NULL)
	return;

    if (text->halted == 3)
	return;

    for (p = str; *p; p++) {
	HText_appendCharacter(text, *p);
    }
}


PRIVATE void remove_special_attr_chars ARGS1(
	char *,		buf)
{
    register char *cp;

    for (cp = buf; *cp != '\0' ; cp++) {
	/*
	 *  Don't print underline chars.
	 */
	if (!IsSpecialAttrChar(*cp)) {
	   *buf = *cp,
	   buf++;
	}
    }
    *buf = '\0';
}


/*
**  This function trims blank lines from the end of the document, and
**  then gets the hightext from the text by finding the char position,
**  and brings the anchors in line with the text by adding the text
**  offset to each of the anchors.
*/
PUBLIC void HText_endAppend ARGS1(
	HText *,	text)
{
    HTLine *line_ptr;

    if (!text)
	return;

    CTRACE(tfp,"Gridtext: Entering HText_endAppend\n");

    /*
     *  Create a  blank line at the bottom.
     */
    new_line(text);

    if (text->halted) {
	/*
	 *  If output was stopped because memory was low, and we made
	 *  it to the end of the document, reset those flags and hope
	 *  things are better now. - kw
	 */
	LYFakeZap(NO);
	text->halted = 0;
    }

    /*
     *  Get the first line.
     */
    line_ptr = text->last_line->next;

    /*
     *  Remove the blank lines at the end of document.
     */
    while (text->last_line->data[0] == '\0' && text->Lines > 2) {
	HTLine *next_to_the_last_line = text->last_line->prev;


	CTRACE(tfp, "GridText: Removing bottom blank line: %s\n",
			    text->last_line->data);
	/*
	 *  line_ptr points to the first line.
	 */
	next_to_the_last_line->next = line_ptr;
	line_ptr->prev = next_to_the_last_line;
	FREE(text->last_line);
	text->last_line = next_to_the_last_line;
	text->Lines--;
	CTRACE(tfp, "GridText: New bottom line: %s\n",
			    text->last_line->data);
    }

    /*
     *  Fix up the anchor structure values and
     *  create the hightext strings. - FM
     */
    HText_trimHightext(text, FALSE);
}


/*
**  This function gets the hightext from the text by finding the char
**  position, and brings the anchors in line with the text by adding the text
**  offset to each of the anchors.
**
**  `Forms input' fields cannot be displayed properly without this function
**  to be invoked (detected in display_partial mode).
**
**  (BOOLEAN value allow us to disable annoying repeated trace messages
**  for display_partial mode).
*/
PUBLIC void HText_trimHightext ARGS2(
	HText *,	text,
	BOOLEAN,	disable_trace)
{
    int cur_line, cur_char, cur_shift;
    TextAnchor *anchor_ptr;
    HTLine *line_ptr;
    unsigned char ch;

    if (!text)
	return;

    CTRACE(tfp,"Gridtext: Entering HText_trimHightext\n");

    if (disable_trace)
    CTRACE(tfp,"HText_trimHightext: trace disabled in display_partial mode\n");

    /*
     *  Get the first line.
     */
    line_ptr = text->last_line->next;
    cur_char = line_ptr->size;
    cur_line = 0;
    cur_shift = 0;

    /*
     *  Fix up the anchor structure values and
     *  create the hightext strings. - FM
     */
    for (anchor_ptr = text->first_anchor;
	 anchor_ptr; anchor_ptr=anchor_ptr->next) {
re_parse:
	/*
	 *  Find the right line.
	 */
	for (; anchor_ptr->start >= cur_char;
	       line_ptr = line_ptr->next,
	       cur_char += line_ptr->size+1,
	       cur_line++) {
	    ; /* null body */
	}
	if (anchor_ptr->start == cur_char) {
	    anchor_ptr->line_pos = line_ptr->size;
	} else {
	    anchor_ptr->line_pos = anchor_ptr->start -
				   (cur_char - line_ptr->size);
	}
	if (anchor_ptr->line_pos < 0)
	    anchor_ptr->line_pos = 0;

	if (!disable_trace)
	CTRACE(tfp, "Gridtext: Anchor found on line:%d col:%d\n",
			    cur_line, anchor_ptr->line_pos);

	/*
	 *  Strip off any spaces or SpecialAttrChars at the beginning,
	 *  if they exist, but only on HYPERTEXT_ANCHORS.
	 */
	if (anchor_ptr->link_type & HYPERTEXT_ANCHOR) {
	    ch = (unsigned char)line_ptr->data[anchor_ptr->line_pos];
	    while (isspace(ch) ||
		   IsSpecialAttrChar(ch)) {
		anchor_ptr->line_pos++;
		anchor_ptr->extent--;
		cur_shift++;
		ch = (unsigned char)line_ptr->data[anchor_ptr->line_pos];
	    }
	}
	if (anchor_ptr->extent < 0) {
	    anchor_ptr->extent = 0;
	}

	if (!disable_trace)
	CTRACE(tfp, "anchor text: '%s'\n",
					   line_ptr->data);
	/*
	 *  If the link begins with an end of line and we have more
	 *  lines, then start the highlighting on the next line. - FM
	 */
	if ((unsigned)anchor_ptr->line_pos >= strlen(line_ptr->data) &&
	    cur_line < text->Lines) {
	    anchor_ptr->start += (cur_shift + 1);
	    cur_shift = 0;
	    CTRACE(tfp, "found anchor at end of line\n");
	    goto re_parse;
	}
	cur_shift = 0;

	/*
	 *  Copy the link name into the data structure.
	 */
	if (line_ptr->data &&
	    anchor_ptr->extent > 0 && anchor_ptr->line_pos >= 0) {
	    StrnAllocCopy(anchor_ptr->hightext,
			  &line_ptr->data[anchor_ptr->line_pos],
			  anchor_ptr->extent);
	} else {
	    StrAllocCopy(anchor_ptr->hightext, "");
	}

	/*
	 *  If true the anchor extends over two lines,
	 *  copy that into the data structure.
	 */
	if ((unsigned)anchor_ptr->extent > strlen(anchor_ptr->hightext)) {
	    HTLine *line_ptr2 = line_ptr->next;
	    /*
	     *  Double check that we have a line pointer,
	     *  and if so, copy into hightext2.
	     */
	    if (line_ptr2) {
		StrnAllocCopy(anchor_ptr->hightext2,
			      line_ptr2->data,
			      (anchor_ptr->extent -
			       strlen(anchor_ptr->hightext)));
		anchor_ptr->hightext2offset = line_ptr2->offset;
		remove_special_attr_chars(anchor_ptr->hightext2);
		if (anchor_ptr->link_type & HYPERTEXT_ANCHOR) {
		    LYTrimTrailing(anchor_ptr->hightext2);
		    if (anchor_ptr->hightext2[0] == '\0') {
			FREE(anchor_ptr->hightext2);
			anchor_ptr->hightext2offset = 0;
		    }
		}
	    }
	}
	remove_special_attr_chars(anchor_ptr->hightext);
	if (anchor_ptr->link_type & HYPERTEXT_ANCHOR) {
	    LYTrimTrailing(anchor_ptr->hightext);
	}

	/*
	 *  Subtract any formatting characters from the x position
	 *  of the link.
	 */
	if (anchor_ptr->line_pos > 0) {
	    register int offset = 0, i = 0;
	    for (; i < anchor_ptr->line_pos; i++)
		if (IS_UTF_EXTRA(line_ptr->data[i]) ||
		    IsSpecialAttrChar(line_ptr->data[i]))
		    offset++;
	    anchor_ptr->line_pos -= offset;
	}

	/*
	 *  Add the offset, and set the line number.
	 */
	anchor_ptr->line_pos += line_ptr->offset;
	anchor_ptr->line_num  = cur_line;

	if (!disable_trace)
	CTRACE(tfp, "GridText:     add link on line %d col %d in HText_trimHightext\n",
		    cur_line, anchor_ptr->line_pos);

	/*
	 *  If this is the last anchor, we're done!
	 */
	if (anchor_ptr == text->last_anchor)
	    break;
    }
}


/*	Dump diagnostics to tfp
*/
PUBLIC void HText_dump ARGS1(
	HText *,	text GCC_UNUSED)
{
    fprintf(tfp, "HText: Dump called\n");
}


/*	Return the anchor associated with this node
*/
PUBLIC HTParentAnchor * HText_nodeAnchor ARGS1(
	HText *,	text)
{
    return text->node_anchor;
}

/*				GridText specials
**				=================
*/
/*
 *  HTChildAnchor() returns the anchor with index N.
 *  The index corresponds to the [number] we print for the anchor.
 */
PUBLIC HTChildAnchor * HText_childNumber ARGS1(
	int,		number)
{
    TextAnchor * a;

    if (!(HTMainText && HTMainText->first_anchor) || number <= 0)
	return (HTChildAnchor *)0;	/* Fail */

    for (a = HTMainText->first_anchor; a; a = a->next) {
	if (a->number == number)
	    return(a->anchor);
    }
    return (HTChildAnchor *)0;	/* Fail */
}

/*
 *  HText_FormDescNumber() returns a description of the form field
 *  with index N.  The index corresponds to the [number] we print
 *  for the field. - FM & LE
 */
PUBLIC void HText_FormDescNumber ARGS2(
	int,		number,
	char **,	desc)
{
    TextAnchor * a;

    if (!desc)
	return;

    if (!(HTMainText && HTMainText->first_anchor) || number <= 0) {
	 *desc = "unknown field or link";
	 return;
    }

    for (a = HTMainText->first_anchor; a; a = a->next) {
	if (a->number == number) {
	    if (!(a->input_field && a->input_field->type)) {
		*desc = "unknown field or link";
		return;
	    }
	    break;
	}
    }

    switch (a->input_field->type) {
	case F_TEXT_TYPE:
	    *desc = "text entry field";
	    return;
	case F_PASSWORD_TYPE:
	    *desc = "password entry field";
	    return;
	case F_CHECKBOX_TYPE:
	    *desc = "checkbox";
	    return;
	case F_RADIO_TYPE:
	    *desc = "radio button";
	    return;
	case F_SUBMIT_TYPE:
	    *desc = "submit button";
	    return;
	case F_RESET_TYPE:
	    *desc = "reset button";
	    return;
	case F_OPTION_LIST_TYPE:
	    *desc = "popup menu";
	    return;
	case F_HIDDEN_TYPE:
	    *desc = "hidden form field";
	    return;
	case F_TEXTAREA_TYPE:
	    *desc = "text entry area";
	    return;
	case F_RANGE_TYPE:
	    *desc = "range entry field";
	    return;
	case F_FILE_TYPE:
	    *desc = "file entry field";
	    return;
	case F_TEXT_SUBMIT_TYPE:
	    *desc = "text-submit field";
	    return;
	case F_IMAGE_SUBMIT_TYPE:
	    *desc = "image-submit button";
	    return;
	case F_KEYGEN_TYPE:
	    *desc = "keygen field";
	    return;
	default:
	    *desc = "unknown form field";
	    return;
    }
}

/*
 *  HTGetLinkInfo returns some link info based on the number.
 *
 *  If want_go is not NULL, caller requests to know a line number for
 *  the link indicated by number.  It will be returned in *go_line, and
 *  *linknum will be set to an index into the links[] array, to use after
 *  the line in *line has been made the new top screen line.
 *  *hightext and *lname are unchanged. - KW
 *
 *  If want_go is 0 and the number doesn't represent an input field, info
 *  on the link indicated by number is deposited in *hightext and *lname.
 */
PUBLIC int HTGetLinkInfo ARGS6(
	int,		number,
	int,		want_go,
	int *,		go_line,
	int *,		linknum,
	char **,	hightext,
	char **,	lname)
{
    TextAnchor *a;
    HTAnchor *link_dest;
#ifndef DONT_TRACK_INTERNAL_LINKS
    HTAnchor *link_dest_intl = NULL;
#endif
    int anchors_this_line = 0, anchors_this_screen = 0;
    int prev_anchor_line = -1, prev_prev_anchor_line = -1;

    if (!HTMainText)
	return(NO);

    for (a = HTMainText->first_anchor; a; a = a->next) {
	/*
	 *  Count anchors, first on current line if there is more
	 *  than one.  We have to count all links, including form
	 *  field anchors and others with a->number == 0, because
	 *  they are or will be included in the links[] array.
	 *  The exceptions are hidden form fields and anchors with
	 *  show_anchor not set, because they won't appear in links[]
	 *  and don't count towards nlinks. - KW
	 */
	if ((a->show_anchor) &&
	    (a->link_type != INPUT_ANCHOR ||
	     a->input_field->type != F_HIDDEN_TYPE)) {
	    if (a->line_num == prev_anchor_line) {
		anchors_this_line++;
	    } else {
		/*
		 *  This anchor is on a different line than the previous one.
		 *  Remember which was the line number of the previous anchor,
		 *  for use in screen positioning later. - KW
		 */
		anchors_this_line = 1;
		prev_prev_anchor_line = prev_anchor_line;
		prev_anchor_line = a->line_num;
	    }
	    if (a->line_num >= HTMainText->top_of_screen) {
		/*
		 *  Count all anchors starting with the top line of the
		 *  currently displayed screen.  Just keep on counting
		 *  beyond this screen's bottom line - we'll know whether
		 *  a found anchor is below the current screen by a check
		 *  against nlinks later. - KW
		 */
		anchors_this_screen++;
	    }
	}

	if (a->number == number) {
	    /*
	     *  We found it.  Now process it, depending
	     *  on what kind of info is requested. - KW
	     */
	    if (want_go || a->link_type == INPUT_ANCHOR) {
		if (a->show_anchor == NO) {
		    /*
		     *  The number requested has been assigned to an anchor
		     *  without any selectable text, so we cannot position
		     *  on it.  The code for suppressing such anchors in
		     *  HText_endAnchor() may not have applied, or it may
		     *  have failed.  Return a failure indication so that
		     *  the user will notice that something is wrong,
		     *  instead of positioning on some other anchor which
		     *  might result in inadvertent activation. - KW
		     */
		    return(NO);
		}
		if (anchors_this_screen > 0 &&
		    anchors_this_screen <= nlinks &&
		    a->line_num >= HTMainText->top_of_screen &&
		    a->line_num < HTMainText->top_of_screen+(display_lines)) {
		    /*
		     *  If the requested anchor is within the current screen,
		     *  just set *go_line so that the screen window won't move
		     *  (keep it as it is), and set *linknum to the index of
		     *  this link in the current links[] array. - KW
		     */
		    *go_line = HTMainText->top_of_screen;
		    if (linknum)
			*linknum = anchors_this_screen - 1;
		} else {
		    /*
		     *  if the requested anchor is not within the currently
		     *  displayed screen, set *go_line such that the top line
		     *  will be either
		     *  (1) the line immediately below the previous
		     *      anchor, or
		     *  (2) about one third of a screenful above the line
		     *      with the target, or
		     *  (3) the first line of the document -
		     *  whichever comes last.  In all cases the line with our
		     *  target will end up being the first line with any links
		     *  on the new screen, so that we can use the
		     *  anchors_this_line counter to point to the anchor in
		     *  the new links[] array.  - kw
		     */
		    int max_offset = SEARCH_GOAL_LINE - 1;
		    if (max_offset < 0)
			max_offset = 0;
		    else if (max_offset >= display_lines)
			max_offset = display_lines - 1;
		    *go_line = prev_anchor_line - max_offset;
		    if (*go_line <= prev_prev_anchor_line)
			*go_line = prev_prev_anchor_line + 1;
		    if (*go_line < 0)
			*go_line = 0;
		    if (linknum)
			*linknum = anchors_this_line - 1;
		}
		return(LINK_LINE_FOUND);
	    } else {
		*hightext= a->hightext;
		link_dest = HTAnchor_followMainLink((HTAnchor *)a->anchor);
		{
		    char *cp_freeme = NULL;
		    if (traversal) {
			cp_freeme = stub_HTAnchor_address(link_dest);
		    } else {
#ifndef DONT_TRACK_INTERNAL_LINKS
			if (a->link_type == INTERNAL_LINK_ANCHOR) {
			    link_dest_intl = HTAnchor_followTypedLink(
				(HTAnchor *)a->anchor, LINK_INTERNAL);
			    if (link_dest_intl && link_dest_intl != link_dest) {

				CTRACE(tfp, "HTGetLinkInfo: unexpected typed link to %s!\n",
					    link_dest_intl->parent->address);
				link_dest_intl = NULL;
			    }
			}
			if (link_dest_intl) {
			    char *cp2 = HTAnchor_address(link_dest_intl);
			    FREE(*lname);
			    *lname = cp2;
			    return(WWW_INTERN_LINK_TYPE);
			} else
#endif
			    cp_freeme = HTAnchor_address(link_dest);
		    }
		    StrAllocCopy(*lname, cp_freeme);
		    FREE(cp_freeme);
		}
		return(WWW_LINK_TYPE);
	    }
	}
    }
    return(NO);
}

/*
 *  This function finds the line indicated by line_num in the
 *  HText structure indicated by text, and searches that line
 *  for the first hit with the string indicated by target.  If
 *  there is no hit, FALSE is returned.  If there is a hit, then
 *  a copy of the line starting at that first hit is loaded into
 *  *data with all IsSpecial characters stripped, it's offset and
 *  the printable target length (without IsSpecial, or extra CJK
 *  or utf8 characters) are loaded into *offset and *tLen, and
 *  TRUE is returned. - FM
 */
PUBLIC BOOL HText_getFirstTargetInLine ARGS7(
	HText *,	text,
	int,		line_num,
	BOOL,		utf_flag,
	int *,		offset,
	int *,		tLen,
	char **,	data,
	char *,		target)
{
    HTLine *line;
    char *LineData;
    int LineOffset, HitOffset, LenNeeded, i;
    char *cp;

    /*
     *  Make sure we have an HText structure, that line_num is
     *  in its range, and that we have a target string. - FM
     */
    if (!(text && line_num >= 0 && line_num <= text->Lines &&
	  target && *target))
	return(FALSE);

    /*
     *  Find the line and set up its data and offset - FM
     */
    for (i = 0, line = text->last_line->next;
	 i < line_num && (line != text->last_line);
	 i++, line = line->next) {
	if (line->next == NULL) {
	    return(FALSE);
	}
    }
    if (!line && line->data[0])
	return(FALSE);
    LineData = (char *)line->data;
    LineOffset = (int)line->offset;

    /*
     *  If the target is on the line, load the offset of
     *  its first character and the subsequent line data,
     *  strip any special characters from the loaded line
     *  data, and return TRUE. - FM
     */
    if ((case_sensitive ?
	 (cp = LYno_attr_mbcs_strstr(LineData,
				     target,
				     utf_flag,
				     &HitOffset,
				     &LenNeeded)) != NULL :
	 (cp = LYno_attr_mbcs_case_strstr(LineData,
				     target,
				     utf_flag,
				     &HitOffset,
				     &LenNeeded)) != NULL) &&
	(LineOffset + LenNeeded) < LYcols) {
	/*
	 *  We had a hit so load the results,
	 *  remove IsSpecial characters from
	 *  the allocated data string, and
	 *  return TRUE. - FM
	 */
	*offset = (LineOffset + HitOffset);
	*tLen = (LenNeeded - HitOffset);
	 StrAllocCopy(*data, cp);
	 remove_special_attr_chars(*data);
	 return(TRUE);
    }

    /*
     *  The line does not contain the target. - FM
     */
    return(FALSE);
}

/*
 *  HText_getNumOfLines returns the number of lines in the
 *  current document.
 */
PUBLIC int HText_getNumOfLines NOARGS
{
    return(HTMainText ? HTMainText->Lines : 0);
}

/*
 *  HText_getTitle returns the title of the
 *  current document.
 */
PUBLIC CONST char * HText_getTitle NOARGS
{
    return(HTMainText ?
	  HTAnchor_title(HTMainText->node_anchor) : 0);
}

#ifdef USE_HASH
PUBLIC CONST char *HText_getStyle NOARGS
{
   return(HTMainText ?
	  HTAnchor_style(HTMainText->node_anchor) : 0);
}
#endif

/*
 *  HText_getSugFname returns the suggested filename of the current
 *  document (normally derived from a Content-Disposition header with
 *  attachment; filename=name.suffix). - FM
 */
PUBLIC CONST char * HText_getSugFname NOARGS
{
    return(HTMainText ?
	  HTAnchor_SugFname(HTMainText->node_anchor) : 0);
}

/*
 *  HTCheckFnameForCompression receives the address of an allocated
 *  string containing a filename, and an anchor pointer, and expands
 *  or truncates the string's suffix if appropriate, based on whether
 *  the anchor indicates that the file is compressed.  We assume
 *  that the file was not uncompressed (as when downloading), and
 *  believe the headers about whether it's compressed or not. - FM
 *
 *  Added third arg - if strip_ok is FALSE, we don't trust the anchor
 *  info enough to remove a compression suffix if the anchor object
 *  does not indicate compression. - kw
 */
PUBLIC void HTCheckFnameForCompression ARGS3(
	char **,		fname,
	HTParentAnchor *,	anchor,
	BOOL,			strip_ok)
{
    char *fn = *fname;
    char *dot = NULL, *cp = NULL;
    CONST char *ct = NULL;
    CONST char *ce = NULL;
    BOOLEAN method = 0;

    /*
     *  Make sure we have a string and anchor. - FM
     */
    if (!(fn && *fn && anchor))
	return;

    /*
     *  Make sure we have a file, not directory, name. -FM
     */
    if ((cp = strrchr(fn, '/')) != NULL) {
	fn = (cp +1);
	if (*fn == '\0') {
	    return;
	}
    }

    /*
     *  Check the anchor's content_type and content_encoding
     *  elements for a gzip or Unix compressed file. - FM
     */
    ct = HTAnchor_content_type(anchor);
    ce = HTAnchor_content_encoding(anchor);
    if (ce == NULL) {
	/*
	 *  No Content-Encoding, so check
	 *  the Content-Type. - FM
	 */
	if (!strncasecomp((ct ? ct : ""), "application/gzip", 16) ||
	    !strncasecomp((ct ? ct : ""), "application/x-gzip", 18)) {
	    method = 1;
	} else if (!strncasecomp((ct ? ct : ""),
				 "application/compress", 20) ||
		   !strncasecomp((ct ? ct : ""),
				 "application/x-compress", 22)) {
	    method = 2;
	}
    } else if (!strcasecomp(ce, "gzip") ||
	       !strcasecomp(ce, "x-gzip")) {
	    /*
	     *  It's gzipped. - FM
	     */
	    method = 1;
    } else if (!strcasecomp(ce, "compress") ||
	       !strcasecomp(ce, "x-compress")) {
	    /*
	     *  It's Unix compressed. - FM
	     */
	    method = 2;
    }

    /*
     *  If no Content-Encoding has been detected via the anchor
     *  pointer, but strip_ok is not set, there is nothing left
     *  to do. - kw
     */
    if (method == 0 && !strip_ok)
	return;

    /*
     *  Seek the last dot, and check whether
     *  we have a gzip or compress suffix. - FM
     */
    if ((dot = strrchr(fn, '.')) != NULL) {
	if (!strcasecomp(dot, ".tgz") ||
	    !strcasecomp(dot, ".gz") ||
	    !strcasecomp(dot, ".Z")) {
	    if (!method) {
		/*
		 *  It has a suffix which signifies a gzipped
		 *  or compressed file for us, but the anchor
		 *  claims otherwise, so tweak the suffix. - FM
		 */
		cp = (dot + 1);
		*dot = '\0';
		if (!strcasecomp(cp, "tgz")) {
		    StrAllocCat(*fname, ".tar");
		}
	    }
	    return;
	}
	if (strlen(dot) > 4) {
	    cp = ((dot + strlen(dot)) - 3);
	    if (!strcasecomp(cp, "-gz") ||
		!strcasecomp(cp, "_gz")) {
		if (!method) {
		    /*
		     *  It has a tail which signifies a gzipped
		     *  file for us, but the anchor claims otherwise,
		     *  so tweak the suffix. - FM
		     */
		    *dot = '\0';
		} else {
		    /*
		     *  The anchor claims it's gzipped, and we
		     *  believe it, so force this tail to the
		     *  conventional suffix. - FM
		     */
#ifdef VMS
		    *cp = '-';
#else
		    *cp = '.';
#endif /* VMS */
		    cp++;
		    *cp = TOLOWER(*cp);
		    cp++;
		    *cp = TOLOWER(*cp);
		}
		return;
	    }
	}
	if (strlen(dot) > 3) {
	    cp = ((dot + strlen(dot)) - 2);
	    if (!strcasecomp(cp, "-Z") ||
		!strcasecomp(cp, "_Z")) {
		if (!method) {
		    /*
		     *  It has a tail which signifies a compressed
		     *  file for us, but the anchor claims otherwise,
		     *  so tweak the suffix. - FM
		     */
		    *dot = '\0';
		} else {
		    /*
		     *  The anchor claims it's compressed, and
		     *  we believe it, so force this tail to the
		     *  conventional suffix. - FM
		     */
#ifdef VMS
		    *cp = '-';
#else
		    *cp = '.';
#endif /* VMS */
		    cp++;
		    *cp = TOUPPER(*cp);
		}
		return;
	    }
	}
    }
    if (!method) {
	/*
	 *  Don't know what compression method
	 *  was used, if any, so we won't do
	 *  anything. - FM
	 */
	return;
    }

    /*
     *  Add the appropriate suffix. - FM
     */
    if (!dot) {
	StrAllocCat(*fname, ((method == 1) ? ".gz" : ".Z"));
	return;
    }
    dot++;
    if (*dot == '\0') {
	StrAllocCat(*fname, ((method == 1) ? "gz" : "Z"));
	return;
    }
#ifdef VMS
    StrAllocCat(*fname, ((method == 1) ? "-gz" : "-Z"));
#else
    StrAllocCat(*fname, ((method == 1) ? ".gz" : ".Z"));
#endif /* !VMS */
    return;
}

/*
 *  HText_getLastModified returns the Last-Modified header
 *  if available, for the current document. - FM
 */
PUBLIC CONST char * HText_getLastModified NOARGS
{
    return(HTMainText ?
	  HTAnchor_last_modified(HTMainText->node_anchor) : 0);
}

/*
 *  HText_getDate returns the Date header
 *  if available, for the current document. - FM
 */
PUBLIC CONST char * HText_getDate NOARGS
{
    return(HTMainText ?
	  HTAnchor_date(HTMainText->node_anchor) : 0);
}

/*
 *  HText_getServer returns the Server header
 *  if available, for the current document. - FM
 */
PUBLIC CONST char * HText_getServer NOARGS
{
    return(HTMainText ?
	  HTAnchor_server(HTMainText->node_anchor) : 0);
}

/*
 *  HText_pageDisplay displays a screen of text
 *  starting from the line 'line_num'-1.
 *  This is the primary call for lynx.
 */
PUBLIC void HText_pageDisplay ARGS2(
	int,		line_num,
	char *,		target)
{
    CTRACE(tfp, "GridText: HText_pageDisplay at line %d started\n", line_num);

#ifdef DISP_PARTIAL
    if (display_partial && detected_forms_input_partial) {
	/*
	**  Garbage is reported from forms input fields in incremental mode.
	**  So we start HText_trimHightext() to forget this side effect.
	**  This function was split-out from HText_endAppend().
	**  It may not be the best solution but it works. - LP
	**  (TRUE =  to disable annoying repeated trace messages)
	**
	**  Side effect is reported from multiply call of HText_trimHightext.
	*/
	HText_trimHightext(HTMainText, TRUE);
    }
    detected_forms_input_partial = FALSE;
#endif

    display_page(HTMainText, line_num-1, target);

#ifdef DISP_PARTIAL
    if (display_partial && debug_display_partial)
	sleep(MessageSecs);
#endif

    is_www_index = HTAnchor_isIndex(HTMainAnchor);

    CTRACE(tfp, "GridText: HText_pageDisplay finished\n");
}

/*
 *  Return YES if we have a whereis search target on the displayed
 *  page. - kw
 */
PUBLIC BOOL HText_pageHasPrevTarget NOARGS
{
    if (!HTMainText)
	return NO;
    else
	return HTMainText->page_has_target;
}

/*
 *  HText_LinksInLines returns the number of links in the
 *  'Lines' number of lines beginning with 'line_num'-1. - FM
 */
PUBLIC int HText_LinksInLines ARGS3(
	HText *,	text,
	int,		line_num,
	int,		Lines)
{
    int total = 0;
    int start = (line_num - 1);
    int end = (start + Lines);
    TextAnchor *Anchor_ptr = NULL;

    if (!text)
	return total;

    for (Anchor_ptr = text->first_anchor;
		Anchor_ptr != NULL && Anchor_ptr->line_num <= end;
			Anchor_ptr = Anchor_ptr->next) {
	if (Anchor_ptr->line_num >= start &&
	    Anchor_ptr->line_num < end &&
	    Anchor_ptr->show_anchor &&
	    (Anchor_ptr->link_type != INPUT_ANCHOR ||
	     Anchor_ptr->input_field->type != F_HIDDEN_TYPE))
	    ++total;
	if (Anchor_ptr == text->last_anchor)
	    break;
    }

    return total;
}

PUBLIC void HText_setStale ARGS1(
	HText *,	text)
{
    text->stale = YES;
}

PUBLIC void HText_refresh ARGS1(
	HText *,	text)
{
    if (text->stale)
	display_page(text, text->top_of_screen, "");
}

PUBLIC int HText_sourceAnchors ARGS1(
	HText *,	text)
{
    return (text ? text->last_anchor_number : -1);
}

PUBLIC BOOL HText_canScrollUp ARGS1(
	HText *,	text)
{
    return (text->top_of_screen != 0);
}

PUBLIC BOOL HText_canScrollDown NOARGS
{
    HText * text = HTMainText;

    return (text != 0)
     && ((text->top_of_screen + display_lines) < text->Lines+1);
}

/*		Scroll actions
*/
PUBLIC void HText_scrollTop ARGS1(
	HText *,	text)
{
    display_page(text, 0, "");
}

PUBLIC void HText_scrollDown ARGS1(
	HText *,	text)
{
    display_page(text, text->top_of_screen + display_lines, "");
}

PUBLIC void HText_scrollUp ARGS1(
	HText *,	text)
{
    display_page(text, text->top_of_screen - display_lines, "");
}

PUBLIC void HText_scrollBottom ARGS1(
	HText *,	text)
{
    display_page(text, text->Lines - display_lines, "");
}


/*		Browsing functions
**		==================
*/

/* Bring to front and highlight it
*/
PRIVATE int line_for_char ARGS2(
	HText *,	text,
	int,		char_num)
{
    int line_number = 0;
    int characters = 0;
    HTLine * line = text->last_line->next;
    for (;;) {
	if (line == text->last_line) return 0;	/* Invalid */
	characters = characters + line->size + 1;
	if (characters > char_num) return line_number;
	line_number ++;
	line = line->next;
    }
}

PUBLIC BOOL HText_select ARGS1(
	HText *,	text)
{
    if (text != HTMainText) {
	HTMainText = text;
	HTMainAnchor = text->node_anchor;

	/*
	 *  Reset flag for whereis search string - cannot be true here
	 *  since text is not our HTMainText. - kw
	 */
	if (text)
	    text->page_has_target = NO;
	/*
	 *  Make this text the most current in the loaded texts list. - FM
	 */
	if (loaded_texts && HTList_removeObject(loaded_texts, text))
	    HTList_addObject(loaded_texts, text);
	  /* let lynx do it */
	/* display_page(text, text->top_of_screen, ""); */
    }
    return YES;
}

/*
 *  This function returns TRUE if doc's post_data, address
 *  and isHEAD elements are identical to those of a loaded
 *  (memory cached) text. - FM
 */
PUBLIC BOOL HText_POSTReplyLoaded ARGS1(
	document *,	doc)
{
    HText *text = NULL;
    HTList *cur = loaded_texts;
    char *post_data, *address;
    BOOL is_head;

    /*
     *  Make sure we have the structures. - FM
     */
    if (!cur || !doc)
	return(FALSE);

    /*
     *  Make sure doc is for a POST reply. - FM
     */
    if ((post_data = doc->post_data) == NULL ||
	(address = doc->address) == NULL)
	return(FALSE);
    is_head = doc->isHEAD;

    /*
     *  Loop through the loaded texts looking for a
     *  POST reply match. - FM
     */
    while (NULL != (text = (HText *)HTList_nextObject(cur))) {
	if (text->node_anchor &&
	    text->node_anchor->post_data &&
	    !strcmp(post_data, text->node_anchor->post_data) &&
	    text->node_anchor->address &&
	    !strcmp(address, text->node_anchor->address) &&
	    is_head == text->node_anchor->isHEAD) {
	    return(TRUE);
	}
    }

    return(FALSE);
}

PUBLIC BOOL HTFindPoundSelector ARGS1(
	char *,		selector)
{
    TextAnchor * a;

    for (a=HTMainText->first_anchor; a; a=a->next) {

	if (a->anchor && a->anchor->tag)
	    if (!strcmp(a->anchor->tag, selector)) {

		www_search_result = a->line_num+1;

		CTRACE(tfp,
		       "HText: Selecting anchor [%d] at character %d, line %d\n",
				     a->number, a->start, www_search_result);
		if (!strcmp(selector, LYToolbarName))
		    --www_search_result;

		 return(YES);
	    }
    }

    return(NO);

}

PUBLIC BOOL HText_selectAnchor ARGS2(
	HText *,		text,
	HTChildAnchor *,	anchor)
{
    TextAnchor * a;

/* This is done later, hence HText_select is unused in GridText.c
   Should it be the contrary ? @@@
    if (text != HTMainText) {
	HText_select(text);
    }
*/

    for (a=text->first_anchor; a; a=a->next) {
	if (a->anchor == anchor) break;
    }
    if (!a) {
	CTRACE(tfp, "HText: No such anchor in this text!\n");
	return NO;
    }

    if (text != HTMainText) {		/* Comment out by ??? */
	HTMainText = text;		/* Put back in by tbl 921208 */
	HTMainAnchor = text->node_anchor;
    }

    {
	 int l = line_for_char(text, a->start);
	 CTRACE(tfp,
	    "HText: Selecting anchor [%d] at character %d, line %d\n",
	    a->number, a->start, l);

	if ( !text->stale &&
	     (l >= text->top_of_screen) &&
	     ( l < text->top_of_screen + display_lines+1))
		 return YES;

	www_search_result = l - (display_lines/3); /* put in global variable */
    }

    return YES;
}


/*		Editing functions		- NOT IMPLEMENTED
**		=================
**
**	These are called from the application. There are many more functions
**	not included here from the original text object.
*/

/*	Style handling:
*/
/*	Apply this style to the selection
*/
PUBLIC void HText_applyStyle ARGS2(
	HText *,	me GCC_UNUSED,
	HTStyle *,	style GCC_UNUSED)
{

}


/*	Update all text with changed style.
*/
PUBLIC void HText_updateStyle ARGS2(
	HText *,	me GCC_UNUSED,
	HTStyle *,	style GCC_UNUSED)
{

}


/*	Return style of  selection
*/
PUBLIC HTStyle * HText_selectionStyle ARGS2(
	HText *,		me GCC_UNUSED,
	HTStyleSheet *,		sheet GCC_UNUSED)
{
    return 0;
}


/*	Paste in styled text
*/
PUBLIC void HText_replaceSel ARGS3(
	HText *,	me GCC_UNUSED,
	CONST char *,	aString GCC_UNUSED,
	HTStyle *,	aStyle GCC_UNUSED)
{
}


/*	Apply this style to the selection and all similarly formatted text
**	(style recovery only)
*/
PUBLIC void HTextApplyToSimilar ARGS2(
	HText *,	me GCC_UNUSED,
	HTStyle *,	style GCC_UNUSED)
{

}


/*	Select the first unstyled run.
**	(style recovery only)
*/
PUBLIC void HTextSelectUnstyled ARGS2(
	HText *,		me GCC_UNUSED,
	HTStyleSheet *,		sheet GCC_UNUSED)
{

}


/*	Anchor handling:
*/
PUBLIC void HText_unlinkSelection ARGS1(
	HText *,	me GCC_UNUSED)
{

}

PUBLIC HTAnchor * HText_referenceSelected ARGS1(
	HText *,	me GCC_UNUSED)
{
     return 0;
}


PUBLIC int HText_getTopOfScreen NOARGS
{
      HText * text = HTMainText;
      return text != 0 ? text->top_of_screen : 0;
}

PUBLIC int HText_getLines ARGS1(
	HText *,	text)
{
      return text->Lines;
}

PUBLIC HTAnchor * HText_linkSelTo ARGS2(
	HText *,	me GCC_UNUSED,
	HTAnchor *,	anchor GCC_UNUSED)
{
    return 0;
}

/*
 *  Utility for freeing the list of previous isindex and whereis queries. - FM
 */
PUBLIC void HTSearchQueries_free NOARGS
{
    char *query;
    HTList *cur = search_queries;

    if (!cur)
	return;

    while (NULL != (query = (char *)HTList_nextObject(cur))) {
	FREE(query);
    }
    HTList_delete(search_queries);
    search_queries = NULL;
    return;
}

/*
 *  Utility for listing isindex and whereis queries, making
 *  any repeated queries the most current in the list. - FM
 */
PUBLIC void HTAddSearchQuery ARGS1(
	char *,		query)
{
    char *new;
    char *old;
    HTList *cur;

    if (!(query && *query))
	return;

    if ((new = (char *)calloc(1, (strlen(query) + 1))) == NULL)
	outofmem(__FILE__, "HTAddSearchQuery");
    strcpy(new, query);

    if (!search_queries) {
	search_queries = HTList_new();
	atexit(HTSearchQueries_free);
	HTList_addObject(search_queries, new);
	return;
    }

    cur = search_queries;
    while (NULL != (old = (char *)HTList_nextObject(cur))) {
	if (!strcmp(old, new)) {
	    HTList_removeObject(search_queries, old);
	    FREE(old);
	    break;
	}
    }
    HTList_addObject(search_queries, new);

    return;
}

PUBLIC int do_www_search ARGS1(
	document *,	doc)
{
    char searchstring[256], temp[256], *cp, *tmpaddress = NULL;
    int ch, recall;
    int QueryTotal;
    int QueryNum;
    BOOLEAN PreviousSearch = FALSE;

    /*
     *  Load the default query buffer
     */
    if ((cp=strchr(doc->address, '?')) != NULL) {
	/*
	 *  This is an index from a previous search.
	 *  Use its query as the default.
	 */
	PreviousSearch = TRUE;
	strcpy(searchstring, ++cp);
	for (cp=searchstring; *cp; cp++)
	    if (*cp == '+')
		*cp = ' ';
	HTUnEscape(searchstring);
	strcpy(temp, searchstring);
	/*
	 *  Make sure it's treated as the most recent query. - FM
	 */
	HTAddSearchQuery(searchstring);
    } else {
	/*
	 *  New search; no default.
	 */
	searchstring[0] = '\0';
	temp[0] = '\0';
    }

    /*
     *  Prompt for a query string.
     */
    if (searchstring[0] == '\0') {
	if (HTMainAnchor->isIndexPrompt)
	    _statusline(HTMainAnchor->isIndexPrompt);
	else
	    _statusline(ENTER_DATABASE_QUERY);
    } else
	_statusline(EDIT_CURRENT_QUERY);
    QueryTotal = (search_queries ? HTList_count(search_queries) : 0);
    recall = (((PreviousSearch && QueryTotal >= 2) ||
	       (!PreviousSearch && QueryTotal >= 1)) ? RECALL : NORECALL);
    QueryNum = QueryTotal;
get_query:
    if ((ch=LYgetstr(searchstring, VISIBLE,
		     sizeof(searchstring), recall)) < 0 ||
	*searchstring == '\0' || ch == UPARROW || ch == DNARROW) {
	if (recall && ch == UPARROW) {
	    if (PreviousSearch) {
		/*
		 *  Use the second to last query in the list. - FM
		 */
		QueryNum = 1;
		PreviousSearch = FALSE;
	    } else {
		/*
		 *  Go back to the previous query in the list. - FM
		 */
		QueryNum++;
	    }
	    if (QueryNum >= QueryTotal)
		/*
		 *  Roll around to the last query in the list. - FM
		 */
		QueryNum = 0;
	    if ((cp=(char *)HTList_objectAt(search_queries,
					    QueryNum)) != NULL) {
		strcpy(searchstring, cp);
		if (*temp && !strcmp(temp, searchstring)) {
		    _statusline(EDIT_CURRENT_QUERY);
		} else if ((*temp && QueryTotal == 2) ||
			   (!(*temp) && QueryTotal == 1)) {
		    _statusline(EDIT_THE_PREV_QUERY);
		} else {
		    _statusline(EDIT_A_PREV_QUERY);
		}
		goto get_query;
	    }
	} else if (recall && ch == DNARROW) {
	    if (PreviousSearch) {
		/*
		 *  Use the first query in the list. - FM
		 */
		QueryNum = QueryTotal - 1;
		PreviousSearch = FALSE;
	    } else {
		/*
		 *  Advance to the next query in the list. - FM
		 */
		QueryNum--;
	    }
	    if (QueryNum < 0)
		/*
		 *  Roll around to the first query in the list. - FM
		 */
		QueryNum = QueryTotal - 1;
	    if ((cp=(char *)HTList_objectAt(search_queries,
					    QueryNum)) != NULL) {
		strcpy(searchstring, cp);
		if (*temp && !strcmp(temp, searchstring)) {
		    _statusline(EDIT_CURRENT_QUERY);
		} else if ((*temp && QueryTotal == 2) ||
			   (!(*temp) && QueryTotal == 1)) {
		    _statusline(EDIT_THE_PREV_QUERY);
		} else {
		    _statusline(EDIT_A_PREV_QUERY);
		}
		goto get_query;
	    }
	}

	/*
	 *  Search cancelled.
	 */
	HTInfoMsg(CANCELLED);
	return(NULLFILE);
    }

    /*
     *  Strip leaders and trailers. - FM
     */
    LYTrimLeading(searchstring);
    if (!(*searchstring)) {
	HTInfoMsg(CANCELLED);
	return(NULLFILE);
    }
    LYTrimTrailing(searchstring);

    /*
     *  Don't resubmit the same query unintentionally.
     */
    if (!LYforce_no_cache && 0 == strcmp(temp, searchstring)) {
	HTUserMsg(USE_C_R_TO_RESUB_CUR_QUERY);
	return(NULLFILE);
    }

    /*
     *  Add searchstring to the query list,
     *  or make it the most current. - FM
     */
    HTAddSearchQuery(searchstring);

    /*
     *  Show the URL with the new query.
     */
    if ((cp=strchr(doc->address, '?')) != NULL)
	*cp = '\0';
    StrAllocCopy(tmpaddress, doc->address);
    StrAllocCat(tmpaddress, "?");
    StrAllocCat(tmpaddress, searchstring);
    user_message(WWW_WAIT_MESSAGE, tmpaddress);
#ifndef VMS
#ifdef SYSLOG_REQUESTED_URLS
    syslog(LOG_INFO|LOG_LOCAL5, "%s", tmpaddress);
#endif /* SYSLOG_REQUESTED_URLS */
#endif /* !VMS */
    FREE(tmpaddress);
    if (cp)
	*cp = '?';

    /*
     *  OK, now we do the search.
     */
    if (HTSearch(searchstring, HTMainAnchor)) {
	/*
	 *	Memory leak fixed.
	 *	05-28-94 Lynx 2-3-1 Garrett Arch Blythe
	 */
	auto char *cp_freeme = NULL;
	if (traversal)
	    cp_freeme = stub_HTAnchor_address((HTAnchor *)HTMainAnchor);
	else
	    cp_freeme = HTAnchor_address((HTAnchor *)HTMainAnchor);
	StrAllocCopy(doc->address, cp_freeme);
	FREE(cp_freeme);

	CTRACE(tfp,"\ndo_www_search: newfile: %s\n",doc->address);

	/*
	 *  Yah, the search succeeded.
	 */
	return(NORMAL);
    }

    /*
     *  Either the search failed (Yuk), or we got redirection.
     *  If it's redirection, use_this_url_instead is set, and
     *  mainloop() will deal with it such that security features
     *  and restrictions are checked before acting on the URL, or
     *  rejecting it. - FM
     */
    return(NOT_FOUND);
}

/*
 *  Print the contents of the file in HTMainText to
 *  the file descriptor fp.
 *  If is_reply is TRUE add ">" to the beginning of each
 *  line to specify the file is a reply to message.
 */
PUBLIC void print_wwwfile_to_fd ARGS2(
	FILE *,		fp,
	int,		is_reply)
{
    register int i;
    int first = TRUE;
    HTLine * line;
#ifdef VMS
    extern BOOLEAN HadVMSInterrupt;
#endif /* VMS */

    if (!HTMainText)
	return;

    line = HTMainText->last_line->next;
    for (;; line = line->next) {
	if (!first
	 && line->data[0] != LY_SOFT_NEWLINE)
	    fputc('\n',fp);
	first = FALSE;

	/*
	 *  Add news-style quotation if requested. - FM
	 */
	if (is_reply) {
	    fputc('>',fp);
	}

	/*
	 *  Add offset.
	 */
	for (i = 0; i < (int)line->offset; i++) {
	     fputc(' ', fp);
	}

	/*
	 *  Add data.
	 */
	for (i = 0; line->data[i] != '\0'; i++) {
	    if (!IsSpecialAttrChar(line->data[i])) {
		fputc(line->data[i], fp);
	    } else if (line->data[i] == LY_SOFT_HYPHEN &&
		line->data[i + 1] == '\0') { /* last char on line */
		if (dump_output_immediately &&
		    LYRawMode &&
		    LYlowest_eightbit[current_char_set] <= 173 &&
		    (LYCharSet_UC[current_char_set].enc == UCT_ENC_8859 ||
		     LYCharSet_UC[current_char_set].like8859 &
				  UCT_R_8859SPECL)) {
		    fputc(0xad, fp); /* the iso8859 byte for SHY */
		} else {
		    fputc('-', fp);
		}
	    } else if (dump_output_immediately && use_underscore) {
		switch (line->data[i]) {
		    case LY_UNDERLINE_START_CHAR:
		    case LY_UNDERLINE_END_CHAR:
			fputc('_', fp);
			break;
		    case LY_BOLD_START_CHAR:
		    case LY_BOLD_END_CHAR:
			break;
		}
	    }
	}

	if (line == HTMainText->last_line)
	    break;

#ifdef VMS
	if (HadVMSInterrupt)
	    break;
#endif /* VMS */
    }
    fputc('\n',fp);

}

/*
 *  Print the contents of the file in HTMainText to
 *  the file descriptor fp.
 *  First output line is "thelink", ie, the URL for this file.
 */
PUBLIC void print_crawl_to_fd ARGS3(
	FILE *,		fp,
	char *,		thelink,
	char *,		thetitle)
{
    register int i;
    int first = TRUE;
    HTLine * line;
#ifdef VMS
    extern BOOLEAN HadVMSInterrupt;
#endif /* VMS */

    if (!HTMainText)
	return;

    line = HTMainText->last_line->next;
    fprintf(fp, "THE_URL:%s\n", thelink);
    if (thetitle != NULL) {
	fprintf(fp, "THE_TITLE:%s\n", thetitle);
    }

    for (;; line = line->next) {
	if (!first
	 && line->data[0] != LY_SOFT_NEWLINE)
	    fputc('\n',fp);
	first = FALSE;
	/*
	 *  Add offset.
	 */
	for (i = 0; i < (int)line->offset; i++) {
	    fputc(' ', fp);
	}

	/*
	 *  Add data.
	 */
	for (i = 0; line->data[i] != '\0'; i++) {
	    if (!IsSpecialAttrChar(line->data[i])) {
		fputc(line->data[i], fp);
	    } else if (line->data[i] == LY_SOFT_HYPHEN &&
		line->data[i + 1] == '\0') { /* last char on line */
		if (dump_output_immediately &&
		    LYRawMode &&
		    LYlowest_eightbit[current_char_set] <= 173 &&
		    (LYCharSet_UC[current_char_set].enc == UCT_ENC_8859 ||
		     LYCharSet_UC[current_char_set].like8859 &
				  UCT_R_8859SPECL)) {
		    fputc(0xad, fp); /* the iso8859 byte for SHY */
		} else {
		    fputc('-', fp);
		}
	     }
	}

	if (line == HTMainText->last_line) {
	    break;
	}
    }
    fputc('\n',fp);

    /*
     *  Add the References list if appropriate
     */
    if ((nolist == FALSE) &&
	(keypad_mode == LINKS_ARE_NUMBERED ||
	 keypad_mode == LINKS_AND_FORM_FIELDS_ARE_NUMBERED)) {
	printlist(fp,FALSE);
    }

#ifdef VMS
    HadVMSInterrupt = FALSE;
#endif /* VMS */
}

PRIVATE void adjust_search_result ARGS3(
    document *,	doc,
    int,	tentative_result,
    int,	start_line)
{
    if (tentative_result > 0) {
	int anch_line = -1;
	TextAnchor * a;
	int nl_closest = -1;
	int goal = SEARCH_GOAL_LINE;
	int max_offset;
	BOOL on_screen = (tentative_result > HTMainText->top_of_screen &&
	    tentative_result <= HTMainText->top_of_screen+display_lines);
	if (goal < 1)
	    goal = 1;
	else if (goal > display_lines)
	    goal = display_lines;
	max_offset = goal - 1;

	if (on_screen && nlinks > 0) {
	    int i;
	    for (i = 0; i < nlinks; i++) {
		if (doc->line + links[i].ly - 1 <= tentative_result)
		    nl_closest = i;
		if (doc->line + links[i].ly - 1 >= tentative_result)
		    break;
	    }
	    if (nl_closest >= 0 &&
		doc->line + links[nl_closest].ly - 1 == tentative_result) {
		www_search_result = doc->line;
		doc->link = nl_closest;
		return;
	    }
	}

	/* find last anchor before or on target line */
	for (a = HTMainText->first_anchor;
	     a && a->line_num <= tentative_result-1; a = a->next) {
	    anch_line = a->line_num + 1;
	}
	/* position such that the anchor found is on first screen line,
	   if it is not too far above the target line; but also try to
	   make sure we move forward. */
	if (anch_line >= 0 &&
	    anch_line >= tentative_result - max_offset &&
	    (anch_line > start_line ||
		tentative_result <= HTMainText->top_of_screen)) {
	    www_search_result = anch_line;
	} else
	if (tentative_result - start_line > 0 &&
	    tentative_result - (start_line + 1) <= max_offset) {
	    www_search_result = start_line + 1;
	} else
	if (tentative_result > HTMainText->top_of_screen &&
	    tentative_result <= start_line && /* have wrapped */
	    tentative_result <= HTMainText->top_of_screen+goal) {
	    www_search_result = HTMainText->top_of_screen + 1;
	} else
	if (tentative_result <= goal)
	    www_search_result = 1;
	else
	    www_search_result = tentative_result - max_offset;
	if (www_search_result == doc->line) {
	    if (nl_closest >= 0) {
		doc->link = nl_closest;
		return;
	    }
	}
    }
}

PUBLIC void www_user_search ARGS3(
	int,		start_line,
	document *,	doc,
	char *,		target)
{
    register HTLine * line;
    register int count;
    int tentative_result = -1;
    TextAnchor *a;
    OptionType *option;
    char *stars = NULL, *cp;

    if (!HTMainText) {
	return;
    }

    /*
     *  Advance to the start line.
     */
    line = HTMainText->last_line->next;
    for (count = 1; count <= start_line; line = line->next, count++) {
	if (line == HTMainText->last_line) {
	    line = HTMainText->last_line->next; /* set to first line */
	    count = 1;
	    break;
	}
    }
    a = HTMainText->first_anchor;
    while (a && a->line_num < count - 1) {
	a = a->next;
    }

    for (;;) {
	while ((a != NULL) && a->line_num == (count - 1)) {
	    if (a->show_anchor &&
		(a->link_type != INPUT_ANCHOR ||
		 a->input_field->type != F_HIDDEN_TYPE)) {
		if (((a->hightext != NULL && case_sensitive == TRUE) &&
		     LYno_attr_char_strstr(a->hightext, target)) ||
		    ((a->hightext != NULL && case_sensitive == FALSE) &&
		     LYno_attr_char_case_strstr(a->hightext, target))) {
		    adjust_search_result(doc, count, start_line);
		    return;
		}
		if (((a->hightext2 != NULL && case_sensitive == TRUE) &&
		     LYno_attr_char_strstr(a->hightext2, target)) ||
		    ((a->hightext2 != NULL && case_sensitive == FALSE) &&
		     LYno_attr_char_case_strstr(a->hightext2, target))) {
		    adjust_search_result(doc, count, start_line);
		    return;
		}

		/*
		 *  Search the relevant form fields, taking the
		 *  case_sensitive setting into account. - FM
		 */
		if ((a->input_field != NULL && a->input_field->value != NULL) &&
		    a->input_field->type != F_HIDDEN_TYPE) {
		    if (a->input_field->type == F_PASSWORD_TYPE) {
			/*
			 *  Check the actual, hidden password, and then
			 *  the displayed string. - FM
			 */
			if (((case_sensitive == TRUE) &&
			     LYno_attr_char_strstr(a->input_field->value,
						   target)) ||
			    ((case_sensitive == FALSE) &&
			     LYno_attr_char_case_strstr(a->input_field->value,
							target))) {
			    adjust_search_result(doc, count, start_line);
			    return;
			}
			StrAllocCopy(stars, a->input_field->value);
			for (cp = stars; *cp != '\0'; cp++)
			    *cp = '*';
			if (((case_sensitive == TRUE) &&
			     LYno_attr_char_strstr(stars, target)) ||
			    ((case_sensitive == FALSE) &&
			     LYno_attr_char_case_strstr(stars, target))) {
			    FREE(stars);
			    adjust_search_result(doc, count, start_line);
			    return;
			}
			FREE(stars);
		   } else if (a->input_field->type == F_OPTION_LIST_TYPE) {
			/*
			 *  Search the option strings that are displayed
			 *  when the popup is invoked. - FM
			 */
			option = a->input_field->select_list;
			while (option != NULL) {
			    if (((option->name != NULL &&
				  case_sensitive == TRUE) &&
				 LYno_attr_char_strstr(option->name,
						       target)) ||
				((option->name != NULL &&
				  case_sensitive == FALSE) &&
				 LYno_attr_char_case_strstr(option->name,
							    target))) {
				adjust_search_result(doc, count, start_line);
				return;
			    }
			    option = option->next;
			}
		    } else if (a->input_field->type == F_RADIO_TYPE) {
			/*
			 *  Search for checked or unchecked parens. - FM
			 */
			if (a->input_field->num_value) {
			    cp = checked_radio;
			} else {
			    cp = unchecked_radio;
			}
			if (((case_sensitive == TRUE) &&
			     LYno_attr_char_strstr(cp, target)) ||
			    ((case_sensitive == FALSE) &&
			     LYno_attr_char_case_strstr(cp, target))) {
			    adjust_search_result(doc, count, start_line);
			    return;
			}
		    } else if (a->input_field->type == F_CHECKBOX_TYPE) {
			/*
			 *  Search for checked or unchecked
			 *  square brackets. - FM
			 */
			if (a->input_field->num_value) {
			    cp = checked_box;
			} else {
			    cp = unchecked_box;
			}
			if (((case_sensitive == TRUE) &&
			     LYno_attr_char_strstr(cp, target)) ||
			    ((case_sensitive == FALSE) &&
			     LYno_attr_char_case_strstr(cp, target))) {
			    adjust_search_result(doc, count, start_line);
			    return;
			}
		    } else {
			/*
			 *  Check the values intended for display.
			 *  May have been found already via the
			 *  hightext search, but make sure here
			 *  that the entire value is searched. - FM
			 */
			if (((case_sensitive == TRUE) &&
			     LYno_attr_char_strstr(a->input_field->value,
						   target)) ||
			    ((case_sensitive == FALSE) &&
			     LYno_attr_char_case_strstr(a->input_field->value,
							target))) {
			    adjust_search_result(doc, count, start_line);
			    return;
			}
		    }
		}
	    }
	    a = a->next;
	}
	if (a != NULL && a->line_num <= (count - 1)) {
	    a = a->next;
	}

	if (case_sensitive && LYno_attr_char_strstr(line->data, target)) {
	    tentative_result = count;
	    break;
	} else if (!case_sensitive &&
		   LYno_attr_char_case_strstr(line->data, target)) {
	    tentative_result = count;
	    break;
	} else if (line == HTMainText->last_line) {  /* next line */
	    break;
	} else {			/* end */
	    line = line->next;
	    count++;
	}
    }
    if (tentative_result > 0) {
	adjust_search_result(doc, tentative_result, start_line);
	return;
    }

    /*
     *  Search from the beginning.
     */
    line = HTMainText->last_line->next; /* set to first line */
    count = 1;
    a = HTMainText->first_anchor;
    while (a && a->line_num < count - 1) {
	a = a->next;
    }

    for (;;) {
	while ((a != NULL) && a->line_num == (count - 1)) {
	    if (a->show_anchor &&
		(a->link_type != INPUT_ANCHOR ||
		 a->input_field->type != F_HIDDEN_TYPE)) {
		if (((a->hightext != NULL && case_sensitive == TRUE) &&
		     LYno_attr_char_strstr(a->hightext, target)) ||
		    ((a->hightext != NULL && case_sensitive == FALSE) &&
		     LYno_attr_char_case_strstr(a->hightext, target))) {
		    adjust_search_result(doc, count, start_line);
		    return;
		}
		if (((a->hightext2 != NULL && case_sensitive == TRUE) &&
		     LYno_attr_char_strstr(a->hightext2, target)) ||
		    ((a->hightext2 != NULL && case_sensitive == FALSE) &&
		     LYno_attr_char_case_strstr(a->hightext2, target))) {
		    adjust_search_result(doc, count, start_line);
		    return;
		}

		/*
		 *  Search the relevant form fields, taking the
		 *  case_sensitive setting into account. - FM
		 */
		if ((a->input_field != NULL && a->input_field->value != NULL) &&
		    a->input_field->type != F_HIDDEN_TYPE) {
		    if (a->input_field->type == F_PASSWORD_TYPE) {
			/*
			 *  Check the actual, hidden password, and then
			 *  the displayed string. - FM
			 */
			if (((case_sensitive == TRUE) &&
			     LYno_attr_char_strstr(a->input_field->value,
						   target)) ||
			    ((case_sensitive == FALSE) &&
			     LYno_attr_char_case_strstr(a->input_field->value,
							target))) {
			    adjust_search_result(doc, count, start_line);
			    return;
			}
			StrAllocCopy(stars, a->input_field->value);
			for (cp = stars; *cp != '\0'; cp++)
			    *cp = '*';
			if (((case_sensitive == TRUE) &&
			     LYno_attr_char_strstr(stars, target)) ||
			    ((case_sensitive == FALSE) &&
			     LYno_attr_char_case_strstr(stars, target))) {
			    FREE(stars);
			    adjust_search_result(doc, count, start_line);
			    return;
			}
			FREE(stars);
		   } else if (a->input_field->type == F_OPTION_LIST_TYPE) {
			/*
			 *  Search the option strings that are displayed
			 *  when the popup is invoked. - FM
			 */
			option = a->input_field->select_list;
			while (option != NULL) {
			    if (((option->name != NULL &&
				  case_sensitive == TRUE) &&
				 LYno_attr_char_strstr(option->name,
						       target)) ||
				((option->name != NULL &&
				  case_sensitive == FALSE) &&
				 LYno_attr_char_case_strstr(option->name,
							    target))) {
				adjust_search_result(doc, count, start_line);
				return;
			    }
			    option = option->next;
			}
		    } else if (a->input_field->type == F_RADIO_TYPE) {
			/*
			 *  Search for checked or unchecked parens. - FM
			 */
			if (a->input_field->num_value) {
			    cp = checked_radio;
			} else {
			    cp = unchecked_radio;
			}
			if (((case_sensitive == TRUE) &&
			     LYno_attr_char_strstr(cp, target)) ||
			    ((case_sensitive == FALSE) &&
			     LYno_attr_char_case_strstr(cp, target))) {
			    adjust_search_result(doc, count, start_line);
			    return;
			}
		    } else if (a->input_field->type == F_CHECKBOX_TYPE) {
			/*
			 *  Search for checked or unchecked
			 *  square brackets. - FM
			 */
			if (a->input_field->num_value) {
			    cp = checked_box;
			} else {
			    cp = unchecked_box;
			}
			if (((case_sensitive == TRUE) &&
			     LYno_attr_char_strstr(cp, target)) ||
			    ((case_sensitive == FALSE) &&
			     LYno_attr_char_case_strstr(cp, target))) {
			    adjust_search_result(doc, count, start_line);
			    return;
			}
		    } else {
			/*
			 *  Check the values intended for display.
			 *  May have been found already via the
			 *  hightext search, but make sure here
			 *  that the entire value is searched. - FM
			 */
			if (((case_sensitive == TRUE) &&
			     LYno_attr_char_strstr(a->input_field->value,
						   target)) ||
			    ((case_sensitive == FALSE) &&
			     LYno_attr_char_case_strstr(a->input_field->value,
							target))) {
			    adjust_search_result(doc, count, start_line);
			    return;
			}
		    }
		}
	    }
	    a = a->next;
	}
	if (a != NULL && a->line_num <= (count - 1)) {
	    a = a->next;
	}

	    if (case_sensitive && LYno_attr_char_strstr(line->data, target)) {
		tentative_result=count;
		break;
	    } else if (!case_sensitive &&
		       LYno_attr_char_case_strstr(line->data, target)) {
		tentative_result = count;
		break;
	    } else if (count > start_line) {  /* next line */
		HTUserMsg2(STRING_NOT_FOUND, target);
		return; 		/* end */
	    } else {
		line = line->next;
		count++;
	}
    }
    if (tentative_result > 0) {
	adjust_search_result(doc, tentative_result, start_line);
    }
}

PUBLIC void user_message ARGS2(
	CONST char *,	message,
	CONST char *,	argument)
{
    char *temp = NULL;
    char temp_arg[256];

    if (message == NULL) {
	mustshow = FALSE;
	return;
    }

    /*
     *  Make sure we don't overrun any buffers.
     */
    LYstrncpy(temp_arg, ((argument == NULL) ? "" : argument), 255);
    temp_arg[255] = '\0';
    temp = (char *)malloc(strlen(message) + strlen(temp_arg) + 1);
    if (temp == NULL)
	outofmem(__FILE__, "user_message");
    sprintf(temp, message, temp_arg);

    statusline(temp);

    FREE(temp);
    return;
}

/*
 *  HText_getOwner returns the owner of the
 *  current document.
 */
PUBLIC CONST char * HText_getOwner NOARGS
{
    return(HTMainText ?
	   HTAnchor_owner(HTMainText->node_anchor) : 0);
}

/*
 *  HText_setMainTextOwner sets the owner for the
 *  current document.
 */
PUBLIC void HText_setMainTextOwner ARGS1(
	CONST char *,	owner)
{
    if (!HTMainText)
	return;

    HTAnchor_setOwner(HTMainText->node_anchor, owner);
}

/*
 *  HText_getRevTitle returns the RevTitle element of the
 *  current document, used as the subject for mailto comments
 *  to the owner.
 */
PUBLIC CONST char * HText_getRevTitle NOARGS
{
    return(HTMainText ?
	   HTAnchor_RevTitle(HTMainText->node_anchor) : 0);
}

/*
 *  HText_getContentBase returns the Content-Base header
 *  of the current document.
 */
PUBLIC CONST char * HText_getContentBase NOARGS
{
    return(HTMainText ?
	   HTAnchor_content_base(HTMainText->node_anchor) : 0);
}

/*
 *  HText_getContentLocation returns the Content-Location header
 *  of the current document.
 */
PUBLIC CONST char * HText_getContentLocation NOARGS
{
    return(HTMainText ?
	   HTAnchor_content_location(HTMainText->node_anchor) : 0);
}

PUBLIC void HTuncache_current_document NOARGS
{
    /*
     *  Should remove current document from memory.
     */
    if (HTMainText) {
	HTParentAnchor * htmain_anchor = HTMainText->node_anchor;

	if (htmain_anchor) {
	    if (!(HTOutputFormat && HTOutputFormat == WWW_SOURCE)) {
		FREE(htmain_anchor->UCStages);
	    }
	}
	CTRACE(tfp, "\rHTuncache.. freeing document for '%s'%s\n",
			    ((htmain_anchor &&
			      htmain_anchor->address) ?
			       htmain_anchor->address : "unknown anchor"),
			    ((htmain_anchor &&
			      htmain_anchor->post_data) ?
				      " with POST data" : ""));
	HTList_removeObject(loaded_texts, HTMainText);
	HText_free(HTMainText);
	HTMainText = NULL;
    } else {
	CTRACE(tfp, "HTuncache.. HTMainText already is NULL!\n");
    }
}

PUBLIC int HTisDocumentSource NOARGS
{
    return (HTMainText != 0) ? HTMainText->source : FALSE;
}

PUBLIC char * HTLoadedDocumentURL NOARGS
{
    if (!HTMainText)
	return ("");

    if (HTMainText->node_anchor && HTMainText->node_anchor->address)
	return(HTMainText->node_anchor->address);
    else
	return ("");
}

PUBLIC char * HTLoadedDocumentPost_data NOARGS
{
    if (!HTMainText)
	return ("");

    if (HTMainText->node_anchor && HTMainText->node_anchor->post_data)
	return(HTMainText->node_anchor->post_data);
    else
	return ("");
}

PUBLIC char * HTLoadedDocumentTitle NOARGS
{
    if (!HTMainText)
	return ("");

    if (HTMainText->node_anchor && HTMainText->node_anchor->title)
	return(HTMainText->node_anchor->title);
    else
	return ("");
}

PUBLIC BOOLEAN HTLoadedDocumentIsHEAD NOARGS
{
    if (!HTMainText)
	return (FALSE);

    if (HTMainText->node_anchor && HTMainText->node_anchor->isHEAD)
	return(HTMainText->node_anchor->isHEAD);
    else
	return (FALSE);
}

PUBLIC BOOLEAN HTLoadedDocumentIsSafe NOARGS
{
    if (!HTMainText)
	return (FALSE);

    if (HTMainText->node_anchor && HTMainText->node_anchor->safe)
	return(HTMainText->node_anchor->safe);
    else
	return (FALSE);
}

PUBLIC char * HTLoadedDocumentCharset NOARGS
{
    if (!HTMainText)
	return (NULL);

    if (HTMainText->node_anchor && HTMainText->node_anchor->charset)
	return(HTMainText->node_anchor->charset);
    else
	return (NULL);
}

PUBLIC BOOL HTLoadedDocumentEightbit NOARGS
{
    if (!HTMainText)
	return (NO);
    else
	return (HTMainText->have_8bit_chars);
}

PUBLIC void HText_setNodeAnchorBookmark ARGS1(
	CONST char *,	bookmark)
{
    if (!HTMainText)
	return;

    if (HTMainText->node_anchor)
	HTAnchor_setBookmark(HTMainText->node_anchor, bookmark);
}

PUBLIC char * HTLoadedDocumentBookmark NOARGS
{
    if (!HTMainText)
	return (NULL);

    if (HTMainText->node_anchor && HTMainText->node_anchor->bookmark)
	return(HTMainText->node_anchor->bookmark);
    else
	return (NULL);
}

PUBLIC int HText_LastLineSize ARGS2(
	HText *,	text,
	BOOL,		IgnoreSpaces)
{
    if (!text || !text->last_line || !text->last_line->size)
	return 0;
    return HText_TrueLineSize(text->last_line, text, IgnoreSpaces);
}

PUBLIC int HText_PreviousLineSize ARGS2(
	HText *,	text,
	BOOL,		IgnoreSpaces)
{
    HTLine * line;

    if (!text || !text->last_line)
	return 0;
    if (!(line = text->last_line->prev))
	return 0;
    return HText_TrueLineSize(line, text, IgnoreSpaces);
}

PRIVATE int HText_TrueLineSize ARGS3(
	HTLine *,	line,
	HText *,	text,
	BOOL,		IgnoreSpaces)
{
    size_t i;
    int true_size = 0;

    if (!(line && line->size))
	return 0;

    if (IgnoreSpaces) {
	for (i = 0; i < line->size; i++) {
	    if (!IsSpecialAttrChar((unsigned char)line->data[i]) &&
		(!(text && text->T.output_utf8) ||
		 (unsigned char)line->data[i] < 128 ||
		 ((unsigned char)(line->data[i] & 0xc0) == 0xc0)) &&
		!isspace((unsigned char)line->data[i]) &&
		(unsigned char)line->data[i] != HT_NON_BREAK_SPACE &&
		(unsigned char)line->data[i] != HT_EM_SPACE) {
		true_size++;
	    }
	}
    } else {
	for (i = 0; i < line->size; i++) {
	    if (!IsSpecialAttrChar(line->data[i]) &&
		(!(text && text->T.output_utf8) ||
		 (unsigned char)line->data[i] < 128 ||
		 ((unsigned char)(line->data[i] & 0xc0) == 0xc0))) {
		true_size++;
	    }
	}
    }
    return true_size;
}

PUBLIC void HText_NegateLineOne ARGS1(
	HText *,	text)
{
    if (text) {
	text->in_line_1 = NO;
    }
    return;
}

/*
 *  This function is for removing the first of two
 *  successive blank lines.  It should be called after
 *  checking the situation with HText_LastLineSize()
 *  and HText_PreviousLineSize().  Any characters in
 *  the removed line (i.e., control characters, or it
 *  wouldn't have tested blank) should have been
 *  reiterated by split_line() in the retained blank
 *  line. - FM
 */
PUBLIC void HText_RemovePreviousLine ARGS1(
	HText *,	text)
{
    HTLine *line, *previous;
    char *data;

    if (!(text && text->Lines > 1))
	return;

    line = text->last_line->prev;
    data = line->data;
    previous = line->prev;
    previous->next = text->last_line;
    text->last_line->prev = previous;
    text->chars -= ((data && *data == '\0') ?
					  1 : strlen(line->data) + 1);
    text->Lines--;
    FREE(line);
}

/*
 *  NOTE: This function presently is correct only if the
 *	  alignment is HT_LEFT.  The offset is still zero,
 *	  because that's not determined for HT_CENTER or
 *	  HT_RIGHT until subsequent characters are received
 *	  and split_line() is called. - FM
 */
PUBLIC int HText_getCurrentColumn ARGS1(
	HText *,	text)
{
    int column = 0;
    BOOL IgnoreSpaces = FALSE;

    if (text) {
	column = (text->in_line_1 ?
		  (int)text->style->indent1st : (int)text->style->leftIndent)
		  + HText_LastLineSize(text, IgnoreSpaces)
		  + (int)text->last_line->offset;
    }
    return column;
}

PUBLIC int HText_getMaximumColumn ARGS1(
	HText *,	text)
{
    int column = (LYcols-2);
    if (text) {
	column = ((int)text->style->rightIndent ? (LYcols-2) :
		  ((LYcols-1) - (int)text->style->rightIndent));
    }
    return column;
}

/*
 *  NOTE: This function uses HText_getCurrentColumn() which
 *	  presently is correct only if the alignment is
 *	  HT_LEFT. - FM
 */
PUBLIC void HText_setTabID ARGS2(
	HText *,	text,
	CONST char *,	name)
{
    HTTabID * Tab = NULL;
    HTList * cur = text->tabs;
    HTList * last = NULL;

    if (!text || !name || !*name)
	return;

    if (!cur) {
	cur = text->tabs = HTList_new();
    } else {
	while (NULL != (Tab = (HTTabID *)HTList_nextObject(cur))) {
	    if (Tab->name && !strcmp(Tab->name, name))
		return; /* Already set.  Keep the first value. */
	    last = cur;
	}
	if (last)
	    cur = last;
    }
    if (!Tab) { /* New name.  Create a new node */
	Tab = (HTTabID *)calloc(1, sizeof(HTTabID));
	if (Tab == NULL)
	    outofmem(__FILE__, "HText_setTabID");
	HTList_addObject(cur, Tab);
	StrAllocCopy(Tab->name, name);
    }
    Tab->column = HText_getCurrentColumn(text);
    return;
}

PUBLIC int HText_getTabIDColumn ARGS2(
	HText *,	text,
	CONST char *,	name)
{
    int column = 0;
    HTTabID * Tab;
    HTList * cur = text->tabs;

    if (text && name && *name && cur) {
	while (NULL != (Tab = (HTTabID *)HTList_nextObject(cur))) {
	    if (Tab->name && !strcmp(Tab->name, name))
		break;
	}
	if (Tab)
	    column = Tab->column;
    }
    return column;
}

/*
 *  This function is for saving the address of a link
 *  which had an attribute in the markup that resolved
 *  to a URL (i.e., not just a NAME or ID attribute),
 *  but was found in HText_endAnchor() to have no visible
 *  content for use as a link name.  It loads the address
 *  into text->hidden_links, whose count can be determined
 *  via HText_HiddenLinks(), below.  The addresses can be
 *  retrieved via HText_HiddenLinkAt(), below, based on
 *  count. - FM
 */
PRIVATE void HText_AddHiddenLink ARGS2(
	HText *,	text,
	TextAnchor *,	textanchor)
{
    HTAnchor *dest;

    /*
     *  Make sure we have an HText structure and anchor. - FM
     */
    if (!(text && textanchor && textanchor->anchor))
	return;

    /*
     *  Create the hidden links list
     *  if it hasn't been already. - FM
     */
    if (text->hidden_links == NULL)
	text->hidden_links = HTList_new();

    /*
     *  Store the address, in reverse list order
     *  so that first in will be first out on
     *  retrievals. - FM
     */
    if ((dest = HTAnchor_followMainLink((HTAnchor *)textanchor->anchor)) &&
	(text->hiddenlinkflag != HIDDENLINKS_IGNORE ||
	 HTList_isEmpty(text->hidden_links)))
	HTList_appendObject(text->hidden_links, HTAnchor_address(dest));

    return;
}

/*
 *  This function returns the number of addresses
 *  that are loaded in text->hidden_links. - FM
 */
PUBLIC int HText_HiddenLinkCount ARGS1(
	HText *,	text)
{
    int count = 0;

    if (text && text->hidden_links)
	count = HTList_count((HTList *)text->hidden_links);

    return(count);
}

/*
 *  This function returns the address, corresponding to
 *  a hidden link, at the position (zero-based) in the
 *  text->hidden_links list of the number argument. - FM
 */
PUBLIC char * HText_HiddenLinkAt ARGS2(
	HText *,	text,
	int,		number)
{
    char *href = NULL;

    if (text && text->hidden_links && number >= 0)
	href = (char *)HTList_objectAt((HTList *)text->hidden_links, number);

    return(href);
}


/*
 *  Form methods
 *    These routines are used to build forms consisting
 *    of input fields
 */
PRIVATE int HTFormMethod;
PRIVATE char * HTFormAction = NULL;
PRIVATE char * HTFormEnctype = NULL;
PRIVATE char * HTFormTitle = NULL;
PRIVATE char * HTFormAcceptCharset = NULL;
PRIVATE BOOLEAN HTFormDisabled = FALSE;
PRIVATE PerFormInfo * HTCurrentForm;

PUBLIC void HText_beginForm ARGS5(
	char *,		action,
	char *,		method,
	char *,		enctype,
	char *,		title,
	CONST char *,	accept_cs)
{
    PerFormInfo * newform;
    HTFormMethod = URL_GET_METHOD;
    HTFormNumber++;
    HTFormFields = 0;
    HTFormDisabled = FALSE;

    /*
     *  Check the ACTION. - FM
     */
    if (action != NULL) {
	if (!strncmp(action, "mailto:", 7)) {
	    HTFormMethod = URL_MAIL_METHOD;
	}
	StrAllocCopy(HTFormAction, action);
    }
    else
	StrAllocCopy(HTFormAction, HTLoadedDocumentURL());

    /*
     *  Check the METHOD. - FM
     */
    if (method != NULL && HTFormMethod != URL_MAIL_METHOD)
	if (!strcasecomp(method,"post") || !strcasecomp(method,"pget"))
	    HTFormMethod = URL_POST_METHOD;

    /*
     *  Check the ENCTYPE. - FM
     */
    if ((enctype != NULL) && *enctype) {
	StrAllocCopy(HTFormEnctype, enctype);
	if (HTFormMethod != URL_MAIL_METHOD &&
	    !strncasecomp(enctype, "multipart/form-data", 19))
	    HTFormMethod = URL_POST_METHOD;
    } else {
	FREE(HTFormEnctype);
    }

    /*
     *  Check the TITLE. - FM
     */
    if ((title != NULL) && *title)
	StrAllocCopy(HTFormTitle, title);
    else
	FREE(HTFormTitle);

    /*
     *  Check for an ACCEPT_CHARSET.  If present, store it and
     *  convert to lowercase and collapse spaces. - kw
     */
    if (accept_cs != NULL) {
	StrAllocCopy(HTFormAcceptCharset, accept_cs);
	LYRemoveBlanks(HTFormAcceptCharset);
	LYLowerCase(HTFormAcceptCharset);
    }

    /*
     *  Create a new "PerFormInfo" structure to hold info on the current
     *  form.  The HTForm* variables could all migrate there, currently
     *  this isn't done (yet?) but it might be less confusing.
     *  Currently the only data saved in this structure that will actually
     *  be used is the accept_cs string.
     *  This will be appended to the forms list kept by the HText object
     *  if and when we reach a HText_endForm. - kw
     */
    newform = (PerFormInfo *)calloc(1, sizeof(PerFormInfo));
    if (newform == NULL)
	outofmem(__FILE__,"HText_beginForm");
    newform->number = HTFormNumber;

    PerFormInfo_free(HTCurrentForm); /* shouldn't happen here - kw */
    HTCurrentForm = newform;

    CTRACE(tfp, "BeginForm: action:%s Method:%d%s%s%s%s%s%s\n",
		HTFormAction, HTFormMethod,
		(HTFormTitle ? " Title:" : ""),
		(HTFormTitle ? HTFormTitle : ""),
		(HTFormEnctype ? " Enctype:" : ""),
		(HTFormEnctype ? HTFormEnctype : ""),
		(HTFormAcceptCharset ? " Accept-charset:" : ""),
		(HTFormAcceptCharset ? HTFormAcceptCharset : ""));
}

PUBLIC void HText_endForm ARGS1(
	HText *,	text)
{
    if (HTFormFields == 1 && text && text->first_anchor) {
	/*
	 *  Support submission of a single text input field in
	 *  the form via <return> instead of a submit button. - FM
	 */
	TextAnchor * a = text->first_anchor;
	/*
	 *  Go through list of anchors and get our input field. - FM
	 */
	while (a) {
	    if (a->link_type == INPUT_ANCHOR &&
		a->input_field->number == HTFormNumber &&
		a->input_field->type == F_TEXT_TYPE) {
		/*
		 *  Got it.  Make it submitting. - FM
		 */
		a->input_field->submit_action = NULL;
		StrAllocCopy(a->input_field->submit_action, HTFormAction);
		if (HTFormEnctype != NULL)
		    StrAllocCopy(a->input_field->submit_enctype,
				 HTFormEnctype);
		if (HTFormTitle != NULL)
		    StrAllocCopy(a->input_field->submit_title, HTFormTitle);
		a->input_field->submit_method = HTFormMethod;
		a->input_field->type = F_TEXT_SUBMIT_TYPE;
		if (HTFormDisabled)
		    a->input_field->disabled = TRUE;
		break;
	    }
	    if (a == text->last_anchor)
		break;
	    a = a->next;
	}
    }
    /*
     *  Append info on the current form to the HText object's list of
     *  forms.
     *  HText_beginInput call will have set some of the data in the
     *  PerFormInfo structure (if there were any form fields at all),
     *  we also fill in the ACCEPT-CHARSET data now (this could have
     *  been done earlier). - kw
     */
    if (HTCurrentForm) {
	if (HTFormDisabled)
	    HTCurrentForm->disabled = TRUE;
	HTCurrentForm->accept_cs = HTFormAcceptCharset;
	HTFormAcceptCharset = NULL;
	if (!text->forms)
	    text->forms = HTList_new();
	HTList_appendObject(text->forms, HTCurrentForm);
	HTCurrentForm = NULL;
    } else {
	CTRACE(tfp, "endForm:    HTCurrentForm is missing!\n");
    }

    FREE(HTCurSelectGroup);
    FREE(HTCurSelectGroupSize);
    FREE(HTCurSelectedOptionValue);
    FREE(HTFormAction);
    FREE(HTFormEnctype);
    FREE(HTFormTitle);
    FREE(HTFormAcceptCharset);
    HTFormFields = 0;
    HTFormDisabled = FALSE;
}

PUBLIC void HText_beginSelect ARGS4(
	char *,		name,
	int,		name_cs,
	BOOLEAN,	multiple,
	char *,		size)
{
   /*
    *  Save the group name.
    */
   StrAllocCopy(HTCurSelectGroup, name);
   HTCurSelectGroupCharset = name_cs;

   /*
    *  If multiple then all options are actually checkboxes.
    */
   if (multiple)
      HTCurSelectGroupType = F_CHECKBOX_TYPE;
   /*
    *  If not multiple then all options are radio buttons.
    */
   else
      HTCurSelectGroupType = F_RADIO_TYPE;

    /*
     *  Length of an option list.
     */
    StrAllocCopy(HTCurSelectGroupSize, size);

    CTRACE(tfp,"HText_beginSelect: name=%s type=%d size=%s\n",
	       ((HTCurSelectGroup == NULL) ?
				  "<NULL>" : HTCurSelectGroup),
		HTCurSelectGroupType,
	       ((HTCurSelectGroupSize == NULL) ?
				      "<NULL>" : HTCurSelectGroupSize));
    CTRACE(tfp,"HText_beginSelect: name_cs=%d \"%s\"\n",
		HTCurSelectGroupCharset,
		(HTCurSelectGroupCharset >= 0 ?
		 LYCharSet_UC[HTCurSelectGroupCharset].MIMEname : "<UNKNOWN>"));
}

/*
**  This function returns the number of the option whose
**  value currently is being accumulated for a select
**  block. - LE && FM
*/
PUBLIC int HText_getOptionNum ARGS1(
	HText *,	text)
{
    TextAnchor *a;
    OptionType *op;
    int n = 1; /* start count at 1 */

    if (!(text && text->last_anchor))
	return(0);

    a = text->last_anchor;
    if (!(a->link_type == INPUT_ANCHOR && a->input_field &&
	  a->input_field->type == F_OPTION_LIST_TYPE))
	return(0);

    for (op = a->input_field->select_list; op; op = op->next)
	n++;
    CTRACE(tfp, "HText_getOptionNum: Got number '%d'.\n", n);
    return(n);
}

/*
**  This function checks for a numbered option pattern
**  as the prefix for an option value.  If present, and
**  we are in the correct keypad mode, it returns a
**  pointer to the actual value, following that prefix.
**  Otherwise, it returns the original pointer.
*/
PRIVATE char * HText_skipOptionNumPrefix ARGS1(
	char *,		opname)
{
    /*
     *  Check if we are in the correct keypad mode.
     */
    if (keypad_mode == LINKS_AND_FORM_FIELDS_ARE_NUMBERED) {
	/*
	 *  Skip the option number embedded in the option name so the
	 *  extra chars won't mess up cgi scripts processing the value.
	 *  The format is (nnn)__ where nnn is a number and there is a
	 *  minimum of 5 chars (no underscores if (nnn) exceeds 5 chars).
	 *  See HTML.c.  If the chars don't exactly match this format,
	 *  just use all of opname.  - LE
	 */
	char *cp = opname;

	if ((cp && *cp && *cp++ == '(') &&
	    *cp && isdigit(*cp++)) {
	    while (*cp && isdigit(*cp))
		++cp;
	    if (*cp && *cp++ == ')') {
		int i = (cp - opname);

		while (i < 5) {
		    if (*cp != '_')
			break;
		    i++;
		    cp++;
		}
		if (i < 5 ) {
		    cp = opname;
		}
	    } else {
		cp = opname;
	    }
	} else {
	    cp = opname;
	}
	return(cp);
    }

    return(opname);
}

/*
**  We couldn't set the value field for the previous option
**  tag so we have to do it now.  Assume that the last anchor
**  was the previous options tag.
*/
PUBLIC char * HText_setLastOptionValue ARGS7(
	HText *,	text,
	char *,		value,
	char*,		submit_value,
	int,		order,
	BOOLEAN,	checked,
	int,		val_cs,
	int,		submit_val_cs)
{
    char *cp, *cp1;
    char *ret_Value = NULL;
    unsigned char *tmp = NULL;
    int number = 0, i, j;

    if (!(text && text->last_anchor &&
	  text->last_anchor->link_type == INPUT_ANCHOR)) {
	CTRACE(tfp, "HText_setLastOptionValue: invalid call!  value:%s!\n",
		    (value ? value : "<NULL>"));
	return NULL;
    }

    CTRACE(tfp, "Entering HText_setLastOptionValue: value:%s, checked:%s\n",
		value, (checked ? "on" : "off"));

    /*
     *  Strip end spaces, newline is also whitespace.
     */
    if (*value) {
	cp = &value[strlen(value)-1];
	while ((cp >= value) && (isspace((unsigned char)*cp) ||
				 IsSpecialAttrChar((unsigned char)*cp)))
	    cp--;
	*(cp+1) = '\0';
    }

    /*
     *  Find first non space
     */
    cp = value;
    while (isspace((unsigned char)*cp) ||
	   IsSpecialAttrChar((unsigned char)*cp))
	cp++;
    if (HTCurSelectGroupType == F_RADIO_TYPE &&
	LYSelectPopups &&
	keypad_mode == LINKS_AND_FORM_FIELDS_ARE_NUMBERED) {
	/*
	 *  Collapse any space between the popup option
	 *  prefix and actual value. - FM
	 */
	if ((cp1 = HText_skipOptionNumPrefix(cp)) > cp) {
	    i = 0, j = (cp1 - cp);
	    while (isspace((unsigned char)cp1[i]) ||
		   IsSpecialAttrChar((unsigned char)cp1[i])) {
		i++;
	    }
	    if (i > 0) {
		while (cp1[i] != '\0')
		    cp[j++] = cp1[i++];
		cp[j] = '\0';
	    }
	}
    }

    if (HTCurSelectGroupType == F_CHECKBOX_TYPE) {
	StrAllocCopy(text->last_anchor->input_field->value, cp);
	text->last_anchor->input_field->value_cs = val_cs;
	/*
	 *  Put the text on the screen as well.
	 */
	HText_appendText(text, cp);

    } else if (LYSelectPopups == FALSE) {
	StrAllocCopy(text->last_anchor->input_field->value,
		     (submit_value ? submit_value : cp));
	text->last_anchor->input_field->value_cs = (submit_value ?
						    submit_val_cs : val_cs);
	/*
	 *  Put the text on the screen as well.
	 */
	HText_appendText(text, cp);

    } else {
	/*
	 *  Create a linked list of option values.
	 */
	OptionType * op_ptr = text->last_anchor->input_field->select_list;
	OptionType * new_ptr = NULL;
	BOOLEAN first_option = FALSE;

	/*
	 *  Deal with newlines or tabs.
	 */
	convert_to_spaces(value, FALSE);

	if (!op_ptr) {
	    /*
	     *  No option items yet.
	     */
	    if (text->last_anchor->input_field->type != F_OPTION_LIST_TYPE) {
		CTRACE(tfp, "HText_setLastOptionValue: last input_field not F_OPTION_LIST_TYPE (%d)\n",
			    F_OPTION_LIST_TYPE);
		CTRACE(tfp, "                          but %d, ignoring!\n",
			    text->last_anchor->input_field->type);
		return NULL;
	    }

	    new_ptr = text->last_anchor->input_field->select_list =
				(OptionType *)calloc(1, sizeof(OptionType));
	    if (new_ptr == NULL)
		outofmem(__FILE__, "HText_setLastOptionValue");

	    first_option = TRUE;
	} else {
	    while (op_ptr->next) {
		number++;
		op_ptr=op_ptr->next;
	    }
	    number++;  /* add one more */

	    op_ptr->next = new_ptr =
				(OptionType *)calloc(1, sizeof(OptionType));
	    if (new_ptr == NULL)
		outofmem(__FILE__, "HText_setLastOptionValue");
	}

	new_ptr->name = NULL;
	new_ptr->cp_submit_value = NULL;
	new_ptr->next = NULL;
	for (i = 0, j = 0; cp[i]; i++) {
	    if (cp[i] == HT_NON_BREAK_SPACE ||
		cp[i] == HT_EM_SPACE) {
		cp[j++] = ' ';
	    } else if (cp[i] != LY_SOFT_HYPHEN &&
		       !IsSpecialAttrChar((unsigned char)cp[i])) {
		cp[j++] = cp[i];
	    }
	}
	cp[j] = '\0';
	if (HTCJK != NOCJK) {
	    if (cp &&
		(tmp = (unsigned char *)calloc(1, strlen(cp)+1))) {
		if (kanji_code == EUC) {
		    TO_EUC((unsigned char *)cp, tmp);
		    val_cs = current_char_set;
		} else if (kanji_code == SJIS) {
		    TO_SJIS((unsigned char *)cp, tmp);
		    val_cs = current_char_set;
		} else {
		    for (i = 0, j = 0; cp[i]; i++) {
			if (cp[i] != '\033') {
			    tmp[j++] = cp[i];
			}
		    }
		}
		StrAllocCopy(new_ptr->name, (CONST char *)tmp);
		FREE(tmp);
	    }
	} else {
	    StrAllocCopy(new_ptr->name, cp);
	}
	StrAllocCopy(new_ptr->cp_submit_value,
		     (submit_value ? submit_value :
		      HText_skipOptionNumPrefix(new_ptr->name)));
	new_ptr->value_cs = (submit_value ? submit_val_cs : val_cs);

	if (first_option) {
	    StrAllocCopy(HTCurSelectedOptionValue, new_ptr->name);
	    text->last_anchor->input_field->num_value = 0;
	    /*
	     *  If this is the first option in a popup select list,
	     *  HText_beginInput may have allocated the value and
	     *  cp_submit_value fields, so free them now to avoid
	     *  a memory leak. - kw
	     */
	    FREE(text->last_anchor->input_field->value);
	    FREE(text->last_anchor->input_field->cp_submit_value);

	    text->last_anchor->input_field->value =
		text->last_anchor->input_field->select_list->name;
	    text->last_anchor->input_field->orig_value =
		text->last_anchor->input_field->select_list->name;
	    text->last_anchor->input_field->cp_submit_value =
		text->last_anchor->input_field->select_list->cp_submit_value;
	    text->last_anchor->input_field->orig_submit_value =
		text->last_anchor->input_field->select_list->cp_submit_value;
	    text->last_anchor->input_field->value_cs =
		new_ptr->value_cs;
	} else {
	    int newlen = strlen(new_ptr->name);
	    int curlen = strlen(HTCurSelectedOptionValue);
		/*
		 *  Make the selected Option Value as long as
		 *  the longest option.
		 */
	    if (newlen > curlen)
		StrAllocCat(HTCurSelectedOptionValue,
			    UNDERSCORES(newlen-curlen));
	}

	if (checked) {
	    int curlen = strlen(new_ptr->name);
	    int newlen = strlen(HTCurSelectedOptionValue);
	    /*
	     *  Set the default option as this one.
	     */
	    text->last_anchor->input_field->num_value = number;
	    text->last_anchor->input_field->value = new_ptr->name;
	    text->last_anchor->input_field->orig_value = new_ptr->name;
	    text->last_anchor->input_field->cp_submit_value =
				   new_ptr->cp_submit_value;
	    text->last_anchor->input_field->orig_submit_value =
				   new_ptr->cp_submit_value;
	    text->last_anchor->input_field->value_cs =
		new_ptr->value_cs;
	    StrAllocCopy(HTCurSelectedOptionValue, new_ptr->name);
	    if (newlen > curlen)
		StrAllocCat(HTCurSelectedOptionValue,
			    UNDERSCORES(newlen-curlen));
	}

	/*
	 *  Return the selected Option value to be sent to the screen.
	 */
	if (order == LAST_ORDER) {
	    /*
	     *  Change the value.
	     */
	    text->last_anchor->input_field->size =
				strlen(HTCurSelectedOptionValue);
	    ret_Value = HTCurSelectedOptionValue;
	}
    }

    if (TRACE) {
	fprintf(tfp,"HText_setLastOptionValue:%s value=%s",
		(order == LAST_ORDER) ? " LAST_ORDER" : "",
		value);
	fprintf(tfp,"            val_cs=%d \"%s\"",
			val_cs,
			(val_cs >= 0 ?
			 LYCharSet_UC[val_cs].MIMEname : "<UNKNOWN>"));
	if (submit_value) {
	    fprintf(tfp, " (submit_val_cs %d \"%s\") submit_value%s=%s\n",
		    submit_val_cs,
		    (submit_val_cs >= 0 ?
		     LYCharSet_UC[submit_val_cs].MIMEname : "<UNKNOWN>"),
		    (HTCurSelectGroupType == F_CHECKBOX_TYPE) ?
						  "(ignored)" : "",
		    submit_value);
	}
	else {
	    fprintf(tfp,"\n");
	}
    }
    return(ret_Value);
}

/*
 *  Assign a form input anchor.
 *  Returns the number of characters to leave
 *  blank so that the input field can fit.
 */
PUBLIC int HText_beginInput ARGS3(
	HText *,		text,
	BOOL,			underline,
	InputFieldData *,	I)
{

    TextAnchor * a = (TextAnchor *) calloc(1, sizeof(*a));
    FormInfo * f = (FormInfo *) calloc(1, sizeof(*f));
    CONST char *cp_option = NULL;
    char *IValue = NULL;
    unsigned char *tmp = NULL;
    int i, j;

    CTRACE(tfp,"Entering HText_beginInput\n");

#ifdef DISP_PARTIAL
    if (display_partial)
	detected_forms_input_partial = TRUE; /* trimHightext temp fix */
#endif

    if (a == NULL || f == NULL)
	outofmem(__FILE__, "HText_beginInput");

    a->start = text->chars + text->last_line->size;
    a->inUnderline = underline;
    a->line_pos = text->last_line->size;


    /*
     *  If this is a radio button, or an OPTION we're converting
     *  to a radio button, and it's the first with this name, make
     *  sure it's checked by default.  Otherwise, if it's checked,
     *  uncheck the default or any preceding radio button with this
     *  name that was checked. - FM
     */
    if (I->type != NULL && !strcmp(I->type,"OPTION") &&
	HTCurSelectGroupType == F_RADIO_TYPE && LYSelectPopups == FALSE) {
	I->type = "RADIO";
	I->name = HTCurSelectGroup;
	I->name_cs = HTCurSelectGroupCharset;
    }
    if (I->name && I->type && !strcasecomp(I->type, "radio")) {
	if (!text->last_anchor) {
	    I->checked = TRUE;
	} else {
	    TextAnchor * b = text->first_anchor;
	    int i2 = 0;
	    while (b) {
		if (b->link_type == INPUT_ANCHOR &&
		    b->input_field->type == F_RADIO_TYPE &&
		    b->input_field->number == HTFormNumber) {
		    if (!strcmp(b->input_field->name, I->name)) {
			if (I->checked && b->input_field->num_value) {
			    b->input_field->num_value = 0;
			    StrAllocCopy(b->input_field->orig_value, "0");
			    break;
			}
			i2++;
		    }
		}
		if (b == text->last_anchor) {
		    if (i2 == 0)
		       I->checked = TRUE;
		    break;
		}
		b = b->next;
	    }
	}
    }

    a->next = 0;
    a->anchor = NULL;
    a->link_type = INPUT_ANCHOR;
    a->show_anchor = YES;

    a->hightext = NULL;
    a->extent = 2;

    a->input_field = f;

    f->select_list = 0;
    f->number = HTFormNumber;
    f->disabled = (HTFormDisabled ? TRUE : I->disabled);
    f->no_cache = NO;

    HTFormFields++;


    /*
     *  Set the no_cache flag if the METHOD is POST. - FM
     */
    if (HTFormMethod == URL_POST_METHOD)
	f->no_cache = TRUE;

    /*
     *  Set up VALUE.
     */
    if (I->value)
	StrAllocCopy(IValue, I->value);
    if (IValue && HTCJK != NOCJK) {
	if ((tmp = (unsigned char *)calloc(1, (strlen(IValue) + 1)))) {
	    if (kanji_code == EUC) {
		TO_EUC((unsigned char *)IValue, tmp);
		I->value_cs = current_char_set;
	    } else if (kanji_code == SJIS) {
		TO_SJIS((unsigned char *)IValue, tmp);
		I->value_cs = current_char_set;
	    } else {
		for (i = 0, j = 0; IValue[i]; i++) {
		    if (IValue[i] != '\033') {
			tmp[j++] = IValue[i];
		    }
		}
	    }
	    StrAllocCopy(IValue, (CONST char *)tmp);
	    FREE(tmp);
	}
    }

    /*
     *  Special case of OPTION.
     *  Is handled above if radio type and LYSelectPopups is FALSE.
     */
    /* set the values and let the parsing below do the work */
    if (I->type != NULL && !strcmp(I->type,"OPTION")) {
	cp_option = I->type;
	if (HTCurSelectGroupType == F_RADIO_TYPE)
	    I->type = "OPTION_LIST";
	else
	    I->type = "CHECKBOX";
	I->name = HTCurSelectGroup;
	I->name_cs = HTCurSelectGroupCharset;

	/*
	 *  The option's size parameter actually gives the length and not
	 *    the width of the list.  Perform the conversion here
	 *    and get rid of the allocated HTCurSelect....
	 *  0 is ok as it means any length (arbitrary decision).
	 */
	if (HTCurSelectGroupSize != NULL) {
	    f->size_l = atoi(HTCurSelectGroupSize);
	    FREE(HTCurSelectGroupSize);
	}
    }

    /*
     *  Set SIZE.
     */
    if (I->size != NULL) {
	f->size = atoi(I->size);
	/*
	 *  Leave at zero for option lists.
	 */
	if (f->size == 0 && cp_option == NULL) {
	   f->size = 20;  /* default */
	}
    } else {
	f->size = 20;  /* default */
    }

    /*
     *  Set MAXLENGTH.
     */
    if (I->maxlength != NULL) {
	f->maxlength = atoi(I->maxlength);
    } else {
	f->maxlength = 0;  /* 0 means infinite */
    }

    /*
     *  Set CHECKED
     *  (num_value is only relevant to check and radio types).
     */
    if (I->checked == TRUE)
	f->num_value = 1;
    else
	f->num_value = 0;

    /*
     *  Set TYPE.
     */
    if (I->type != NULL) {
	if (!strcasecomp(I->type,"password")) {
	    f->type = F_PASSWORD_TYPE;
	} else if (!strcasecomp(I->type,"checkbox")) {
	    f->type = F_CHECKBOX_TYPE;
	} else if (!strcasecomp(I->type,"radio")) {
	    f->type = F_RADIO_TYPE;
	} else if (!strcasecomp(I->type,"submit")) {
	    f->type = F_SUBMIT_TYPE;
	} else if (!strcasecomp(I->type,"image")) {
	    f->type = F_IMAGE_SUBMIT_TYPE;
	} else if (!strcasecomp(I->type,"reset")) {
	    f->type = F_RESET_TYPE;
	} else if (!strcasecomp(I->type,"OPTION_LIST")) {
	    f->type = F_OPTION_LIST_TYPE;
	} else if (!strcasecomp(I->type,"hidden")) {
	    f->type = F_HIDDEN_TYPE;
	    HTFormFields--;
	    f->size = 0;
	} else if (!strcasecomp(I->type,"textarea")) {
	    f->type = F_TEXTAREA_TYPE;
	} else if (!strcasecomp(I->type,"range")) {
	    f->type = F_RANGE_TYPE;
	} else if (!strcasecomp(I->type,"file")) {
	    f->type = F_FILE_TYPE;
	} else if (!strcasecomp(I->type,"keygen")) {
	    f->type = F_KEYGEN_TYPE;
	} else {
	    /*
	     *  Note that TYPE="scribble" defaults to TYPE="text". - FM
	     */
	    f->type = F_TEXT_TYPE; /* default */
	}
    } else {
	f->type = F_TEXT_TYPE;
    }

    /*
     *  Set NAME.
     */
    if (I->name != NULL) {
	StrAllocCopy(f->name,I->name);
	f->name_cs = I->name_cs;
    } else {
	if (f->type == F_RESET_TYPE ||
	    f->type == F_SUBMIT_TYPE ||
	    f->type == F_IMAGE_SUBMIT_TYPE) {
	    /*
	     *  Set name to empty string.
	     */
	    StrAllocCopy(f->name, "");
	} else {
	    /*
	     *  Error!  NAME must be present.
	     */
	    CTRACE(tfp,
		  "GridText: No name present in input field; not displaying\n");
	    FREE(a);
	    FREE(f);
	    FREE(IValue);
	    return(0);
	}
    }

    /*
     *  Add this anchor to the anchor list
     */
    if (text->last_anchor) {
	text->last_anchor->next = a;
    } else {
	text->first_anchor = a;
    }

    /*
     *  Set VALUE, if it exists.  Otherwise, if it's not
     *  an option list make it a zero-length string. - FM
     */
    if (IValue != NULL) {
	/*
	 *  OPTION VALUE is not actually the value to be seen but is to
	 *    be sent....
	 */
	if (f->type == F_OPTION_LIST_TYPE ||
	    f->type == F_CHECKBOX_TYPE) {
	    /*
	     *  Fill both with the value.  The f->value may be
	     *  overwritten in HText_setLastOptionValue....
	     */
	    StrAllocCopy(f->value, IValue);
	    StrAllocCopy(f->cp_submit_value, IValue);
	} else {
	    StrAllocCopy(f->value, IValue);
	}
	f->value_cs = I->value_cs;
    } else if (f->type != F_OPTION_LIST_TYPE) {
	StrAllocCopy(f->value, "");
	/*
	 *  May be an empty INPUT field.  The text entered will then
	 *  probably be in the current display character set. - kw
	 */
	f->value_cs = current_char_set;
    }

    /*
     *  Run checks and fill in necessary values.
     */
    if (f->type == F_RESET_TYPE) {
	if (f->value && *f->value != '\0') {
	    f->size = strlen(f->value);
	} else {
	    StrAllocCopy(f->value, "Reset");
	    f->size = 5;
	}
    } else if (f->type == F_IMAGE_SUBMIT_TYPE ||
	       f->type == F_SUBMIT_TYPE) {
	if (f->value && *f->value != '\0') {
	    f->size = strlen(f->value);
	} else if (f->type == F_IMAGE_SUBMIT_TYPE) {
	    StrAllocCopy(f->value, "[IMAGE]-Submit");
	    f->size = 14;
	} else {
	    StrAllocCopy(f->value, "Submit");
	    f->size = 6;
	}
	f->submit_action = NULL;
	StrAllocCopy(f->submit_action, HTFormAction);
	if (HTFormEnctype != NULL)
	    StrAllocCopy(f->submit_enctype, HTFormEnctype);
	if (HTFormTitle != NULL)
	    StrAllocCopy(f->submit_title, HTFormTitle);
	f->submit_method = HTFormMethod;

    } else if (f->type == F_RADIO_TYPE || f->type == F_CHECKBOX_TYPE) {
	f->size=3;
	if (IValue == NULL)
	   StrAllocCopy(f->value, (f->type == F_CHECKBOX_TYPE ? "on" : ""));

    }
    FREE(IValue);

    /*
     *  Set original values.
     */
    if (f->type == F_RADIO_TYPE || f->type == F_CHECKBOX_TYPE ) {
	if (f->num_value)
	    StrAllocCopy(f->orig_value, "1");
	else
	    StrAllocCopy(f->orig_value, "0");
    } else if (f->type == F_OPTION_LIST_TYPE) {
	f->orig_value = NULL;
    } else {
	StrAllocCopy(f->orig_value, f->value);
    }

    /*
     *  Store accept-charset if present, converting to lowercase
     *  and collapsing spaces. - kw
     */
    if (I->accept_cs) {
	StrAllocCopy(f->accept_cs, I->accept_cs);
	LYRemoveBlanks(f->accept_cs);
	LYLowerCase(f->accept_cs);
    }

    /*
     *  Add numbers to form fields if needed. - LE & FM
     */
    switch (f->type) {
	/*
	 *  Do not supply number for hidden fields, nor
	 *  for types that are not yet implemented.
	 */
	case F_HIDDEN_TYPE:
	case F_FILE_TYPE:
	case F_RANGE_TYPE:
	case F_KEYGEN_TYPE:
	    a->number = 0;
	    break;

	default:
	    if (keypad_mode == LINKS_AND_FORM_FIELDS_ARE_NUMBERED)
		a->number = ++(text->last_anchor_number);
	    else
		a->number = 0;
	    break;
    }
    if (keypad_mode == LINKS_AND_FORM_FIELDS_ARE_NUMBERED && a->number > 0) {
	char marker[16];

	if (f->type != F_OPTION_LIST_TYPE)
	    /*
	     *  '[' was already put out for a popup menu
	     *  designator.  See HTML.c.
	     */
	    HText_appendCharacter(text, '[');
	sprintf(marker,"%d]", a->number);
	HText_appendText(text, marker);
	if (f->type == F_OPTION_LIST_TYPE)
	    /*
	     *  Add option list designation char.
	     */
	    HText_appendCharacter(text, '[');
	a->start = text->chars + text->last_line->size;
	a->line_pos = text->last_line->size;
    }

    /*
     *  Restrict SIZE to maximum allowable size.
     */
    switch (f->type) {
	int MaximumSize;

	case F_SUBMIT_TYPE:
	case F_IMAGE_SUBMIT_TYPE:
	case F_RESET_TYPE:
	case F_TEXT_TYPE:
	case F_TEXTAREA_TYPE:
	    /*
	     *  For submit and reset buttons, and for text entry
	     *  fields and areas, we limit the size element to that
	     *  of one line for the current style because that's
	     *  the most we could highlight on overwrites, and/or
	     *  handle in the line editor.  The actual values for
	     *  text entry lines can be long, and will be scrolled
	     *  horizontally within the editing window. - FM
	     */
	    MaximumSize = (LYcols - 1) -
			  (int)text->style->leftIndent -
			  (int)text->style->rightIndent;

	    /*
	     *  If we are numbering form links, take that into
	     *  account as well. - FM
	     */
	    if (keypad_mode == LINKS_AND_FORM_FIELDS_ARE_NUMBERED)
		MaximumSize -= ((a->number/10) + 3);
	    if (f->size > MaximumSize)
		f->size = MaximumSize;

	    /*
	     *  Save value for submit/reset buttons so they
	     *  will be visible when printing the page. - LE
	     */
	    I->value = f->value;
	    break;


	default:
	    /*
	     *  For all other fields we limit the size element to
	     *  10 less than the screen width, because either they
	     *  are types with small placeholders, and/or are a
	     *  type which is handled via a popup window. - FM
	     */
	    if (f->size > LYcols-10)
		f->size = LYcols-10;  /* maximum */
	    break;
    }

    /*
     *  Add this anchor to the anchor list
     */
    text->last_anchor = a;

    if (HTCurrentForm) {	/* should always apply! - kw */
	if (!HTCurrentForm->first_field) {
	    HTCurrentForm->first_field = f;
	}
	HTCurrentForm->last_field = f;
	HTCurrentForm->nfields++; /* will count hidden fields - kw */
	/*
	 *  Propagate form field's accept-charset attribute to enclosing
	 *  form if the form itself didn't have an accept-charset - kw
	 */
	if (f->accept_cs && !HTFormAcceptCharset) {
	    StrAllocCopy(HTFormAcceptCharset, f->accept_cs);
	}
	if (!text->forms) {
	    text->forms = HTList_new();
	}
    } else {
	CTRACE(tfp, "beginInput: HTCurrentForm is missing!\n");
    }

    CTRACE(tfp, "Input link: name=%s\nvalue=%s\nsize=%d\n",
			f->name,
			((f->value != NULL) ? f->value : ""),
			f->size);
    CTRACE(tfp, "Input link: name_cs=%d \"%s\" (from %d \"%s\")\n",
			f->name_cs,
			(f->name_cs >= 0 ?
			 LYCharSet_UC[f->name_cs].MIMEname : "<UNKNOWN>"),
			I->name_cs,
			(I->name_cs >= 0 ?
			 LYCharSet_UC[I->name_cs].MIMEname : "<UNKNOWN>"));
    CTRACE(tfp, "            value_cs=%d \"%s\" (from %d \"%s\")\n",
			f->value_cs,
			(f->value_cs >= 0 ?
			 LYCharSet_UC[f->value_cs].MIMEname : "<UNKNOWN>"),
			I->value_cs,
			(I->value_cs >= 0 ?
			 LYCharSet_UC[I->value_cs].MIMEname : "<UNKNOWN>"));

    /*
     *  Return the SIZE of the input field.
     */
    return(f->size);
}

/*
 *  Get a translation (properly: transcoding) quality, factoring in
 *  our ability to translate (an UCTQ_t) and a possible q parameter
 *  on the given charset string, for cs_from -> givenmime.
 *  The parsed input string will be mutilated on exit(!).
 *  Note that results are not normalised to 1.0, but results from
 *  different calls of this function can be compared. - kw
 *
 *  Obsolete, it was planned to use here a quality parametr UCTQ_t,
 *  which is boolean now.
 */
PRIVATE double get_trans_q ARGS2(
    int,		cs_from,
    char *,		givenmime)
{
    double df = 1.0;
    BOOL tq;
    char *p;
    if (!givenmime || !(*givenmime))
	return 0.0;
    if ((p = strchr(givenmime,';')) != NULL) {
	*p++ = '\0';
    }
    if (!strcmp(givenmime, "*"))
	tq = UCCanTranslateFromTo(cs_from,
				  UCGetLYhndl_byMIME("utf-8"));
    else
	tq = UCCanTranslateFromTo(cs_from,
				  UCGetLYhndl_byMIME(givenmime));
    if (!tq)
	return 0.0;
    if (p && *p) {
	char *pair, *field = p, *pval, *ptok;
	/* Get all the parameters to the Charset */
	while ((pair = HTNextTok(&field, ";", "\"", NULL)) != NULL) {
	    if ((ptok = HTNextTok(&pair, "= ", NULL, NULL)) != NULL &&
		(pval = HTNextField(&pair)) != NULL) {
		if (0==strcasecomp(ptok,"q")) {
		    df = strtod(pval, NULL);
		    break;
		}
	    }
	}
	return (df * tq);
    } else {
	return tq;
    }
}

/*
 *  Find the best charset for submission, if we have an ACCEPT_CHARSET
 *  list.  It factors in how well we can translate (just as guess, and
 *  not a very good one..) and possible  ";q=" factors.  Yes this is
 *  more general than it needs to be here.
 *
 *  Input is cs_in and acceptstring.
 *
 *  Will return charset handle as int.
 *  best_csname will point to a newly allocated MIME string for the
 *  charset corresponding to the return value if return value >= 0.
 *  - kw
 */
PRIVATE int find_best_target_cs ARGS3(
    char **,		best_csname,
    int,		cs_from,
    CONST char *,	acceptstring)
{
    char *paccept = NULL;
    double bestq = -1.0;
    char *bestmime = NULL;
    char *field, *nextfield;
    StrAllocCopy(paccept, acceptstring);
    nextfield = paccept;
    while ((field = HTNextTok(&nextfield, ",", "\"", NULL)) != NULL) {
	double q;
	if (*field != '\0') {
	    /* Get the Charset*/
	    q = get_trans_q(cs_from, field);
	    if (q > bestq) {
		bestq = q;
		bestmime = field;
	    }
	}
    }
    if (bestmime) {
	if (!strcmp(bestmime, "*")) /* non-standard for HTML attribute.. */
	    StrAllocCopy(*best_csname, "utf-8");
	else
	    StrAllocCopy(*best_csname, bestmime);
	FREE(paccept);
	if (bestq > 0)
	    return (UCGetLYhndl_byMIME(*best_csname));
	else
	    return (-1);
    }
    FREE(paccept);
    return (-1);
}

PUBLIC void HText_SubmitForm ARGS4(
	FormInfo *,	submit_item,
	document *,	doc,
	char *,		link_name,
	char *,		link_value)
{
    TextAnchor *anchor_ptr;
    int form_number = submit_item->number;
    FormInfo *form_ptr;
    PerFormInfo *thisform;
    int len;
    char *query = NULL;
    char *escaped1 = NULL, *escaped2 = NULL;
    int first_one = 1;
    char *last_textarea_name = NULL;
    int textarea_lineno = 0;
    char *previous_blanks = NULL;
    BOOLEAN PlainText = FALSE;
    BOOLEAN SemiColon = FALSE;
    char *Boundary = NULL;
    char *MultipartContentType = NULL;
    int target_cs = -1;
    CONST char *out_csname;
    CONST char *target_csname = NULL;
    char *name_used = "";
    BOOL form_has_8bit = NO, form_has_special = NO;
    BOOL field_has_8bit = NO, field_has_special = NO;
    BOOL name_has_8bit = NO, name_has_special = NO;
    BOOL have_accept_cs = NO;
    BOOL success;
    BOOL had_chartrans_warning = NO;
    char *val_used = "";
    char *copied_val_used = NULL;
    char *copied_name_used = NULL;

    if (!HTMainText)
	return;

    thisform = HTList_objectAt(HTMainText->forms, form_number - 1);
    /*  Sanity check */
    if (!thisform) {
	CTRACE(tfp, "SubmitForm: form %d not in HTMainText's list!\n",
		    form_number);
    } else if (thisform->number != form_number) {
	CTRACE(tfp, "SubmitForm: failed sanity check, %d!=%d !\n",
		    thisform->number, form_number);
	thisform = NULL;
    }

    if (submit_item->submit_action) {
	/*
	 *  If we're mailing, make sure it's a mailto ACTION. - FM
	 */
	if ((submit_item->submit_method == URL_MAIL_METHOD) &&
	    strncmp(submit_item->submit_action, "mailto:", 7)) {
	    HTAlert(BAD_FORM_MAILTO);
	    return;
	}

	/*
	 *  Set length plus breathing room.
	 */
	len = strlen(submit_item->submit_action) + 2048;
    } else {
	return;
    }

    /*
     *  Check the ENCTYPE and set up the appropriate variables. - FM
     */
    if (submit_item->submit_enctype &&
	!strncasecomp(submit_item->submit_enctype, "text/plain", 10)) {
	/*
	 *  Do not hex escape, and use physical newlines
	 *  to separate name=value pairs. - FM
	 */
	PlainText = TRUE;
    } else if (submit_item->submit_enctype &&
	       !strncasecomp(submit_item->submit_enctype,
			     "application/sgml-form-urlencoded", 32)) {
	/*
	 *  Use semicolons instead of ampersands as the
	 *  separators for name=value pairs. - FM
	 */
	SemiColon = TRUE;
    } else if (submit_item->submit_enctype &&
	       !strncasecomp(submit_item->submit_enctype,
			     "multipart/form-data", 19)) {
	/*
	 *  Use the multipart MIME format.  We should generate
	 *  a boundary string which we are sure doesn't occur
	 *  in the content, but for now we'll just assume that
	 *  this string doesn't. - FM
	 */
	Boundary = "xnyLAaB03X";
    }

    /*
     *  Determine in what character encoding (aka. charset) we should
     *  submit.  We call this target_cs and the MIME name for it
     *  target_csname.
     *  TODO:   - actually use ACCEPT-CHARSET stuff from FORM
     *  TODO:   - deal with list in ACCEPT-CHARSET, find a "best"
     *		  charset to submit
     */

    /* Look at ACCEPT-CHARSET on the submitting field if we have one. */
    if (thisform && submit_item->accept_cs &&
	strcasecomp(submit_item->accept_cs, "UNKNOWN")) {
	have_accept_cs = YES;
	target_cs = find_best_target_cs(&thisform->thisacceptcs,
					current_char_set,
					submit_item->accept_cs);
    }
    /* Look at ACCEPT-CHARSET on form as a whole if submitting field
     * didn't have one. */
    if (thisform && !have_accept_cs && thisform->accept_cs &&
	strcasecomp(thisform->accept_cs, "UNKNOWN")) {
	have_accept_cs = YES;
	target_cs = find_best_target_cs(&thisform->thisacceptcs,
					current_char_set,
					thisform->accept_cs);
    }
    if (have_accept_cs && (target_cs >= 0) && thisform->thisacceptcs) {
	target_csname = thisform->thisacceptcs;
    }

    if (target_cs < 0 &&
	HTMainText->node_anchor->charset &&
	*HTMainText->node_anchor->charset) {
	target_cs = UCGetLYhndl_byMIME(HTMainText->node_anchor->charset);
	if (target_cs >= 0) {
	    target_csname = HTMainText->node_anchor->charset;
	} else {
	    target_cs = UCLYhndl_for_unspec; /* always >= 0 */
	    target_csname = LYCharSet_UC[target_cs].MIMEname;
	}
    }
    if (target_cs < 0) {
	target_cs = UCLYhndl_for_unspec;  /* always >= 0 */
    }

    /*
     *  Go through list of anchors and get size first.
     */
    /*
     *  also get a "max." charset parameter - kw
     */
    anchor_ptr = HTMainText->first_anchor;
    while (anchor_ptr) {
	if (anchor_ptr->link_type == INPUT_ANCHOR) {
	    if (anchor_ptr->input_field->number == form_number) {

		char *p;
		char * val;
		form_ptr = anchor_ptr->input_field;
		val = form_ptr->cp_submit_value != NULL ?
				    form_ptr->cp_submit_value : form_ptr->value;
		field_has_8bit = NO;
		field_has_special = NO;

		len += (strlen(form_ptr->name) + (Boundary ? 100 : 10));
		/*
		 *  Calculate by the option submit value if present.
		 */
		if (form_ptr->cp_submit_value != NULL) {
		    len += (strlen(form_ptr->cp_submit_value) + 10);
		} else {
		    len += (strlen(form_ptr->value) + 10);
		}
		len += 32; /* plus and ampersand + safety net */

		for (p = val;
		     p && *p && !(field_has_8bit && field_has_special);
		     p++)
		    if ((*p == HT_NON_BREAK_SPACE) ||
			(*p == HT_EM_SPACE) ||
			(*p == LY_SOFT_HYPHEN)) {
			field_has_special = YES;
		    } else if ((*p & 0x80) != 0) {
			field_has_8bit = YES;
		    }
		for (p = form_ptr->name;
		     p && *p && !(name_has_8bit && name_has_special);
		     p++)
		    if ((*p == HT_NON_BREAK_SPACE) ||
			(*p == HT_EM_SPACE) ||
			(*p == LY_SOFT_HYPHEN)) {
			name_has_special = YES;
		    } else if ((*p & 0x80) != 0) {
			name_has_8bit = YES;
		    }

		if (field_has_8bit || name_has_8bit)
		    form_has_8bit = YES;
		if (field_has_special || name_has_special)
		    form_has_special = YES;

		if (!field_has_8bit && !field_has_special) {
		    /* already ok */
		} else if (target_cs < 0) {
		    /* already confused */
		} else if (!field_has_8bit &&
		    (LYCharSet_UC[target_cs].enc == UCT_ENC_8859 ||
		     (LYCharSet_UC[target_cs].like8859 & UCT_R_8859SPECL))) {
		    /* those specials will be trivial */
		} else if (UCNeedNotTranslate(form_ptr->value_cs, target_cs)) {
		    /* already ok */
		} else if (UCCanTranslateFromTo(form_ptr->value_cs, target_cs)) {
		    /* also ok */
		} else if (UCCanTranslateFromTo(target_cs, form_ptr->value_cs)) {
		    target_cs = form_ptr->value_cs;	/* try this */
		    target_csname = NULL; /* will be set after loop */
		} else {
		    target_cs = -1; /* don't know what to do */
		}

		/*  Same for name */
		if (!name_has_8bit && !name_has_special) {
		    /* already ok */
		} else if (target_cs < 0) {
		    /* already confused */
		} else if (!name_has_8bit &&
		    (LYCharSet_UC[target_cs].enc == UCT_ENC_8859 ||
		     (LYCharSet_UC[target_cs].like8859 & UCT_R_8859SPECL))) {
		    /* those specials will be trivial */
		} else if (UCNeedNotTranslate(form_ptr->name_cs, target_cs)) {
		    /* already ok */
		} else if (UCCanTranslateFromTo(form_ptr->name_cs, target_cs)) {
		    /* also ok */
		} else if (UCCanTranslateFromTo(target_cs, form_ptr->name_cs)) {
		    target_cs = form_ptr->value_cs;	/* try this */
		    target_csname = NULL; /* will be set after loop */
		} else {
		    target_cs = -1; /* don't know what to do */
		}

	    } else if (anchor_ptr->input_field->number > form_number) {
		break;
	    }
	}

	if (anchor_ptr == HTMainText->last_anchor)
	    break;

	anchor_ptr = anchor_ptr->next;
    }

    if (target_csname == NULL && target_cs >= 0) {
	if (form_has_8bit) {
	    target_csname = LYCharSet_UC[target_cs].MIMEname;
	} else if (form_has_special) {
	    target_csname = LYCharSet_UC[target_cs].MIMEname;
	} else {
	    target_csname = "us-ascii";
	}
    }
    /*
     *  Get query ready.
     */
    query = (char *)calloc(1, len);
    if (query == NULL)
	outofmem(__FILE__, "HText_SubmitForm");

    if (submit_item->submit_method == URL_GET_METHOD && Boundary == NULL) {
	strcpy (query, submit_item->submit_action);
	/*
	 *  Method is GET.  Clip out any anchor in the current URL.
	 */
	strtok (query, "#");
	/*
	 *  Clip out any old query in the current URL.
	 */
	strtok (query, "?");
	/*
	 *  Add the lead question mark for the new URL.
	 */
	strcat(query,"?");
    } else {
	query[0] = '\0';
	/*
	 *  We are submitting POST content to a server,
	 *  so load the post_content_type element. - FM
	 */
	if (SemiColon == TRUE) {
	    StrAllocCopy(doc->post_content_type,
			 "application/sgml-form-urlencoded");
	} else if (PlainText == TRUE) {
	    StrAllocCopy(doc->post_content_type,
			 "text/plain");
	} else if (Boundary != NULL) {
	    StrAllocCopy(doc->post_content_type,
			 "multipart/form-data; boundary=");
	    StrAllocCat(doc->post_content_type, Boundary);
	} else {
	    StrAllocCopy(doc->post_content_type,
			 "application/x-www-form-urlencoded");
	}

	/*
	 *  If the ENCTYPE is not multipart/form-data, append the
	 *  charset we'll be sending to the post_content_type, IF
	 *  (1) there was an explicit accept-charset attribute, OR
	 *  (2) we have 8-bit or special chars, AND the document had
	 *      an explicit (recognized and accepted) charset parameter,
	 *	AND it or target_csname is different from iso-8859-1,
	 *      OR
	 *  (3) we have 8-bit or special chars, AND the document had
	 *      no explicit (recognized and accepted) charset parameter,
	 *	AND target_cs is different from the currently effective
	 *	assumed charset (which should have been set by the user
	 *	so that it reflects what the server is sending, if the
	 *	document is rendered correctly).
	 *  For multipart/form-data the equivalent will be done later,
	 *  separately for each form field. - kw
	 */
	if (have_accept_cs ||
	    (form_has_8bit || form_has_special)) {
	    if (target_cs >= 0 && target_csname) {
		if (Boundary == NULL) {
		    if ((HTMainText->node_anchor->charset &&
			 (strcmp(HTMainText->node_anchor->charset,
				 "iso-8859-1") ||
			  strcmp(target_csname, "iso-8859-1"))) ||
			(!HTMainText->node_anchor->charset &&
			 target_cs != UCLYhndl_for_unspec)) {
			StrAllocCat(doc->post_content_type, "; charset=");
			StrAllocCat(doc->post_content_type, target_csname);
		    }
		}
	    } else {
		had_chartrans_warning = YES;
		_user_message(
		    CANNOT_TRANSCODE_FORM,
		    target_csname ? target_csname : "UNKNOWN");
		sleep(AlertSecs);
	    }
	}
    }


    out_csname = target_csname;

    /*
     *  Reset anchor->ptr.
     */
    anchor_ptr = HTMainText->first_anchor;
    /*
     *  Go through list of anchors and assemble URL query.
     */
    while (anchor_ptr) {
	if (anchor_ptr->link_type == INPUT_ANCHOR) {
	    if (anchor_ptr->input_field->number == form_number) {
		char *p;
		int out_cs;
		form_ptr = anchor_ptr->input_field;

		if (form_ptr->type != F_TEXTAREA_TYPE)
		    textarea_lineno = 0;

		switch(form_ptr->type) {
		case F_RESET_TYPE:
		    break;
		case F_SUBMIT_TYPE:
		case F_TEXT_SUBMIT_TYPE:
		case F_IMAGE_SUBMIT_TYPE:
		    if (!(form_ptr->name && *form_ptr->name != '\0' &&
			  !strcmp(form_ptr->name, link_name))) {
			CTRACE(tfp,
				    "SubmitForm: skipping submit field with ");
			CTRACE(tfp, "name \"%s\" for link_name \"%s\", %s.\n",
				    form_ptr->name ? form_ptr->name : "???",
				    link_name ? link_name : "???",
				    (form_ptr->name && *form_ptr->name) ?
				    "not current link" : "no field name");
			break;
		    }
		    if (!(form_ptr->type == F_TEXT_SUBMIT_TYPE ||
			(form_ptr->value && *form_ptr->value != '\0' &&
			 !strcmp(form_ptr->value, link_value)))) {
			if (TRACE) {
			    fprintf(tfp,
				    "SubmitForm: skipping submit field with ");
			    fprintf(tfp,
				    "name \"%s\" for link_name \"%s\", %s!\n",
				    form_ptr->name ? form_ptr->name : "???",
				    link_name ? link_name : "???",
				    "values are different");
			}
			break;
		    }
		    /*  fall through  */
		case F_RADIO_TYPE:
		case F_CHECKBOX_TYPE:
		case F_TEXTAREA_TYPE:
		case F_PASSWORD_TYPE:
		case F_TEXT_TYPE:
		case F_OPTION_LIST_TYPE:
		case F_HIDDEN_TYPE:
		    /*
		     *	Be sure to actually look at the option submit value.
		     */
		    if (form_ptr->cp_submit_value != NULL) {
			val_used = form_ptr->cp_submit_value;
		    } else {
			val_used = form_ptr->value;
		    }

		    /*
		     *  Charset-translate value now, because we need
		     *  to know the charset parameter for multipart
		     *  bodyparts. - kw
		     */
		    field_has_8bit = NO;
		    field_has_special = NO;
		    for (p = val_used;
			 p && *p && !(field_has_8bit && field_has_special);
			 p++) {
			if ((*p == HT_NON_BREAK_SPACE) ||
			    (*p == HT_EM_SPACE) ||
			    (*p == LY_SOFT_HYPHEN)) {
			    field_has_special = YES;
			} else if ((*p & 0x80) != 0) {
			    field_has_8bit = YES;
			}
		    }

		    if (field_has_8bit || field_has_special) {
			/*  We should translate back. */
			StrAllocCopy(copied_val_used, val_used);
			success = LYUCTranslateBackFormData(&copied_val_used,
							form_ptr->value_cs,
							target_cs, PlainText);
			CTRACE(tfp, "SubmitForm: field \"%s\" %d %s -> %d %s %s\n",
				    form_ptr->name ? form_ptr->name : "",
				    form_ptr->value_cs,
				    form_ptr->value_cs >= 0 ?
				    LYCharSet_UC[form_ptr->value_cs].MIMEname :
									  "???",
				    target_cs,
				    target_csname ? target_csname : "???",
				    success ? "OK" : "FAILED");
			if (success) {
			    val_used = copied_val_used;
			}
		    } else {  /* We can use the value directly. */
			CTRACE(tfp, "SubmitForm: field \"%s\" %d %s OK\n",
				    form_ptr->name ? form_ptr->name : "",
				    target_cs,
				    target_csname ? target_csname : "???");
			success = YES;
		    }
		    if (!success) {
			if (!had_chartrans_warning) {
			    had_chartrans_warning = YES;
			    _user_message(
				CANNOT_TRANSCODE_FORM,
				target_csname ? target_csname : "UNKNOWN");
			    sleep(AlertSecs);
			}
			out_cs = form_ptr->value_cs;
		    } else {
			out_cs = target_cs;
		    }
		    if (out_cs >= 0)
			out_csname = LYCharSet_UC[out_cs].MIMEname;
		    if (Boundary) {
			if (!success && form_ptr->value_cs < 0) {
			    /*  This is weird. */
			    StrAllocCopy(MultipartContentType,
					 "\r\nContent-Type: text/plain; charset=");
			    StrAllocCat(MultipartContentType, "UNKNOWN-8BIT");
			} else if (!success) {
			    StrAllocCopy(MultipartContentType,
					 "\r\nContent-Type: text/plain; charset=");
			    StrAllocCat(MultipartContentType, out_csname);
			    target_csname = NULL;
			} else {
			    if (!target_csname) {
				target_csname = LYCharSet_UC[target_cs].MIMEname;
			    }
			    StrAllocCopy(MultipartContentType,
					 "\r\nContent-Type: text/plain; charset=");
			    StrAllocCat(MultipartContentType, out_csname);
			}
		    }

		    /*
		     *  Charset-translate name now, because we need
		     *  to know the charset parameter for multipart
		     *  bodyparts. - kw
		     */
		    if (form_ptr->type == F_TEXTAREA_TYPE) {
			textarea_lineno++;
			if (textarea_lineno > 1 &&
			    last_textarea_name && form_ptr->name &&
			    !strcmp(last_textarea_name, form_ptr->name)) {
			    break;
			}
		    }
		    name_used = (form_ptr->name ?
				 form_ptr->name : "");

		    name_has_8bit = NO;
		    name_has_special = NO;
		    for (p = name_used;
			 p && *p && !(name_has_8bit && name_has_special);
			 p++) {
			if ((*p == HT_NON_BREAK_SPACE) ||
			    (*p == HT_EM_SPACE) ||
			    (*p == LY_SOFT_HYPHEN)) {
			    name_has_special = YES;
			} else if ((*p & 0x80) != 0) {
			    name_has_8bit = YES;
			}
		    }

		    if (name_has_8bit || name_has_special) {
			/*  We should translate back. */
			StrAllocCopy(copied_name_used, name_used);
			success = LYUCTranslateBackFormData(&copied_name_used,
							form_ptr->name_cs,
							target_cs, PlainText);
			CTRACE(tfp, "SubmitForm: name \"%s\" %d %s -> %d %s %s\n",
				    form_ptr->name ? form_ptr->name : "",
				    form_ptr->name_cs,
				    form_ptr->name_cs >= 0 ?
				    LYCharSet_UC[form_ptr->name_cs].MIMEname :
									  "???",
				    target_cs,
				    target_csname ? target_csname : "???",
				    success ? "OK" : "FAILED");
			if (success) {
			    name_used = copied_name_used;
			}
			if (Boundary) {
			    if (!success) {
				StrAllocCopy(MultipartContentType, "");
				target_csname = NULL;
			    } else {
				if (!target_csname)
				    target_csname = LYCharSet_UC[target_cs].MIMEname;
			    }
			}
		    } else {  /* We can use the name directly. */
			CTRACE(tfp, "SubmitForm: name \"%s\" %d %s OK\n",
				    form_ptr->name ? form_ptr->name : "",
				    target_cs,
				    target_csname ? target_csname : "???");
			success = YES;
			if (Boundary) {
			    StrAllocCopy(copied_name_used, name_used);
			}
		    }
		    if (!success) {
			if (!had_chartrans_warning) {
			    had_chartrans_warning = YES;
			    _user_message(
				CANNOT_TRANSCODE_FORM,
				target_csname ? target_csname : "UNKNOWN");
			    sleep(AlertSecs);
			}
		    }
		    if (Boundary) {
			/*
			 *  According to RFC 1867, Non-ASCII field names
			 *  "should be encoded according to the prescriptions
			 *  of RFC 1522 [...].  I don't think RFC 1522 actually
			 *  is meant to apply to parameters like this, and it
			 *  is unknown whether any server would make sense of
			 *  it, so for now just use some quoting/escaping and
			 *  otherwise leave 8-bit values as they are.
			 *  Non-ASCII characters in form field names submitted
			 *  as multipart/form-data can only occur if the form
			 *  provider specifically asked for it anyway. - kw
			 */
			HTMake822Word(&copied_name_used);
			name_used = copied_name_used;
		    }

		    break;
		default:
		    CTRACE(tfp, "SubmitForm: What type is %d?\n",
				form_ptr->type);
		}

		switch(form_ptr->type) {

		case F_RESET_TYPE:
		    break;

		case F_SUBMIT_TYPE:
		case F_TEXT_SUBMIT_TYPE:
		case F_IMAGE_SUBMIT_TYPE:
		    /*
		     *  If it has a non-zero length name (e.g., because
		     *  it's IMAGE_SUBMIT_TYPE to be handled homologously
		     *  to an image map, or a SUBMIT_TYPE in a set of
		     *  multiple submit buttons, or a single type="text"
		     *  that's been converted to a TEXT_SUBMIT_TYPE),
		     *  include the name=value pair, or fake name.x=0 and
		     *  name.y=0 pairs for IMAGE_SUBMIT_TYPE. - FM
		     */
		    if ((form_ptr->name && *form_ptr->name != '\0' &&
			!strcmp(form_ptr->name, link_name)) &&
		       (form_ptr->type == F_TEXT_SUBMIT_TYPE ||
			(form_ptr->value && *form_ptr->value != '\0' &&
			 !strcmp(form_ptr->value, link_value)))) {
			int cdisp_name_startpos = 0;
			if (first_one) {
			    if (Boundary) {
				sprintf(&query[strlen(query)],
					"--%s\r\n", Boundary);
			    }
			    first_one=FALSE;
			} else {
			    if (PlainText) {
				strcat(query, "\n");
			    } else if (SemiColon) {
				strcat(query, ";");
			    } else if (Boundary) {
				sprintf(&query[strlen(query)],
					"\r\n--%s\r\n", Boundary);
			    } else {
				strcat(query, "&");
			    }
			}

			if (PlainText) {
			    StrAllocCopy(escaped1, name_used);
			} else if (Boundary) {
			    StrAllocCopy(escaped1,
				    "Content-Disposition: form-data; name=");
			    cdisp_name_startpos = strlen(escaped1);
			    StrAllocCat(escaped1, name_used);
			    if (MultipartContentType)
				StrAllocCat(escaped1, MultipartContentType);
			    StrAllocCat(escaped1, "\r\n\r\n");
			} else {
			    escaped1 = HTEscapeSP(name_used, URL_XALPHAS);
			}

			if (PlainText || Boundary) {
			    StrAllocCopy(escaped2,
					 (val_used ?
					  val_used : ""));
			} else {
			    escaped2 = HTEscapeSP(val_used, URL_XALPHAS);
			}

			if (form_ptr->type == F_IMAGE_SUBMIT_TYPE) {
			    /*
			     *  It's a clickable image submit button.
			     *  Fake a 0,0 coordinate pair, which
			     *  typically returns the image's default. - FM
			     */
			    if (Boundary) {
				escaped1[cdisp_name_startpos] = '\0';
				sprintf(&query[strlen(query)],
				    "%s.x\r\n\r\n0\r\n--%s\r\n%s.y\r\n\r\n0",
					escaped1,
					Boundary,
					escaped1);
			    } else {
				sprintf(&query[strlen(query)],
					"%s.x=0%s%s.y=0%s",
					escaped1,
					(PlainText ?
					      "\n" : (SemiColon ?
							    ";" : "&")),
					escaped1,
					((PlainText && *escaped1) ?
							     "\n" : ""));
			    }
			} else {
			    /*
			     *  It's a standard submit button.
			     *  Use the name=value pair. = FM
			     */
			    sprintf(&query[strlen(query)],
				    "%s%s%s%s%s",
				    escaped1,
				    (Boundary ?
					   "" : "="),
				    (PlainText ?
					  "\n" : ""),
				    escaped2,
				    ((PlainText && *escaped2) ?
							 "\n" : ""));
			}
			FREE(escaped1);
			FREE(escaped2);
		    }
		    FREE(copied_name_used);
		    FREE(copied_val_used);
		    break;

		case F_RADIO_TYPE:
		case F_CHECKBOX_TYPE:
		    /*
		     *  Only add if selected.
		     */
		    if (form_ptr->num_value) {
			if (first_one) {
			    if (Boundary) {
				sprintf(&query[strlen(query)],
					"--%s\r\n", Boundary);
			    }
			    first_one=FALSE;
			} else {
			    if (PlainText) {
				strcat(query, "\n");
			    } else if (SemiColon) {
				strcat(query, ";");
			    } else if (Boundary) {
				sprintf(&query[strlen(query)],
					"\r\n--%s\r\n", Boundary);
			    } else {
				strcat(query, "&");
			    }
			}

			if (PlainText) {
			    StrAllocCopy(escaped1, name_used);
			} else if (Boundary) {
			    StrAllocCopy(escaped1,
				     "Content-Disposition: form-data; name=");
			    StrAllocCat(escaped1,
					name_used);
			    if (MultipartContentType)
				StrAllocCat(escaped1, MultipartContentType);
			    StrAllocCat(escaped1, "\r\n\r\n");
			} else {
			    escaped1 = HTEscapeSP(name_used, URL_XALPHAS);
			}
			if (PlainText || Boundary) {
			    StrAllocCopy(escaped2,
					 (val_used ?
					  val_used : ""));
			} else {
			    escaped2 = HTEscapeSP(val_used, URL_XALPHAS);
			}

			sprintf(&query[strlen(query)],
				"%s%s%s%s%s",
				escaped1,
				(Boundary ?
				       "" : "="),
				(PlainText ?
				      "\n" : ""),
				escaped2,
				((PlainText && *escaped2) ?
						     "\n" : ""));
			FREE(escaped1);
			FREE(escaped2);
		    }
		    FREE(copied_name_used);
		    FREE(copied_val_used);
		    break;

		case F_TEXTAREA_TYPE:
		    if (PlainText || Boundary) {
			StrAllocCopy(escaped2,
				     (val_used ?
				      val_used : ""));
		    } else {
			escaped2 = HTEscapeSP(val_used, URL_XALPHAS);
		    }

		    if (!last_textarea_name ||
			strcmp(last_textarea_name, form_ptr->name)) {
			textarea_lineno = 1;
			/*
			 *  Names are different so this is the first
			 *  textarea or a different one from any before
			 *  it.
			 */
			if (Boundary) {
			    StrAllocCopy(previous_blanks, "\r\n");
			} else {
			    FREE(previous_blanks);
			}
			if (first_one) {
			    if (Boundary) {
				sprintf(&query[strlen(query)],
					"--%s\r\n", Boundary);
			    }
			    first_one=FALSE;
			} else {
			    if (PlainText) {
				strcat(query, "\n");
			    } else if (SemiColon) {
				strcat(query, ";");
			    } else if (Boundary) {
				sprintf(&query[strlen(query)],
					"\r\n--%s\r\n", Boundary);
			    } else {
				strcat(query, "&");
			    }
			}
			if (PlainText) {
			    StrAllocCopy(escaped1, name_used);
			} else if (Boundary) {
			    StrAllocCopy(escaped1,
				    "Content-Disposition: form-data; name=");
			    StrAllocCat(escaped1, name_used);
			    if (MultipartContentType)
				StrAllocCat(escaped1, MultipartContentType);
			    StrAllocCat(escaped1, "\r\n\r\n");
			} else {
			    escaped1 = HTEscapeSP(name_used, URL_XALPHAS);
			}
			sprintf(&query[strlen(query)],
				"%s%s%s%s%s",
				escaped1,
				(Boundary ?
				       "" : "="),
				(PlainText ?
				      "\n" : ""),
				escaped2,
				((PlainText && *escaped2) ?
						     "\n" : ""));
			FREE(escaped1);
			last_textarea_name = form_ptr->name;
		    } else {
			/*
			 *  This is a continuation of a previous textarea
			 *  add %0a (\n) and the escaped string.
			 */
			if (escaped2[0] != '\0') {
			    if (previous_blanks) {
				strcat(query, previous_blanks);
				FREE(previous_blanks);
			    }
			    if (PlainText) {
				sprintf(&query[strlen(query)], "%s\n",
							       escaped2);
			    } else if (Boundary) {
				sprintf(&query[strlen(query)], "%s\r\n",
							       escaped2);
			    } else {
				sprintf(&query[strlen(query)], "%%0a%s",
							       escaped2);
			    }
			} else {
			    if (PlainText) {
				StrAllocCat(previous_blanks, "\n");
			    } else if (Boundary) {
				StrAllocCat(previous_blanks, "\r\n");
			    } else {
				StrAllocCat(previous_blanks, "%0a");
			    }
			}
		    }
		    FREE(escaped2);
		    FREE(copied_val_used);
		    break;

		case F_PASSWORD_TYPE:
		case F_TEXT_TYPE:
		case F_OPTION_LIST_TYPE:
		case F_HIDDEN_TYPE:
		    if (first_one) {
			if (Boundary) {
			    sprintf(&query[strlen(query)],
				    "--%s\r\n", Boundary);
			}
			first_one=FALSE;
		    } else {
			if (PlainText) {
			    strcat(query, "\n");
			} else if (SemiColon) {
			    strcat(query, ";");
			} else if (Boundary) {
			    sprintf(&query[strlen(query)],
				    "\r\n--%s\r\n", Boundary);
			} else {
			    strcat(query, "&");
			}
		    }

		    if (PlainText) {
		       StrAllocCopy(escaped1, name_used);
		    } else if (Boundary) {
			StrAllocCopy(escaped1,
				    "Content-Disposition: form-data; name=");
			StrAllocCat(escaped1, name_used);
			if (MultipartContentType)
			    StrAllocCat(escaped1, MultipartContentType);
			StrAllocCat(escaped1, "\r\n\r\n");
		    } else {
			escaped1 = HTEscapeSP(name_used, URL_XALPHAS);
		    }

		    if (PlainText || Boundary) {
			StrAllocCopy(escaped2,
				     (val_used ?
				      val_used : ""));
		    } else {
			escaped2 = HTEscapeSP(val_used, URL_XALPHAS);
		    }

		    sprintf(&query[strlen(query)],
			    "%s%s%s%s%s",
			    escaped1,
			    (Boundary ?
				   "" : "="),
			    (PlainText ?
				  "\n" : ""),
			    escaped2,
			    ((PlainText && *escaped2) ?
						 "\n" : ""));
		    FREE(escaped1);
		    FREE(escaped2);
		    FREE(copied_name_used);
		    FREE(copied_val_used);
		    break;
		}
	    } else if (anchor_ptr->input_field->number > form_number) {
		break;
	    }
	}

	if (anchor_ptr == HTMainText->last_anchor)
	    break;

	anchor_ptr = anchor_ptr->next;
    }
    FREE(copied_name_used);
    if (Boundary) {
	sprintf(&query[strlen(query)], "\r\n--%s--\r\n", Boundary);
    }
    FREE(previous_blanks);

    if (submit_item->submit_method == URL_MAIL_METHOD) {
	HTUserMsg2("Submitting %s", submit_item->submit_action);
	CTRACE(tfp, "\nGridText - mailto_address: %s\n",
			    (submit_item->submit_action+7));
	CTRACE(tfp, "GridText - mailto_subject: %s\n",
			    ((submit_item->submit_title &&
			      *submit_item->submit_title) ?
			      (submit_item->submit_title) :
					(HText_getTitle() ?
					 HText_getTitle() : "")));
	CTRACE(tfp,"GridText - mailto_content: %s\n",query);
	mailform((submit_item->submit_action+7),
		 ((submit_item->submit_title &&
		   *submit_item->submit_title) ?
		   (submit_item->submit_title) :
			     (HText_getTitle() ?
			      HText_getTitle() : "")),
		 query,
		 doc->post_content_type);
	FREE(query);
	FREE(doc->post_content_type);
	return;
    } else {
	_statusline(SUBMITTING_FORM);
    }

    if (submit_item->submit_method == URL_POST_METHOD || Boundary) {
	StrAllocCopy(doc->post_data, query);
	CTRACE(tfp,"GridText - post_data: %s\n",doc->post_data);
	StrAllocCopy(doc->address, submit_item->submit_action);
	FREE(query);
	return;
    } else { /* GET_METHOD */
	StrAllocCopy(doc->address, query);
	FREE(doc->post_data);
	FREE(doc->post_content_type);
	FREE(query);
	return;
    }
}

PUBLIC void HText_DisableCurrentForm NOARGS
{
    TextAnchor * anchor_ptr;

    HTFormDisabled = TRUE;
    if (!HTMainText)
	return;

    /*
     *  Go through list of anchors and set the disabled flag.
     */
    anchor_ptr = HTMainText->first_anchor;
    while (anchor_ptr) {
	if (anchor_ptr->link_type == INPUT_ANCHOR &&
	    anchor_ptr->input_field->number == HTFormNumber) {

	    anchor_ptr->input_field->disabled = TRUE;
	}

	if (anchor_ptr == HTMainText->last_anchor)
	    break;


	anchor_ptr = anchor_ptr->next;
    }

    return;
}

PUBLIC void HText_ResetForm ARGS1(
	FormInfo *,	form)
{
    TextAnchor * anchor_ptr;

    _statusline(RESETTING_FORM);
    if (!HTMainText)
	return;

    /*
     *  Go through list of anchors and reset values.
     */
    anchor_ptr = HTMainText->first_anchor;
    while (anchor_ptr) {
	if (anchor_ptr->link_type == INPUT_ANCHOR) {
	    if (anchor_ptr->input_field->number == form->number) {

		 if (anchor_ptr->input_field->type == F_RADIO_TYPE ||
		     anchor_ptr->input_field->type == F_CHECKBOX_TYPE) {

		    if (anchor_ptr->input_field->orig_value[0] == '0')
			anchor_ptr->input_field->num_value = 0;
		    else
			anchor_ptr->input_field->num_value = 1;

		 } else if (anchor_ptr->input_field->type ==
			    F_OPTION_LIST_TYPE) {
		    anchor_ptr->input_field->value =
				anchor_ptr->input_field->orig_value;

		    anchor_ptr->input_field->cp_submit_value =
				anchor_ptr->input_field->orig_submit_value;

		 } else {
		    StrAllocCopy(anchor_ptr->input_field->value,
					anchor_ptr->input_field->orig_value);
		 }
	     } else if (anchor_ptr->input_field->number > form->number) {
		 break;
	     }

	}

	if (anchor_ptr == HTMainText->last_anchor)
	    break;


	anchor_ptr = anchor_ptr->next;
    }
}

PUBLIC void HText_activateRadioButton ARGS1(
	FormInfo *,	form)
{
    TextAnchor * anchor_ptr;
    int form_number = form->number;

    if (!HTMainText)
	return;
    anchor_ptr = HTMainText->first_anchor;
    while (anchor_ptr) {
	if (anchor_ptr->link_type == INPUT_ANCHOR &&
		anchor_ptr->input_field->type == F_RADIO_TYPE) {

	    if (anchor_ptr->input_field->number == form_number) {

		    /* if it has the same name and its on */
		 if (!strcmp(anchor_ptr->input_field->name, form->name) &&
		     anchor_ptr->input_field->num_value) {
		    anchor_ptr->input_field->num_value = 0;
		    break;
		 }
	    } else if (anchor_ptr->input_field->number > form_number) {
		    break;
	    }

	}

	if (anchor_ptr == HTMainText->last_anchor)
	    break;

	anchor_ptr = anchor_ptr->next;
   }

   form->num_value = 1;
}

/*
 *	Purpose:	Free all currently loaded HText objects in memory.
 *	Arguments:	void
 *	Return Value:	void
 *	Remarks/Portability/Dependencies/Restrictions:
 *		Usage of this function should really be limited to program
 *			termination.
 *	Revision History:
 *		05-27-94	created Lynx 2-3-1 Garrett Arch Blythe
 */
PRIVATE void free_all_texts NOARGS
{
    HText *cur = NULL;

    if (!loaded_texts)
	return;

    /*
     *  Simply loop through the loaded texts list killing them off.
     */
    while (loaded_texts && !HTList_isEmpty(loaded_texts)) {
	if ((cur = (HText *)HTList_removeLastObject(loaded_texts)) != NULL) {
	    HText_free(cur);
	}
    }

    /*
     *  Get rid of the text list.
     */
    if (loaded_texts) {
	HTList_delete(loaded_texts);
    }

    /*
     *  Insurance for bad HTML.
     */
    FREE(HTCurSelectGroup);
    FREE(HTCurSelectGroupSize);
    FREE(HTCurSelectedOptionValue);
    FREE(HTFormAction);
    FREE(HTFormEnctype);
    FREE(HTFormTitle);
    FREE(HTFormAcceptCharset);
    PerFormInfo_free(HTCurrentForm);

    return;
}

/*
**  stub_HTAnchor_address is like HTAnchor_address, but it returns the
**  parent address for child links.  This is only useful for traversal's
**  where one does not want to index a text file N times, once for each
**  of N internal links.  Since the parent link has already been taken,
**  it won't go again, hence the (incorrect) links won't cause problems.
*/
PUBLIC char * stub_HTAnchor_address ARGS1(
	HTAnchor *,	me)
{
    char *addr = NULL;
    if (me)
	StrAllocCopy (addr, me->parent->address);
    return addr;
}

PUBLIC void HText_setToolbar ARGS1(
	HText *,	text)
{
    if (text)
	text->toolbar = TRUE;
    return;
}

PUBLIC BOOL HText_hasToolbar ARGS1(
	HText *,	text)
{
    return ((text && text->toolbar) ? TRUE : FALSE);
}

PUBLIC void HText_setNoCache ARGS1(
	HText *,	text)
{
    if (text)
	text->no_cache = TRUE;
    return;
}

PUBLIC BOOL HText_hasNoCacheSet ARGS1(
	HText *,	text)
{
    return ((text && text->no_cache) ? TRUE : FALSE);
}

PUBLIC BOOL HText_hasUTF8OutputSet ARGS1(
	HText *,	text)
{
    return ((text && text->T.output_utf8) ? TRUE : FALSE);
}

/*
**  Check charset and set the kcode element. - FM
**  Info on the input charset may be passed in in two forms,
**  as a string (if given explicitly) and as a pointer to
**  a LYUCcharset (from chartrans mechanism); either can be NULL.
**  For Japanese the kcode will be reset at a space or explicit
**  line or paragraph break, so what we set here may not last for
**  long.  It's potentially more important not to set HTCJK to
**  NOCJK unless we are sure. - kw
*/
PUBLIC void HText_setKcode ARGS3(
	HText *,	text,
	CONST char *,	charset,
	LYUCcharset *,	p_in)
{
    if (!text)
	return;

    /*
    **  Check whether we have some kind of info. - kw
    */
    if (!charset && !p_in) {
	return;
    }
    /*
    **  If no explicit charset string, use the implied one. - kw
    */
    if (!charset || *charset == '\0') {
	charset = p_in->MIMEname;
    }
    /*
    **  Check whether we have a specified charset. - FM
    */
    if (!charset || *charset == '\0') {
	return;
    }

    /*
    **  We've included the charset, and not forced a download offer,
    **  only if the currently selected character set can handle it,
    **  so check the charset value and set the text->kcode element
    **  appropriately. - FM
    */
    if (!strcmp(charset, "shift_jis") ||
	!strcmp(charset, "x-shift-jis")) {
	text->kcode = SJIS;
    } else if ((p_in && (p_in->enc == UCT_ENC_CJK)) ||
	       !strcmp(charset, "euc-jp") ||
	       !strncmp(charset, "x-euc-", 6) ||
	       !strcmp(charset, "iso-2022-jp") ||
	       !strcmp(charset, "iso-2022-jp-2") ||
	       !strcmp(charset, "euc-kr") ||
	       !strcmp(charset, "iso-2022-kr") ||
	       !strcmp(charset, "big5") ||
	       !strcmp(charset, "cn-big5") ||
	       !strcmp(charset, "euc-cn") ||
	       !strcmp(charset, "gb2312") ||
	       !strncmp(charset, "cn-gb", 5) ||
	       !strcmp(charset, "iso-2022-cn")) {
	text->kcode = EUC;
    } else {
	/*
	**  If we get to here, it's not CJK, so disable that if
	**  it is enabled.  But only if we are quite sure. - FM & kw
	*/
	text->kcode = NOKANJI;
	if (HTCJK != NOCJK) {
	    if (!p_in || p_in->enc != UCT_ENC_CJK)
		HTCJK = NOCJK;
	}
    }

    return;
}

/*
**  Set a permissible split at the current end of the last line. - FM
*/
PUBLIC void HText_setBreakPoint ARGS1(
	HText *,	text)
{
    if (!text)
	return;

    /*
     *  Can split here. - FM
     */
    text->permissible_split = text->last_line->size;

    return;
}

/*
**  This function determines whether a document which
**  would be sought via the a URL that has a fragment
**  directive appended is otherwise identical to the
**  currently loaded document, and if so, returns
**  FALSE, so that any no_cache directives can be
**  overridden "safely", on the grounds that we are
**  simply acting on the equivalent of a paging
**  command.  Otherwise, it returns TRUE, i.e, that
**  the target document might differ from the current,
**  based on any caching directives or analyses which
**  claimed or suggested this. - FM
*/
PUBLIC BOOL HText_AreDifferent ARGS2(
	HTParentAnchor *,	anchor,
	CONST char *,		full_address)
{
    HTParentAnchor *MTanc;
    char *MTaddress;
    char *MTpound;
    char *TargetPound;

    /*
     *  Do we have a loaded document and both
     *  arguments for this function?
     */
    if (!(HTMainText && anchor && full_address))
	return TRUE;

    /*
     *  Do we have both URLs?
     */
    MTanc = HTMainText->node_anchor;
    if (!(MTanc->address && anchor->address))
	return (TRUE);

    /*
     *  Do we have a fragment associated with the target?
     */
    if ((TargetPound = strchr(full_address, '#')) == NULL)
	return (TRUE);

    /*
     *  Always treat client-side image map menus
     *  as potentially stale, so we'll create a
     *  fresh menu from the LynxMaps HTList.
     */
    if (!strncasecomp(anchor->address, "LYNXIMGMAP:", 11))
	return (TRUE);

    /*
     *  Do the docs differ in the type of request?
     */
    if (MTanc->isHEAD != anchor->isHEAD)
	return (TRUE);

    /*
     *  Are the actual URLs different, after factoring
     *  out a "LYNXIMGMAP:" leader in the MainText URL
     *  and its fragment, if present?
     */
    MTaddress = (strncasecomp(MTanc->address,
			      "LYNXIMGMAP:", 11) ?
				  MTanc->address : (MTanc->address + 11));
    if ((MTpound = strchr(MTaddress, '#')) != NULL)
	*MTpound = '\0';
    if (strcmp(MTaddress, anchor->address)) {
	if (MTpound != NULL) {
	    *MTpound = '#';
	}
	return(TRUE);
    }
    if (MTpound != NULL) {
	*MTpound = '#';
    }

    /*
     *  If the MainText is not an image map menu,
     *  do the docs have different POST contents?
     */
    if (MTaddress == MTanc->address) {
	if (MTanc->post_data) {
	    if (anchor->post_data) {
		if (strcmp(MTanc->post_data, anchor->post_data)) {
		    /*
		     *  Both have contents, and they differ.
		     */
		    return(TRUE);
		}
	    } else {
		/*
		 *  The loaded document has content, but the
		 *  target doesn't, so they're different.
		 */
		return(TRUE);
	    }
	} else if (anchor->post_data) {
	    /*
	     *  The loaded document does not have content, but
	     *  the target does, so they're different.
	     */
	    return(TRUE);
	}
    }

    /*
     *  We'll assume the target is a position in the currently
     *  displayed document, and thus can ignore any header, META,
     *  or other directives not to use a cached rendition. - FM
     */
    return(FALSE);
}
