Milán Major

netscape-revival

1 branch
Code

lib/layout/laytags.c

/* -*- Mode: C; tab-width: 4; -*-
 */
#include "xp.h"
#include "pa_parse.h"
#include "layout.h"
#include "glhist.h"
#include "il.h"
#include "java.h"
#include "libi18n.h"
#include "edt.h"

#ifdef MOCHA
#include "libmocha.h"
#endif

#ifdef XP_WIN16
#define SIZE_LIMIT 32000
#endif

#define	LIST_MARGIN_INC		(FEUNITS_X(40, context))

#define MIN_FONT_SIZE		1
#define MAX_FONT_SIZE		7

#define	HYPE_ANCHOR		"about:hype"
#define	HYPE_TAG_BECOMES	"SRC=internal-gopher-sound BORDER=0>"


/*************************************
 * Function: LO_ChangeFontSize
 *
 * Description: Utility function to change a size field based on a
 *	string which is either a new absolute value, or a relative
 *	change prefaced with + or -.  Also sanifies the result
 *	to a valid font size.
 *
 * Params: Original size, and change string.
 *
 * Returns: New size.
 *************************************/
intn
LO_ChangeFontSize(intn size, char *size_str)
{
	intn new_size;

	if ((size_str == NULL)||(*size_str == '\0'))
	{
		return(size);
	}

	if (*size_str == '+')
	{
		new_size = size + XP_ATOI((size_str + 1));
	}
	else if (*size_str == '-')
	{
		new_size = size - XP_ATOI((size_str + 1));
	}
	else
	{
		new_size = XP_ATOI(size_str);
	}

	if (new_size < MIN_FONT_SIZE)
	{
		new_size = MIN_FONT_SIZE;
	}
	if (new_size > MAX_FONT_SIZE)
	{
		new_size = MAX_FONT_SIZE;
	}

	return(new_size);
}


/*
 * Filter out tags we should ignore based on current state.
 * Return FALSE for tags we should ignore, and TRUE for
 * tags we should keep.
 */
Bool
lo_FilterTag(MWContext *context, lo_DocState *state, PA_Tag *tag)
{
	lo_TopState *top_state;

	top_state = state->top_state;
	if (top_state == NULL)
	{
		return(FALSE);
	}

	/*
	 * If we are in a GRID, all non-grid tags are discarded
	 * If we are in a NOGRIDS, all tags except its end are discarded.
	 */
	if (top_state->is_grid != FALSE)
	{
		if (tag->type == P_NOGRIDS)
		{
			if (tag->is_end == FALSE)
			{
				top_state->in_nogrids = TRUE;
			}
			else
			{
				top_state->in_nogrids = FALSE;
			}
			return(FALSE);
		}
		else if (top_state->in_nogrids != FALSE)
		{
			return(FALSE);
		}
		else if ((tag->type != P_GRID)&&
			 (tag->type != P_GRID_CELL))
		{
			return(FALSE);
		}
		return(TRUE);
	}

#ifdef JAVA
	/*
	 * If the user has disabled Java, act like we don't understand
	 * the APPLET tag.
	 */
	if (LJ_GetJavaEnabled() != FALSE)
	{
		/*
		 * APPLETs can appear anywhere.  Check for them and
		 * maintain proper in_applet state.
		 */
		if (tag->type == P_JAVA_APPLET)
		{
			if (tag->is_end == FALSE)
			{
				top_state->in_applet = TRUE;
				IL_UseDefaultColormapThisPage(context);
			}
			else
			{
				top_state->in_applet = FALSE;
			}
			return(TRUE);
		}

		/*
		 * Inside applet ignore everything except param and
		 * closing applets.
		 */
		if (top_state->in_applet != FALSE)
		{
			if (tag->type != P_PARAM)
			{
				return(FALSE);
			}
			return(TRUE);
		}
	}
#endif /* JAVA */

#ifdef MOCHA
	if (LM_GetMochaEnabled() != FALSE)
	{
		/*
		 * NOSCRIPT can appear anywhere.  Check for them and
		 * maintain proper in noscript state.
		 */
		if (tag->type == P_NOSCRIPT)
		{
			top_state->in_noscript = !tag->is_end;
			return(FALSE);
		}

		/*
		 * Inside NOSCRIPT ignore everything except the closing tag.
		 */
		if (top_state->in_noscript != FALSE)
		{
			return(FALSE);
		}
	}
#endif /* MOCHA */

/* UNIX can't do embed yet, so display the NOEMBED stuff */
#if !defined(XP_UNIX) || defined(UNIX_EMBED)
	/*
	 * NOEMBED can appear anywhere.  Check for them and maintain proper
	 * in noembed state.
	 */
	if (tag->type == P_NOEMBED)
	{
		if (tag->is_end == FALSE)
		{
			top_state->in_noembed = TRUE;
		}
		else
		{
			top_state->in_noembed = FALSE;
		}
		return(FALSE);
	}

	/*
	 * Inside NOEMBED ignore everything except the closing tag.
	 */
	if (top_state->in_noembed != FALSE)
	{
		return(FALSE);
	}
#endif /* !defined(XP_UNIX) || defined(UNIX_EMBED) */

	/*
	 * If we have an embed that passed the filter, we must use the
	 * default colormap.
	 */
	if (tag->type == P_EMBED)
	{
		IL_UseDefaultColormapThisPage(context);
	}

	return(TRUE);
}

static void
lo_CloseParagraph(MWContext *context, lo_DocState *state)
{
	/*
	 * If we are want to close a paragraph we are out of
	 * the HEAD section of the HTML and into the BODY
	 *
	 * With the exception of TITLE which can be in head, but tries to
	 * close paragraphs in case it was erroneously placed within body.
	 * This is taken care of in lo_process_title_tag()
	 */
	state->top_state->in_head = FALSE;
	state->top_state->in_body = TRUE;

	if (state->in_paragraph != FALSE)
	{
		if ((state->align_stack != NULL)&&
		    (state->align_stack->type == P_PARAGRAPH))
		{
			lo_AlignStack *aptr;

			lo_SoftLineBreak(context, state, FALSE, 1);

			aptr = lo_PopAlignment(state);
			if (aptr != NULL)
			{
				XP_DELETE(aptr);
			}
		}
	}
}


static void
lo_OpenHeader(MWContext *context, lo_DocState *state, PA_Tag *tag)
{
	PA_Block buff;
	char *str;

	buff = lo_FetchParamValue(context, tag, PARAM_ALIGN);
	if (buff != NULL)
	{
		int32 alignment;

		PA_LOCK(str, char *, buff);
		alignment = (int32)lo_EvalDivisionAlignParam(str);
		PA_UNLOCK(buff);
		PA_FREE(buff);

		lo_PushAlignment(state, tag, alignment);
	}
}


static void
lo_CloseHeader(MWContext *context, lo_DocState *state)
{
	Bool aligned_header;

	/*
	 * Check the top of the alignment stack to see if we are closing
	 * a header that explicitly set an alignment.
	 */
	aligned_header = FALSE;
	if (state->align_stack != NULL)
	{
		intn type;

		type = state->align_stack->type;
		if ((type == P_HEADER_1)||(type == P_HEADER_2)||
		    (type == P_HEADER_3)||(type == P_HEADER_4)||
		    (type == P_HEADER_5)||(type == P_HEADER_6))
		{
			aligned_header = TRUE;
		}
	}

	if (aligned_header != FALSE)
	{
		lo_AlignStack *aptr;

		lo_SoftLineBreak(context, state, FALSE, 1);

		aptr = lo_PopAlignment(state);
		if (aptr != NULL)
		{
			XP_DELETE(aptr);
		}
	}
	else
	{
		lo_SoftLineBreak(context, state, FALSE, 1);
	}
	/*
	 * Now that we are on the line after the header, we
	 * don't want the next blank line to be the height of the probably
	 * large header, so reset it to the default here.
	 */
	state->line_height = state->default_line_height;
}


static void
lo_process_text_tag(MWContext *context, lo_DocState *state, PA_Tag *tag)
{
	char *tptr;
	char *tptr2;
	int16 charset;
	INTL_FontEncodingList *fe;
	INTL_FontEncodingList *origFE;
	void (*formatFunc)(MWContext *, lo_DocState *, char *);

	/*
	 * This is normal text, formatted or preformatted.
	 */
	if ((state->text_divert == P_UNKNOWN)||(state->text_divert == P_TEXT))
	{
		/*
		 * If you don't have a current text element
		 * to add this text to, make one here.
		 */
		if (state->cur_ele_type != LO_TEXT)
		{
			lo_FreshText(state);
			state->cur_ele_type = LO_TEXT;
		}

		PA_LOCK (tptr, char *, tag->data);

		/*
		 * If we are processing non-head-data text, and it is
		 * not just white space, then we are out of
		 * the HEAD section of the HTML.
		 *
		 * If the text is inside an unknown element in the HEAD
		 * we can assume this is content we should ignore.
		 */
		if (state->top_state->in_head != FALSE)
		{
			char *cptr;
			Bool real_text;

			real_text = FALSE;
			cptr = tptr;
			while (*cptr != '\0')
			{
				if (!XP_IS_SPACE(*cptr))
				{
					real_text = TRUE;
					break;
				}
				cptr++;
			}
			if (real_text != FALSE)
			{
				if (state->top_state->unknown_head_tag != NULL)
				{
#ifdef DEBUG
XP_TRACE(("Toss out content in unknown HEAD (%s)\n", tptr));
#endif /* DEBUG */
					return;
				}
				/*
				 * If the content isnot ignore, we have left
				 * the HEAD and are in the BODY.
				 */
				state->top_state->in_head = FALSE;
				state->top_state->in_body = TRUE;
			}
		}

		if (state->preformatted != PRE_TEXT_NO)
		{
			formatFunc = lo_PreformatedText;
		}
		else
		{
			formatFunc = lo_FormatText;
		}
		charset = state->font_stack->text_attr->charset;
		origFE = INTL_InternalToFontEncoding(charset, (unsigned char *) tptr);
		fe = origFE;
		while (fe)
		{
			state->font_stack->text_attr->charset = fe->charset;
			(*formatFunc)(context, state, (char *) fe->text);
			fe = fe->next;
		}
		state->font_stack->text_attr->charset = charset;
		if (origFE)
		{
			INTL_FreeFontEncodingList(origFE);
		}
		PA_UNLOCK(tag->data);
	}
	/*
	 * These tags just capture the text flow as is.
	 */
	else if ((state->text_divert == P_TITLE)||
		(state->text_divert == P_TEXTAREA)||
		(state->text_divert == P_OPTION)||
		(state->text_divert == P_CERTIFICATE)
#ifdef MOCHA /* added by jwz */
			 || (state->text_divert == P_SCRIPT)
#endif
			 )
	{
		int32 copy_len;

		if ((state->line_buf_len + tag->data_len + 1) >
			state->line_buf_size)
		{
			int32 new_size;

			new_size = state->line_buf_size +
				tag->data_len + LINE_BUF_INC;
#ifdef XP_WIN16
			if (new_size > SIZE_LIMIT)
			{
				new_size = SIZE_LIMIT;
			}
#endif /* XP_WIN16 */
			state->line_buf = PA_REALLOC(state->line_buf, new_size);
			if (state->line_buf == NULL)
			{
				state->top_state->out_of_memory = TRUE;
				return;
			}
			state->line_buf_size = new_size;
		}

		copy_len = tag->data_len;
#ifdef XP_WIN16
		if ((state->line_buf_len + copy_len) >
			(state->line_buf_size - 1))
		{
			copy_len = state->line_buf_size - 1 -
				state->line_buf_len;
		}
#endif /* XP_WIN16 */

		PA_LOCK(tptr, char *, tag->data);
		PA_LOCK(tptr2, char *, state->line_buf);
		XP_BCOPY(tptr, (char *)(tptr2 + state->line_buf_len),
			(copy_len + 1));
		state->line_buf_len += copy_len;
#ifdef XP_WIN16
		tptr2[state->line_buf_len] = '\0';
#endif /* XP_WIN16 */
		PA_UNLOCK(state->line_buf);
		PA_UNLOCK(tag->data);
	}
	else /* Just ignore this text, it shouldn't be here */
	{
	}
}


static void
lo_process_title_tag(MWContext *context, lo_DocState *state, PA_Tag *tag)
{
	Bool tmp_in_head;
	Bool tmp_in_body;

	/*
	 * Don't let closing a paragraph before a title change the
	 * in_head or in_body state.
	 */
	tmp_in_head = state->top_state->in_head;
	tmp_in_body = state->top_state->in_body;
	if (state->in_paragraph != FALSE)
	{
		lo_CloseParagraph(context, state);
	}
	state->top_state->in_head = tmp_in_head;
	state->top_state->in_body = tmp_in_body;

	if (tag->is_end == FALSE)
	{
		/*
		 * Flush the line buffer so we can
		 * start storing the title text there.
		 */
		if ((state->cur_ele_type == LO_TEXT)&&
			(state->line_buf_len != 0))
		{
			lo_FlushLineBuffer(context, state);
		}

		state->line_buf_len = 0;
		state->text_divert = tag->type;
	}
	else
	{
	    /*
	     * Only if we captured some title text
	     */
	    if (state->line_buf_len != 0)
	    {
		int32 len;
		char *tptr2;

		PA_LOCK(tptr2, char *, state->line_buf);
		/*
		 * Title text is cleaned up before passing
		 * to the front-end.
		 */
		len = lo_CleanTextWhitespace(tptr2, state->line_buf_len);
		/*
		 * Subdocs can't have titles.
		 */
		if ((state->is_a_subdoc == SUBDOC_NOT)&&
			(state->top_state->have_title == FALSE))
		{
			int16 charset;

			charset = state->font_stack->text_attr->charset;
			tptr2 = FE_TranslateISOText(context, charset, tptr2);
#ifdef MAXTITLESIZE
			if ((XP_STRLEN(tptr2) > MAXTITLESIZE)&&
			    (MAXTITLESIZE >= 0))
			{
				tptr2[MAXTITLESIZE] = '\0';
			}
#endif
			FE_SetDocTitle(context, tptr2);
			state->top_state->have_title = TRUE;
		}
		PA_UNLOCK(state->line_buf);
	    }
	    state->line_buf_len = 0;
	    state->text_divert = P_UNKNOWN;
	}
}


static void
lo_process_anchor_tag(MWContext *context, lo_DocState *state, PA_Tag *tag)
{
	LO_TextAttr tmp_attr;

	/*
	 * Opening a new anchor
	 */
	if (tag->is_end == FALSE)
	{
		LO_TextAttr *old_attr;
		LO_TextAttr *attr;
		char *url;
		char *full_url;
		PA_Block buff;
		Bool is_new;
		PA_Block anchor;

		/*
		 * Get the NAME param for the name list
		 */
		buff = lo_FetchParamValue(context, tag, PARAM_NAME);
		if (buff != NULL)
		{
		    char *name;

		    /*
		     * If we are looking to start at a name,
		     * fetch the NAME and compare.
		     */
		    if (state->display_blocking_element_id == -1)
		    {
			PA_LOCK(name, char *, buff);
			if (XP_STRCMP(name,
			    (char *)(state->top_state->name_target + 1))
				== 0)
			{
			    XP_FREE(state->top_state->name_target);
			    state->top_state->name_target = NULL;
			    state->display_blocking_element_id =
				state->top_state->element_id;
			}
			PA_UNLOCK(buff);
		    }

		    if (state->current_named_anchor != NULL)
		    {
			if (lo_SetNamedAnchor(state,
			    state->current_named_anchor, NULL))
			{
#ifdef MOCHA
			    lo_ReflectNamedAnchor(context, state, tag,
						  state->name_list);
#endif
			}
		    }
		    state->current_named_anchor = buff;
		}

		is_new = TRUE;
		state->current_anchor = NULL;
		/*
		 * Fetch the HREF, and turn it into an
		 * absolute URL.
		 */
		anchor = lo_FetchParamValue(context, tag, PARAM_HREF);
		if (anchor != NULL)
		{
		    char *target;
		    PA_Block targ_buff;

		    PA_LOCK(url, char *, anchor);
		    if (url != NULL)
		    {
			int32 len;

			len = lo_StripTextWhitespace(url, XP_STRLEN(url));
		    }
		    url = NET_MakeAbsoluteURL(state->top_state->base_url, url);
		    if (url == NULL)
		    {
			buff = NULL;
		    }
		    else
		    {
			buff = PA_ALLOC(XP_STRLEN(url)+1);
			if (buff != NULL)
			{
			    PA_LOCK(full_url, char *, buff);
			    XP_STRCPY(full_url, url);
			    PA_UNLOCK(buff);

			    if (GH_CheckGlobalHistory(url) != -1)
			    {
				is_new = FALSE;
			    }
			}
			else
			{
#ifdef DEBUG
				assert (state);
#endif
				state->top_state->out_of_memory = TRUE;
			}
			
			XP_FREE(url);
		    }
		    PA_UNLOCK(anchor);
		    PA_FREE(anchor);
		    anchor = buff;

		    if (anchor != NULL)
		    {
			targ_buff = lo_FetchParamValue(context, tag, PARAM_TARGET);
			if (targ_buff != NULL)
			{
			    int32 len;

			    PA_LOCK(target, char *, targ_buff);
			    len = lo_StripTextWhitespace(target,
					XP_STRLEN(target));
			    if ((*target == '\0')||
				    (lo_IsValidTarget(target) == FALSE))
			    {
				PA_UNLOCK(targ_buff);
				PA_FREE(targ_buff);
				targ_buff = NULL;
			    }
			    else
			    {
				PA_UNLOCK(targ_buff);
			    }
			}

			/*
			 * If there was no target use the default one.
			 * (default provided by BASE tag)
			 */
			if ((targ_buff == NULL)&&
			    (state->top_state->base_target != NULL))
			{
				targ_buff = PA_ALLOC(XP_STRLEN(
					state->top_state->base_target) + 1);
				if (targ_buff != NULL)
				{
					char *targ;

					PA_LOCK(targ, char *, targ_buff);
					XP_STRCPY(targ,
						state->top_state->base_target);
					PA_UNLOCK(targ_buff);
				}
				else
				{
					state->top_state->out_of_memory = TRUE;
				}
			}

			state->current_anchor =
				lo_NewAnchor(state, anchor, targ_buff);
			if (state->current_anchor == NULL)
			{
			    PA_FREE(anchor);
			    if (targ_buff != NULL)
			    {
				PA_FREE(targ_buff);
			    }
			}
		    }

		    /*
		     * Add this url's block to the list
		     * of all allocated urls so we can free
		     * it later.
		     */
		    lo_AddToUrlList(context, state, state->current_anchor);
		    if (state->top_state->out_of_memory != FALSE)
		    {
			return;
		    }
#ifdef MOCHA
		    lo_ReflectLink(context, state, tag, state->current_anchor,
				   state->top_state->url_list_len - 1);
#endif
		}

		/*
		 * Change the anchor attribute only for
		 * anchors with hrefs.
		 */
		if (state->current_anchor != NULL)
		{
			old_attr = state->font_stack->text_attr;
			lo_CopyTextAttr(old_attr, &tmp_attr);

			tmp_attr.attrmask = (old_attr->attrmask|
				LO_ATTR_ANCHOR);
			if (is_new != FALSE)
			{
				tmp_attr.fg.red =   STATE_UNVISITED_ANCHOR_RED(state);
				tmp_attr.fg.green = STATE_UNVISITED_ANCHOR_GREEN(state);
				tmp_attr.fg.blue =  STATE_UNVISITED_ANCHOR_BLUE(state);
			}
			else
			{
				tmp_attr.fg.red =   STATE_VISITED_ANCHOR_RED(state);
				tmp_attr.fg.green = STATE_VISITED_ANCHOR_GREEN(state);
				tmp_attr.fg.blue =  STATE_VISITED_ANCHOR_BLUE(state);
			}
			attr = lo_FetchTextAttr(state, &tmp_attr);
			lo_PushFont(state, tag->type, attr);
		}
	}
	/*
	 * Closing an anchor.  Anchors can't be nested, so close
	 * ALL anchors.
	 */
	else
	{
		lo_PopAllAnchors(state);
		state->current_anchor = NULL;
	}
}


static void
lo_process_caption_tag(MWContext *context, lo_DocState *state, PA_Tag *tag)
{
	if ((state->current_table != NULL)&&
	    (state->current_table->caption == NULL))
	{
	    lo_TableRec *table;

	    table = state->current_table;
	    if (tag->is_end == FALSE)
	    {
		/*
		 * If there is an unterminated row open,
		 * terminate it now.
		 */
		if ((table->row_ptr != NULL)&&
			(table->row_ptr->row_done == FALSE))
		{
			lo_EndTableRow(context, state, table);
		}
		lo_BeginTableCaption(context, state, table,tag);
	    }
	    else
	    {
		/*
		 * This never happens because the
		 * close caption feeds into the subdoc.
		 */
	    }
	}
	else if ((state->current_table == NULL)&&
		(state->is_a_subdoc == SUBDOC_CAPTION))
	{
	    if (tag->is_end == FALSE)
	    {
	    }
	    else
	    {
		lo_EndTableCaption(context, state);
	    }
	}
	else if ((state->current_table == NULL)&&
		(state->is_a_subdoc == SUBDOC_CELL))
	{
	    if (tag->is_end == FALSE)
	    {
		lo_TopState *top_state;
		lo_DocState *new_state;
		int32 doc_id;

		/*
		 * This can only happen if the close cell
		 * was omitted, AND the close row
		 * after it was omitted.
		 */
		lo_EndTableCell(context, state);

		/*
		 * restore state to the new lowest level.
		 */
		doc_id = XP_DOCID(context);
		top_state = lo_FetchTopState(doc_id);
		new_state = lo_TopSubState(top_state);

		/*
		 * Now act just like a new caption has started.
		 */
		if (new_state->current_table != NULL)
		{
		    lo_TableRec *table;

		    table = new_state->current_table;
		    /*
		     * If there is an unterminated row open,
		     * terminate it now.
		     */
		    if ((table->row_ptr != NULL)&&
			(table->row_ptr->row_done == FALSE))
		    {
			lo_EndTableRow(context,new_state,table);
		    }
		    lo_BeginTableCaption(context, new_state,
					table, tag);
		}
	    }
	}
}

static void
lo_process_table_cell_tag(MWContext *context, lo_DocState *state, PA_Tag *tag)
{
	if ((state->current_table != NULL)&&
	    (state->current_table->row_ptr != NULL)&&
	    (state->current_table->row_ptr->row_done == FALSE))
	{
	    lo_TableRec *table;
	    Bool is_a_header;

	    if (tag->type == P_TABLE_HEADER)
	    {
		is_a_header = TRUE;
	    }
	    else
	    {
		is_a_header = FALSE;
	    }

	    table = state->current_table;
	    if (tag->is_end == FALSE)
	    {
		lo_BeginTableCell(context, state, table, tag, is_a_header);
	    }
	    else
	    {
		/*
		 * This never happens because the
		 * close cell feeds into the subdoc.
		 */
	    }
	}
	else if ((state->current_table != NULL)&&
	    ((state->current_table->row_ptr == NULL)||
	    (state->current_table->row_ptr->row_done != FALSE)))
	{
	    lo_TableRec *table;
	    Bool is_a_header;

	    if (tag->is_end == FALSE)
	    {
		table = state->current_table;
		/*
		 * This is a starting data cell, with no open
		 * row.  Open up the row for them.
		 */
		lo_BeginTableRow(context, state, table, tag);

		if (tag->type == P_TABLE_HEADER)
		{
		    is_a_header = TRUE;
		}
		else
		{
		    is_a_header = FALSE;
		}

		lo_BeginTableCell(context, state, table, tag, is_a_header);
	    }
	}
	else if ((state->current_table == NULL)&&
		(state->is_a_subdoc == SUBDOC_CELL))
	{
	    if (tag->is_end == FALSE)
	    {
		lo_TopState *top_state;
		lo_DocState *new_state;
		int32 doc_id;

		/*
		 * This can only happen if the close cell
		 * was omitted.
		 */
		lo_EndTableCell(context, state);

		/*
		 * restore state to the new lowest level.
		 */
		doc_id = XP_DOCID(context);
		top_state = lo_FetchTopState(doc_id);
		new_state = lo_TopSubState(top_state);

		/*
		 * Now act just like a new cell has started
		 * as above.
		 */
		if ((new_state->current_table != NULL)&&
		    (new_state->current_table->row_ptr != NULL)&&
		    (new_state->current_table->row_ptr->row_done == FALSE))
		{
		    lo_TableRec *table;
		    Bool is_a_header;

		    if (tag->type == P_TABLE_HEADER)
		    {
			is_a_header = TRUE;
		    }
		    else
		    {
			is_a_header = FALSE;
		    }

		    table = new_state->current_table;
		    lo_BeginTableCell(context, new_state, table, tag,
				is_a_header);
		}
	    }
	    else
	    {
		lo_EndTableCell(context, state);
	    }
	}
}


static void
lo_process_table_row_tag(MWContext *context, lo_DocState *state, PA_Tag *tag)
{
	if (state->current_table != NULL)
	{
	    lo_TableRec *table;

	    table = state->current_table;
	    if (tag->is_end == FALSE)
	    {
		/*
		 * If there is an unterminated row open,
		 * terminate it now.
		 */
		if ((table->row_ptr != NULL)&&
			(table->row_ptr->row_done == FALSE))
		{
			lo_EndTableRow(context, state, table);
		}
		lo_BeginTableRow(context, state, table, tag);
	    }
	    else
	    {
		if ((table->row_ptr != NULL)&&
			(table->row_ptr->row_done == FALSE))
		{
			lo_EndTableRow(context, state, table);
		}
	    }
	}
	else if ((state->current_table == NULL)&&
		(state->is_a_subdoc == SUBDOC_CELL))
	{
	    if (tag->is_end == FALSE)
	    {
		lo_TopState *top_state;
		lo_DocState *new_state;
		int32 doc_id;

		/*
		 * This can only happen if the close cell
		 * was omitted, AND the close row
		 * after it was omitted.
		 */
		lo_EndTableCell(context, state);

		/*
		 * restore state to the new lowest level.
		 */
		doc_id = XP_DOCID(context);
		top_state = lo_FetchTopState(doc_id);
		new_state = lo_TopSubState(top_state);

		/*
		 * Now act just like a new row has started.
		 */
		if (new_state->current_table != NULL)
		{
		    lo_TableRec *table;

		    table = new_state->current_table;
		    /*
		     * If there is an unterminated row open,
		     * terminate it now.
		     */
		    if ((table->row_ptr != NULL)&&
			(table->row_ptr->row_done == FALSE))
		    {
			lo_EndTableRow(context, new_state, table);
		    }
		    lo_BeginTableRow(context, new_state, table, tag);
		}
	    }
	    else
	    {
		/*
		 * This can only happen if the close cell
		 * was omitted.
		 */
		lo_EndTableCell(context, state);

		/*
		 * We should really go on to repeat all the code
		 * here to process the end row tag, but since
		 * we know our layout can recover fine without
		 * it, we don't bother.
		 */
	    }
	}
	else if ((state->current_table == NULL)&&
		(state->is_a_subdoc == SUBDOC_CAPTION))
	{
	    if (tag->is_end == FALSE)
	    {
		lo_TopState *top_state;
		lo_DocState *new_state;
		int32 doc_id;

		/*
		 * This can only happen if the close caption
		 * was omitted.
		 */
		lo_EndTableCaption(context, state);

		/*
		 * restore state to the new lowest level.
		 */
		doc_id = XP_DOCID(context);
		top_state = lo_FetchTopState(doc_id);
		new_state = lo_TopSubState(top_state);

		/*
		 * Now act just like a new row has started.
		 */
		if (new_state->current_table != NULL)
		{
		    lo_TableRec *table;

		    table = new_state->current_table;
		    /*
		     * If there is an unterminated row open,
		     * terminate it now.
		     */
		    if ((table->row_ptr != NULL)&&
			(table->row_ptr->row_done == FALSE))
		    {
			lo_EndTableRow(context, new_state, table);
		    }
		    lo_BeginTableRow(context, new_state, table, tag);
		}
	    }
	}
}


static void
lo_close_table(MWContext *context, lo_DocState *state)
{
	if (state->current_table != NULL)
	{
		lo_TableRec *table;

		table = state->current_table;
		/*
		 * If there is an unterminated row open,
		 * terminate it now.
		 */
		if ((table->row_ptr != NULL)&&
		    (table->row_ptr->row_done == FALSE))
		{
			lo_EndTableRow(context, state, table);
		}
		lo_EndTable(context, state, state->current_table);
	}
	else if ((state->current_table == NULL)&&
		(state->is_a_subdoc == SUBDOC_CELL))
	{
		lo_TopState *top_state;
		lo_DocState *new_state;
		int32 doc_id;

		/*
		 * This can only happen if the close cell
		 * was omitted, AND the close row
		 * after it was omitted.
		 */
		lo_EndTableCell(context, state);

		/*
		 * restore state to the new lowest level.
		 */
		doc_id = XP_DOCID(context);
		top_state = lo_FetchTopState(doc_id);
		new_state = lo_TopSubState(top_state);

		/*
		 * Now act like a close table with
		 * an omitted close row.
		 */
		if (new_state->current_table != NULL)
		{
			lo_TableRec *table;

			table = new_state->current_table;
			/*
			 * If there is an unterminated row open,
			 * terminate it now.
			 */
			if ((table->row_ptr != NULL)&&
			    (table->row_ptr->row_done == FALSE))
			{
				lo_EndTableRow(context, new_state, table);
			}
			lo_EndTable(context, new_state,
				new_state->current_table);
		}
	}
	else if ((state->current_table == NULL)&&
		(state->is_a_subdoc == SUBDOC_CAPTION))
	{
		lo_TopState *top_state;
		lo_DocState *new_state;
		int32 doc_id;

		/*
		 * This can only happen if the close
		 * caption was omitted.
		 */
		lo_EndTableCaption(context, state);

		/*
		 * restore state to the new lowest level.
		 */
		doc_id = XP_DOCID(context);
		top_state = lo_FetchTopState(doc_id);
		new_state = lo_TopSubState(top_state);

		/*
		 * Now act like a close table.
		 */
		if (new_state->current_table != NULL)
		{
			lo_TableRec *table;

			table = new_state->current_table;
			/*
			 * If there is an unterminated row open,
			 * terminate it now.
			 */
			if ((table->row_ptr != NULL)&&
			    (table->row_ptr->row_done == FALSE))
			{
				lo_EndTableRow(context, new_state, table);
			}
			lo_EndTable(context, new_state,
				new_state->current_table);
		}
	}
}


static void
lo_process_table_tag(MWContext *context, lo_DocState *state, PA_Tag *tag)
{
	if (state->in_paragraph != FALSE)
	{
		lo_CloseParagraph(context, state);
	}
	if (tag->is_end == FALSE)
	{
		lo_BeginTable(context, state, tag);
	}
	else
	{
		lo_close_table(context, state);
	}
}


void
lo_CloseOutTable(MWContext *context, lo_DocState *state)
{
	lo_close_table(context, state);
}


#ifdef HTML_CERTIFICATE_SUPPORT

static void
lo_process_certificate_tag(MWContext *context, lo_DocState *state, PA_Tag *tag)
{
	Bool tmp_in_head;
	lo_Certificate *lo_cert;

	/*
	 * Do not let closing a paragraph before a cert change the
	 * in_head state.
	 */
	tmp_in_head = state->top_state->in_head;
	if (state->in_paragraph != FALSE)
	{
		lo_CloseParagraph(context, state);
	}
	state->top_state->in_head = tmp_in_head;

	if (tag->is_end == FALSE)
	{
		/*
		 * Flush the line buffer so we can start
		 * storing the certificate data there.
		 */
		if ((state->cur_ele_type == LO_TEXT)&&
		    (state->line_buf_len != 0))
		{
			lo_FlushLineBuffer(context, state);
		}

		state->line_buf_len = 0;

		lo_cert = XP_NEW(lo_Certificate);
		if (lo_cert == NULL)
		{
			state->top_state->out_of_memory = TRUE;
			return;
		}

		lo_cert->cert = NULL;
		lo_cert->name = lo_FetchParamValue(context, tag, PARAM_NAME);

		lo_cert->next = state->top_state->cert_list;
		state->top_state->cert_list = lo_cert;

		state->text_divert = tag->type;
		return;
	}

	/*
	 * Get this out of the way so we can just return if we hit
	 * errors below.
	 */
	state->text_divert = P_UNKNOWN;

	/*
	 * Only if we captured a certificate
	 */
	if (state->line_buf_len != 0)
	{
		char *tptr;

		state->line_buf_len = 0;

		lo_cert = state->top_state->cert_list;
		if (lo_cert == NULL)
			return;

		PA_LOCK(tptr, char *, state->line_buf);
		lo_cert->cert = XP_STRDUP(tptr);
		PA_UNLOCK(state->line_buf);

		if (lo_cert->cert == NULL)
		{
			state->top_state->cert_list = lo_cert->next;
			PA_FREE(lo_cert->name);
			XP_FREE(lo_cert);
		}
	}
}

#endif /* HTML_CERTIFICATE_SUPPORT */


/*************************************
 * Function: lo_LayoutTag
 *
 * Description: This function begins the process of laying
 *	out a single MDL tag.
 *
 * Params: Window context and document state, and the tag to be laid out.
 *
 * Returns: Nothing
 *************************************/
void
lo_LayoutTag(MWContext *context, lo_DocState *state, PA_Tag *tag)
{
	char *tptr;
	char *tptr2;
	LO_TextAttr tmp_attr;

	XP_ASSERT(state);

#ifdef LOCAL_DEBUG
XP_TRACE(("lo_LayoutTag(%d)\n", tag->type));
#endif /* LOCAL_DEBUG */

	if (tag->data != NULL)
	{
		state->top_state->layout_bytes += tag->true_len;
	}

#ifdef MOCHA_CACHES_WRITES
	if (state->in_relayout == FALSE)
	{
		NET_StreamClass *stream = state->top_state->mocha_write_stream;

		if ((stream != NULL) &&
			(state->top_state->in_script == SCRIPT_TYPE_NOT))
		{
			switch (tag->type)
			{
				case P_TEXT:
					if (tag->data != NULL)
					{
						stream->put_block(stream->data_object,
										  (char *)tag->data,
										  tag->data_len);
					}
					break;

				case P_UNKNOWN:
					if (tag->data != NULL)
					{
						char *tag_str = PR_smprintf("<%s%s",
													tag->is_end ? "/" : "",
													tag->data);

						if (tag_str == NULL)
						{
							state->top_state->out_of_memory = TRUE;
						}
						else
						{
							stream->put_block(stream->data_object, tag_str,
											  XP_STRLEN(tag_str));
							XP_FREE(tag_str);
						}
					}
					break;

#ifdef MOCHA /* added by jwz */
				case P_SCRIPT:
					break;
#endif /* MOCHA -- added by jwz */

				default:
					{
						const char *tag_name;
						char *tag_str;

						tag_name = pa_PrintTagToken(tag->type);
						if ((tag->data != NULL) &&
							(*(char *)tag->data != '>'))
						{
							tag_str = PR_smprintf("<%s%s%s",
												  tag->is_end ? "/" : "",
												  tag_name, tag->data);
						}
						else
						{
							tag_str = PR_smprintf("<%s%s>",
												  tag->is_end ? "/" : "",
												  tag_name);
						}
						if (tag_str == NULL)
						{
							state->top_state->out_of_memory = TRUE;
						}
						else
						{
							stream->put_block(stream->data_object, tag_str,
											  XP_STRLEN(tag_str));
							XP_FREE(tag_str);
						}
					}
					break;
			}
		}
	}
#endif /* MOCHA_CACHES_WRITES */

	/*
	 * If we get a non-text tag, and we have some previous text
	 * elements we have been merging into state, flush those
	 * before processing the new tag.
	 * Exceptions: P_TITLE does special stuff with title text.
	 * Exceptions: P_TEXTAREA does special stuff with text.
	 * Exceptions: P_OPTION does special stuff with text.
	 * Exceptions: P_SELECT does special stuff with option text on close.
	 * Exceptions: P_SCRIPT does special stuff with Mocha script text.
	 * Exceptions: P_CERTIFICATE does special stuff with text.
	 * Exceptions: P_BASEFONT may not change text flow.
	 * Exceptions: P_BASE only affects URLs not text flow.
	 */
	if ((tag->type != P_TEXT)&&(tag->type != P_TITLE)&&
		(tag->type != P_BASEFONT)&&
		(tag->type != P_TEXTAREA)&&
		(tag->type != P_OPTION)&&
		(tag->type != P_SELECT)&&
#ifdef MOCHA /* added by jwz */
		(tag->type != P_SCRIPT)&&
#endif
		(tag->type != P_CERTIFICATE)&&
		(state->cur_ele_type == LO_TEXT)&&(state->line_buf_len != 0))
	{
		lo_FlushLineBuffer(context, state);
		if (state->top_state->out_of_memory)
		{
			return;
		}
	}

#ifdef EDITOR
	/*  
	 * The editor needs all text elements to be individual text elements.
	 * Force the line buffer to be flushed so each individual text item
	 * remains such.
	 */
	if (EDT_IS_EDITOR(context)&& 
		(state->cur_ele_type == LO_TEXT)&&(state->line_buf_len != 0))
	{
		lo_FlushLineBuffer(context, state);
		if (state->top_state->out_of_memory)
		{
			return;
		}
	}

	/* LTNOTE:
	 *	This is a kludge.  We should allow all Layout elements to have
	 *	pointers to edit elements.  As should also be setting the
	 *	edit_element at the point we actually parse the tag.
	 */

	if( !state->edit_force_offset )
	{
		state->edit_current_offset = 0;
	}

	if( tag->type == P_TEXT 
			|| tag->type == P_IMAGE 
			|| tag->type == P_HRULE
			|| tag->type == P_LINEBREAK
			)
	{
		state->edit_current_element = tag->edit_element;
		if( tag->type != P_TEXT )
		{
			state->edit_current_offset = 0;
		}
	}
	else 
	{
		state->edit_current_element = 0;
	}
#endif

	switch(tag->type)
	{
		case P_TEXT:
			lo_process_text_tag(context, state, tag);
			break;

		/*
		 * Title text is special, it is not displayed, just
		 * captured and sent to the front end.
		 */
		case P_TITLE:
			lo_process_title_tag(context, state, tag);
			break;

		/*
		 * Certificate text is special, it is not displayed,
		 * but decoded and saved for later reference.
		 */
		case P_CERTIFICATE:
#ifdef HTML_CERTIFICATE_SUPPORT
			lo_process_certificate_tag(context, state, tag);
#endif
			break;

		/*
		 * All these tags just flip the fixed width font
		 * bit, and push a new font on the font stack.
		 */
		case P_FIXED:
		case P_CODE:
		case P_SAMPLE:
		case P_KEYBOARD:
			if (tag->is_end == FALSE)
			{
				LO_TextAttr *old_attr;
				LO_TextAttr *attr;

				old_attr = state->font_stack->text_attr;
				lo_CopyTextAttr(old_attr, &tmp_attr);
				tmp_attr.fontmask |= LO_FONT_FIXED;
				attr = lo_FetchTextAttr(state, &tmp_attr);

				lo_PushFont(state, tag->type, attr);
			}
			else
			{
				LO_TextAttr *attr;

				attr = lo_PopFont(state, tag->type);
			}

			break;

#ifdef EDITOR
		process_server:
		case P_SERVER:
			if (tag->is_end == FALSE)
			{
				LO_TextAttr *old_attr;
				LO_TextAttr *attr;

				state->preformatted = PRE_TEXT_YES;
				FE_BeginPreSection(context);

				old_attr = state->font_stack->text_attr;
				lo_CopyTextAttr(old_attr, &tmp_attr);
				tmp_attr.fontmask |= LO_FONT_FIXED;
				if( tag->type == P_SCRIPT ){
					tmp_attr.fg.green = 0;
				    tmp_attr.fg.red = 0xff;
				    tmp_attr.fg.blue = 0;
				}
				else {
					tmp_attr.fg.green = 0;
				    tmp_attr.fg.red = 0;
				    tmp_attr.fg.blue = 0xff;
				}
				tmp_attr.size = 3;
				attr = lo_FetchTextAttr(state, &tmp_attr);

				lo_PushFont(state, tag->type, attr);
			}
			else
			{
				LO_TextAttr *attr;

				attr = lo_PopFont(state, tag->type);
				state->preformatted = PRE_TEXT_NO;
				FE_EndPreSection(context);
			}

			break;
#endif


		/*
		 * All these tags just flip the bold font
		 * bit, and push a new font on the font stack.
		 */
		case P_BOLD:
		case P_STRONG:
			if (tag->is_end == FALSE)
			{
				LO_TextAttr *old_attr;
				LO_TextAttr *attr;

				old_attr = state->font_stack->text_attr;
				lo_CopyTextAttr(old_attr, &tmp_attr);
				tmp_attr.fontmask |= LO_FONT_BOLD;
				attr = lo_FetchTextAttr(state, &tmp_attr);

				lo_PushFont(state, tag->type, attr);
			}
			else
			{
				LO_TextAttr *attr;

				attr = lo_PopFont(state, tag->type);
			}
			break;

		/*
		 * All these tags just flip the italic font
		 * bit, and push a new font on the font stack.
		 */
		case P_ITALIC:
		case P_EMPHASIZED:
		case P_VARIABLE:
		case P_CITATION:
			if (tag->is_end == FALSE)
			{
				LO_TextAttr *old_attr;
				LO_TextAttr *attr;

				old_attr = state->font_stack->text_attr;
				lo_CopyTextAttr(old_attr, &tmp_attr);
				tmp_attr.fontmask |= LO_FONT_ITALIC;
				attr = lo_FetchTextAttr(state, &tmp_attr);

				lo_PushFont(state, tag->type, attr);
			}
			else
			{
				LO_TextAttr *attr;

				attr = lo_PopFont(state, tag->type);
			}
			break;

		/*
		 * All headers force whitespace, then set the bold
		 * font bit and unset the fixed and/or italic if set.
		 * In addition, they set the size attribute of the
		 * new font they will push, like <font size=num>
		 * Sizes are:
		 * 	H1 -> 6
		 * 	H2 -> 5
		 * 	H3 -> 4
		 * 	H4 -> 3
		 * 	H5 -> 2
		 * 	H6 -> 1
		 */
		case P_HEADER_1:
			if (state->in_paragraph != FALSE)
			{
				lo_CloseParagraph(context, state);
			}
			if (tag->is_end == FALSE)
			{
				LO_TextAttr *old_attr;
				LO_TextAttr *attr;

				lo_SoftLineBreak(context, state, FALSE, 2);
				lo_OpenHeader(context, state, tag);

				old_attr = state->font_stack->text_attr;
				lo_CopyTextAttr(old_attr, &tmp_attr);
				tmp_attr.fontmask |= LO_FONT_BOLD;
				tmp_attr.fontmask &= (~(LO_FONT_FIXED|LO_FONT_ITALIC));
				tmp_attr.size = 6;
				attr = lo_FetchTextAttr(state, &tmp_attr);

				lo_PushFont(state, tag->type, attr);
			}
			else
			{
				LO_TextAttr *attr;

				attr = lo_PopFont(state, tag->type);

				lo_CloseHeader(context, state);
				lo_SoftLineBreak(context, state, FALSE, 2);
			}
			break;
		/*
		 * See the comment for P_HEADER_1.
		 */
		case P_HEADER_2:
			if (state->in_paragraph != FALSE)
			{
				lo_CloseParagraph(context, state);
			}
			if (tag->is_end == FALSE)
			{
				LO_TextAttr *old_attr;
				LO_TextAttr *attr;

				lo_SoftLineBreak(context, state, FALSE, 2);
				lo_OpenHeader(context, state, tag);

				old_attr = state->font_stack->text_attr;
				lo_CopyTextAttr(old_attr, &tmp_attr);
				tmp_attr.fontmask |= LO_FONT_BOLD;
				tmp_attr.fontmask &= (~(LO_FONT_FIXED|LO_FONT_ITALIC));
				tmp_attr.size = 5;
				attr = lo_FetchTextAttr(state, &tmp_attr);

				lo_PushFont(state, tag->type, attr);
			}
			else
			{
				LO_TextAttr *attr;

				attr = lo_PopFont(state, tag->type);

				lo_CloseHeader(context, state);
				lo_SoftLineBreak(context, state, FALSE, 2);
			}
			break;
		/*
		 * See the comment for P_HEADER_1.
		 */
		case P_HEADER_3:
			if (state->in_paragraph != FALSE)
			{
				lo_CloseParagraph(context, state);
			}
			if (tag->is_end == FALSE)
			{
				LO_TextAttr *old_attr;
				LO_TextAttr *attr;

				lo_SoftLineBreak(context, state, FALSE, 2);
				lo_OpenHeader(context, state, tag);

				old_attr = state->font_stack->text_attr;
				lo_CopyTextAttr(old_attr, &tmp_attr);
				tmp_attr.fontmask |= LO_FONT_BOLD;
				tmp_attr.fontmask &= (~(LO_FONT_FIXED|LO_FONT_ITALIC));
				tmp_attr.size = 4;
				attr = lo_FetchTextAttr(state, &tmp_attr);

				lo_PushFont(state, tag->type, attr);
			}
			else
			{
				LO_TextAttr *attr;

				attr = lo_PopFont(state, tag->type);

				lo_CloseHeader(context, state);
				lo_SoftLineBreak(context, state, FALSE, 2);
			}
			break;
		/*
		 * See the comment for P_HEADER_1.
		 */
		case P_HEADER_4:
			if (state->in_paragraph != FALSE)
			{
				lo_CloseParagraph(context, state);
			}
			if (tag->is_end == FALSE)
			{
				LO_TextAttr *old_attr;
				LO_TextAttr *attr;

				lo_SoftLineBreak(context, state, FALSE, 2);
				lo_OpenHeader(context, state, tag);

				old_attr = state->font_stack->text_attr;
				lo_CopyTextAttr(old_attr, &tmp_attr);
				tmp_attr.fontmask |= LO_FONT_BOLD;
				tmp_attr.fontmask &= (~(LO_FONT_FIXED|LO_FONT_ITALIC));
				tmp_attr.size = 3;
				attr = lo_FetchTextAttr(state, &tmp_attr);

				lo_PushFont(state, tag->type, attr);
			}
			else
			{
				LO_TextAttr *attr;

				attr = lo_PopFont(state, tag->type);

				lo_CloseHeader(context, state);
				lo_SoftLineBreak(context, state, FALSE, 2);
			}
			break;
		/*
		 * See the comment for P_HEADER_1.
		 */
		case P_HEADER_5:
			if (state->in_paragraph != FALSE)
			{
				lo_CloseParagraph(context, state);
			}
			if (tag->is_end == FALSE)
			{
				LO_TextAttr *old_attr;
				LO_TextAttr *attr;

				lo_SoftLineBreak(context, state, FALSE, 2);
				lo_OpenHeader(context, state, tag);

				old_attr = state->font_stack->text_attr;
				lo_CopyTextAttr(old_attr, &tmp_attr);
				tmp_attr.fontmask |= LO_FONT_BOLD;
				tmp_attr.fontmask &= (~(LO_FONT_FIXED|LO_FONT_ITALIC));
				tmp_attr.size = 2;
				attr = lo_FetchTextAttr(state, &tmp_attr);

				lo_PushFont(state, tag->type, attr);
			}
			else
			{
				LO_TextAttr *attr;

				attr = lo_PopFont(state, tag->type);

				lo_CloseHeader(context, state);
				lo_SoftLineBreak(context, state, FALSE, 2);
			}
			break;
		/*
		 * See the comment for P_HEADER_1.
		 */
		case P_HEADER_6:
			if (state->in_paragraph != FALSE)
			{
				lo_CloseParagraph(context, state);
			}
			if (tag->is_end == FALSE)
			{
				LO_TextAttr *old_attr;
				LO_TextAttr *attr;

				lo_SoftLineBreak(context, state, FALSE, 2);
				lo_OpenHeader(context, state, tag);

				old_attr = state->font_stack->text_attr;
				lo_CopyTextAttr(old_attr, &tmp_attr);
				tmp_attr.fontmask |= LO_FONT_BOLD;
				tmp_attr.fontmask &= (~(LO_FONT_FIXED|LO_FONT_ITALIC));
				tmp_attr.size = 1;
				attr = lo_FetchTextAttr(state, &tmp_attr);

				lo_PushFont(state, tag->type, attr);
			}
			else
			{
				LO_TextAttr *attr;

				attr = lo_PopFont(state, tag->type);

				lo_CloseHeader(context, state);
				lo_SoftLineBreak(context, state, FALSE, 2);
			}
			break;

		/*
		 * Description titles are just normal,
		 * unless value > 1 and then they are outdented
		 * the width of one list indention.
		 */
		case P_DESC_TITLE:
			if (state->in_paragraph != FALSE)
			{
				lo_CloseParagraph(context, state);
			}
			if (tag->is_end == FALSE)
			{
				/*
				 * Undent to the left for a title if necessary.
				 */
				if ((state->list_stack->level != 0)&&
				    (state->list_stack->type == P_DESC_LIST)&&
				    (state->list_stack->value > 1))
				{
					state->list_stack->old_left_margin -=
						LIST_MARGIN_INC;
					state->list_stack->value = 1;
				}

				if (state->linefeed_state >= 1)
				{
					lo_FindLineMargins(context, state);
				}
				else
				{
					lo_SoftLineBreak(context,state,FALSE,1);
				}
				state->x = state->left_margin;
			}
			break;

		/*
		 * Description text, is just normal text, but since there
		 * is no ending <dd> tag, we need to put the
		 * linebreak from the previous line in here.
		 * Compact lists may not linebreak since they try to
		 * put description text on the same line as title
		 * text if there is room.
		 * If value == 1 then we need to indent for the description
		 * text, if it is > 1 then we are already indented.
		 */
		case P_DESC_TEXT:
			if (state->in_paragraph != FALSE)
			{
				lo_CloseParagraph(context, state);
			}
			if (tag->is_end == FALSE)
			{
				/*
				 * Indent from the title if necessary.
				 */
				if ((state->list_stack->type == P_DESC_LIST)&&
					(state->list_stack->value == 1))
				{
					state->list_stack->old_left_margin +=
						LIST_MARGIN_INC;
					state->list_stack->value = 2;
				}

				if (state->list_stack->compact == FALSE)
				{
					if (state->linefeed_state >= 1)
					{
					    lo_FindLineMargins(context, state);
					}
					else
					{
					    lo_SoftLineBreak(context, state,
							FALSE, 1);
					}
				}
				else
				{
					if (state->x >=
					    state->list_stack->old_left_margin)
					{
						if (state->linefeed_state >= 1)
						{
						    lo_FindLineMargins(context,
							state);
						}
						else
						{
						    lo_SoftLineBreak(context,
							state, FALSE, 1);
						}
					}
					else
					{
						lo_FindLineMargins(context,
							state);
						state->x = state->left_margin;
					}
				}
				state->x = state->left_margin;

				/*
				 * Bug compatibility for <dd> outside a <dl>
				 */
				if (state->list_stack->type != P_DESC_LIST)
				{
					state->x += LIST_MARGIN_INC;
				}
			}
			break;
		/*
		 * A new list level, but don't move the
		 * left margin in until you need to.
		 * val is overloaded to tell us if we are
		 * not indented (val=1) or indented (val > 1).
		 * Push a new element on the list stack.
		 */
		case P_DESC_LIST:
			if (state->in_paragraph != FALSE)
			{
				lo_CloseParagraph(context, state);
			}
			if (tag->is_end == FALSE)
			{
				int32 type;
				int32 val;
				Bool compact;
				PA_Block buff;

				type = BULLET_BASIC;
				val = 1;

				buff = lo_FetchParamValue(context, tag, PARAM_COMPACT);
				if (buff != NULL)
				{
					PA_FREE(buff);
					compact = TRUE;
				}
				else
				{
					compact = FALSE;
				}

				if (state->list_stack->level == 0)
				{
					lo_SoftLineBreak(context, state,
						FALSE, 2);
				}
				else
				{
					lo_SoftLineBreak(context, state,
						FALSE, 1);
				}
/* THIS BREAKS FLOATING IMAGES IN LISTS
				state->list_stack->old_left_margin =
					state->left_margin;
				state->list_stack->old_right_margin =
					state->right_margin;
*/

				/*
				 * handle nested description lists
				 * with the right amount of indenting
				 */
				if ((state->list_stack->type == P_DESC_LIST)&&
					(state->list_stack->value == 1))
				{
					state->left_margin += LIST_MARGIN_INC;
				}

				lo_PushList(state, tag);

				state->x = state->left_margin;
				state->list_stack->old_left_margin =
					state->left_margin;
				state->list_stack->bullet_type = (intn) type;
				state->list_stack->value = val;
				state->list_stack->compact = compact;
			}
			else
			{
				lo_ListStack *lptr;

				lptr = lo_PopList(state, tag);
				if (lptr != NULL)
				{
					XP_DELETE(lptr);
				}
				state->left_margin =
					state->list_stack->old_left_margin;
				state->right_margin =
					state->list_stack->old_right_margin;

				if (state->list_stack->level == 0)
				{
					if (state->linefeed_state >= 2)
					{
						lo_FindLineMargins(context, state);
					}
					else
					{
						lo_SoftLineBreak(context, state, FALSE,2);
					}
				}
				else
				{
					if (state->linefeed_state >= 1)
					{
						lo_FindLineMargins(context, state);
					}
					else
					{
						lo_SoftLineBreak(context, state, FALSE,1);
					}
				}
				state->x = state->left_margin;
			}
			break;
		/*
		 * Move the left margin in another indent step.
		 * Push a new element on the list stack.
		 */
		case P_NUM_LIST:
		case P_UNUM_LIST:
		case P_MENU:
		case P_DIRECTORY:
			if (state->in_paragraph != FALSE)
			{
				lo_CloseParagraph(context, state);
			}
			if (tag->is_end == FALSE)
			{
				int32 type;
				int32 val;
				int32 old_right_margin;
				Bool compact;
				PA_Block buff;

				type = BULLET_BASIC;
				buff = lo_FetchParamValue(context, tag, PARAM_TYPE);
				if ((buff != NULL)&&(tag->type == P_NUM_LIST))
				{
					char *type_str;

					PA_LOCK(type_str, char *, buff);
					if (*type_str == 'A')
					{
						type = BULLET_ALPHA_L;
					}
					else if (*type_str == 'a')
					{
						type = BULLET_ALPHA_S;
					}
					else if (*type_str == 'I')
					{
						type = BULLET_NUM_L_ROMAN;
					}
					else if (*type_str == 'i')
					{
						type = BULLET_NUM_S_ROMAN;
					}
					else
					{
						type = BULLET_NUM;
					}
					PA_UNLOCK(buff);
				}
				else if ((buff != NULL)&&
					(tag->type != P_DESC_LIST))
				{
					char *type_str;

					PA_LOCK(type_str, char *, buff);
					if (pa_TagEqual("round", type_str))
					{
						type = BULLET_ROUND;
					}
					else if (pa_TagEqual("circle",type_str))
					{
						type = BULLET_ROUND;
					}
					else if (pa_TagEqual("square",type_str))
					{
						type = BULLET_SQUARE;
					}
					else
					{
						type = BULLET_BASIC;
					}
					PA_UNLOCK(buff);
				}
				else if (tag->type == P_NUM_LIST)
				{
					type = BULLET_NUM;
				}
				else if (tag->type != P_DESC_LIST)
				{
					intn lev;

					lev = state->list_stack->level;
					if (lev < 1)
					{
						type = BULLET_BASIC;
					}
					else if (lev == 1)
					{
						type = BULLET_ROUND;
					}
					else if (lev > 1)
					{
						type = BULLET_SQUARE;
					}
				}
				
				if (buff != NULL) 
				{
					PA_FREE(buff);
				}
				
				buff = lo_FetchParamValue(context, tag, PARAM_START);
				if (buff != NULL)
				{
					char *val_str;

					PA_LOCK(val_str, char *, buff);
					val = XP_ATOI(val_str);
					if (val < 1)
					{
						val = 1;
					}
					PA_UNLOCK(buff);
					PA_FREE(buff);
				}
				else
				{
					val = 1;
				}

				buff = lo_FetchParamValue(context, tag, PARAM_COMPACT);
				if (buff != NULL)
				{
					PA_FREE(buff);
					compact = TRUE;
				}
				else
				{
					compact = FALSE;
				}

				if (state->list_stack->level == 0)
				{
					lo_SoftLineBreak(context, state,
						FALSE, 2);
				}
				else
				{
					lo_SoftLineBreak(context, state,
						FALSE, 1);
				}
/* THIS BREAKS FLOATING IMAGES IN LISTS
				state->list_stack->old_left_margin =
					state->left_margin;
				state->list_stack->old_right_margin =
					state->right_margin;
*/
				/*
				 * For lists, we leave the right
				 * margin unchanged.
				 */
				old_right_margin =
					state->list_stack->old_right_margin;
				lo_PushList(state, tag);
				state->left_margin += LIST_MARGIN_INC;
				state->x = state->left_margin;
				state->list_stack->old_left_margin =
					state->left_margin;
				/*
				 * Set right margin to be last list's right,
				 * irregardless of current margin stack.
				 */
				state->list_stack->old_right_margin =
					old_right_margin;
				state->list_stack->bullet_type = (intn) type;
				state->list_stack->value = val;
				state->list_stack->compact = compact;
			}
			else
			{
				lo_ListStack *lptr;

				/*
				 * Reset fake linefeed state used to
				 * fake out headers in lists.
				 */
				if (state->at_begin_line == FALSE)
				{
					state->linefeed_state = 0;
				}

				lptr = lo_PopList(state, tag);
				if (lptr != NULL)
				{
					XP_DELETE(lptr);
				}
				state->left_margin =
					state->list_stack->old_left_margin;
				state->right_margin =
					state->list_stack->old_right_margin;
				/*
				 * Reset the margins properly in case
				 * there is a right-aligned image here.
				 */
				lo_FindLineMargins(context, state);
				if (state->list_stack->level == 0)
				{
					if (state->linefeed_state >= 2)
					{
						lo_FindLineMargins(context, state);
					}
					else
					{
						lo_SoftLineBreak(context, state, FALSE,2);
					}
				}
				else
				{
					if (state->linefeed_state >= 1)
					{
						lo_FindLineMargins(context, state);
					}
					else
					{
						lo_SoftLineBreak(context, state, FALSE,1);
					}
				}
				state->x = state->left_margin;
			}
			break;

		/*
		 * Complex because we have to place one of many types
		 * of bullet here, or one of several types of numbers.
		 */
		case P_LIST_ITEM:
			if (state->in_paragraph != FALSE)
			{
				lo_CloseParagraph(context, state);
			}
			if (tag->is_end == FALSE)
			{
				int type;
				int bullet_type;
				PA_Block buff;

				/*
				 * Reset fake linefeed state used to
				 * fake out headers in lists.
				 */
				if (state->at_begin_line == FALSE)
				{
					state->linefeed_state = 0;
				}

				lo_SoftLineBreak(context, state, FALSE, 1);
				/*
				 * Artificially setting state to 2 here
				 * allows us to put headers on list items
				 * even if they aren't double spaced.
				 */
				state->linefeed_state = 2;

				type = state->list_stack->bullet_type;
				buff = lo_FetchParamValue(context, tag, PARAM_TYPE);
				if ((buff != NULL)&&
					(state->list_stack->type == P_NUM_LIST))
				{
					char *type_str;

					PA_LOCK(type_str, char *, buff);
					if (*type_str == 'A')
					{
						type = BULLET_ALPHA_L;
					}
					else if (*type_str == 'a')
					{
						type = BULLET_ALPHA_S;
					}
					else if (*type_str == 'I')
					{
						type = BULLET_NUM_L_ROMAN;
					}
					else if (*type_str == 'i')
					{
						type = BULLET_NUM_S_ROMAN;
					}
					else
					{
						type = BULLET_NUM;
					}
					PA_UNLOCK(buff);
				}
				else if ((buff != NULL)&&
					(state->list_stack->type !=P_DESC_LIST))
				{
					char *type_str;

					PA_LOCK(type_str, char *, buff);
					if (pa_TagEqual("round", type_str))
					{
						type = BULLET_ROUND;
					}
					else if (pa_TagEqual("circle",type_str))
					{
						type = BULLET_ROUND;
					}
					else if (pa_TagEqual("square",type_str))
					{
						type = BULLET_SQUARE;
					}
					else
					{
						type = BULLET_BASIC;
					}
					PA_UNLOCK(buff);
				}
				state->list_stack->bullet_type = type;

				if (buff != NULL) 
				{
					PA_FREE(buff);
				}

				buff = lo_FetchParamValue(context, tag, PARAM_VALUE);
				if (buff != NULL)
				{
					int32 val;
					char *val_str;

					PA_LOCK(val_str, char *, buff);
					val = XP_ATOI(val_str);
					if (val < 1)
					{
						val = 1;
					}
					PA_UNLOCK(buff);
					state->list_stack->value = val;

					PA_FREE(buff);
				}

				bullet_type = state->list_stack->bullet_type;
				if ((bullet_type == BULLET_NUM)||
				    (bullet_type == BULLET_ALPHA_L)||
				    (bullet_type == BULLET_ALPHA_S)||
				    (bullet_type == BULLET_NUM_L_ROMAN)||
				    (bullet_type == BULLET_NUM_S_ROMAN))
				{
					lo_PlaceBulletStr(context, state);
					state->list_stack->value++;
				}
				else
				{
					lo_PlaceBullet(context, state);
				}

				/*
				 * Make at_begin_line be accurate
				 * so we can detect the header
				 * linefeed state deception later.
				 */
				state->at_begin_line = FALSE;

				/*
				 * After much soul-searching (and brow-beating
				 * by Jamie, I've agreed that really whitespace
				 * should be compressed out at the start of a
				 * list item.  They can always add non-breaking
				 * spaces if they want them.
				 * Setting trailing space true means it won't
				 * let the users add whitespace because it
				 * thinks there already is some.
				 */
				state->trailing_space = TRUE;
			}
			break;

		/*
		 * Another font attribute, another font on the font stack
		 */
		case P_BLINK:
			if (tag->is_end == FALSE)
			{
				LO_TextAttr *old_attr;
				LO_TextAttr *attr;

				old_attr = state->font_stack->text_attr;
				lo_CopyTextAttr(old_attr, &tmp_attr);
				tmp_attr.attrmask |= LO_ATTR_BLINK;
				attr = lo_FetchTextAttr(state, &tmp_attr);

				lo_PushFont(state, tag->type, attr);
			}
			else
			{
				LO_TextAttr *attr;

				attr = lo_PopFont(state, tag->type);
			}
			break;

		/*
		 * Another font attribute, another font on the font stack
		 * Because of spec mutation, two tags do the same thing.
		 * <s> and <strike> are the same.
		 */
		case P_STRIKEOUT:
		case P_STRIKE:
			if (tag->is_end == FALSE)
			{
				LO_TextAttr *old_attr;
				LO_TextAttr *attr;

				old_attr = state->font_stack->text_attr;
				lo_CopyTextAttr(old_attr, &tmp_attr);
				tmp_attr.attrmask |= LO_ATTR_STRIKEOUT;
				attr = lo_FetchTextAttr(state, &tmp_attr);

				lo_PushFont(state, tag->type, attr);
			}
			else
			{
				LO_TextAttr *attr;

				attr = lo_PopFont(state, tag->type);
			}
			break;

		/*
		 * Another font attribute, another font on the font stack
		 */
		case P_UNDERLINE:
			if (tag->is_end == FALSE)
			{
				LO_TextAttr *old_attr;
				LO_TextAttr *attr;

				old_attr = state->font_stack->text_attr;
				lo_CopyTextAttr(old_attr, &tmp_attr);
				tmp_attr.attrmask |= LO_ATTR_UNDERLINE;
				attr = lo_FetchTextAttr(state, &tmp_attr);

				lo_PushFont(state, tag->type, attr);
			}
			else
			{
				LO_TextAttr *attr;

				attr = lo_PopFont(state, tag->type);
			}
			break;

		/*
		 * Center all following lines.  Forces a single
		 * line break to start and end the new centered lines.
		 */
		case P_CENTER:
			lo_SoftLineBreak(context, state, FALSE, 1);
			if (tag->is_end == FALSE)
			{
				lo_PushAlignment(state, tag, LO_ALIGN_CENTER);
			}
			else
			{
				lo_AlignStack *aptr;

				aptr = lo_PopAlignment(state);
				if (aptr != NULL)
				{
					XP_DELETE(aptr);
				}
			}
			break;

		/*
		 * For plaintext there is no endtag, so this is
		 * really the last tag will we be parsing if this
		 * is a plaintext tag.
		 *
		 * Difference between these three tags are
		 * all based on what things the parser allows to
		 * be tags while inside them.
		 */
		case P_PREFORMAT:
		case P_PLAIN_PIECE:
		case P_PLAIN_TEXT:
			if (state->in_paragraph != FALSE)
			{
				lo_CloseParagraph(context, state);
			}
			if (tag->is_end == FALSE)
			{
				LO_TextAttr *old_attr;
				LO_TextAttr *attr;
				PA_Block buff;

				lo_SoftLineBreak(context, state, FALSE, 2);

				state->preformatted = PRE_TEXT_YES;
				FE_BeginPreSection(context);

				/*
				 * Special WRAP attribute of the PRE tag
				 * to make wrapped pre sections.
				 */
				if (tag->type == P_PREFORMAT)
				{
					buff = lo_FetchParamValue(context, tag,
							PARAM_WRAP);
					if (buff != NULL)
					{
						state->preformatted =
							PRE_TEXT_WRAP;
						PA_FREE(buff);
					}
				}

				old_attr = state->font_stack->text_attr;
				lo_CopyTextAttr(old_attr, &tmp_attr);

				/*
				 * Allow <PRE VARIABLE> to create a variable
				 * width font preformatted section.  This
				 * is only to be used internally for 
				 * mail and news in Netscape 2.0 and greater.
				 */
				buff = lo_FetchParamValue(context, tag, PARAM_VARIABLE);
				if ((tag->type == P_PREFORMAT)&&(buff != NULL))
				{
					tmp_attr.fontmask &= (~(LO_FONT_FIXED));
				}
				else
				{
					tmp_attr.fontmask |= LO_FONT_FIXED;
				}
				/*
				 * If VARIABLE attribute was there, free
				 * the value buffer.
				 */
				if (buff != NULL)
				{
					PA_FREE(buff);
				}

				attr = lo_FetchTextAttr(state, &tmp_attr);

				lo_PushFont(state, tag->type, attr);
			}
			else
			{
				LO_TextAttr *attr;

				attr = lo_PopFont(state, tag->type);

				state->preformatted = PRE_TEXT_NO;
				lo_SoftLineBreak(context, state, FALSE, 2);
				FE_EndPreSection(context);
			}
			break;

		/*
		 * Historic text type.  Paragraph break, font size change
		 * and font set to fixed.  Was supposed to support
		 * lineprinter listings if you can believe that!
		 */
		case P_LISTING_TEXT:
			if (state->in_paragraph != FALSE)
			{
				lo_CloseParagraph(context, state);
			}
			if (tag->is_end == FALSE)
			{
				LO_TextAttr *old_attr;
				LO_TextAttr *attr;

				lo_SoftLineBreak(context, state, FALSE, 2);

				state->preformatted = PRE_TEXT_YES;
				FE_BeginPreSection(context);

				old_attr = state->font_stack->text_attr;
				lo_CopyTextAttr(old_attr, &tmp_attr);
				tmp_attr.fontmask |= LO_FONT_FIXED;
				tmp_attr.size = DEFAULT_BASE_FONT_SIZE - 1;
				attr = lo_FetchTextAttr(state, &tmp_attr);

				lo_PushFont(state, tag->type, attr);
			}
			else
			{
				LO_TextAttr *attr;

				attr = lo_PopFont(state, tag->type);

				state->preformatted = PRE_TEXT_NO;
				lo_SoftLineBreak(context, state, FALSE, 2);
				FE_EndPreSection(context);
			}
			break;

		/*
		 * Another historic tag.  Break a line, and switch to italic.
		 */
		case P_ADDRESS:
			if (state->in_paragraph != FALSE)
			{
				lo_CloseParagraph(context, state);
			}
			if (tag->is_end == FALSE)
			{
				LO_TextAttr *old_attr;
				LO_TextAttr *attr;

				lo_SoftLineBreak(context, state, FALSE, 1);

				old_attr = state->font_stack->text_attr;
				lo_CopyTextAttr(old_attr, &tmp_attr);
				tmp_attr.fontmask |= LO_FONT_ITALIC;
				attr = lo_FetchTextAttr(state, &tmp_attr);

				lo_PushFont(state, tag->type, attr);
			}
			else
			{
				LO_TextAttr *attr;

				attr = lo_PopFont(state, tag->type);

				lo_SoftLineBreak(context, state, FALSE, 1);
			}
			break;

		/*
		 * Subscript.  Move down so the middle of the ascent
		 * of the new text aligns with the baseline of the old.
		 */
		case P_SUB:
			if (state->font_stack != NULL)
			{
				PA_Block buff;
				char *str;
				LO_TextStruct tmp_text;
				LO_TextInfo text_info;

				if (tag->is_end == FALSE)
				{
					LO_TextAttr *old_attr;
					LO_TextAttr *attr;
					intn new_size;

					old_attr = state->font_stack->text_attr;
					lo_CopyTextAttr(old_attr, &tmp_attr);
					new_size = LO_ChangeFontSize(tmp_attr.size,
								"-1");
					tmp_attr.size = new_size;
					attr = lo_FetchTextAttr(state,
						&tmp_attr);

					lo_PushFont(state, tag->type, attr);
				}

				/*
				 * All this work is to get the text_info
				 * filled in for the current font in the font
				 * stack. Yuck, there must be a better way.
				 */
				memset (&tmp_text, 0, sizeof (tmp_text));
				buff = PA_ALLOC(1);
				if (buff == NULL)
				{
					state->top_state->out_of_memory = TRUE;
					return;
				}
				PA_LOCK(str, char *, buff);
				str[0] = ' ';
				PA_UNLOCK(buff);
				tmp_text.text = buff;
				tmp_text.text_len = 1;
				tmp_text.text_attr =
					state->font_stack->text_attr;
				FE_GetTextInfo(context, &tmp_text, &text_info);
				PA_FREE(buff);

				if (tag->is_end == FALSE)
				{
					state->baseline +=
						(text_info.ascent / 2);
				}
				else
				{
					LO_TextAttr *attr;

					state->baseline -=
						(text_info.ascent / 2);

					attr = lo_PopFont(state, tag->type);
				}
			}
			break;

		/*
		 * Superscript.  Move up so the baseline of the new text
		 * is raised above the baseline of the old by 1/2 the
		 * ascent of the new text.
		 */
		case P_SUPER:
			if (state->font_stack != NULL)
			{
				PA_Block buff;
				char *str;
				LO_TextStruct tmp_text;
				LO_TextInfo text_info;

				if (tag->is_end == FALSE)
				{
					LO_TextAttr *old_attr;
					LO_TextAttr *attr;
					intn new_size;

					old_attr = state->font_stack->text_attr;
					lo_CopyTextAttr(old_attr, &tmp_attr);
					new_size = LO_ChangeFontSize(tmp_attr.size,
								"-1");
					tmp_attr.size = new_size;
					attr = lo_FetchTextAttr(state,
						&tmp_attr);

					lo_PushFont(state, tag->type, attr);
				}

				/*
				 * All this work is to get the text_info
				 * filled in for the current font in the font
				 * stack. Yuck, there must be a better way.
				 */
				memset (&tmp_text, 0, sizeof (tmp_text));
				buff = PA_ALLOC(1);
				if (buff == NULL)
				{
					state->top_state->out_of_memory = TRUE;
					return;
				}
				PA_LOCK(str, char *, buff);
				str[0] = ' ';
				PA_UNLOCK(buff);
				tmp_text.text = buff;
				tmp_text.text_len = 1;
				tmp_text.text_attr =
					state->font_stack->text_attr;
				FE_GetTextInfo(context, &tmp_text, &text_info);
				PA_FREE(buff);

				if (tag->is_end == FALSE)
				{
					state->baseline -=
						(text_info.ascent / 2);
				}
				else
				{
					LO_TextAttr *attr;

					state->baseline +=
						(text_info.ascent / 2);

					attr = lo_PopFont(state, tag->type);
				}
			}
			break;

		/*
		 * Change the base font size that relative font size changes
		 * are figured from.
		 */
		case P_BASEFONT:
			{
				LO_TextAttr *old_attr;
				LO_TextAttr *attr;
				PA_Block buff;
				intn new_size;
				char *size_str;
				lo_FontStack *fptr;

				buff = lo_FetchParamValue(context, tag, PARAM_SIZE);
				if (buff != NULL)
				{
					PA_LOCK(size_str, char *, buff);
					new_size = LO_ChangeFontSize(
						state->base_font_size,
						size_str);
					PA_UNLOCK(buff);
					PA_FREE(buff);
				}
				else
				{
					new_size = DEFAULT_BASE_FONT_SIZE;
				}
				state->base_font_size = new_size;

				fptr = state->font_stack;
				/*
				 * If the current font is the basefont
				 * we are changing the current font size.
				 */
				if (fptr->tag_type == P_UNKNOWN)
				{
					/*
					 * Flush all current text before
					 * changing the font size.
					 */
					if ((state->cur_ele_type == LO_TEXT)&&
						(state->line_buf_len != 0))
					{
						lo_FlushLineBuffer(context,
							state);
					}
				}

				/*
				 * Traverse the font stack to the base font
				 * entry.
				 */
				while (fptr->tag_type != P_UNKNOWN)
				{
					fptr = fptr->next;
				}

				/*
				 * Copy the old base entry, change its
				 * size, and stuff the new one in its place
				 */
				old_attr = fptr->text_attr;
				lo_CopyTextAttr(old_attr, &tmp_attr);
				tmp_attr.size = state->base_font_size;
				attr = lo_FetchTextAttr(state, &tmp_attr);
				if (attr != NULL)
				{
					fptr->text_attr = attr;
				}
			}
			break;


		/*
		 * Change font, currently only lets you change size.
		 * Thanks to Microsoft we now let you change color.
		 * Thanks again to Microsoft for the evil of FACE.
		 */
		case P_FONT:
			if (tag->is_end == FALSE)
			{
				LO_TextAttr *old_attr;
				LO_TextAttr *attr;
				PA_Block buff;
				intn new_size;
				char *size_str;
				char *str;
				char *new_face;
				Bool has_face;
				Bool has_size;
				Bool new_colors;
				uint8 red, green, blue;

				new_size = state->base_font_size;
				has_size = FALSE;
				has_face = FALSE;
				buff = lo_FetchParamValue(context, tag, PARAM_SIZE);
				if (buff != NULL)
				{
					PA_LOCK(size_str, char *, buff);
					new_size = LO_ChangeFontSize(
						state->base_font_size,
						size_str);
					PA_UNLOCK(buff);
					PA_FREE(buff);
					has_size = TRUE;
				}

				/*
				 * For backwards compatibility, a font
				 * tag with NO attributes will reset the
				 * font size to the base size.
				 */
				if (PA_TagHasParams(tag) == FALSE)
				{
					new_size = state->base_font_size;
					has_size = TRUE;
				}

				new_colors = FALSE;
				buff = lo_FetchParamValue(context, tag, PARAM_COLOR);
				if (buff != NULL)
				{
				    PA_LOCK(str, char *, buff);
				    LO_ParseRGB(str, &red, &green, &blue);
				    PA_UNLOCK(buff);
				    PA_FREE(buff);
				    new_colors = TRUE;
				}

				buff = lo_FetchParamValue(context, tag,
						PARAM_FACE);
				new_face = NULL;
				if (buff != NULL)
				{
				    PA_LOCK(str, char *, buff);
				    new_face = lo_FetchFontFace(context, state,
							str);
				    PA_UNLOCK(buff);
				    PA_FREE(buff);
				    has_face = TRUE;
				}

				old_attr = state->font_stack->text_attr;
				lo_CopyTextAttr(old_attr, &tmp_attr);

				if (has_size != FALSE)
				{
					tmp_attr.size = new_size;
				}

				if (new_colors != FALSE)
				{
				    tmp_attr.fg.red = red;
				    tmp_attr.fg.green = green;
				    tmp_attr.fg.blue = blue;
				}

				if (has_face != FALSE)
				{
				    tmp_attr.font_face = new_face;
				}

				attr = lo_FetchTextAttr(state, &tmp_attr);

				lo_PushFont(state, tag->type, attr);
			}
			else
			{
				LO_TextAttr *attr;

				attr = lo_PopFont(state, tag->type);
			}
			break;

		/*
		 * Change font to bigger than current font.
		 */
		case P_BIG:
			if (tag->is_end == FALSE)
			{
				LO_TextAttr *old_attr;
				LO_TextAttr *attr;
				intn new_size;

				old_attr = state->font_stack->text_attr;
				lo_CopyTextAttr(old_attr, &tmp_attr);
				new_size = LO_ChangeFontSize(tmp_attr.size, "+1");
				tmp_attr.size = new_size;
				attr = lo_FetchTextAttr(state, &tmp_attr);

				lo_PushFont(state, tag->type, attr);
			}
			else
			{
				LO_TextAttr *attr;

				attr = lo_PopFont(state, tag->type);
			}
			break;

		/*
		 * Change font to smaller than current font.
		 */
		case P_SMALL:
			if (tag->is_end == FALSE)
			{
				LO_TextAttr *old_attr;
				LO_TextAttr *attr;
				intn new_size;

				old_attr = state->font_stack->text_attr;
				lo_CopyTextAttr(old_attr, &tmp_attr);
				new_size = LO_ChangeFontSize(tmp_attr.size, "-1");
				tmp_attr.size = new_size;
				attr = lo_FetchTextAttr(state, &tmp_attr);

				lo_PushFont(state, tag->type, attr);
			}
			else
			{
				LO_TextAttr *attr;

				attr = lo_PopFont(state, tag->type);
			}
			break;

		/*
		 * Change the absolute URL that describes where this
		 * docuemtn came from for all following
		 * relative URLs.
		 */
		case P_BASE:
			{
				PA_Block buff;
				char *url;
				char *target;

				buff = lo_FetchParamValue(context, tag, PARAM_HREF);
				if (buff != NULL)
				{
					PA_LOCK(url, char *, buff);
					if (url != NULL)
					{
					    int32 len;

					    len = lo_StripTextWhitespace(
							url, XP_STRLEN(url));
					}
					if (state->top_state->base_url != NULL)
					{
					    XP_FREE(state->top_state->base_url);
					    state->top_state->base_url = NULL;
					}
					state->top_state->base_url =
							XP_STRDUP(url);
					PA_UNLOCK(buff);
					PA_FREE(buff);
				}

				buff = lo_FetchParamValue(context, tag, PARAM_TARGET);
				if (buff != NULL)
				{
					PA_LOCK(target, char *, buff);
					if (target != NULL)
					{
					    int32 len;

					    len = lo_StripTextWhitespace(
						target, XP_STRLEN(target));
					}
				        if (lo_IsValidTarget(target) == FALSE)
					{
					    PA_UNLOCK(buff);
					    PA_FREE(buff);
					    target = NULL;
					}
					if (state->top_state->base_target !=
						NULL)
					{
					    XP_FREE(
						state->top_state->base_target);
					    state->top_state->base_target =NULL;
					}
					if (target != NULL)
					{
					    state->top_state->base_target =
							XP_STRDUP(target);
					}
					PA_UNLOCK(buff);
					PA_FREE(buff);
				}
			}
			break;

		/*
		 * Begin an anchor.  Change font colors if
		 * there is an HREF here.  Make the HREF absolute
		 * based on the base url.  Check name in case we
		 * are jumping to a named anchor inside this document.
		 */
		case P_ANCHOR:
			lo_process_anchor_tag(context, state, tag);
			if (state->top_state->out_of_memory != FALSE)
			{
				return;
			}
			break;

		/*
		 * Insert a wordbreak.  Allows layout to break a line
		 * at this location in case it needs to.
		 */
		case P_WORDBREAK:
			lo_InsertWordBreak(context, state);
			break;

		/*
		 * All text inside these tags must be considered one
		 * word, and not broken (unless there is a word break
		 * in there).
		 */
		case P_NOBREAK:
			if (tag->is_end == FALSE)
			{
				state->breakable = FALSE;
			}
			else
			{
				state->breakable = TRUE;
			}
			break;

		/*
		 * spacer element to move stuff around
		 */
		case P_SPACER:
			if (tag->is_end == FALSE)
			{
				lo_ProcessSpacerTag(context, state, tag);
			}
			break;

		case P_MULTICOLUMN:
			if (tag->is_end == FALSE)
			{
				lo_BeginMulticolumn(context, state, tag);
			}
			else
			{
				if (state->current_multicol != NULL)
				{
					lo_EndMulticolumn(context, state, tag,
						state->current_multicol);
				}
			}
			break;

		/*
		 * Break the current line.
		 * Optionally break furthur down to get past floating
		 * elements in the margins.
		 */
		case P_LINEBREAK:
			/*
			 * <br> tag diabled in preformatted text
			 */
#ifdef EDITOR
			if (state->preformatted != PRE_TEXT_NO && !EDT_IS_EDITOR(context))
#else
			if (state->preformatted != PRE_TEXT_NO)
#endif
			{
				lo_SoftLineBreak(context, state, FALSE, 1);
				break;
			}
			lo_HardLineBreak(context, state, FALSE);
#ifdef EDITOR
			/* editor hack.  Make breaks always do something. */
			if ( EDT_IS_EDITOR(context) ) {
				state->linefeed_state = 0;
            }
#endif

			{
			    PA_Block buff;

			    buff = lo_FetchParamValue(context, tag, PARAM_CLEAR);
			    if (buff != NULL)
			    {
				char *clear_str;

				PA_LOCK(clear_str, char *, buff);
				if (pa_TagEqual("left", clear_str))
				{
					lo_ClearToLeftMargin(context, state);
				}
				else if (pa_TagEqual("right", clear_str))
				{
					lo_ClearToRightMargin(context, state);
				}
				else if (pa_TagEqual("all", clear_str))
				{
					lo_ClearToBothMargins(context, state);
				}
				else if (pa_TagEqual("both", clear_str))
				{
					lo_ClearToBothMargins(context, state);
				}
				PA_UNLOCK(buff);
				PA_FREE(buff);
				/*
				 * Reset the margins properly in case
				 * we are inside a list.
				 */
				lo_FindLineMargins(context, state);
				state->x = state->left_margin;
			    }
			}
			break;

		/*
		 * The head tag.  The tag is supposed to be a container
		 * for the head of the HTML document.
		 * We use it to try and detect when we should
		 * suppress the content of unknown head tags.
		 */
		case P_HEAD:
			/*
			 * The end HEAD tags defines the end of
			 * the HEAD section of the HTML
			 */
			if (tag->is_end != FALSE)
			{
				state->top_state->in_head = FALSE;
			}
			break;

		/*
		 * The body tag.  The tag is supposed to be a container
		 * for the body of the HTML document.
		 * Right now we are just using it to specify
		 * the document qualities and colors.
		 */
		case P_BODY:
			/*
			 * The start of BODY definitely ends
			 * the HEAD section of the HTML and starts the BODY
			 */
			state->top_state->in_head = FALSE;
			state->top_state->in_body = TRUE;

			lo_ProcessBodyTag(context, state, tag);
			break;

		/*
		 * The colormap tag.  This is used to specify the colormap
		 * to be used by all the images on this page.
		 */
		case P_COLORMAP:
			if ((tag->is_end == FALSE)&&
				(state->end_last_line == NULL))
			{
				PA_Block buff;
				char *str;
				char *image_url;

				image_url = NULL;
				buff = lo_FetchParamValue(context, tag, PARAM_SRC);
				if (buff != NULL)
				{
					PA_LOCK(str, char *, buff);
					if (str != NULL)
					{
						int32 len;

						len = lo_StripTextWhitespace(
							str, XP_STRLEN(str));
					}
					image_url = NET_MakeAbsoluteURL(
						state->top_state->base_url,str);
					PA_UNLOCK(buff);
					PA_FREE(buff);
				}
				(void)IL_ColormapTag(image_url, context);
				if (image_url != NULL)
				{
					XP_FREE(image_url);
				}
			}
			break;

		/*
		 * The meta tag.  This tag can only appear in the
		 * head of the document.
		 * It overrides HTTP headers sent by the
		 * web server that served this document.
		 * If URLStruct somehow became NULL, META must
		 * be ignored.
		 */
		case P_META:
			if ((tag->is_end == FALSE)&&
				(state->end_last_line == NULL)&&
				(state->top_state->nurl != NULL))
			{
				PA_Block buff;
				PA_Block buff2;
				PA_Block buff3;
				char *name;
				char *value;
				char *tptr;

				buff = lo_FetchParamValue(context, tag,
					PARAM_HTTP_EQUIV);
				if (buff != NULL)
				{
				    /*
				     * Lou needs a colon on the end of
				     * the name.
				     */
				    PA_LOCK(tptr, char *, buff);
				    buff3 = PA_ALLOC(XP_STRLEN(tptr) + 2);
				    if (buff3 != NULL)
				    {
					PA_LOCK(name, char *, buff3);
					XP_STRCPY(name, tptr);
					XP_STRCAT(name, ":");
					PA_UNLOCK(buff);
					PA_FREE(buff);

					buff2 = lo_FetchParamValue(context, tag,
						PARAM_CONTENT);
					if (buff2 != NULL)
					{
						Bool ok;

						PA_LOCK(value, char *, buff2);
						ok = NET_ParseMimeHeader(
							context,
							state->top_state->nurl,
							name, value);
						PA_UNLOCK(buff2);
						PA_FREE(buff2);

/* DEBUG_jwz */
#if 0  /* jwz: never ever do that fucking relayout!
		  it's useless to me, slow, and occasionally corrupts memory.
	    */

						if (context->relayout == METACHARSET_REQUESTRELAYOUT)
						{
							context->relayout = METACHARSET_FORCERELAYOUT;
							INTL_Relayout(context);
						}
#endif

					}
					PA_UNLOCK(buff3);
					PA_FREE(buff3);
				    }
				    else
				    {
					PA_UNLOCK(buff);
					PA_FREE(buff);
				    }
				}
			}
			break;

		/*
		 * The hype tag is just for fun.
		 * It only effects the UNIX version
		 * which can affor to have a sound file
		 * compiled into the binary.
		 */
		case P_HYPE:
#if defined(XP_UNIX) || defined(XP_MAC)
			if (tag->is_end == FALSE)
			{
			    PA_Tag tmp_tag;
			    PA_Block buff;
			    PA_Block abuff;
			    LO_AnchorData *hold_current_anchor;
			    LO_AnchorData *hype_anchor;
			    char *str;

			    /*
			     * Try to allocate the hype anchor as if we
			     * were inside:
			     * <A HREF="HYPE_ANCHOR">
			     * Save the real current anchor to restore later
			     */
			    hype_anchor = NULL;
			    abuff = PA_ALLOC(XP_STRLEN(HYPE_ANCHOR) + 1);
			    if (abuff != NULL)
			    {
				hype_anchor = lo_NewAnchor(state, NULL, NULL);
				if (hype_anchor == NULL)
				{
				    PA_FREE(abuff);
				    abuff = NULL;
				}
				else
				{
				    PA_LOCK(str, char *, abuff);
				    XP_STRCPY(str, HYPE_ANCHOR);
				    PA_UNLOCK(abuff);

				    hype_anchor->anchor = abuff;
				    hype_anchor->target = NULL;

				    /*
				     * Add this url's block to the list
				     * of all allocated urls so we can free
				     * it later.
				     */
				    lo_AddToUrlList(context, state,
					hype_anchor);
				    if (state->top_state->out_of_memory !=FALSE)
				    {
					PA_FREE(abuff);
					XP_DELETE(hype_anchor);
					return;
				    }
				}
			    }
			    hold_current_anchor = state->current_anchor;
			    state->current_anchor = hype_anchor;

			    /*
			     * If we have the memory, create a fake image
			     * tag to replace the <HYPE> tag and process it.
			     */
			    buff = PA_ALLOC(XP_STRLEN(HYPE_TAG_BECOMES)+1);
			    if (buff != NULL)
			    {

				PA_LOCK(str, char *, buff);
				XP_STRCPY(str, HYPE_TAG_BECOMES);
				PA_UNLOCK(buff);

				tmp_tag.type = P_IMAGE;
				tmp_tag.is_end = FALSE;
				tmp_tag.newline_count = tag->newline_count;
				tmp_tag.data = buff;
				tmp_tag.data_len = XP_STRLEN(HYPE_TAG_BECOMES);
				tmp_tag.true_len = 0;
				tmp_tag.lo_data = NULL;
				tmp_tag.next = NULL;
				lo_FormatImage(context, state, &tmp_tag);
				PA_FREE(buff);
			    }

			    /*
			     * Restore the proper current_anchor.
			     */
			    state->current_anchor = hold_current_anchor;
			}
#endif /* XP_UNIX */
			break;

		/*
		 * Insert a horizontal rule element.
		 * They always take up an entire line.
		 */
		case P_HRULE:
			if (state->in_paragraph != FALSE)
			{
				lo_CloseParagraph(context, state);
			}

			/*
			 * HR now does alignment the same as P, H?, and DIV.
			 * Ignore </hr>.
			 */
			if (tag->is_end == FALSE)
			{
				PA_Block buff;
				char *str;
				int32 alignment;
				lo_AlignStack *aptr;

				lo_SoftLineBreak(context, state, FALSE, 1);

				/*
				 * IF no ALIGN and no container, horizontal
				 * rules default to center alignments.
				 */
				alignment = LO_ALIGN_CENTER;
				buff = lo_FetchParamValue(context, tag, PARAM_ALIGN);
				if (buff != NULL)
				{
					PA_LOCK(str, char *, buff);
					alignment =
					  (int32)lo_EvalDivisionAlignParam(str);
					PA_UNLOCK(buff);
					PA_FREE(buff);
				}
				/*
				 * If there is no specific ALIGN,
				 * default to the container's alignment.
				 */
				else if (state->align_stack != NULL)
				{
					alignment =
						state->align_stack->alignment;
				}
				lo_PushAlignment(state, tag, alignment);

				lo_HorizontalRule(context, state, tag);
				lo_HardLineBreak(context, state, FALSE);

				aptr = lo_PopAlignment(state);
				if (aptr != NULL)
				{
					XP_DELETE(aptr);
				}
			}
			break;

		/*
		 * Experimental!  May be used to implement TABLES
		 */
		case P_CELL:
			if (state->in_paragraph != FALSE)
			{
				lo_CloseParagraph(context, state);
			}
			if (tag->is_end == FALSE)
			{
				lo_SoftLineBreak(context, state, FALSE, 1);
				lo_BeginCell(context, state, tag);
			}
			else
			{
				if (state->current_cell != NULL)
				{
					lo_SoftLineBreak(context, state,
						FALSE, 1);
					lo_EndCell(context, state,
						state->current_cell);
				}
			}
			break;


		/*
		 * The start of a caption in a table
		 */
		case P_CAPTION:
			lo_process_caption_tag(context, state, tag);
			break;

		/*
		 * The start of a header or data cell
		 * in a row in a table
		 */
		case P_TABLE_HEADER:
		case P_TABLE_DATA:
			lo_process_table_cell_tag(context, state, tag);
			break;

		/*
		 * The start of a row in a table
		 */
		case P_TABLE_ROW:
			lo_process_table_row_tag(context, state, tag);
			break;

		/*
		 * The start of a table
		 */
		case P_TABLE:
			lo_process_table_tag(context, state, tag);
			break;

		/*
		 * Experimental!  Everything in these tags is a complete
		 * separate sub document, it should be formatted, and the
		 * resulting entire document placed just like an image.
		 */
		case P_SUBDOC:
#ifdef NETSCAPE1_5
			if (state->in_paragraph != FALSE)
			{
				lo_CloseParagraph(context, state);
			}
			if (tag->is_end == FALSE)
			{
				lo_BeginSubDoc(context, state, tag);
			}
			else
			{
				lo_TopState *top_state;
				lo_DocState *old_state;
				int32 doc_id;

				/*
				 * makes sure we are at the bottom
				 * of everything in the document.
				 */
				lo_CloseOutLayout(context, state);

				old_state = state;
				/*
				 * Get the unique document ID, and retreive this
				 * documents layout state.
				 */
				doc_id = XP_DOCID(context);
				top_state = lo_FetchTopState(doc_id);
				state = top_state->doc_state;

				while ((state->sub_state != NULL)&&
					(state->sub_state != old_state))
				{
					state = state->sub_state;
				}

				if ((state != old_state)&&
					(state->current_ele != NULL))
				{
					lo_EndSubDoc(context, state,
						old_state, state->current_ele);
					state->sub_state = NULL;
				}
			}
#endif /* NETSCAPE1_5 */
			break;

		/*
		 * Begin a grid.  MUST be the very first thing inside
		 * this HTML document.  After a grid is started, all non-grid
		 * tags will be ignored!
		 * Also, right now the text and postscript FEs can't
		 * handle grids, so we block them.
		 */
		case P_GRID:
			if ((context->type != MWContextText)&&
			    (context->type != MWContextPostScript)&&
				(EDT_IS_EDITOR(context) == FALSE)&&
			    (state->top_state->nothing_displayed != FALSE)&&
			    (state->top_state->in_body == FALSE)&&
			    (state->end_last_line == NULL)&&
			    (state->line_list == NULL)&&
			    (state->float_list == NULL)&&
			    (state->top_state->the_grid == NULL))
			{
			    if (tag->is_end == FALSE)
			    {
				if (state->current_grid == NULL)
				{
					state->top_state->is_grid = TRUE;
					/*
					 * We may be restoring a grid from
					 * history.  If so we recreate it
					 * from saved data, and ignore all
					 * the rest of the tags in this
					 * document.  This happens because
					 * we will set the_grid and not
					 * set current_grid, so all
					 * grid tags are ignored, and since
					 * we have set is_grid, all non-grid
					 * tags will be ignored.
					 *
					 * Only recreate if main window
					 * size has not changed.
					 */
					if (state->top_state->savedData.Grid->the_grid != NULL)
					{
					    lo_SavedGridData *savedGridData;
					    int32 width, height;

					    width = state->win_width;
					    height = state->win_height;
					    FE_GetFullWindowSize(context,
						&width, &height);

					    savedGridData =
					      state->top_state->savedData.Grid;
					    if ((savedGridData->main_width ==
							width)&&
					        (savedGridData->main_height ==
							height))
					    {
						lo_RecreateGrid(context, state,
						    savedGridData->the_grid);
						savedGridData->the_grid =NULL;
					    }
					    else
					    {
						/*
						 * If window size has changed
						 * on the grid we are restoring
						 * we process normally, but
						 * hang on to the old data so
						 * we can restore the grid cell
						 * contents once new sizes are
						 * calculated.
						 */
						state->top_state->old_grid =
						    savedGridData->the_grid;
						savedGridData->the_grid =NULL;

						lo_BeginGrid(context, state,
								tag);
					    }
					}
					else
					{
					    lo_BeginGrid(context, state, tag);
					}
				}
				else
				{
					lo_BeginSubgrid(context, state, tag);
				}
			    }
			    else if ((tag->is_end != FALSE)&&
					(state->current_grid != NULL))
			    {
				lo_GridRec *grid;

				grid = state->current_grid;
				if (grid->subgrid == NULL)
				{
					state->current_grid = NULL;
					lo_EndGrid(context, state, grid);
				}
				else
				{
					lo_EndSubgrid(context, state, grid);
				}
			    }
			}
			break;

		/*
		 * A grid cell can only happen inside a grid
		 */
		case P_GRID_CELL:
			if ((state->top_state->nothing_displayed != FALSE)&&
			    (state->top_state->in_body == FALSE)&&
			    (state->end_last_line == NULL)&&
			    (state->line_list == NULL)&&
			    (state->float_list == NULL)&&
			    (state->current_grid != NULL))
			{
				if (tag->is_end == FALSE)
				{
					lo_BeginGridCell(context, state, tag);
				}
			}
			break;

		/*
		 * Begin a map.  Maps define area in client-side
		 * image maps.
		 */
		case P_MAP:
			if ((tag->is_end == FALSE)&&
			    (state->top_state->current_map == NULL))
			{
				lo_BeginMap(context, state, tag);
			}
			else if ((tag->is_end != FALSE)&&
				 (state->top_state->current_map != NULL))
			{
				lo_MapRec *map;

				map = state->top_state->current_map;
				state->top_state->current_map = NULL;
				lo_EndMap(context, state, map);
			}
			break;

		/*
		 * An area definition, can only happen inside a MAP
		 * definition for a client-side image map.
		 */
		case P_AREA:
			if ((state->top_state->current_map != NULL)&&
				(tag->is_end == FALSE))
			{
				lo_BeginMapArea(context, state, tag);
			}
			break;

		/*
		 * Shortcut and old historic tag to auto-place
		 * a simple single item query form.
		 */
		case P_INDEX:
			/*
			 * No forms in the scrolling document
			 * hack
			 */
			if (state->top_state->scrolling_doc != FALSE)
			{
				break;
			}

			if ((tag->is_end == FALSE)&&
				(state->top_state->in_form == FALSE))
			{
				if (state->in_paragraph != FALSE)
				{
					lo_CloseParagraph(context, state);
				}
				lo_ProcessIsIndexTag(context, state, tag);
			}
			break;

		/*
		 * Begin a form.
		 * Form cannot be nested!
		 */
		case P_FORM:
			/*
			 * No forms in the scrolling document
			 * hack
			 */
			if (state->top_state->scrolling_doc != FALSE)
			{
				break;
			}

			if (state->in_paragraph != FALSE)
			{
				lo_CloseParagraph(context, state);
			}
			if (tag->is_end == FALSE)
			{
				/*
				 * Sorry, no nested forms
				 */
				if (state->top_state->in_form == FALSE)
				{
					lo_SoftLineBreak(context, state,
						FALSE, 2);
					lo_BeginForm(context, state, tag);
				}
			}
			else if (state->top_state->in_form != FALSE)
			{
				lo_EndForm(context, state);
				lo_SoftLineBreak(context, state, FALSE, 2);
			}
			break;

		/*
		 * Deceptive simple form element tag.  Can only be inside a
		 * form.  Really multiplies into one of many possible
		 * input elements.
		 */
		case P_INPUT:
			/*
			 * Input tags only inside an open form.
			 */
			if ((state->top_state->in_form != FALSE)&&
				(tag->is_end == FALSE))
			{
				if (state->in_paragraph != FALSE)
				{
					lo_CloseParagraph(context, state);
				}
				lo_ProcessInputTag(context, state, tag);
			}
			break;

		/*
		 * A multi-line text input form element.  It encloses text
		 * that will be its default text.
		 */
		case P_TEXTAREA:
			/*
			 * Textarea tags only inside an open form.
			 */
			if (state->top_state->in_form != FALSE)
			{
			    if (state->in_paragraph != FALSE)
			    {
				lo_CloseParagraph(context, state);
			    }
			    if (tag->is_end == FALSE)
			    {
				if ((state->cur_ele_type == LO_TEXT)&&
					(state->line_buf_len != 0))
				{
					lo_FlushLineBuffer(context, state);
				}

				state->line_buf_len = 0;
				state->text_divert = tag->type;
				lo_BeginTextareaTag(context, state, tag);
			    }
			    else if (state->text_divert == tag->type)
			    {
				PA_Block buff;
				int32 len;
				int32 new_len;

				if (state->line_buf_len != 0)
				{
				    PA_LOCK(tptr2, char *,
					state->line_buf);
				    /*
				     * Don't do this, they were already
				     * expanded by the parser.
				    (void)pa_ExpandEscapes(tptr2,
					state->line_buf_len, &len, TRUE);
				     */
				    len = state->line_buf_len;
				    buff = lo_ConvertToFELinebreaks(tptr2, len,
						&new_len);
				    PA_UNLOCK(state->line_buf);
				}
				else
				{
				    buff = NULL;
				    new_len = 0;
				}
				lo_EndTextareaTag(context, state, buff);
				state->line_buf_len = 0;
				state->text_divert = P_UNKNOWN;
			    }
			}
			break;

		/*
		 * Option tag can only occur inside a select inside a form
		 */
		case P_OPTION:
			/*
			 * Select tags only inside an open select
			 * in an open form.
			 */
			if ((state->top_state->in_form != FALSE)&&
				(tag->is_end == FALSE)&&
				(state->current_ele != NULL)&&
				(state->current_ele->type == LO_FORM_ELE)&&
                                ((state->current_ele->lo_form.element_data->type
                                        == FORM_TYPE_SELECT_ONE)||
                                 (state->current_ele->lo_form.element_data->type
                                        == FORM_TYPE_SELECT_MULT)))
			{
			    if (state->in_paragraph != FALSE)
			    {
				lo_CloseParagraph(context, state);
			    }
			    if (state->text_divert == P_OPTION)
			    {
				PA_Block buff;
				int32 len;

				if (state->line_buf_len != 0)
				{
				    PA_LOCK(tptr2, char *,
					state->line_buf);
				    /*  
				     * Don't do this, they were already
				     * expanded by the parser.
				    (void)pa_ExpandEscapes(tptr2,
					state->line_buf_len, &len, TRUE);
				     */
				    len = state->line_buf_len;
				    len = lo_CleanTextWhitespace(tptr2, len);
				    buff = PA_ALLOC(len + 1);
				    if (buff != NULL)
				    {
					PA_LOCK(tptr, char *, buff);
					XP_BCOPY(tptr2, tptr, len);
					tptr[len] = '\0';
					PA_UNLOCK(buff);
				    }
				    PA_UNLOCK(state->line_buf);
				}
				else
				{
				    buff = NULL;
				    len = 0;
				}
				lo_EndOptionTag(context, state, buff);
			    }
			    state->line_buf_len = 0;
			    lo_BeginOptionTag(context, state, tag);
			    state->text_divert = tag->type;
			}
			break;

		/*
		 * Select tag, either an option menu or a scrolled
		 * list inside a form.  Lets you chose N of Many.
		 */
		case P_SELECT:
			/*
			 * Select tags only inside an open form.
			 */
			if (state->top_state->in_form != FALSE)
			{
			    /*
			     * Because SELECT doesn't automatically flush,
			     * we must do it here if this is the start of a
			     * select.
			     */
			    if ((tag->is_end == FALSE)&&
				(state->current_ele == NULL)&&
				(state->cur_ele_type == LO_TEXT)&&
				(state->line_buf_len != 0))
			    {
				lo_FlushLineBuffer(context, state);
			    }

			    if (state->in_paragraph != FALSE)
			    {
				lo_CloseParagraph(context, state);
			    }
			    if ((tag->is_end == FALSE)&&
				(state->current_ele == NULL))
			    {
				/*
				 * Don't allow text between beginning of select
				 * and beginning of first option.
				 */
			        state->text_divert = tag->type;

				lo_BeginSelectTag(context, state, tag);
			    }
			    else if ((state->current_ele != NULL)&&
				(state->current_ele->type == LO_FORM_ELE)&&
				((state->current_ele->lo_form.element_data->type
					== FORM_TYPE_SELECT_ONE)||
				 (state->current_ele->lo_form.element_data->type
					== FORM_TYPE_SELECT_MULT)))
			    {
			        if (state->text_divert == P_OPTION)
			        {
				    PA_Block buff;
				    int32 len;

				    if (state->line_buf_len != 0)
				    {
				        PA_LOCK(tptr2, char *,
					    state->line_buf);
					/*  
					 * Don't do this, they were already
					 * expanded by the parser.
				        (void)pa_ExpandEscapes(tptr2,
					    state->line_buf_len, &len, TRUE);
					 */
					len = state->line_buf_len;
				        len = lo_CleanTextWhitespace(tptr2,len);
				        buff = PA_ALLOC(len + 1);
				        if (buff != NULL)
				        {
					    PA_LOCK(tptr, char *, buff);
					    XP_BCOPY(tptr2, tptr, len);
					    tptr[len] = '\0';
					    PA_UNLOCK(buff);
				        }
				        PA_UNLOCK(state->line_buf);
				    }
				    else
				    {
				        buff = NULL;
				        len = 0;
				    }
				    lo_EndOptionTag(context, state, buff);
			        }
			        state->line_buf_len = 0;
			        state->text_divert = P_UNKNOWN;

				lo_EndSelectTag(context, state);
			    }
			}
			break;

		/*
		 * Place an embedded object.
		 * Just reserve an area of layout for that object
		 * to use as it wishes.
		 */
		case P_EMBED:
			if (tag->is_end == FALSE)
			{
				/*
				 * If we have started loading an EMBED we are
				 * out if the HEAD section of the HTML
				 * and into the BODY
				 */
				state->top_state->in_head = FALSE;
				state->top_state->in_body = TRUE;
				lo_FormatEmbed(context, state, tag);
			}
			break;

		/*
		 * Generate a public/private key pair.  The user sees
		 * a selection of key sizes to choose from; an ascii
		 * representation of the public key becomes the form
		 * field value.
		 */
		case P_KEYGEN:
			/*
			 * Keygen tags only inside an open form.
			 */
			if ((state->top_state->in_form != FALSE)&&
			    (tag->is_end == FALSE))
			{
				if (state->in_paragraph != FALSE)
				{
					lo_CloseParagraph(context, state);
				}
				lo_ProcessKeygenTag(context, state, tag);
			}
			break;

		/*
		 * Process Mocha script source in a <SCRIPT LANGUAGE="Mocha">
		 * ...</SCRIPT> container.  Drop all unknown language SCRIPT
		 * contents on the floor.  Process SRC=<url> attributes for
		 * known languages.
		 */
		case P_SCRIPT:
#ifdef EDITOR
			if( EDT_IS_EDITOR( context ) )
			{
				goto process_server;
			}
#endif

#ifdef MOCHA
			lo_ProcessScriptTag(context, state, tag);
			break;
#else
			goto unknown;
#endif

		/*
		 * This is the NEW HTML-WG approved embedded JAVA
		 * application.
		 */
#ifdef JAVA
		case P_JAVA_APPLET:
		    /*
		     * If the user has disabled Java, act like it we don't
		     * understand the APPLET tag.
		     */
		    if (LJ_GetJavaEnabled() != FALSE)
		    {
			if (tag->is_end == FALSE)
			{
			    /*
			     * If we open a new java applet without closing
			     * an old one, force a close of the old one
			     * first.
			     * Now open the new java applet.
			     */
			    if (state->current_java != NULL)
			    {
				lo_CloseJavaApp(context, state,
						state->current_java);
				state->current_java = NULL;

				lo_FormatJavaApp(context, state, tag);
			    }
			    /*
			     * Else this is a new java applet, open it.
			     */
			    else
			    {
				/*
				 * If we have started loading an APPLET we are
				 * out if the HEAD section of the HTML
				 * and into the BODY
				 */
				state->top_state->in_head = FALSE;
				state->top_state->in_body = TRUE;
				
				lo_FormatJavaApp(context, state, tag);
			    }
			}
			else
			{
			    /*
			     * Only close a java app if it exists.
			     */
			    if (state->current_java != NULL)
			    {
				lo_CloseJavaApp(context, state,
						state->current_java);
				state->current_java = NULL;
			    }
			}
		    }
			break;

		/*
		 * This is the NEW HTML-WG approved parameter tag
		 * for embedded JAVA applications.
		 */
		case P_PARAM:
			if ((tag->is_end == FALSE)&&
			    (state->current_java != NULL))
			{
				lo_JavaAppParam(context, state, tag);
			}
			break;
#endif

		/*
		 * Place an image.  Special because if layout of all
		 * other tags was blocked, image tags are still
		 * partially processed at that time.
		 */
		case P_IMAGE:
		case P_NEW_IMAGE:
			if (tag->is_end == FALSE)
			{
				/*
				 * If we have started loading an IMAGE we are
				 * out if the HEAD section of the HTML
				 * and into the BODY
				 */
				state->top_state->in_head = FALSE;
				state->top_state->in_body = TRUE;

				if (tag->lo_data != NULL)
				{
					lo_PartialFormatImage(context, state,tag);
				}
				else
				{
					lo_FormatImage(context, state, tag);
				}
			}
			break;

		/*
		 * Paragraph break, a single line of whitespace.
		 */
		case P_PARAGRAPH:
			if (tag->is_end == FALSE)
			{
				PA_Block buff;
				char *str;

				if (state->in_paragraph != FALSE)
				{
					lo_CloseParagraph(context, state);
				}

				lo_SoftLineBreak(context, state, FALSE, 2);

				buff = lo_FetchParamValue(context, tag, PARAM_ALIGN);
				if (buff != NULL)
				{
					int32 alignment;

					PA_LOCK(str, char *, buff);
					alignment =
					  (int32)lo_EvalDivisionAlignParam(str);
					PA_UNLOCK(buff);
					PA_FREE(buff);

					lo_PushAlignment(state, tag, alignment);
				}
				state->in_paragraph = TRUE;
			}
			else
			{
				lo_SoftLineBreak(context, state, FALSE, 2);
				if (state->in_paragraph != FALSE)
				{
					lo_CloseParagraph(context, state);
				}
			}
			break;

		/*
		 * Division.  General alignment text block
		 */
		case P_DIVISION:
			lo_SoftLineBreak(context, state, FALSE, 1);
			if (tag->is_end == FALSE)
			{
				PA_Block buff;
				char *str;
				int32 alignment;

				alignment = LO_ALIGN_LEFT;
				buff = lo_FetchParamValue(context, tag, PARAM_ALIGN);
				if (buff != NULL)
				{
					PA_LOCK(str, char *, buff);
					alignment =
					  (int32)lo_EvalDivisionAlignParam(str);
					PA_UNLOCK(buff);
					PA_FREE(buff);
				}
				lo_PushAlignment(state, tag, alignment);
			}
			else
			{
				lo_AlignStack *aptr;

				aptr = lo_PopAlignment(state);
				if (aptr != NULL)
				{
					XP_DELETE(aptr);
				}
			}
			break;

		/*
		 * Just move the left and right margins in for this paragraph
		 * of text.
		 */
		case P_BLOCKQUOTE:
			if (state->in_paragraph != FALSE)
			{
				lo_CloseParagraph(context, state);
			}
			if (tag->is_end == FALSE)
			{
				lo_SoftLineBreak(context, state, FALSE, 2);

/* THIS BREAKS FLOATING IMAGES IN BLOCKQUOTES
				state->list_stack->old_left_margin =
					state->left_margin;
				state->list_stack->old_right_margin =
					state->right_margin;
*/
				lo_PushList(state, tag);
				state->left_margin += LIST_MARGIN_INC;
				state->right_margin -= LIST_MARGIN_INC;
				state->x = state->left_margin;
				state->list_stack->old_left_margin =
					state->left_margin;
				state->list_stack->old_right_margin =
					state->right_margin;
			}
			else
			{
				lo_ListStack *lptr;

				lptr = lo_PopList(state, tag);
				if (lptr != NULL)
				{
					XP_DELETE(lptr);
				}
				state->left_margin =
					state->list_stack->old_left_margin;
				state->right_margin =
					state->list_stack->old_right_margin;

				/*
				 * Reset the margins properly in case
				 * there is a right-aligned image here.
				 */
				lo_FindLineMargins(context, state);
				if (state->linefeed_state >= 2)
				{
					lo_FindLineMargins(context, state);
				}
				else
				{
					lo_SoftLineBreak(context, state, FALSE, 2);
				}

				state->x = state->left_margin;
			}
			break;

		/*
		 * An internal tag to allow libmocha/lm_doc.c's
		 * doc_open_stream to get the new document's top
		 * state struct early.
		 */
		case P_NSCP_OPEN:
			break;

		/*
		 * An internal tag to allow us to merge two
		 * possibly incomplete HTML docs as neatly
		 * as possible.
		 */
		case P_NSCP_CLOSE:
			if (tag->is_end == FALSE)
			{
				LO_CloseAllTags(context);
			}
			break;

		/*
		 * Normally unknown tags are just ignored.
		 * But if the unknown tag is in the head of a document, we
		 * assume it is a tag that can contain content, and ignore
		 * that content.
		 * WARNING:  In special cases with omitted </head> tags,
		 * this can result in the loss of body data!
		 */
		case P_UNKNOWN:
#ifndef MOCHA
		unknown:
#endif
			if (state->top_state->in_head != FALSE)
			{
			    char *tdata;
			    char *tptr;
			    char tchar;

			    PA_LOCK(tdata, char *, tag->data);

			    /*
			     * Throw out non-tags like <!doctype>
			     */
			    if ((tag->data_len < 1)||(tdata[0] == '!'))
			    {
				PA_UNLOCK(tag->data);
				break;
			    }

			    /*
			     * Remove the unknown tag name from any attributes
			     * it might have.
			     */
			    tptr = tdata;
			    while ((!XP_IS_SPACE(*tptr))&&(*tptr != '>'))
			    {
				tptr++;
			    }
			    tchar = *tptr;
			    *tptr = '\0';

			    /*
			     * Save the unknown tag name to match to its
			     * end tag.
			     */
			    if (tag->is_end == FALSE)
			    {
				if (state->top_state->unknown_head_tag == NULL)
				{
				    state->top_state->unknown_head_tag =
					XP_STRDUP(tdata);
				}
			    }
			    /*
			     * Else if we already have a saved start and this
			     * is the matching end.  Clear the knowledge
			     * that we are in an unknown head tag.
			     */
			    else
			    {
				if ((state->top_state->unknown_head_tag != NULL)
				    &&(XP_STRCASECMP(state->top_state->unknown_head_tag, tdata) == 0))
				{
				    XP_FREE(state->top_state->unknown_head_tag);
				    state->top_state->unknown_head_tag = NULL;
				}
			    }

			    *tptr = tchar;
			    PA_UNLOCK(tag->data);
			}
			break;

		default:
			break;
	}

#ifdef EDITOR
	/*  
	 * The editor needs all text elements to be individual text elements.
	 * Force the line buffer to be flushed so each individual text item
	 * remains such.
	 */
	if (EDT_IS_EDITOR(context) &&
	    (state->cur_ele_type == LO_TEXT)&&(state->line_buf_len != 0))
	{
		lo_FlushLineBuffer(context, state);
	}
#endif

}