/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */


#include "xp.h"
#include "net.h"
#include "glhist.h"
#include "shist.h"
#include "merrors.h"
#include "layout.h"
#include "laylayer.h"
#include "layers.h"

#define IL_CLIENT               /* XXXM12N Defined by Image Library clients */
#include "libimg.h"             /* Image Library public API. */

#include "pa_parse.h"
#include "edt.h"
#include "libmocha.h"
#include "libevent.h"
#include "laystyle.h"
#include "hk_funcs.h"
#include "pics.h"
#include "xp_ncent.h"
#include "prefetch.h"

/* WEBFONTS are defined only in laytags.c and layout.c */
#define WEBFONTS

#ifdef WEBFONTS
#include "nf.h"
#include "Mnffbu.h"
#endif /* WEBFONTS */

#ifdef HTML_CERTIFICATE_SUPPORT
#include "cert.h"
#endif

#ifndef MAX
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#endif
 
extern int MK_OUT_OF_MEMORY;


#ifdef PROFILE
#pragma profile on
#endif

#ifdef TEST_16BIT
#define XP_WIN16
#endif /* TEST_16BIT */

#ifdef XP_WIN16
#define SIZE_LIMIT		32000
#endif /* XP_WIN16 */


#define DEF_LINE_HEIGHT		20
#define URL_LIST_INC		20
#define LINE_INC		100


typedef struct lo_StateList_struct {
	int32 doc_id;
	lo_TopState *state;
	struct lo_StateList_struct *next;
} lo_StateList;

static lo_StateList *StateList = NULL;

LO_Color lo_master_colors[] = {
	{LO_DEFAULT_BG_RED, LO_DEFAULT_BG_GREEN, LO_DEFAULT_BG_BLUE},
	{LO_DEFAULT_FG_RED, LO_DEFAULT_FG_GREEN, LO_DEFAULT_FG_BLUE},
	{LO_UNVISITED_ANCHOR_RED, LO_UNVISITED_ANCHOR_GREEN,
	 LO_UNVISITED_ANCHOR_BLUE},
	{LO_VISITED_ANCHOR_RED, LO_VISITED_ANCHOR_GREEN,
	 LO_VISITED_ANCHOR_BLUE},
	{LO_SELECTED_ANCHOR_RED, LO_SELECTED_ANCHOR_GREEN,
	 LO_SELECTED_ANCHOR_BLUE}
};
static char *Master_backdrop_url = NULL;
Bool UserOverride = FALSE;

/********** BEGIN PROTOTYPES **************/

static void lo_process_deferred_image_info(void *closure);
static void lo_DisplayLine(MWContext *context, lo_DocState *state, int32 line_num,
						   int32 x, int32 y, uint32 w, uint32 h);
lo_SavedEmbedListData *	lo_NewDocumentEmbedListData(void);
lo_SavedGridData *		lo_NewDocumentGridData(void);
int32					lo_calc_push_right_for_justify(lo_DocState *state, int32 *remainder);
void					lo_BodyMargins(MWContext *context, lo_DocState *state, PA_Tag *tag);
void					lo_FinishLayout_OutOfMemory(MWContext *context, lo_DocState *state);
#ifdef MEMORY_ARENAS 
void					lo_GetRecycleList(
							MWContext* context, int32 doc_id, pa_DocData* doc_data, 
				 			 LO_Element* *recycle_list, lo_arena* *old_arena);
#else
void					lo_GetRecycleList(
							MWContext* context, int32 doc_id, pa_DocData* doc_data, 
				  			LO_Element* *recycle_list);
#endif /* MEMORY_ARENAS */
intn					lo_ProcessTag_OutOfMemory(MWContext* context, LO_Element* recycle_list, lo_TopState* top_state);

/********** END PROTOTYPES **************/

void
LO_SetDefaultBackdrop(char *url)
{
	if ((url != NULL)&&(*url != '\0'))
	{
		Master_backdrop_url = XP_STRDUP(url);
	}
	else
	{
		Master_backdrop_url = NULL;
	}
}


void
LO_SetUserOverride(Bool override)
{
	UserOverride = override;
}


void
LO_SetDefaultColor(intn type, uint8 red, uint8 green, uint8 blue)
{
	LO_Color *color;

	if (type < sizeof lo_master_colors / sizeof lo_master_colors[0])
	{
		color = &lo_master_colors[type];
		color->red = red;
		color->green = green;
		color->blue = blue;
	}
}

Bool
lo_InitDocLists(MWContext *context, lo_DocLists *doc_lists)
{
	LO_AnchorData **anchor_array;
	int32 i;

    doc_lists->embed_list = NULL;
    doc_lists->embed_list_count = 0;
    doc_lists->applet_list = NULL;
    doc_lists->applet_list_count = 0;

    doc_lists->name_list = NULL;
    doc_lists->image_list = doc_lists->last_image = NULL;
    doc_lists->image_list_count = 0;
    doc_lists->anchor_count = 0;
	doc_lists->form_list = NULL;
	doc_lists->current_form_num = 0;

	doc_lists->url_list = 
        XP_ALLOC_BLOCK(URL_LIST_INC * sizeof(LO_AnchorData *));
	if (doc_lists->url_list == NULL)
	{
		return(FALSE);
	}
	doc_lists->url_list_size = URL_LIST_INC;
	/* url_list is a Handle of (LO_AnchorData*) so the pointer is a (LO_AnchorData**) */
	XP_LOCK_BLOCK(anchor_array, LO_AnchorData **, 
                  doc_lists->url_list);
	for (i=0; i < URL_LIST_INC; i++)
	{
		anchor_array[i] = NULL;
	}
	XP_UNLOCK_BLOCK(doc_lists->url_list);
	doc_lists->url_list_len = 0;

#ifdef XP_WIN16
	{
		XP_Block *ulist_array;

		doc_lists->ulist_array = XP_ALLOC_BLOCK(sizeof(XP_Block));
		if (doc_lists->ulist_array == NULL)
		{
			XP_FREE_BLOCK(doc_lists->url_list);
			return(FALSE);
		}
		XP_LOCK_BLOCK(ulist_array, XP_Block *, 
                      doc_lists->ulist_array);
		ulist_array[0] = doc_lists->url_list;
		XP_UNLOCK_BLOCK(doc_lists->ulist_array);
		doc_lists->ulist_array_size = 1;
	}
#endif /* XP_WIN16 */

     return TRUE;
}


lo_TopState *
lo_NewTopState(MWContext *context, char *url)
{
	lo_TopState *top_state;
	char *name_target;
	LO_TextAttr **text_attr_hash;
	int32 i;
    lo_LayerDocState *layer_state;
    
	top_state = XP_NEW_ZAP(lo_TopState);
	if (top_state == NULL)
	{
		return(NULL);
	}

#ifdef MEMORY_ARENAS
    if ( EDT_IS_EDITOR(context) ) {
        top_state->current_arena = NULL;
    }
    else
    {
	    lo_InitializeMemoryArena(top_state);
	    if (top_state->first_arena == NULL)
	    {
		    XP_DELETE(top_state);
		    return(NULL);
	    }
    }
#endif /* MEMORY_ARENAS */

	top_state->tags = NULL;
	top_state->tags_end = &top_state->tags;
	top_state->insecure_images = FALSE;
	top_state->out_of_memory = FALSE;
	top_state->force_reload = NET_DONT_RELOAD;
    top_state->script_tag_count = 0;
	top_state->script_lineno = 0;
	top_state->in_script = SCRIPT_TYPE_NOT;
	top_state->default_style_script_type = SCRIPT_TYPE_CSS;
	top_state->resize_reload = FALSE;
	top_state->version = JSVERSION_UNKNOWN;
        top_state->scriptData = NULL;
	top_state->doc_state = NULL;

	if (url == NULL)
	{
		top_state->url = NULL;
	}
	else
	{
		top_state->url = XP_STRDUP(url);
	}
    top_state->base_url = NULL;
    top_state->inline_stream_blocked_base_url = NULL;
    top_state->main_stream_blocked_base_url = NULL;
	top_state->base_target = NULL;
    lo_SetBaseUrl(top_state, url, FALSE);

	/*
	 * This is the special named anchor we are jumping to in this
	 * document, it changes the starting display position.
	 */
	name_target = NET_ParseURL(top_state->url, GET_HASH_PART);
	if ((name_target[0] != '#')||(name_target[1] == '\0'))
	{
		XP_FREE(name_target);
		top_state->name_target = NULL;
	}
	else
	{
		top_state->name_target = name_target;
	}

	top_state->element_id = 0;
	top_state->layout_blocking_element = NULL;
	top_state->current_script = NULL;
	top_state->doc_specified_bg = FALSE;
	top_state->nothing_displayed = TRUE;
	top_state->in_head = TRUE;
	top_state->in_body = FALSE;
	top_state->body_attr = 0;
	top_state->have_title = FALSE;
	top_state->scrolling_doc = FALSE;
	top_state->is_grid = FALSE;
    top_state->ignore_tag_nest_level = 0;
    top_state->ignore_layer_nest_level = 0;
	top_state->in_applet = FALSE;
	top_state->unknown_head_tag = NULL;
	top_state->the_grid = NULL;
	top_state->old_grid = NULL;

	top_state->map_list = NULL;
	top_state->current_map = NULL;

    top_state->layers = NULL;
    top_state->current_layer_num = -1; /* incremented on layer allocation */
    top_state->num_layers_allocated = 0;
    top_state->max_layer_num = 0;
    
    layer_state = lo_NewLayerState(context);
    if (!layer_state) {
        XP_FREE_BLOCK(top_state);
        return NULL;
    }
    if (context->compositor)
        layer_state->layer = CL_GetCompositorRoot(context->compositor);
    lo_append_to_layer_array(context, top_state, NULL, layer_state);
    lo_PushLayerState(top_state, layer_state);

	top_state->in_form = FALSE;
	top_state->savedData.FormList = NULL;
	top_state->savedData.EmbedList = NULL;
	top_state->savedData.Grid = NULL;
	top_state->savedData.OnLoad = NULL;
	top_state->savedData.OnUnload = NULL;
	top_state->savedData.OnFocus = NULL;
	top_state->savedData.OnBlur = NULL;
 	top_state->savedData.OnHelp = NULL;
	top_state->savedData.OnMouseOver = NULL;
	top_state->savedData.OnMouseOut = NULL;
	top_state->savedData.OnDragDrop = NULL;
	top_state->savedData.OnMove = NULL;
	top_state->savedData.OnResize = NULL;
	top_state->embed_count = 0;

	top_state->total_bytes = 0;
	top_state->current_bytes = 0;
	top_state->layout_bytes = 0;
	top_state->script_bytes = 0;
	top_state->layout_percent = 0;

	top_state->text_attr_hash = XP_ALLOC_BLOCK(FONT_HASH_SIZE *
		sizeof(LO_TextAttr *));
	if (top_state->text_attr_hash == NULL)
	{
		XP_DELETE(top_state);
		return(NULL);
	}
	XP_LOCK_BLOCK(text_attr_hash, LO_TextAttr **,top_state->text_attr_hash);
	for (i=0; i<FONT_HASH_SIZE; i++)
	{
		text_attr_hash[i] = NULL;
	}
	XP_UNLOCK_BLOCK(top_state->text_attr_hash);

	top_state->font_face_array = NULL;
	top_state->font_face_array_len = 0;
	top_state->font_face_array_size = 0;

    if (!lo_InitDocLists(context, &top_state->doc_lists))
    {
		XP_FREE_BLOCK(top_state->text_attr_hash);
        XP_FREE_BLOCK(top_state);
        return(NULL);
    }
    /* Document layer state keeps its doc_lists in the top_state */
    if ( layer_state->doc_lists )
    {
		lo_DeleteDocLists(context, layer_state->doc_lists);
		XP_FREE( layer_state->doc_lists );
    }
    layer_state->doc_lists = &top_state->doc_lists;
    
	top_state->recycle_list = NULL;
	top_state->trash = NULL;

	top_state->mocha_write_stream = NULL;
	top_state->mocha_loading_applets_count = 0;
	top_state->mocha_loading_embeds_count = 0;
	top_state->mocha_has_onload = FALSE;
	top_state->mocha_has_onunload = FALSE;

	top_state->doc_data = NULL;
	for (i = 0; i < MAX_INPUT_WRITE_LEVEL; i++)
		top_state->input_write_point[i] = NULL;
	top_state->input_write_level = 0;
    top_state->tag_from_inline_stream = FALSE;

#ifdef HTML_CERTIFICATE_SUPPORT
	top_state->cert_list = NULL;
#endif

	top_state->style_stack = SML_StyleStack_Factory_Create();
	if(!top_state->style_stack)
	{
		XP_FREE_BLOCK(top_state->text_attr_hash);
		XP_FREE_BLOCK(top_state->doc_lists.url_list);
		XP_DELETE(top_state);
		return(NULL);
	}

	STYLESTACK_Init(top_state->style_stack, context);

	top_state->diff_state = FALSE;
	top_state->state_pushes = 0;
	top_state->state_pops = 0;

	top_state->object_stack = NULL;
	top_state->object_cache = NULL;

	top_state->tag_count = 0;

    top_state->flushing_blockage = FALSE;
    top_state->wedged_on_mocha = FALSE;
	top_state->in_cell_relayout = FALSE;  /* Used for resize without reload stuff */
	
	return(top_state);
}


/*************************************
 * Function: lo_PushStateLevel
 *
 * Description: Record that we're going one level further
 *  in the state hierarchy.
 *
 * Params: Window context.
 *************************************/
void
lo_PushStateLevel(MWContext *context)
{
	lo_TopState *top_state;

	top_state = lo_FetchTopState( XP_DOCID(context) );
	top_state->state_pushes++;
	
	/*
	 * If we've pushed back to our original state level, then we
	 * need to set a flag saying that we really have a new
	 * state record at the original level. We know this to
	 * be true as we've just added a new state record to the
	 * end of the list.
	 */
	if ( top_state->state_pushes == top_state->state_pops )
	{
		top_state->diff_state = TRUE;
	}
}


/*************************************
 * Function: lo_PopStateLevel
 *
 * Description: Record that we've popped one level up
 *  in the state hierarchy.
 *
 * Params: Window context.
 *************************************/
void
lo_PopStateLevel(MWContext *context)
{
	lo_TopState *top_state;

	top_state = lo_FetchTopState( XP_DOCID(context) );
	top_state->state_pops++;
}

/*************************************
 * Function: lo_GetCurrentDocLists
 *
 * Description: Get the doc_lists for the
 * "current" document i.e. from the top_state
 * in the normal case, from the layer if a 
 * layer is being laid out.
 *
 * Params: DocState
 *************************************/
lo_DocLists *
lo_GetCurrentDocLists(lo_DocState *state)
{
    lo_LayerDocState *layer_state = lo_CurrentLayerState(state);
    return layer_state->doc_lists;
}

lo_DocLists *
lo_GetDocListsById(lo_DocState *state, int32 id)
{
    lo_LayerDocState *layer_state;

    if (id > state->top_state->max_layer_num)
        return NULL;

    layer_state = state->top_state->layers[id];
    if (layer_state) {
        XP_ASSERT(layer_state->doc_lists);
        return layer_state->doc_lists;
    }
    else
        return NULL;
}

/*************************************
 * Function: lo_NewLayout
 *
 * Description: This function creates and initializes a new
 *	layout state structure to be used for all state information
 *	about this document (or sub-document) during its lifetime.
 *
 * Params: Window context,
 *	the width and height of the
 *	window we are formatting to.
 *
 * Returns: A pointer to a lo_DocState structure.
 *	Returns a NULL on error (such as out of memory);
 *************************************/
lo_DocState *
lo_NewLayout(MWContext *context, int32 width, int32 height,
	int32 margin_width, int32 margin_height, lo_DocState* clone_state )
{
    lo_DocLists *doc_lists;
	lo_TopState *top_state;
	lo_DocState *state;
#ifdef XP_WIN16
	int32 i;
	PA_Block *sblock_array;
#endif /* XP_WIN16 */
	int32 doc_id;

	doc_id = XP_DOCID(context);
	top_state = lo_FetchTopState(doc_id);
	if (top_state == NULL)
	{
		return(NULL);
	}

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

	state->subdoc_tags = NULL;
	state->subdoc_tags_end = NULL;

    if (top_state->doc_state)
        doc_lists = lo_GetCurrentDocLists(top_state->doc_state);
    else
        doc_lists = lo_GetCurrentDocLists(state);

    if (!lo_InitDocState(state, context,
                         width, height, margin_width, margin_height,
                         clone_state, doc_lists, PR_FALSE))
    {
		top_state->out_of_memory = TRUE;
        XP_FREE(state);
        return NULL;
    }
    return state;
}

        
lo_DocState *
lo_InitDocState(lo_DocState *state, MWContext *context,
                int32 width, int32 height,
                int32 margin_width, int32 margin_height,
                lo_DocState* clone_state,
                lo_DocLists *doc_lists,
                PRBool is_for_new_layer)
{
    lo_TopState *top_state = state->top_state;
    
	/*
	 * Set colors early so default text is correct.
	 */

	if( clone_state == NULL ){
		state->text_fg.red = lo_master_colors[LO_COLOR_FG].red;
		state->text_fg.green = lo_master_colors[LO_COLOR_FG].green;
		state->text_fg.blue = lo_master_colors[LO_COLOR_FG].blue;

		state->text_bg.red = lo_master_colors[LO_COLOR_BG].red;
		state->text_bg.green = lo_master_colors[LO_COLOR_BG].green;
		state->text_bg.blue = lo_master_colors[LO_COLOR_BG].blue;

		state->anchor_color.red = lo_master_colors[LO_COLOR_LINK].red;
		state->anchor_color.green = lo_master_colors[LO_COLOR_LINK].green;
		state->anchor_color.blue = lo_master_colors[LO_COLOR_LINK].blue;

		state->visited_anchor_color.red = lo_master_colors[LO_COLOR_VLINK].red;
		state->visited_anchor_color.green = lo_master_colors[LO_COLOR_VLINK].green;
		state->visited_anchor_color.blue = lo_master_colors[LO_COLOR_VLINK].blue;

		state->active_anchor_color.red = lo_master_colors[LO_COLOR_ALINK].red;
		state->active_anchor_color.green = lo_master_colors[LO_COLOR_ALINK].green;
		state->active_anchor_color.blue = lo_master_colors[LO_COLOR_ALINK].blue;
	}
	else
	{
		state->text_fg.red = clone_state->text_fg.red;
		state->text_fg.green = clone_state->text_fg.green;
		state->text_fg.blue = clone_state->text_fg.blue;

		state->text_bg.red = clone_state->text_bg.red;
		state->text_bg.green = clone_state->text_bg.green;
		state->text_bg.blue = clone_state->text_bg.blue;

		state->anchor_color.red = clone_state->anchor_color.red;
		state->anchor_color.green = clone_state->anchor_color.green;
		state->anchor_color.blue = clone_state->anchor_color.blue;

		state->visited_anchor_color.red = clone_state->visited_anchor_color.red;
		state->visited_anchor_color.green = clone_state->visited_anchor_color.green;
		state->visited_anchor_color.blue = clone_state->visited_anchor_color.blue;

		state->active_anchor_color.red = clone_state->active_anchor_color.red;
		state->active_anchor_color.green = clone_state->active_anchor_color.green;
		state->active_anchor_color.blue = clone_state->active_anchor_color.blue;
	}

	state->win_top = margin_height;
	state->win_bottom = margin_height;
	state->win_width = width;
	state->win_height = height;

	state->base_x = 0;
	state->base_y = 0;
	state->x = 0;
	state->y = 0;
	state->width = 0;

    /* Layers don't use the line array, and resetting the line_num while laying out
       a layer causes display bugs when displaying the _BODY layer. */
    if (!is_for_new_layer)
        state->line_num = 1;

	state->win_left = margin_width;
	state->win_right = margin_width;

	state->max_width = state->win_left + state->win_right;
	state->max_height = state->win_top + state->win_bottom;

	state->x = state->win_left;
	state->y = state->win_top;

	state->left_margin = state->win_left;
	state->right_margin = state->win_width - state->win_right;
	state->left_margin_stack = NULL;
	state->right_margin_stack = NULL;

	state->break_holder = state->x;
	state->min_width = 0;
	state->text_divert = P_UNKNOWN;
	state->linefeed_state = 2;
	state->delay_align = FALSE;
	state->breakable = TRUE;
	state->allow_amp_escapes = TRUE;
	state->preformatted = PRE_TEXT_NO;
	state->preformat_cols = 0;

	state->in_paragraph = FALSE;

	state->display_blocked = FALSE;
	state->display_blocking_element_id = 0;
	state->display_blocking_element_y = 0;

	state->line_list = NULL;
	state->end_last_line = NULL;
	state->float_list = NULL;

    /* Don't bother creating a line array for a layer. Layers don't use them. */
    if (!is_for_new_layer) {
        LO_Element **line_array;

        state->line_array = XP_ALLOC_BLOCK(LINE_INC * sizeof(LO_Element *));
        if (state->line_array == NULL)
        {
            return(NULL);
        }
        XP_LOCK_BLOCK(line_array, LO_Element **, state->line_array);
        line_array[0] = NULL;
        XP_UNLOCK_BLOCK(state->line_array);
        state->line_array_size = LINE_INC;

#ifdef XP_WIN16
        {
            XP_Block *larray_array;

            state->larray_array = XP_ALLOC_BLOCK(sizeof(XP_Block));
            if (state->larray_array == NULL)
            {
                XP_FREE_BLOCK(state->line_array);
                XP_DELETE(state);
                return(NULL);
            }
            XP_LOCK_BLOCK(larray_array, XP_Block *, state->larray_array);
            larray_array[0] = state->line_array;
            XP_UNLOCK_BLOCK(state->larray_array);
            state->larray_array_size = 1;
        }
#endif /* XP_WIN16 */
    }

    /* XXX - No font information is reset upon entering a layer, nor
       is it restored when the layer ends.  Is this the right thing to do ? */
    if (!is_for_new_layer || !state->font_stack) {
        state->base_font_size = DEFAULT_BASE_FONT_SIZE;
        state->font_stack = lo_DefaultFont(state, context);
        if (state->font_stack == NULL)
        {
            XP_FREE_BLOCK(state->line_array);
#ifdef XP_WIN16
            XP_FREE_BLOCK(state->larray_array);
#endif /* XP_WIN16 */
            return(NULL);
        }
        state->font_stack->text_attr->size = state->base_font_size;

        state->text_info.max_width = 0;
        state->text_info.ascent = 0;
        state->text_info.descent = 0;
        state->text_info.lbearing = 0;
        state->text_info.rbearing = 0;
    }

	state->align_stack = NULL;
	state->line_height_stack = NULL;

	state->list_stack = lo_DefaultList(state);
	if (state->list_stack == NULL)
	{
		XP_FREE_BLOCK(state->line_array);
#ifdef XP_WIN16
		XP_FREE_BLOCK(state->larray_array);
#endif /* XP_WIN16 */
		XP_DELETE(state->font_stack);
		return(NULL);
	}

	/*
	 * To initialize the default line height, we need to
	 * jump through hoops here to get the front end
	 * to tell us the default fonts line height.
	 */
	{
		LO_TextStruct tmp_text;
		unsigned char str[1];

		memset (&tmp_text, 0, sizeof (tmp_text));
		str[0] = ' ';
		tmp_text.text = (PA_Block)str;
		tmp_text.text_len = 1;
		tmp_text.text_attr = state->font_stack->text_attr;
		FE_GetTextInfo(context, &tmp_text, &(state->text_info));

		state->default_line_height = state->text_info.ascent +
			state->text_info.descent;
	}
	if (state->default_line_height <= 0)
	{
		state->default_line_height = FEUNITS_Y(DEF_LINE_HEIGHT,context);
	}

	state->line_buf_size = 0;
	state->line_buf = PA_ALLOC(LINE_BUF_INC * sizeof(char));
	if (state->line_buf == NULL)
	{
		XP_FREE_BLOCK(state->line_array);
#ifdef XP_WIN16
		XP_FREE_BLOCK(state->larray_array);
#endif /* XP_WIN16 */
		XP_DELETE(state->font_stack);
		return(NULL);
	}
	state->line_buf_size = LINE_BUF_INC;
	state->line_buf_len = 0;

	state->baseline = 0;
	state->line_height = 0;
	state->break_pos = -1;
	state->break_width = 0;
	state->last_char_CR = FALSE;
	state->trailing_space = FALSE;
	state->at_begin_line = TRUE;
	state->hide_content = FALSE;

	state->old_break = NULL;
	state->old_break_block = NULL;
	state->old_break_pos = -1;
	state->old_break_width = 0;

	state->current_named_anchor = NULL;
	state->current_anchor = NULL;

	state->cur_ele_type = LO_NONE;
	state->current_ele = NULL;
	state->current_java = NULL;

	state->current_table = NULL;
	state->current_grid = NULL;
	state->current_multicol = NULL;

    if (!is_for_new_layer)
        state->layer_nest_level = 0;

	/*
	 * These values are saved into the (sub)doc here
	 * so that if it gets Relayout() later, we know where
	 * to reset the from counts to.
	 */
	if (doc_lists->form_list != NULL)
	{
		lo_FormData *form_list;

		form_list = doc_lists->form_list;
		state->form_ele_cnt = form_list->form_ele_cnt;
		state->form_id = form_list->id;
	}
	else
	{
		state->form_ele_cnt = 0;
		state->form_id = 0;
	}
	state->start_in_form = top_state->in_form;
	if (top_state->savedData.FormList != NULL)
	{
		lo_SavedFormListData *all_form_ele;

		all_form_ele = top_state->savedData.FormList;
		state->form_data_index = all_form_ele->data_index;
	}
	else
	{
		state->form_data_index = 0;
	}
	
	/*
	 * Remember number of embeds we had before beginning this
	 * (sub)doc. This information is used in laytable.c so
	 * it can preserve embed indices across a relayout.
	 */
	state->embed_count_base = top_state->embed_count;

	/*
	 * Ditto for anchors, images, applets and embeds
	 */
	state->url_count_base = doc_lists->url_list_len;
    state->image_list_count_base = doc_lists->image_list_count;
    state->applet_list_count_base = doc_lists->applet_list_count;
    state->embed_list_count_base = doc_lists->embed_list_count;
    if (!is_for_new_layer) {
        state->current_layer_num_base = top_state->current_layer_num;
        state->current_layer_num_max = top_state->current_layer_num;
    }

	state->must_relayout_subdoc = FALSE;
	state->allow_percent_width = TRUE;
	state->allow_percent_height = TRUE;
	state->is_a_subdoc = SUBDOC_NOT;
	state->current_subdoc = 0;
	state->sub_documents = NULL;
	state->sub_state = NULL;

	state->current_cell = NULL;

    if (!is_for_new_layer) {
        state->extending_start = FALSE;
        state->selection_start = NULL;
        state->selection_start_pos = 0;
        state->selection_end = NULL;
        state->selection_end_pos = 0;
        state->selection_new = NULL;
        state->selection_new_pos = 0;
        state->selection_layer = NULL;
    }
    
#ifdef EDITOR
	state->edit_force_offset = FALSE;
	state->edit_current_element = 0;
	state->edit_current_offset = 0;
	state->edit_relayout_display_blocked = FALSE;
#endif
	state->in_relayout = FALSE;
	state->tab_stop = DEF_TAB_WIDTH;
	state->beginning_tag_count = top_state->tag_count;

	state->cur_text_block = NULL;
	state->need_min_width = FALSE;
	
	return(state);
}

void LO_SetBackgroundImage( MWContext *context, char *pUrl )
{
	lo_TopState* top_state;

	/*
	 * Get the unique document ID, and retreive this
	 * documents layout state.
	 */
	top_state = lo_FetchTopState(XP_DOCID(context));
	if ((top_state == NULL)||(top_state->doc_state == NULL))
	{
		return;
	}
    if (!top_state->doc_layer)
        return;

	top_state->doc_specified_bg = TRUE;

    LO_SetLayerBackdropURL(top_state->doc_layer, pUrl);
}

static void
lo_load_user_backdrop(MWContext *context, lo_DocState *state)
{
	LO_SetBackgroundImage( context, Master_backdrop_url );
}

void
LO_SetDocBgColor(MWContext *context, LO_Color *rgb)
{
	lo_TopState* top_state;

	/*
	 * Get the unique document ID, and retreive this
	 * documents layout state.
	 */
	top_state = lo_FetchTopState(XP_DOCID(context));
	if ((top_state == NULL)||(top_state->doc_state == NULL))
	{
		return;
	}
    
    if (!top_state->doc_layer)
        return;
    
#ifdef XP_MAC
    /* This allows the front end to set a background color for
       this context only. Without this call, the color will
       still be the one from the global table */
    FE_GetDefaultBackgroundColor(context, rgb);
#endif
    if (context->compositor)
        LO_SetLayerBgColor(top_state->doc_layer, rgb);
}

int32
lo_calc_push_right_for_justify(lo_DocState *state, int32 *remainder)
{
	int32 count = -1;  /* start at -1 */
	int32 push_right;
	LO_Element *tptr;
	LO_Element *last;

	tptr = state->line_list;

	push_right = state->right_margin - state->x;

	if(push_right > (state->right_margin - state->left_margin)/4)
	{
		return 0; /* don't do justify if it's too large */
	}

	while (tptr != NULL)
	{
		if(tptr->type == LO_TEXT 
		   && tptr->lo_text.text)
			count++;
		last = tptr;
		tptr = tptr->lo_any.next;
	}

	/* if the last element is a space character then
	 * this must be the last line of a paragraph.
	 * don't justify.
	 */
	if(last->type == LO_TEXT
		&& (*last).lo_text.text
		&& !XP_STRCMP((char*)last->lo_text.text, " "))
		return 0;

	/* the push_right is the number of pixels to add to
	 * each space
	 */

	if(count > 0)
	{
		*remainder = push_right % count;
		return push_right/count;
	}
	else
	{
		return 0;
	}
}

PRIVATE void
lo_add_to_y_for_all_elements_in_line(lo_DocState *state, int32 y_add)
{
	LO_Element *tptr;

	if(!state)
		return;

	tptr = state->line_list;

	while (tptr != NULL)
	{
		tptr->lo_any.y_offset += y_add;

		if (tptr->type == LO_CELL)
		{
			lo_ShiftCell((LO_CellStruct *)tptr, 0, y_add);
		}
		tptr = tptr->lo_any.next;
	}
}

void
lo_use_default_doc_background(MWContext *context, lo_DocState *state)
{
    lo_TopState *top_state = state->top_state;
    if (top_state->doc_specified_bg == FALSE) {
        if ((Master_backdrop_url != NULL) && 
			(UserOverride == FALSE || context->type == MWContextDialog)) {
            lo_load_user_backdrop(context, state);
        } else {
            /* Force an opaque solid background. */
            LO_SetDocBgColor(context, &state->text_bg);
        }
    }
}

/*************************************
 * Function: lo_FlushLineList
 *
 * Description: This function adds a linefeed element to the end
 *	of the current line, and now that the line is done, it asks
 *	the front end to display the line.  This function is only
 * 	called by a hard linebreak.
 *
 * Params: Window context, document state, and flag for whether this is
 *	a breaking linefeed of not.  A breaking linefeed is one inserted
 *	just to break a text flow to the current window width.
 *
 * Returns: Nothing
 *************************************/
void
lo_FlushLineList(MWContext *context, lo_DocState *state, uint32 break_type, uint32 clear_type, Bool breaking)
{
	
	LO_LinefeedStruct *linefeed;
	/*
	LO_Element **line_array;
	*/
	/* int32 justification_remainder=0;*/
	/*
#ifdef XP_WIN16
	int32 a_size;
	int32 a_indx;
	int32 a_line;
	XP_Block *larray_array;
#endif 
	*/
	/* XP_WIN16 */

	lo_UpdateStateWhileFlushingLine( context, state );

	/*
	if (state->top_state->nothing_displayed != FALSE) {
	*/
		/*
		 * If we are displaying elements we are
		 * no longer in the HEAD section of the HTML
		 * we are in the BODY section.
		 */
	/*
		state->top_state->in_head = FALSE;
		state->top_state->in_body = TRUE;

        lo_use_default_doc_background(context, state);
        state->top_state->nothing_displayed = FALSE;
    }
	*/

	/*
	 * There is a break at the end of this line.
	 * this may change min_width.
	 */
	/*
	{
		int32 new_break_holder;
		int32 min_width;
		int32 indent;

		new_break_holder = state->x;
		min_width = new_break_holder - state->break_holder;
		indent = state->list_stack->old_left_margin - state->win_left;
		min_width += indent;
		if (min_width > state->min_width)
		{
			state->min_width = min_width;
		}
	*/
		/* If we are not within <NOBR> content, allow break_holder
		 * to be set to the new position where a line break can occur.
		 * This fixes BUG #70782
		 */
	/*
		if (state->breakable != FALSE) {
			state->break_holder = new_break_holder;
		}
	}
	*/

	/*
	 * If in a division centering or right aligning this line
	 */
	/*
	if ((state->align_stack != NULL)&&(state->delay_align == FALSE))
	{
		int32 push_right;
		LO_Element *tptr;

		if (state->align_stack->alignment == LO_ALIGN_CENTER)
		{
			push_right = (state->right_margin - state->x) / 2;
		}
		else if (state->align_stack->alignment == LO_ALIGN_RIGHT)
		{
			push_right = state->right_margin - state->x;
		}
		else if(state->align_stack->alignment == LO_ALIGN_JUSTIFY)
		{
			push_right = lo_calc_push_right_for_justify(state, &justification_remainder);
		}
		else
		{
			push_right = 0;
		}

		if ((push_right > 0 || justification_remainder)
			&&(state->line_list != NULL))
		{
			int32 count = 0;
			int32 move_delta;
			tptr = state->line_list;

			if(state->align_stack->alignment == LO_ALIGN_JUSTIFY)
				move_delta = 0;
			else
				move_delta = push_right;

			while (tptr != NULL)
			{
	*/
                /* 
                 * We don't shift over inflow layers, since their contents
                 * have already been shifted over.
                 */
		/*
		 * We also don't shift bullets of type BULLET_MQUOTE.
		 */
	/*
                if (((tptr->type != LO_CELL)||
			(!tptr->lo_cell.cell_inflow_layer))&&
			((tptr->type != LO_BULLET)||
			((tptr->type == LO_BULLET)&&
			 (tptr->lo_bullet.bullet_type != BULLET_MQUOTE))))
		{
				tptr->lo_any.x += move_delta;
		}
	*/

				/* justification push_rights are additive */
	/*
				if(state->align_stack->alignment == LO_ALIGN_JUSTIFY)
				{
					move_delta += push_right;
	*/
					/* if there is a justification remainder, add a pixel
					 * to the first n word breaks
					 */
	/*
					if(count < justification_remainder)
						move_delta++;
				}
	*/
                /* 
                 * Note that if this is an inflow layer, we don't want
                 * to shift it since the alignment has already propogated
                 * to its contents.
                 */
	/*
				if ((tptr->type == LO_CELL) &&
                    !tptr->lo_cell.cell_inflow_layer)
				{
					lo_ShiftCell((LO_CellStruct *)tptr, move_delta, 0);
				}
				tptr = tptr->lo_any.next;

				count++;
			}

			if(state->align_stack->alignment == LO_ALIGN_JUSTIFY)
				state->x = state->right_margin;
			else
				state->x += push_right;
		}
	}
	*/
#if 0
	/* apply line-height stack mods (if exists) */
	if(state->line_height_stack && state->end_last_line) 
	{
		LO_LinefeedStruct *linefeed = (LO_LinefeedStruct*)state->end_last_line;
		int32 cur_line_height;
		int32 new_line_height;
		int32 line_height_diff;

		if(state->line_height == 0)
		{
        	int32 new_height = state->text_info.ascent +
            						state->text_info.descent;
			int32 new_baseline = state->text_info.ascent;

        	if ((new_height <= 0)&&(state->font_stack != NULL)&&
            	(state->font_stack->text_attr != NULL))
			{
				lo_fillin_text_info(context, state);

            	new_height = state->text_info.ascent + state->text_info.descent;
				new_baseline = state->text_info.ascent;

			}

			if(new_height <= 0)
			{
				new_height = state->default_line_height;
				new_baseline = state->default_line_height;
			}

			state->line_height = new_height;
			state->baseline = new_baseline;
		}

		cur_line_height = (state->y + state->baseline) - 
								(linefeed->y + linefeed->baseline);

		new_line_height = state->line_height_stack->height;
		line_height_diff = new_line_height - cur_line_height;

		/* only allow increasing line heights 
		 * explicitly disallow negative diffs	
		 */
		if(line_height_diff > 0)
		{
			lo_add_to_y_for_all_elements_in_line(state, line_height_diff);

			state->baseline += line_height_diff;
			state->line_height += line_height_diff;
		}
	}
#endif


	/*
	 * Tack a linefeed element on to the end of the line list.
	 * Take advantage of this necessary pass thorugh the line's
	 * elements to set the line_height field in all
	 * elements, and to look and see if we have reached a
	 * possibly display blocking element.
	 */
	linefeed = lo_NewLinefeed(state, context, break_type, clear_type);

	if (linefeed != NULL)
	{
		lo_AppendLineFeed( context, state, linefeed, breaking, TRUE );

		/*
		LO_Element *tptr;

		if (breaking != FALSE)
		{
			linefeed->ele_attrmask |= LO_ELE_BREAKING;
		}
		*/

		/*
		 * Horrible bitflag overuse!!!  For multicolumn text
		 * we need to know if a line of text can be used for
		 * a column break, or if it cannot because it is wrapped
		 * around some object in the margin.  For lines that can be
		 * used for column breaks, we will set the BREAKABLE
		 * flag in their element mask.
		 */
		/*
		if ((state->left_margin_stack == NULL)&&
			(state->right_margin_stack == NULL))
		{
			linefeed->ele_attrmask |= LO_ELE_BREAKABLE;
		}

		if ((state->align_stack != NULL)&&
		    (state->delay_align != FALSE)&&
		    (state->align_stack->alignment != LO_ALIGN_LEFT))
		{
			if (state->align_stack->alignment == LO_ALIGN_CENTER)
			{
				linefeed->ele_attrmask |= LO_ELE_DELAY_CENTER;
			}
			else if (state->align_stack->alignment == LO_ALIGN_RIGHT)
			{
				linefeed->ele_attrmask |= LO_ELE_DELAY_RIGHT;
			}
		}

		tptr = state->line_list;

		if (tptr == NULL)
		{
			state->line_list = (LO_Element *)linefeed;
			linefeed->prev = NULL;
		}
		else
		{
			while (tptr != NULL)
			{
		*/
				/*
				 * If the display is blocked for an element
				 * we havn't reached yet, check to see if
				 * it is in this line, and if so, save its
				 * y position.
				 */
		/*
				if ((state->display_blocked != FALSE)&&
#ifdef EDITOR
				    (!state->edit_relayout_display_blocked)&&
#endif
				    (state->is_a_subdoc == SUBDOC_NOT)&&
				    (state->display_blocking_element_y == 0)&&
				    (state->display_blocking_element_id != -1)&&
				    (tptr->lo_any.ele_id >=
					state->display_blocking_element_id))
				{
					state->display_blocking_element_y =
						state->y;
		*/
					/*
					 * Special case, if the blocking element
					 * is on the first line, no blocking
					 * at all needs to happen.
					 */
		/*
					if (state->y == state->win_top)
					{
						state->display_blocked = FALSE;
						FE_SetDocPosition(context,
						    FE_VIEW, 0, state->base_y);
                        if (context->compositor)
						{
						  XP_Rect rect;
						  
						  rect.left = 0;
						  rect.top = 0;
						  rect.right = state->win_width;
						  rect.bottom = state->win_height;
						  CL_UpdateDocumentRect(context->compositor,
                                                &rect, (PRBool)FALSE);
						}
					}
				}
				tptr->lo_any.line_height = state->line_height;
		*/
				/*
				 * Special for bullets of type BULLET_MQUOTE
				 * They should always be as tall as the line.
				 */
		/*
				if ((tptr->type == LO_BULLET)&&
					(tptr->lo_bullet.bullet_type ==
						BULLET_MQUOTE))
				{
					tptr->lo_any.y_offset = 0;
					tptr->lo_any.height =
						tptr->lo_any.line_height;
				}
				tptr = tptr->lo_any.next;
			}
			linefeed->prev = tptr;
		}
		state->x += linefeed->width;
		*/
	} 
	else
	{
		return;		/* ALEKS OUT OF MEMORY RETURN */
	}

	/*
	 * Nothing to flush
	 */
	if (state->line_list == NULL)
	{
		return;
	}

	/*
	 * We are in a layer within this (sub)doc, stuff the line there instead.
	 */
	if (state->layer_nest_level > 0)
	{
		lo_AddLineListToLayer(context, state, (LO_Element *)linefeed);
		state->line_list = NULL;
		state->old_break = NULL;
		state->old_break_block = NULL;
		state->old_break_pos = -1;
		state->old_break_width = 0;
		state->baseline = 0;
		return;
	}

	/*
	 * If necessary, grow the line array to hold more lines.
	 */
/*
#ifdef XP_WIN16
	a_size = SIZE_LIMIT / sizeof(LO_Element *);
	a_indx = (state->line_num - 1) / a_size;
	a_line = state->line_num - (a_indx * a_size);

	XP_LOCK_BLOCK(larray_array, XP_Block *, state->larray_array);
	state->line_array = larray_array[a_indx];

	if (a_line == a_size)
	{
		state->line_array = XP_ALLOC_BLOCK(LINE_INC *
					sizeof(LO_Element *));
		if (state->line_array == NULL)
		{
			XP_UNLOCK_BLOCK(state->larray_array);
			state->top_state->out_of_memory = TRUE;
			return;
		}
		XP_LOCK_BLOCK(line_array, LO_Element **, state->line_array);
		line_array[0] = NULL;
		XP_UNLOCK_BLOCK(state->line_array);
		state->line_array_size = LINE_INC;

		state->larray_array_size++;
		XP_UNLOCK_BLOCK(state->larray_array);
		state->larray_array = XP_REALLOC_BLOCK(
			state->larray_array, (state->larray_array_size
			* sizeof(XP_Block)));
		if (state->larray_array == NULL)
		{
			state->top_state->out_of_memory = TRUE;
			return;
		}
		XP_LOCK_BLOCK(larray_array, XP_Block *, state->larray_array);
		larray_array[state->larray_array_size - 1] = state->line_array;
		state->line_array = larray_array[a_indx];
	}
	else if (a_line >= state->line_array_size)
	{
		state->line_array_size += LINE_INC;
		if (state->line_array_size > a_size)
		{
			state->line_array_size = (intn)a_size;
		}
		state->line_array = XP_REALLOC_BLOCK(state->line_array,
		    (state->line_array_size * sizeof(LO_Element *)));
		if (state->line_array == NULL)
		{
			XP_UNLOCK_BLOCK(state->larray_array);
			state->top_state->out_of_memory = TRUE;
			return;
		}
		larray_array[a_indx] = state->line_array;
	}
*/
	/*
	 * Place this line of elements into the line array.
	 */
/*
	XP_LOCK_BLOCK(line_array, LO_Element **, state->line_array);
	line_array[a_line - 1] = state->line_list;
	XP_UNLOCK_BLOCK(state->line_array);

	XP_UNLOCK_BLOCK(state->larray_array);
#else
	if (state->line_num > state->line_array_size)
	{
		int32 line_inc;

		if (state->line_array_size > (LINE_INC * 10))
		{
			line_inc = state->line_array_size / 10;
		}
		else
		{
			line_inc = LINE_INC;
		}

		state->line_array = XP_REALLOC_BLOCK(state->line_array,
			((state->line_array_size + line_inc) *
			sizeof(LO_Element *)));
		if (state->line_array == NULL)
		{
			state->top_state->out_of_memory = TRUE;
			return;
		}
		state->line_array_size += line_inc;
	}
*/
	/*
	 * Place this line of elements into the line array.
	 */
/*
	 XP_LOCK_BLOCK(line_array, LO_Element **, state->line_array);
	line_array[state->line_num - 1] = state->line_list;
	XP_UNLOCK_BLOCK(state->line_array);
#endif */

/* XP_WIN16 */

	/*
	 * connect the complete doubly linked list between this line
	 * and the last one.
	 */
/*
	if (state->end_last_line != NULL)
	{
		state->end_last_line->lo_any.next = state->line_list;
		state->line_list->lo_any.prev = state->end_last_line;
	}
	state->end_last_line = (LO_Element *)linefeed;

	state->line_list = NULL;
	state->line_num++;
*/
	/* 
	 *Don't draw if we're doing layers...we'll just refresh when the 
	 * the layer size increases.
	 */
/*
#ifdef LAYERS
*/
	/*
	 * Have the front end display this line.
	 */
    /* For layers, only draw if a compositor doesn't exist */
/*
    if (!context->compositor)
*/
        /* We need to supply a display rectangle that is guaranteed to
           encompass all elements on the line.  The special 0x3fffffff
           value is approximately half the range of a 32-bit int, so
           it leaves room for overflow if arithmetic is done on these
           values. */
/*
        lo_DisplayLine(context, state, (state->line_num - 2),
                       0, 0, 0x3fffffffL, 0x3fffffffL);
#else
        lo_DisplayLine(context, state, (state->line_num - 2),
                       0, 0, 0x3fffffffL, 0x3fffffffL);
#endif */ /* LAYERS */

	lo_UpdateStateAfterFlushingLine( context, state, linefeed, FALSE );
	/*
	 * We are starting a new line.  Clear any old break
	 * positions left over, clear the line_list, and increase
	 * the line number.
	 */
	/*
	state->old_break = NULL;
	state->old_break_block = NULL;
	state->old_break_pos = -1;
	state->old_break_width = 0;
	state->baseline = 0;
	*/
}


/*************************************
 * Function: lo_CleanTextWhitespace
 *
 * Description: Utility function to pass through a line an clean up
 * 	all its whitespace.   Compress multiple whitespace between
 *	element to a single space, and remove heading and
 *	trailing whitespace.
 *	Text is cleaned in place in the passed buffer.
 *
 * Params: Pointer to text to be cleand, and its length.
 *
 * Returns: Length of cleaned text.
 *************************************/
int32
lo_CleanTextWhitespace(char *text, int32 text_len)
{
	char *from_ptr;
	char *to_ptr;
	int32 len;
	int32 new_len;

	if (text == NULL)
	{
		return(0);
	}

	len = 0;
	new_len = 0;
	from_ptr = text;
	to_ptr = text;

	while (len < text_len)
	{
		/*
		 * Compress chunk of whitespace
		 */
		while ((len < text_len)&&(XP_IS_SPACE(*from_ptr)))
		{
			len++;
			from_ptr++;
		}
		if (len == text_len)
		{
			continue;
		}

		/*
		 * Skip past "good" text
		 */
		while ((len < text_len)&&(!XP_IS_SPACE(*from_ptr)))
		{
			*to_ptr++ = *from_ptr++;
			len++;
			new_len++;
		}

		/*
		 * Put in one space to represent the compressed spaces.
		 */
		if (len != text_len)
		{
			*to_ptr++ = ' ';
			new_len++;
		}
	}

	/*
	 * Remove the trailing space, and terminate string.
	 */
	if ((new_len > 0)&&(*(to_ptr - 1) == ' '))
	{
		to_ptr--;
		new_len--;
	}
	*to_ptr = '\0';

	return(new_len);
}


/*
 * Used to strip white space off of HREF parameter values to make
 * valid URLs out of them.  Remove whitespace from both ends, and
 * remove all non-space whitespace from the middle.
 */
int32
lo_StripTextWhitespace(char *text, int32 text_len)
{
	char *from_ptr;
	char *to_ptr;
	int32 len;
	int32 tail;

	if ((text == NULL)||(text_len < 1))
	{
		return(0);
	}

	len = 0;
	from_ptr = text;
	/*
	 * strip leading whitespace
	 */
	while ((len < text_len)&&(XP_IS_SPACE(*from_ptr)))
	{
		len++;
		from_ptr++;
	}

	if (len == text_len)
	{
		*text = '\0';
		return(0);
	}

	tail = 0;
	from_ptr = (char *)(text + text_len - 1);
	/*
	 * Remove any trailing space
	 */
	while (XP_IS_SPACE(*from_ptr))
	{
		from_ptr--;
		tail++;
	}

	/*
	 * terminate string
	 */
	from_ptr++;
	*from_ptr = '\0';

	/*
	 * remove all non-space whitespace from the middle of the string.
	 */
	from_ptr = (char *)(text + len);
	len = text_len - len - tail;
	to_ptr = text;
	while (*from_ptr != '\0')
	{
		if (XP_IS_SPACE(*from_ptr) && (*from_ptr != ' '))
		{
			from_ptr++;
			len--;
		}
		else
		{
			*to_ptr++ = *from_ptr++;
		}
	}
	*to_ptr = '\0';

	return(len);
}


LO_AnchorData *
lo_NewAnchor(lo_DocState *state, PA_Block href, PA_Block targ)
{
	LO_AnchorData *anchor_data;
	lo_LayerDocState *layer_state;

	anchor_data = XP_NEW_ZAP(LO_AnchorData);
	if (anchor_data)
	{
		anchor_data->anchor = href;
		anchor_data->target = targ;
		layer_state = lo_CurrentLayerState(state);
		if (layer_state )
			anchor_data->layer = layer_state->layer;
	}
	else
	{
		state->top_state->out_of_memory = TRUE;
	}
	return anchor_data;
}


void
lo_DestroyAnchor(LO_AnchorData *anchor_data)
{
	if (anchor_data->anchor != NULL)
	{
		PA_FREE(anchor_data->anchor);
	}
	if (anchor_data->target != NULL)
	{
		PA_FREE(anchor_data->target);
	}
	XP_DELETE(anchor_data);
}


/*
 * Add this url's block to the list
 * of all allocated urls so we can free
 * it later.
 */
void
lo_AddToUrlList(MWContext *context, lo_DocState *state,
	LO_AnchorData *url_buff)
{
	lo_TopState *top_state;
	int32 i;
	LO_AnchorData **anchor_array;
	intn a_url;
#ifdef XP_WIN16
	intn a_size;
	intn a_indx;
	XP_Block *ulist_array;
#endif /* XP_WIN16 */
    lo_DocLists *doc_lists;
    
    doc_lists = lo_GetCurrentDocLists(state);

	/*
	 * Error checking
	 */
	if ((url_buff == NULL)||(state == NULL))
	{
		return;
	}

	top_state = state->top_state;

	/*
	 * We may need to grow the url_list.
	 */
#ifdef XP_WIN16
	a_size = SIZE_LIMIT / sizeof(XP_Block *);
	a_indx = doc_lists->url_list_len / a_size;
	a_url = doc_lists->url_list_len - (a_indx * a_size);

	XP_LOCK_BLOCK(ulist_array, XP_Block *, doc_lists->ulist_array);


	if ((a_url == 0)&&(a_indx > 0))
	{
		doc_lists->url_list = XP_ALLOC_BLOCK(URL_LIST_INC *
					sizeof(LO_AnchorData *));
		if (doc_lists->url_list == NULL)
		{
			XP_UNLOCK_BLOCK(doc_lists->ulist_array);
			top_state->out_of_memory = TRUE;
			return;
		}
		doc_lists->url_list_size = URL_LIST_INC;
		XP_LOCK_BLOCK(anchor_array, LO_AnchorData **,
			doc_lists->url_list);
		for (i=0; i < URL_LIST_INC; i++)
		{
			anchor_array[i] = NULL;
		}
		XP_UNLOCK_BLOCK(doc_lists->url_list);

		doc_lists->ulist_array_size++;
		XP_UNLOCK_BLOCK(doc_lists->ulist_array);
		doc_lists->ulist_array = XP_REALLOC_BLOCK(
			doc_lists->ulist_array, (doc_lists->ulist_array_size
			* sizeof(XP_Block)));
		if (doc_lists->ulist_array == NULL)
		{
			top_state->out_of_memory = TRUE;
			return;
		}
		XP_LOCK_BLOCK(ulist_array, XP_Block *, doc_lists->ulist_array);
		ulist_array[doc_lists->ulist_array_size - 1] = doc_lists->url_list;
		doc_lists->url_list = ulist_array[a_indx];
	}
	else if (a_url >= doc_lists->url_list_size)
	{
		int32 url_list_inc;

		if ((doc_lists->url_list_size / 10) > URL_LIST_INC)
		{
			url_list_inc = doc_lists->url_list_size / 10;
		}
		else
		{
			url_list_inc = URL_LIST_INC;
		}
		doc_lists->url_list_size += (intn)url_list_inc;
		if (doc_lists->url_list_size > a_size)
		{
			doc_lists->url_list_size = a_size;
		}

		doc_lists->url_list = ulist_array[a_indx];
		doc_lists->url_list = XP_REALLOC_BLOCK(doc_lists->url_list,
			(doc_lists->url_list_size * sizeof(LO_AnchorData *)));
		if (doc_lists->url_list == NULL)
		{
			XP_UNLOCK_BLOCK(doc_lists->ulist_array);
			top_state->out_of_memory = TRUE;
			return;
		}
		ulist_array[a_indx] = doc_lists->url_list;
		/*
		 * Clear the new entries
		 */
		XP_LOCK_BLOCK(anchor_array, LO_AnchorData **,
			doc_lists->url_list);
		for (i = doc_lists->url_list_len; i < doc_lists->url_list_size; i++)
		{
			anchor_array[i] = NULL;
		}
		XP_UNLOCK_BLOCK(doc_lists->url_list);
	}
	doc_lists->url_list = ulist_array[a_indx];
#else
	if (doc_lists->url_list_len ==
		doc_lists->url_list_size)
	{
		int32 url_list_inc;

		if ((doc_lists->url_list_size / 10) > URL_LIST_INC)
		{
			url_list_inc = doc_lists->url_list_size / 10;
		}
		else
		{
			url_list_inc = URL_LIST_INC;
		}
		doc_lists->url_list_size += url_list_inc;
		doc_lists->url_list = XP_REALLOC_BLOCK(doc_lists->url_list,
					(doc_lists->url_list_size *
					sizeof(LO_AnchorData *)));
		if (doc_lists->url_list == NULL)
		{
			top_state->out_of_memory = TRUE;
			return;
		}

		/*
		 * Clear the new entries
		 */
		XP_LOCK_BLOCK(anchor_array, LO_AnchorData **, doc_lists->url_list);
		for (i = doc_lists->url_list_len; i < doc_lists->url_list_size; i++)
		{
			anchor_array[i] = NULL;
		}
		XP_UNLOCK_BLOCK(doc_lists->url_list);
	}
	a_url = doc_lists->url_list_len;
#endif /* XP_WIN16 */

	XP_LOCK_BLOCK(anchor_array, LO_AnchorData **, doc_lists->url_list);
	if (anchor_array[a_url] != NULL)
	{
		lo_DestroyAnchor(anchor_array[a_url]);
	}
	anchor_array[a_url] = url_buff;
	doc_lists->url_list_len++;
	XP_UNLOCK_BLOCK(doc_lists->url_list);
}


void
lo_BodyMargins(MWContext *context, lo_DocState *state, PA_Tag *tag)
{
	PA_Block buff;
	char *str;
	int32 margin_width;
	int32 margin_height;
	Bool changes;

	margin_width = state->win_left;
	margin_height = state->win_top;
	changes = FALSE;

	/*
	 * Get the margin width.
	 */
	buff = lo_FetchParamValue(context, tag, PARAM_MARGINWIDTH);
	if (buff != NULL)
	{
		int32 val;

		PA_LOCK(str, char *, buff);
		val = XP_ATOI(str);
		if (val < 0)
		{
			val = 0;
		}
		margin_width = val;
		PA_UNLOCK(buff);
		PA_FREE(buff);
		margin_width = FEUNITS_X(margin_width, context);
		/*
		 * Sanify based on window width.
		 */
		if (margin_width > ((state->win_width  / 2) - 1))
		{
			margin_width = ((state->win_width  / 2) - 1);
		}
		state->top_state->body_attr |= BODY_ATTR_MARGINS;
		changes = TRUE;
	}

	/*
	 * Get the margin height.
	 */
	buff = lo_FetchParamValue(context, tag, PARAM_MARGINHEIGHT);
	if (buff != NULL)
	{
		int32 val;

		PA_LOCK(str, char *, buff);
		val = XP_ATOI(str);
		if (val < 0)
		{
			val = 0;
		}
		margin_height = val;
		PA_UNLOCK(buff);
		PA_FREE(buff);
		margin_height = FEUNITS_Y(margin_height, context);
		/*
		 * Sanify based on window height.
		 */
		if (margin_height > ((state->win_height  / 2) - 1))
		{
			margin_height = ((state->win_height  / 2) - 1);
		}
		state->top_state->body_attr |= BODY_ATTR_MARGINS;
		changes = TRUE;
	}

	if (changes != FALSE)
	{
		state->win_top = margin_height;
		state->win_bottom = margin_height;

		state->win_left = margin_width;
		state->win_right = margin_width;

		state->max_width = state->win_left + state->win_right;
		state->max_height = state->win_top + state->win_bottom;

		state->x = state->win_left;
		state->y = state->win_top;

		state->left_margin = state->win_left;
		state->right_margin = state->win_width - state->win_right;

		state->break_holder = state->x;

		/*
		 * Need to reset the margin values in the default list stack
		 */
		if ((state->list_stack->type == P_UNKNOWN)&&
			(state->list_stack->next == NULL))
		{
			state->list_stack->old_left_margin = state->win_left;
			state->list_stack->old_right_margin =
					state->win_width - state->win_right;
		}
		else
		{
		/*
		 * This should never happen.  If it doesm it means we somehow
		 * started a list without knowing that we had entered the BODY
		 * of the document.
		 */
		}
	}
}


void
lo_ProcessBodyTag(MWContext *context, lo_DocState *state, PA_Tag *tag)
{
	if (tag->is_end == FALSE)
	{
		if ((state->end_last_line == NULL)&&
		    ((state->top_state->body_attr & BODY_ATTR_MARGINS) == 0))
		{
			lo_BodyMargins(context, state, tag);
		}
		if (UserOverride == FALSE || context->type == MWContextDialog)
		{
			lo_BodyBackground(context, state, tag, FALSE);
		}
		if (( !EDT_IS_EDITOR( context ) )&&
			((state->top_state->body_attr & BODY_ATTR_JAVA) == 0))
		{
			if (lo_ProcessContextEventHandlers(context, state, tag))
			{
				state->top_state->body_attr |= BODY_ATTR_JAVA;
			}
		}
	}
}

void
lo_SetBaseUrl(lo_TopState *top_state, char *url, Bool is_blocked)
{
    if (url)
        url = XP_STRDUP(url);

    if (is_blocked)
    {
        if (top_state->tag_from_inline_stream)
        {
            XP_FREEIF(top_state->inline_stream_blocked_base_url);
            top_state->inline_stream_blocked_base_url = url;
        }
        else
        {
            XP_FREEIF(top_state->main_stream_blocked_base_url);
            top_state->main_stream_blocked_base_url = url;
        }
    }
    else 
    {
        XP_FREEIF(top_state->base_url);
        top_state->base_url = url;
    }
}

/*************************************
 * Function: lo_BlockTag
 *
 * Description: This function blocks the layout of the tag, and
 *	saves them in the context.
 *
 * Params: Window context and document state, and the tag to be blocked.
 *
 * Returns: Nothing
 *************************************/
void
lo_BlockTag(MWContext *context, lo_DocState *state, PA_Tag *tag)
{
	PA_Tag ***tags_end_ptr, **tags_end;	/* triple-indirect, wow! see below */
	int32 doc_id;
	lo_TopState *top_state;
	lo_DocState *new_state;
	char *save_base;

	/*
	 * All blocked tags should be at the top level of state
	 */
	doc_id = XP_DOCID(context);
	top_state = lo_FetchTopState(doc_id);
	if ((top_state == NULL)||(top_state->doc_state == NULL))
	{
		return;
	}
	state = top_state->doc_state;

	new_state = lo_CurrentSubState(state);

	if (top_state->tags == NULL)
	    PA_HoldDocData(top_state->doc_data);

	/*
	 * For everything except BASE tags, we need to use
	 * the blocked base here as our real base so that
	 * relative URLs are processed properly.
	 */
	if (tag->type != P_BASE)
    {
        save_base = top_state->base_url;
        if (top_state->tag_from_inline_stream)
        {
            if (top_state->inline_stream_blocked_base_url)
                top_state->base_url = top_state->inline_stream_blocked_base_url;
        }
        else
        {
            if (top_state->main_stream_blocked_base_url)
                top_state->base_url = top_state->main_stream_blocked_base_url;
        }
    }

	switch (tag->type)
	{
		/*
		 * We need to process these when blocked so that
		 * relative URLs of images that are processed when
		 * blocked are correct.
		 */
		case P_BASE:
			{
				PA_Block buff;
				char *url;

				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));
					}
                    lo_SetBaseUrl(top_state, url, TRUE);
					PA_UNLOCK(buff);
					PA_FREE(buff);
				}
			}
			break;
		case P_IMAGE:
		case P_NEW_IMAGE:
			if (tag->is_end == FALSE)
			{
                /*
                 * For images that are part of the main document, we 
                 * can use the doc_data's url as the base URL. For
                 * images that are part of a LAYER that has a SRC
                 * attribute, we use the top_state's version (the base_url
                 * is temporarily changed to that of the LAYER while
                 * we process tags within the layer). The input_write_level
                 * tells us whether the tag is part of the main document
                 * or the layer.
                 */
                lo_BlockedImageLayout(context, new_state, tag,
                                      top_state->base_url);
			}
			break;
		case P_SCRIPT:
		case P_STYLE:
		case P_LINK:
			lo_BlockScriptTag(context, new_state, tag);
			break;
        case P_LAYER:
            lo_BlockLayerTag(context, new_state, tag);
            break;
		case P_PARAM:
			lo_ProcessParamTag(context, new_state, tag, TRUE);
			break;
		case P_OBJECT:          
			lo_ProcessObjectTag(context, new_state, tag, TRUE);
			break;
	}
	/*
	 * Now restore the base that you switched if it
	 * wasn't a BASE tag.
	 */
	if (tag->type != P_BASE)
	{
		top_state->base_url = save_base;
	}

	/*
	 * Unify the basis and induction cases of singly-linked list insert
	 * using a double-indirect pointer.  Instead of
	 *
	 *	if (head == NULL)
	 *		head = tail = elem;
	 *	else
	 *		tail = tail->next = elem;
	 *
	 * where head = tail = NULL is the empty state and elem->next = NULL,
	 * use a pointer to the pointer to the last element:
	 *
	 *	*tail_ptr = elem;
	 *	tail_ptr = &elem->next;
	 *
	 * where head = NULL, tail_ptr = &head is the empty state.
	 *
	 * The triple-indirection below is to avoid further splitting cases
	 * based on whether we're inserting at the input_write_point or 
	 * at the end of the tags list.  The 
	 * 'if (top_state->input_write_level)' test is unavoidable, but 
	 * once the tags_end_ptr variable is set to point at the right 
	 * double-indirect "tail_ptr" (per above sketch), there is shared code.
	 */

	if (top_state->input_write_level)
	{
	    tags_end_ptr = &top_state->input_write_point[top_state->input_write_level-1];
		if (*tags_end_ptr == NULL)
		{
			*tags_end_ptr = top_state->tags_end;
		}
		if (top_state->tags_end == *tags_end_ptr)
		{
			*tags_end_ptr = &tag->next;
			tags_end_ptr = &top_state->tags_end;
		}
	}
	else
	{
		tags_end_ptr = &top_state->tags_end;
	}
	tags_end = *tags_end_ptr;
	tag->next = *tags_end;
	*tags_end = tag;
	*tags_end_ptr = &tag->next;
}


lo_DocState *
lo_TopSubState(lo_TopState *top_state)
{
	lo_DocState *new_state;

	if ((top_state == NULL)||(top_state->doc_state == NULL))
	{
		return(NULL);
	}

	new_state = top_state->doc_state;
	while (new_state->sub_state != NULL)
	{
		new_state = new_state->sub_state;
	}

	return(new_state);
}


lo_DocState *
lo_CurrentSubState(lo_DocState *state)
{
	lo_DocState *new_state;

	if (state == NULL)
	{
		return(NULL);
	}

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

	return(new_state);
}


/*************************************
 * Function: lo_FetchTopState
 *
 * Description: This function uses the document ID to locate the
 *	lo_TopState structure for this document.
 *	Eventually this state will be kept somewhere in the
 *	Window context.
 *
 * Params: A unique document ID as derived from the window context.
 *
 * Returns: A pointer to the lo_TopState structure for this document.
 *	    NULL on error.
 *************************************/
lo_TopState *
lo_FetchTopState(int32 doc_id)
{
	lo_StateList *sptr;
	lo_TopState *state;

	sptr = StateList;
	while (sptr != NULL)
	{
		if (sptr->doc_id == doc_id)
		{
			break;
		}
		sptr = sptr->next;
	}
	if (sptr == NULL)
	{
		state = NULL;
	}
	else
	{
		state = sptr->state;
	}

	return(state);
}


static void
lo_RemoveTopState(int32 doc_id)
{
	lo_StateList *state;
	lo_StateList *sptr;

	state = StateList;
	sptr = StateList;
	while (sptr != NULL)
	{
		if (sptr->doc_id == doc_id)
		{
			break;
		}
		state = sptr;
		sptr = sptr->next;
	}
	if (sptr != NULL)
	{
		if (sptr == StateList)
		{
			StateList = StateList->next;
		}
		else
		{
			state->next = sptr->next;
		}
		XP_DELETE(sptr);
	}
}


Bool
lo_StoreTopState(int32 doc_id, lo_TopState *new_state)
{
	lo_StateList *sptr;

	sptr = StateList;
	while (sptr != NULL)
	{
		if (sptr->doc_id == doc_id)
		{
			break;
		}
		sptr = sptr->next;
	}

	if (sptr == NULL)
	{
		sptr = XP_NEW(lo_StateList);
		if (sptr == NULL)
		{
			return FALSE;
		}
		sptr->doc_id = doc_id;
		sptr->next = StateList;
		StateList = sptr;
	}
	sptr->state = new_state;
	return TRUE;
}


void
lo_SaveSubdocTags(MWContext *context, lo_DocState *state, PA_Tag *tag)
{
	PA_Tag *tag_ptr;

	tag->next = NULL;

	if (state->subdoc_tags_end == NULL)
	{
		if (state->subdoc_tags == NULL)
		{
			state->subdoc_tags = tag;
			state->subdoc_tags_end = tag;
		}
		else
		{
			state->subdoc_tags->next = tag;
			state->subdoc_tags = tag;
			state->subdoc_tags_end = tag;
		}
	}
	else
	{
		tag_ptr = state->subdoc_tags_end;
		tag_ptr->next = tag;
		state->subdoc_tags_end = tag;
	}
}



void
lo_FlushBlockage(MWContext *context, lo_DocState *state,
				 lo_DocState *main_doc_state)
{
	PA_Tag *tag_ptr;
	int32 doc_id;
	lo_TopState *top_state;
	lo_DocState *orig_state;
	lo_DocState *up_state;
	Bool was_blockage;

	/*
	 * All blocked tags should be at the top level of state
	 */
	doc_id = XP_DOCID(context);
	top_state = lo_FetchTopState(doc_id);
	if ((top_state == NULL)||(top_state->doc_state == NULL))
	{
		return;
	}
    /* Prevent re-entering */
    if (top_state->flushing_blockage)
        return;
    top_state->flushing_blockage = TRUE;

	was_blockage = (top_state->tags != NULL);
 	tag_ptr = top_state->tags;
   
	orig_state = top_state->doc_state;
	
	up_state = NULL;
	state = orig_state;
	while (state->sub_state != NULL)
	{
		up_state = state;
		state = state->sub_state;
	}

	top_state->layout_blocking_element = NULL;

	while ((tag_ptr != NULL)&&(top_state->layout_blocking_element == NULL))
	{
		PA_Tag *tag;
		lo_DocState *new_state;
		lo_DocState *new_up_state;
		lo_DocState *tmp_state;
		Bool may_save;
		int32 state_diff;

		tag = tag_ptr;
		tag_ptr = tag_ptr->next;
		tag->next = NULL;

		/*
		 * Always keep top_state->tags sane
		 */
		top_state->tags = tag_ptr;

		/*
		 * Since script processing is asynchronous we need to do this
		 *   whenever we flush blockage
		 */
		if (lo_FilterTag(context, state, tag) == FALSE)
		{
			PA_FreeTag(tag);
			continue;
		}
				
		if ((state->is_a_subdoc == SUBDOC_CELL)||
			(state->is_a_subdoc == SUBDOC_CAPTION))
		{
			may_save = TRUE;
		}
		else
		{
			may_save = FALSE;
		}

		/*
		 * Reset these so we can tell if anything happened
		 */
		top_state->diff_state = FALSE;
		top_state->state_pushes = 0;
		top_state->state_pops = 0;

		if (state->top_state->out_of_memory == FALSE)
		{
			lo_LayoutTag(context, state, tag);
            if (tag->type == P_LAYER)
            {
                lo_UnblockLayerTag(state);
            }
			if(top_state->wedged_on_mocha) {
			    top_state->wedged_on_mocha = FALSE;
			    goto done;
			}
		}

		/* how has our state level changed? */
		state_diff = top_state->state_pushes - top_state->state_pops;

		new_up_state = NULL;
		new_state = orig_state;
		while (new_state->sub_state != NULL)
		{
			new_up_state = new_state;
			new_state = new_state->sub_state;
		}
		tmp_state = new_state;
		if (may_save != FALSE)
		{
			if (state_diff == -1)
			{
				/*
				 * That tag popped us up one state level.  If this new
				 * state is still a subdoc, save the tag there.
				*/
				if ((tmp_state->is_a_subdoc == SUBDOC_CELL)||
				    (tmp_state->is_a_subdoc == SUBDOC_CAPTION))
				{

                    /* if we just popped a table we need to insert
                     * a dummy end tag to pop the dummy start tag
                     * we shove on the stack after createing a table
                     */
   					PA_Tag *new_tag = LO_CreateStyleSheetDummyTag(tag);
					if(new_tag)
					{
						lo_SaveSubdocTags(context, tmp_state, new_tag);
					}

				    lo_SaveSubdocTags(context, tmp_state, tag);
				}
				else
				{
				    PA_FreeTag(tag);
				}
			}
			else if (( up_state != NULL ) &&
				( top_state->diff_state != FALSE ) &&
				( state_diff == 0 ))
			{
				/*
				 * We have returned to the same state level but
				 * we have a different subdoc.
				 */
				if ((up_state->is_a_subdoc == SUBDOC_CELL)||
				     (up_state->is_a_subdoc == SUBDOC_CAPTION))
				{
				    lo_SaveSubdocTags(context, up_state, tag);
				}
				else
				{
				    PA_FreeTag(tag);
				}
			}
			else if (( top_state->diff_state == FALSE ) &&
				( state_diff == 0 ))
			{
				/*
				 * We are still in the same subdoc
				 */
				lo_SaveSubdocTags(context, state, tag);
			}
			else if (( state_diff == 1 ))
			{
				PA_Tag *new_tag;

				/*
				 * That tag started a new, nested subdoc.
				 * Add the starting tag to the parent.
				 */
				lo_SaveSubdocTags(context, state, tag);
				/*
				 * Since we have extended the parent chain,
				 * we need to reset the child to the new
				 * parent end-chain.
				 */
				if ((tmp_state->is_a_subdoc == SUBDOC_CELL)||
				    (tmp_state->is_a_subdoc == SUBDOC_CAPTION))
				{
					tmp_state->subdoc_tags =
						state->subdoc_tags_end;

					/* add an aditional dummy tag so that style sheets
					 * can use it to query styles from for this entry
					 * that created a table
					 */
					new_tag = LO_CreateStyleSheetDummyTag(tag);
					if(new_tag)
					{
						lo_SaveSubdocTags(context, tmp_state, new_tag);
					}
				}

			}
			/*
			 * This can never happen.
			 */
			else
			{
				PA_FreeTag(tag);
			}
		}
		else
		{
			if(state_diff == 1)
			{
				/* everytime a table is started we need to save the
				 * tag that created it so that style sheets can apply
				 * styles to it.  So we will save even in the "dont save"
				 * case
				 */
				PA_Tag *new_tag = LO_CreateStyleSheetDummyTag(tag);
				if(new_tag)
				{
					/* We just pushed another table into a previous table 
					 * we need to do some table magic to make the subdoc
					 * list work right
					 */
					lo_SaveSubdocTags(context, tmp_state, new_tag);
				}
			}
            else if(state_diff == -1)
            {
                /* if we just popped a table we need to insert
                 * a dummy end tag to pop the dummy start tag
                 * we shove on the stack after createing a table
                 */
   				PA_Tag *new_tag = LO_CreateStyleSheetDummyTag(tag);
				if(new_tag)
				{
					lo_SaveSubdocTags(context, tmp_state, new_tag);
				}

				lo_SaveSubdocTags(context, tmp_state, tag);

            }
			PA_FreeTag(tag);
		}
		tag_ptr = top_state->tags;
		state = new_state;
		up_state = new_up_state;
	}

	if (tag_ptr != NULL)
	{
		top_state->tags = tag_ptr;
	}
	else
	{
	    int i;
		top_state->tags = NULL;
		top_state->tags_end = &top_state->tags;
		for (i = 0; i < MAX_INPUT_WRITE_LEVEL; i++)
			top_state->input_write_point[i] = NULL;
		if (was_blockage) {
			NET_StreamClass s;
			s.data_object=(pa_DocData *)top_state->doc_data;
		    top_state->doc_data = PA_DropDocData(&s); 
		}
	}

	if ((top_state->layout_blocking_element == NULL)&&
		(top_state->tags == NULL)&&
		(top_state->layout_status == PA_COMPLETE))
	{
		/*
		 * makes sure we are at the bottom
		 * of everything in the document.
		 */
		state = lo_CurrentSubState(main_doc_state);
		lo_CloseOutLayout(context, state);
		lo_FinishLayout(context, state, EVENT_LOAD);
	}
done:
	top_state->flushing_blockage = FALSE;
}


/*************************************
 * Function: lo_DisplayLine
 *
 * Description: This function displays a single line
 *	of the document, by having the front end individually
 *	display each element in it.
 *
 * Params: Window context, docuemnt state, and an index into
 *	the line array.
 *
 * Returns: Nothing.
 *************************************/
void
lo_DisplayLine(MWContext *context, lo_DocState *state, int32 line_num,
	int32 x, int32 y, uint32 w, uint32 h)
{
	LO_Element *tptr;
	LO_Element *end_ptr;
	LO_Element **line_array;

#ifdef LOCAL_DEBUG
XP_TRACE(("lo_DisplayLine(%d)\n", line_num));
#endif
	if (state == NULL)
	{
		return;
	}

	if (state->display_blocked != FALSE)
	{
		return;
	}

#ifdef XP_WIN16
	{
		intn a_size;
		intn a_indx;
		intn a_line;
		XP_Block *larray_array;

		a_size = SIZE_LIMIT / sizeof(LO_Element *);
		a_indx = (intn)(line_num / a_size);
		a_line = (intn)(line_num - (a_indx * a_size));

		XP_LOCK_BLOCK(larray_array, XP_Block *, state->larray_array);
		state->line_array = larray_array[a_indx];
		XP_LOCK_BLOCK(line_array, LO_Element **, state->line_array);
		tptr = line_array[a_line];

		if (line_num >= (state->line_num - 2))
		{
			end_ptr = NULL;
		}
		else
		{
			if ((a_line + 1) == a_size)
			{
				XP_UNLOCK_BLOCK(state->line_array);
				state->line_array = larray_array[a_indx + 1];
				XP_LOCK_BLOCK(line_array, LO_Element **,
					state->line_array);
				end_ptr = line_array[0];
			}
			else
			{
				end_ptr = line_array[a_line + 1];
			}
		}
		XP_UNLOCK_BLOCK(state->line_array);
		XP_UNLOCK_BLOCK(state->larray_array);
	}
#else
	XP_LOCK_BLOCK(line_array, LO_Element **, state->line_array);
	tptr = line_array[line_num];

	if (line_num >= (state->line_num - 2))
	{
		end_ptr = NULL;
	}
	else
	{
		end_ptr = line_array[line_num + 1];
	}
	XP_UNLOCK_BLOCK(state->line_array);
#endif /* XP_WIN16 */

#ifdef LOCAL_DEBUG
	if (tptr == NULL)
	{
		XP_TRACE(("	line %d is NULL!\n", line_num));
	}
#endif

	while (tptr != NULL && tptr != end_ptr)
	{
		lo_DisplayElement(context, tptr,
						  state->base_x, state->base_y,
						  x, y, w, h);
	  
		tptr = tptr->lo_any.next;
	}
}

/*************************************
 * Function: lo_PointToLine
 *
 * Description: This function calculates the line which contains
 *	a point. It's used for tracking mouse motion and button presses.
 *
 * Params: Window context, x, y position of point.
 *
 * Returns: The line that contains the point.
 *	Returns -1 on error.
 *************************************/
int32
lo_PointToLine (MWContext *context, lo_DocState *state, int32 x, int32 y)
{
	int32 line, start, middle, end;
	LO_Element **line_array;
#ifdef XP_WIN16
	int32 line_add;
#endif /* XP_WIN16 */

	line = -1;

	/*
	 * No document state yet.
	 */
	if (state == NULL)
	{
		return(line);
	}

	/*
	 * No laid out lines yet.
	 */
	if (state->line_num == 1 || !state->line_array)
	{
		return(line);
	}

#ifdef XP_WIN16
	{
		intn a_size;
		intn a_indx;
		intn a_line;
		XP_Block *larray_array;

		a_size = SIZE_LIMIT / sizeof(LO_Element *);
		a_indx = (intn)((state->line_num - 2) / a_size);
		a_line = (intn)((state->line_num - 2) - (a_indx * a_size));

		XP_LOCK_BLOCK(larray_array, XP_Block *, state->larray_array);
		while (a_indx > 0)
		{
			XP_LOCK_BLOCK(line_array, LO_Element **,
					larray_array[a_indx]);
			if (y < line_array[0]->lo_any.y)
			{
				XP_UNLOCK_BLOCK(larray_array[a_indx]);
				a_indx--;
			}
			else
			{
				XP_UNLOCK_BLOCK(larray_array[a_indx]);
				break;
			}
		}
		state->line_array = larray_array[a_indx];
		XP_UNLOCK_BLOCK(state->larray_array);

		/*
		 * We are about to do a binary search through the line
		 * array, lock it down.
		 */
		XP_LOCK_BLOCK(line_array, LO_Element **, state->line_array);
		line_add = a_indx * a_size;
		start = 0;
		end = ((a_indx + 1) * a_size) - 1;
		if (end > (state->line_num - 2))
		{
			end = state->line_num - 2;
		}
		end = end - line_add;
	}
#else
	/*
	 * We are about to do a binary search through the line
	 * array, lock it down.
	 */
	XP_LOCK_BLOCK(line_array, LO_Element **, state->line_array);
	start = 0;
	end = (intn) state->line_num - 2;
#endif /* XP_WIN16 */

	/*
	 * Special case if area is off the top.
	 */
	if (y <= 0)
	{
		line = 0;
	}
	/*
	 * Else another special case if area is off the bottom.
	 */
	else if (y >= line_array[end]->lo_any.y)
	{
		line = end;
	}
	/*
	 * Else a binary search through the document
	 * line list to find the line that contains the point.
	 */
	else
	{
		/*
		 * find the containing line.
		 */
		while ((end - start) > 0)
		{
			middle = (start + end + 1) / 2;
			if (y < line_array[middle]->lo_any.y)
			{
				end = middle - 1;
			}
			else
			{
				start = middle;
			}
		}
		line = start;
		end = (intn) state->line_num - 2; /* reset end */
	}

	/* check for a match higher up 
	 * if it matches find the highest matching line
	 *
	 * matches higher up can be caused by stylesheet 
	 * negative margins
	 */
	while(line > 0 
		  && line_array[line-1]->lo_any.y <= y 
		  && line_array[line-1]->lo_any.y+line_array[line-1]->lo_any.height > y)
	{
		line--;
	}

    /* search through any lines that have the same Y coordinates and
	 * find the closes x directly to the left of the current point
	 */
    if(line != end
	   && line_array[line+1]->lo_any.y <= y 
	   && line_array[line+1]->lo_any.y+line_array[line+1]->lo_any.height > y)
	{
		int32 closest=line;
		int32 cur_line=line+1;

		for(; cur_line <= end
			  && line_array[cur_line]->lo_any.y <= y 
			  && line_array[cur_line]->lo_any.y+line_array[cur_line]->lo_any.height > y
			; cur_line++)
		{
			if(line_array[cur_line]->lo_any.x < x
				&& (line_array[closest]->lo_any.x > x
				    || line_array[closest]->lo_any.x < line_array[cur_line]->lo_any.x))
			{
				closest = cur_line;
			}
		}

		line = closest;
	}

	XP_UNLOCK_BLOCK(state->line_array);
#ifdef XP_WIN16
	line = line + line_add;
#endif /* XP_WIN16 */
	return(line);
}



/*************************************
 * Function: lo_RegionToLines
 *
 * Description: This function calculates the lines which intersect
 * a front end drawable region. It's used for drawing and in
 * calculating page breaks for printing.
 *
 * Params: Window context, x, y position of upper left corner,
 *	and width X height of the rectangle. Also pointers to
 * return values. dontCrop is true if we do not want to draw lines
 * which would be cropped (fall on the top/bottom region boundary).
 * This is for printing.
 *
 * Returns: Sets top and bottom to the lines numbers
 * to display. Sets them to -1 on error.
 *************************************/
void
lo_RegionToLines (MWContext *context, lo_DocState *state, int32 x, int32 y,
	uint32 width, uint32 height, Bool dontCrop, int32 * topLine, int32 * bottomLine)
{
	LO_Element **line_array;
	int32 top, bottom;
	int32 y2;
	int32	topCrop;
	int32	bottomCrop;
	
	/*
		Set drawable lines to -1 (assuming error by default)
	*/
	*topLine = -1;
	*bottomLine = -1;
	
	/*
	 * No document state yet.
	 */
	if (state == NULL)
	{
		return;
	}

	/*
	 * No laid out lines yet.
	 */
	if (state->line_num == 1)
	{
		return;
	}

	y2 = y + height;

	top = lo_PointToLine(context, state, x, y);
	bottom = lo_PointToLine(context, state, x, y2);

	if ( dontCrop )
	{
		XP_LOCK_BLOCK(line_array, LO_Element**, state->line_array );
		
		topCrop = line_array[ top ]->lo_any.y;
		bottomCrop = line_array[ bottom ]->lo_any.y + line_array[ bottom ]->lo_any.line_height;
		XP_UNLOCK_BLOCK( state->line_array );
		
		if ( y > topCrop )
		{
			top++;
			if ( top > ( state->line_num - 2 ) )
				top = -1;
		} 
		if ( y2 < bottomCrop )
			bottom--;
	}
	
	/*
	 * Sanity check
	 */
	if (bottom < top)
	{
		bottom = top;
	}

	*topLine = top;
	*bottomLine = bottom;
}

/*
	LO_CalcPrintArea
    
    Takes a region and adjusts it inward so that the top and bottom lines
    are not cropped. Results can be passed to LO_RefreshArea to print
    one page. To calculate the next page, use y+height as the next y.
*/
intn
LO_CalcPrintArea (MWContext *context, int32 x, int32 y,
				  uint32 width, uint32 height,
				  int32 *top, int32 *bottom)
{
	LO_Element **line_array;
	int32 doc_id;
	lo_TopState *top_state;
	lo_DocState *state;
	int32		lineTop;
	int32		lineBottom;
	
	*top = -1;
	*bottom = -1;
	/*
	 * Get the unique document ID, and retreive this
	 * documents layout state.
	 */
	doc_id = XP_DOCID(context);
	top_state = lo_FetchTopState(doc_id);
	if ((top_state == NULL)||(top_state->doc_state == NULL))
	{
		return(0);
	}
	state = top_state->doc_state;
	
	/*	
		Find which lines intersect the drawable region.
	*/
	lo_RegionToLines( context, state, x, y, width, height, TRUE, &lineTop, &lineBottom );
	
	if ( lineTop != -1 && lineBottom != -1 )
	{
		XP_LOCK_BLOCK(line_array, LO_Element **, state->line_array );
	
		*top = line_array[ lineTop ]->lo_any.y;
		*bottom = line_array[ lineBottom ]->lo_any.y + line_array[ lineBottom ]->lo_any.line_height - 1;
		
		XP_UNLOCK_BLOCK( state->line_array );
		return (1);
	}
	return (0);
}

void
lo_CloseOutLayout(MWContext *context, lo_DocState *state)
{
	lo_FlushLineBuffer(context, state);

	/*
	 * makes sure we are at the bottom
	 * of everything in the document.
	 *
	 * Only close forms if we aren't in a subdoc because
	 * forms can span subdocs.
	 */
	if ((state->is_a_subdoc == SUBDOC_NOT)&&
		(state->top_state->in_form != FALSE))
	{
		lo_EndForm(context, state);
	}

	lo_SetLineBreakState( context, state, FALSE, LO_LINEFEED_BREAK_SOFT, 1, FALSE);
	lo_ClearToBothMargins(context, state);

	/* Dummy Floating elements on a new line at the end of a document were not getting put into
	   the line_array.  This code should ensure that anything left on the line_list gets flushed
	   to the line_array. */
	if (state->line_list != NULL)
		lo_AppendLineListToLineArray( context, state, NULL);
}

/* Relayout version of lo_CloseOutLayout() */
void lo_EndLayoutDuringReflow( MWContext *context, lo_DocState *state )
{
	/*
	 * makes sure we are at the bottom
	 * of everything in the document.
	 *
	 * Only close forms if we aren't in a subdoc because
	 * forms can span subdocs.
	 */
	if ((state->is_a_subdoc == SUBDOC_NOT)&&
		(state->top_state->in_form != FALSE))
	{
		state->top_state->in_form = FALSE;
	}
	

	lo_SetLineBreakState( context, state, FALSE, LO_LINEFEED_BREAK_SOFT, 1, TRUE);
	lo_ClearToBothMargins(context, state);
}

void
LO_CloseAllTags(MWContext *context)
{
	int32 doc_id;
	lo_TopState *top_state;
	lo_DocState *state;
	lo_DocState *sub_state;

	/*
	 * Get the unique document ID, and retreive this
	 * documents layout state.
	 */
	doc_id = XP_DOCID(context);
	top_state = lo_FetchTopState(doc_id);
	if ((top_state == NULL)||(top_state->doc_state == NULL))
	{
		return;
	}
	state = top_state->doc_state;

	/*
	 * If we are in a table, get us out.
	 */
	sub_state = lo_CurrentSubState(state);
	while (sub_state != state)
	{
		lo_DocState *old_state;

		lo_CloseOutTable(context, sub_state);
		old_state = sub_state;
		sub_state = lo_CurrentSubState(state);
		/*
		 * If sub_state doesn't change we could be in a loop.
		 * break it here.
		 */
		if (sub_state == old_state)
		{
			break;
		}
	}
	state->sub_state = NULL;

	/*
	 * First do the normal closing.
	 */
	lo_CloseOutLayout(context, state);

	/*
	 * Reset a bunch of state variables.
	 */
	state->text_divert = P_UNKNOWN;
	state->delay_align = FALSE;
	state->breakable = TRUE;
	state->allow_amp_escapes = TRUE;
	state->preformatted = PRE_TEXT_NO;
	state->preformat_cols = 0;
	state->in_paragraph = FALSE;
	/*
	 * clear the alignment stack.
	 */
	while (lo_PopAlignment(state) != NULL) {;}
	/*
	 * clear the list stack.
	 */
	while (lo_PopList(state, NULL) != NULL) {;}
	/*
	 * clear all open anchors.
	 */
	lo_PopAllAnchors(state);
	/*
	 * clear the font stack.
	 */
	while (state->font_stack->next != NULL)
	{
		(void)lo_PopFont(state, P_UNKNOWN);
	}
	state->base_font_size = DEFAULT_BASE_FONT_SIZE;
	state->font_stack->text_attr->size = state->base_font_size;
	state->current_anchor = NULL;
	state->current_ele = NULL;
	state->current_table = NULL;
	state->current_cell = NULL;
	state->current_java = NULL;
	state->current_multicol = NULL;
	state->must_relayout_subdoc = FALSE;
	state->allow_percent_width = TRUE;
	state->allow_percent_height = TRUE;
	state->is_a_subdoc = SUBDOC_NOT;
	state->current_subdoc = 0;

	/*
	 * Escape an open script
	 */
	if (top_state->in_script != SCRIPT_TYPE_NOT)
	{
		top_state->in_script = SCRIPT_TYPE_NOT;
		state->line_buf_len = 0;
		lo_UnblockLayout(context, top_state);
	}

	/*
	 * Reset a few top_state variables.
	 */
	top_state->current_map = NULL;
	top_state->in_nogrids = FALSE;
    top_state->ignore_tag_nest_level = 0;
    top_state->ignore_layer_nest_level = 0;
	top_state->in_applet = FALSE;
}


void
lo_free_layout_state_data(MWContext *context, lo_DocState *state)
{
	/*
	 * Short circuit out if the state was freed out from
	 * under us.
	 */
	if (state == NULL)
	{
		return;
	}

	if (state->current_table != NULL)
	{
		lo_FreePartialTable(context, state, state->current_table);
		state->current_table = NULL;
	}

	if (state->left_margin_stack != NULL)
	{
		lo_MarginStack *mptr;
		lo_MarginStack *margin;

		mptr = state->left_margin_stack;
		while (mptr != NULL)
		{
			margin = mptr;
			mptr = mptr->next;
			XP_DELETE(margin);
		}
		state->left_margin_stack = NULL;
	}
	if (state->right_margin_stack != NULL)
	{
		lo_MarginStack *mptr;
		lo_MarginStack *margin;

		mptr = state->right_margin_stack;
		while (mptr != NULL)
		{
			margin = mptr;
			mptr = mptr->next;
			XP_DELETE(margin);
		}
		state->right_margin_stack = NULL;
	}

	if (state->line_list != NULL)
	{
		LO_Element *eptr;
		LO_Element *element;

		eptr = state->line_list;
		while (eptr != NULL)
		{
			element = eptr;
			eptr = eptr->lo_any.next;
			lo_FreeElement(context, element, TRUE);
		}
		state->line_list = NULL;
	}

	if (state->font_stack != NULL)
	{
		lo_FontStack *fstack;
		lo_FontStack *fptr;

		fptr = state->font_stack;
		while (fptr != NULL)
		{
			fstack = fptr;
			fptr = fptr->next;
			XP_DELETE(fstack);
		}
		state->font_stack = NULL;
	}

	if (state->align_stack != NULL)
	{
		lo_AlignStack *aptr;
		lo_AlignStack *align;

		aptr = state->align_stack;
		while (aptr != NULL)
		{
			align = aptr;
			aptr = aptr->next;
			XP_DELETE(align);
		}
		state->align_stack = NULL;
	}

	if (state->list_stack != NULL)
	{
		lo_ListStack *lptr;
		lo_ListStack *list;

		lptr = state->list_stack;
		while (lptr != NULL)
		{
			list = lptr;
			lptr = lptr->next;
			XP_DELETE(list);
		}
		state->list_stack = NULL;
	}

    if (state->line_height_stack != NULL)
    {
        lo_LineHeightStack *lptr;
        lo_LineHeightStack *height;
        
        lptr = state->line_height_stack;
        while (lptr != NULL) {
            height = lptr;
            lptr = lptr->next;
            XP_DELETE(height);
        }
        state->line_height_stack = NULL;
    }
            
	if (state->line_buf != NULL)
	{
		PA_FREE(state->line_buf);
		state->line_buf = NULL;
	}
}


/* Clean up the layout data structures. This used to be inside lo_FinishLayout,
 * but we needed to call it from the editor, without all of the UI code that
 * lo_FinishLayout also calls.
 */

void
lo_FreeLayoutData(MWContext *context, lo_DocState *state)
{
	int32 i, cnt;

	/*
	 * Clean out the lo_DocState
	 */
	lo_free_layout_state_data(context, state);

	/*
	 * Short circuit out if the state was freed out from
	 * under us.
	 */
	if (state == NULL)
	{
		return;
	}

	LO_LockLayout();

	/*
	 * These are tags that were blocked instead of laid out.
	 * If there was an image in here, we already started the
	 * layout of it, we better free it now.
	 */
	if (state->top_state->tags != NULL)
	{
		PA_Tag *tptr;
		PA_Tag *tag;

		tptr = state->top_state->tags;
		while (tptr != NULL)
		{
			tag = tptr;
			tptr = tptr->next;
			if (((tag->type == P_IMAGE)||
			     (tag->type == P_NEW_IMAGE))&&
			    (tag->lo_data != NULL))
			{
				LO_Element *element;
				element = (LO_Element *)tag->lo_data;
				lo_FreeElement(context, element, TRUE);
				tag->lo_data = NULL;
			}
			PA_FreeTag(tag);
		}
		state->top_state->tags = NULL;
		state->top_state->tags_end = &state->top_state->tags;
		for (i = 0; i < MAX_INPUT_WRITE_LEVEL; i++)
			state->top_state->input_write_point[i] = NULL;
	}

	/*
	 * If we were blocked on and image when we left this page, we need to
	 * free up that partially loaded image structure.
	 */
	if (state->top_state->layout_blocking_element != NULL)
	{
		LO_Element *element;

		element = state->top_state->layout_blocking_element;
		lo_FreeElement(context, element, TRUE);
		state->top_state->layout_blocking_element = NULL;
	}

	/* Delete object stack */ 
    if (state->top_state->object_stack != NULL)
    {
        lo_ObjectStack* top;
        lo_ObjectStack* object;
        
        top = state->top_state->object_stack;
        while (top != NULL)
        {
            object = top;
            top = top->next;
			lo_DeleteObjectStack(object);
        }
        state->top_state->object_stack = NULL;
    }

	/* Delete object cache */
	if (state->top_state->object_cache != NULL)
    {
        lo_ObjectStack* top;
        lo_ObjectStack* object;
        
        top = state->top_state->object_cache;
        while (top != NULL)
        {
            object = top;
            top = top->next;
			lo_DeleteObjectStack(object);
        }
        state->top_state->object_cache = NULL;
    }

	LO_UnlockLayout();

	/*
	 * now we're done laying out the entire document. we had saved all
	 * the layout structures from the previous document so we're throwing
	 * away unused ones.
	 */
	cnt = lo_FreeRecycleList(context, state->top_state->recycle_list);
#ifdef MEMORY_ARENAS
	if (state->top_state->current_arena != NULL)
	{
		cnt = lo_FreeMemoryArena(state->top_state->current_arena->next);
		state->top_state->current_arena->next = NULL;
	}
#endif /* MEMORY_ARENAS */
	state->top_state->recycle_list = NULL;
}


void
lo_CloseMochaWriteStream(lo_TopState *top_state, int mocha_event)
{
	NET_StreamClass *stream = top_state->mocha_write_stream;

	if (stream != NULL)
	{
		top_state->mocha_write_stream = NULL;
		if (mocha_event == EVENT_LOAD)
		{
			stream->complete(stream);
		}
		else
		{
			stream->abort(stream, top_state->layout_status);
		}
		XP_DELETE(stream);
	}
}



void
lo_FinishLayout_OutOfMemory(MWContext *context, lo_DocState *state)
{
	lo_TopState *top_state;
	lo_DocState *main_state;
	lo_DocState *sub_state;

	/* Reset state for force loading images. */
	LO_SetForceLoadImage(NULL, FALSE);

	/*
	 * Short circuit out if the state was freed out from
	 * under us.
	 */
	if (state == NULL)
	{
		return;
	}

	/*
	 * Check for dangling sub-states, and clean them up if they exist.
	 */
	top_state = state->top_state;
	main_state = top_state->doc_state;
	sub_state = lo_TopSubState(top_state);
	while ((sub_state != NULL)&&(sub_state != main_state))
	{
		state = main_state;
		while ((state != NULL)&&(state->sub_state != sub_state))
		{
			state = state->sub_state;
		}
		if (state != NULL)
		{
			lo_free_layout_state_data(context, sub_state);
			XP_DELETE(sub_state);
			state->sub_state = NULL;
		}
		sub_state = lo_TopSubState(top_state);
	}
	state = lo_TopSubState(top_state);

	lo_CloseMochaWriteStream(top_state, EVENT_ABORT);

	/* Could be left over if someone forgets to close a tag */
	if (state->top_state->scriptData)
		lo_DestroyScriptData(state->top_state->scriptData);

	/*
	 * Short circuit out if the state was freed out from
	 * under us.
	 */
	if (state == NULL)
	{
		return;
	}

	lo_free_layout_state_data(context, state);
}


void
lo_FinishLayout(MWContext *context, lo_DocState *state, int32 mocha_event)
{
	lo_TopState *top_state;
	lo_DocState *main_state;
	lo_DocState *sub_state;

    /* Close any layers (blocks) that were opened in this document, in
	   case the document author forgot to. */
    if (state != NULL ) {
        if (state->layer_nest_level) {
            while (state->layer_nest_level > 0)
                lo_EndLayer(context, state, PR_TRUE);
            /* 
             * This call ensures that we flush the line list (which still might
             * have something on it if we had unclosed ILAYERs).
             */
            lo_CloseOutLayout(context, state); 
        }
    }

	if ((state != NULL) &&
		(state->top_state != NULL))
	{
		lo_CloseMochaWriteStream(state->top_state, mocha_event);

		/* Could be left over if someone forgets to close a tag */
		if (state->top_state->scriptData)
			lo_DestroyScriptData(state->top_state->scriptData);

        /* Handle the page background for the case of a document that contains 
           no displayed layout elements. */
        if (state->top_state->nothing_displayed != FALSE)
            lo_use_default_doc_background(context, state);
	}

        if (state && state->top_state)
            ET_SendLoadEvent(context, mocha_event, NULL, NULL, 
                             LO_DOCUMENT_LAYER_ID, 
                             state->top_state->resize_reload);
        else
            ET_SendLoadEvent(context, mocha_event, NULL, NULL, 
                             LO_DOCUMENT_LAYER_ID, FALSE);

	/* Reset state for force loading images. */
	LO_SetForceLoadImage(NULL, FALSE);

	/*
	 * Short circuit out if the state was freed out from
	 * under us.
	 */
	if (state == NULL)
	{
		FE_SetProgressBarPercent(context, 100);
#ifdef OLD_MSGS
		FE_Progress(context, "Layout Phase Complete");
#endif /* OLD_MSGS */

        /* Flush out layer callbacks so that document dimensions are correct. */
        if (context->compositor)
            CL_CompositeNow(context->compositor);

		FE_FinishedLayout(context);
		return;
	}

	/*
	 * Check for dangling sub-states, and clean them up if they exist.
	 */
	top_state = state->top_state;
	main_state = top_state->doc_state;
	sub_state = lo_TopSubState(top_state);
	while ((sub_state != NULL)&&(sub_state != main_state))
	{
		state = main_state;
		while ((state != NULL)&&(state->sub_state != sub_state))
		{
			state = state->sub_state;
		}
		if (state != NULL)
		{
			lo_free_layout_state_data(context, sub_state);
			XP_DELETE(sub_state);
			state->sub_state = NULL;
		}
		sub_state = lo_TopSubState(top_state);
	}
	state = lo_TopSubState(top_state);

	/*
	 * Short circuit out if the state was freed out from
	 * under us.
	 */
	if (state == NULL)
	{
		FE_SetProgressBarPercent(context, 100);
#ifdef OLD_MSGS
		FE_Progress(context, "Layout Phase Complete");
#endif /* OLD_MSGS */

        /* Flush out layer callbacks so that document dimensions are correct. */
        if (context->compositor)
            CL_CompositeNow(context->compositor);

		FE_FinishedLayout(context);
		return;
	}

    lo_FreeLayoutData(context, state);

	if (state->display_blocked != FALSE)
	{
		int32 y;

		if (state->top_state->name_target != NULL)
		{
			XP_FREE(state->top_state->name_target);
			state->top_state->name_target = NULL;
		}
		state->display_blocking_element_id = 0;
		state->display_blocked = FALSE;
		y = state->display_blocking_element_y;
		state->display_blocking_element_y = 0;
		state->y += state->win_bottom;

		LO_SetDocumentDimensions(context, state->max_width, state->y);

		if (y != state->win_top)
		{
			y = state->y - state->win_height;
			if (y < 0)
			{
				y = 0;
			}
		}
		FE_SetDocPosition(context, FE_VIEW, 0, y);

        if (context->compositor)
        {
            XP_Rect rect;
            
            rect.left = 0;
            rect.top = y;
            rect.right = state->win_width;
            rect.bottom = y + state->win_height;
            CL_UpdateDocumentRect(context->compositor,
                                  &rect, (PRBool)FALSE);
        }
	}
	else
	{
		state->y += state->win_bottom;
		LO_SetDocumentDimensions(context, state->max_width, state->y);
	}

	/* get rid of the style stack data since it wont
	 * be needed any more
	 */
	if(top_state->style_stack)
	{
		/* LJM: Need to purge stack to free memory
		 */
		STYLESTACK_Purge(top_state->style_stack);
	}

	if(!state->top_state->is_binary)
		FE_SetProgressBarPercent(context, 100);

#ifdef OLD_MSGS
	FE_Progress(context, "Layout Phase Complete");
#endif /* OLD_MSGS */

    /* Flush out layer callbacks so that document dimensions are correct. */
    if (context->compositor)
        CL_CompositeNow(context->compositor);

	if (state->is_a_subdoc == SUBDOC_NOT && state->top_state && !state->top_state->have_title)
	{
		/* We never got a title. We better notify the FE that the title is blank */
		
		FE_SetDocTitle(context, "");
	}		

	FE_FinishedLayout(context);
#ifdef EDITOR
	if (EDT_IS_EDITOR (context))
		EDT_FinishedLayout(context);
#endif

    /*
     * Update the blink layers to reflect the current positions of their
     * associated text elements.
     */
    if (context->compositor)
        lo_UpdateBlinkLayers(context);
}

/*******************************************************************************
 * LO_ProcessTag helper routines
 ******************************************************************************/

#ifdef MEMORY_ARENAS 
void
lo_GetRecycleList(MWContext* context, int32 doc_id, pa_DocData* doc_data, 
				  LO_Element* *recycle_list, lo_arena* *old_arena)
#else
void
lo_GetRecycleList(MWContext* context, int32 doc_id, pa_DocData* doc_data, 
				  LO_Element* *recycle_list)
#endif /* MEMORY_ARENAS */
{
	lo_TopState* old_top_state;
	lo_DocState* old_state;

	LO_LockLayout();

	/*
	 * Free up any old document displayed in this
	 * window.
	 */
	old_top_state = lo_FetchTopState(XP_DOCID(context));
	if (old_top_state == NULL)
	{
		old_state = NULL;
	}
	else
	{
		old_state = old_top_state->doc_state;
	}
	if (old_state != NULL)
	{
#ifdef MEMORY_ARENAS
		*old_arena = old_top_state->first_arena;
#endif /* MEMORY_ARENAS */
		lo_SaveFormElementState(context, old_state, TRUE);
		/*
		 * If this document has no session history
		 * there is no place to save this data.
		 * It will be freed by history cleanup.
		 */
		if (SHIST_GetCurrent(&context->hist) == NULL)
		{
			lo_TopState *top_state = old_state->top_state;
			top_state->savedData.FormList = NULL;
			top_state->savedData.EmbedList = NULL;
			top_state->savedData.Grid = NULL;
		}
		*recycle_list =
			lo_InternalDiscardDocument(context, old_state, doc_data, TRUE);
	}

	/* Now that we've discarded the old document, set the new doc_id. */
	XP_SET_DOCID(context, doc_id);
	LO_UnlockLayout();

}

intn
lo_ProcessTag_OutOfMemory(MWContext* context, LO_Element* recycle_list, lo_TopState* top_state)
{
	int32 cnt;
	
	if ((top_state != NULL)&&(top_state->doc_state != NULL))
	{
		lo_FinishLayout_OutOfMemory(context, top_state->doc_state);
	}

	cnt = lo_FreeRecycleList(context, recycle_list);

#ifdef MEMORY_ARENAS
	if ((top_state != NULL)&&(top_state->first_arena != NULL))
	{
		cnt = lo_FreeMemoryArena(top_state->first_arena->next);
		top_state->first_arena->next = NULL;
	}
#endif /* MEMORY_ARENAS */

	return MK_OUT_OF_MEMORY;
}

#define RESTORE_SAVED_DATA(Type, var, doc_data, recycle_list, top_state) \
{ \
	var = (lo_Saved##Type##Data *) \
		(doc_data->url_struct->savedData.Type); \
	if (var == NULL) \
	{ \
		/* \
		 * A document we have never visited, insert a pointer \
		 * to a form list into its history, if it has one. \
		 */ \
		var = lo_NewDocument##Type##Data(); \
		if (SHIST_GetCurrent(&context->hist) != NULL) \
		{ \
			SHIST_SetCurrentDoc##Type##Data(context, (void *)var); \
		} \
		/* \
		 * Stuff it in the url_struct as well for \
		 * continuous loading documents. \
		 */ \
		doc_data->url_struct->savedData.Type = (void *)var; \
	} \
	else if (doc_data->from_net != FALSE) \
	{ \
		lo_FreeDocument##Type##Data(context, var); \
	} \
	 \
	/* \
	 * If we don't have a form list, something very bad \
	 * has happened.  Like we are out of memory. \
	 */ \
	if (var == NULL) \
		return lo_ProcessTag_OutOfMemory(context, recycle_list, top_state); \
	 \
	top_state->savedData.Type = var; \
}

/****************************************************************************
 ****************************************************************************
 **
 **  Beginning of Public Utility Functions
 **
 ****************************************************************************
 ****************************************************************************/


static void *hooked_data_object = NULL;
static intn hooked_status = 0;
static PA_Tag *hooked_tag = NULL;

/*************************************
 * Function: LO_ProcessTag
 *
 * Description: This function is the main layout entry point.
 *	New tags or tag lists are sent here along with the
 *	status as to where they came from.
 *
 * Params: A blank data object from the parser, right now it
 *	is parse document data.  The tag or list of tags to process.
 * 	and a status field as to why it came.
 *
 * Returns: 1 - Done ok, continuing.
 *	    0 - Done ok, stopping.
 *	   -1 - not done, error.
 *************************************/
intn
LO_ProcessTag(void *data_object, PA_Tag *tag, intn status)
{
	pa_DocData *doc_data;
	int32 doc_id;
	MWContext *context;
    CL_Layer *doc_layer = NULL, *body_layer = NULL;
	lo_TopState *top_state;
	lo_DocState *state;
	lo_DocState *orig_state;
	lo_DocState *up_state;

	/*
	 * If this call came from a hook feed
	 */
	if (hooked_data_object != NULL)
	{
		data_object = hooked_data_object;
		status = hooked_status;
		/*
		 * Ignore hooked calls with no tags.
		 */
		if (tag == NULL)
		{
			return(1);
		}
		if (hooked_tag != NULL)
		{
			tag->newline_count += hooked_tag->newline_count;
		}
	}

	doc_data = (pa_DocData *)data_object;
	doc_id = doc_data->doc_id;
	top_state = (lo_TopState *)(doc_data->layout_state);

    /* 
     * If this is an "inline" stream and we don't have a top_state
     * associated with it, it's targetted at a layer in
     * the current document. In this case, we need to get the 
     * top_state using the context and not directly from the doc_data.
     */
    if ((top_state == NULL) && (doc_data->is_inline_stream == TRUE)) {
        top_state = lo_FetchTopState(doc_id);
        doc_data->layout_state = top_state;
        if (top_state) {
            top_state->doc_data = doc_data;
            top_state->nurl = doc_data->url_struct;
        }
    }

	if (top_state == NULL)
	{
		state = NULL;
	}
	else
	{
		state = top_state->doc_state;
	}
	context = doc_data->window_id;
	
	/*
	 * if we get called with abort/complete then ignore oom condition
	 * and clean up
	 */
	if ((state != NULL)&&(state->top_state != NULL)&&
		(state->top_state->out_of_memory != FALSE)&&(tag != NULL))
	{
		lo_FinishLayout_OutOfMemory(context, state);
		return(MK_OUT_OF_MEMORY);
	}

	if ((tag != NULL)&&(context != NULL)&&(hooked_data_object == NULL)&&
		(HK_IsHook(HK_TAG, (void *)tag)))
	{
		char *tptr;
		char *in_str;
		char *out_str;
		XP_Bool delete_tag;
		XP_Bool replace_tag;

		out_str = NULL;
		delete_tag = FALSE;
		replace_tag = FALSE;
		if (tag->type == P_TEXT)
		{
			PA_LOCK (tptr, char *, tag->data);
			in_str = XP_ALLOC(tag->data_len + 1);
			if (in_str == NULL)
			{
				return(MK_OUT_OF_MEMORY);
			}
			XP_BCOPY(tptr, in_str, tag->data_len);
			in_str[tag->data_len] = '\0';
			PA_UNLOCK(tag->data);
		}
		else if (tag->type == P_UNKNOWN)
		{
			int32 start;

			PA_LOCK (tptr, char *, tag->data);
			in_str = XP_ALLOC(tag->data_len + 3);
			if (in_str == NULL)
			{
				return(MK_OUT_OF_MEMORY);
			}
			in_str[0] = '<';
			start = 1;
			if (tag->is_end != FALSE)
			{
				in_str[1] = '/';
				start = 2;
			}
			XP_BCOPY(tptr, (char *)(in_str + start), tag->data_len);
			in_str[tag->data_len + start] = '\0';
			PA_UNLOCK(tag->data);
		}
		else
		{
			XP_Bool free_tag_str;
			const char *tag_str;
			int32 start;

			free_tag_str = FALSE;
			tag_str = PA_TagString((int32)tag->type);
			if (tag_str == NULL)
			{
				tag_str = XP_STRDUP("UNKNOWN");
				if (tag_str == NULL)
				{
					return(MK_OUT_OF_MEMORY);
				}
				free_tag_str = TRUE;
			}
			start = XP_STRLEN(tag_str);

			PA_LOCK (tptr, char *, tag->data);
			in_str = XP_ALLOC(tag->data_len + start + 3);
			if (in_str == NULL)
			{
				if (free_tag_str != FALSE)
				{
					XP_FREE((char *)tag_str);
				}
				return(MK_OUT_OF_MEMORY);
			}
			in_str[0] = '<';
			in_str[1] = '\0';
			start += 1;
			if (tag->is_end != FALSE)
			{
				in_str[1] = '/';
				in_str[2] = '\0';
				start += 1;
			}
			XP_STRCAT(in_str, tag_str);
			XP_BCOPY(tptr, (char *)(in_str + start), tag->data_len);
			in_str[tag->data_len + start] = '\0';
			PA_UNLOCK(tag->data);
			if (free_tag_str != FALSE)
			{
				XP_FREE((char *)tag_str);
			}
		}
		if (HK_CallHook(HK_TAG, (void *)tag, XP_CONTEXTID(context),
			in_str, &out_str))
		{
			/*
			 * out_str of NULL means change nothing.
			 * Returning the same string passed in also
			 * changes nothing.
			 */
			if ((out_str != NULL)&&(out_str[0] == '\0'))
			{
				delete_tag = TRUE;
			}
			else if ((out_str != NULL)&&
				(XP_STRCMP(in_str, out_str) != 0))
			{
				replace_tag = TRUE;
			}
		}
		XP_FREE(in_str);

		if (delete_tag != FALSE)
		{
			PA_FreeTag(tag);
			tag = NULL;
			XP_FREE(out_str);
			return(1);
		}
		else if (replace_tag != FALSE)
		{
			intn ok;

			hooked_data_object = data_object;
			hooked_status = status;
			hooked_tag = tag;

			ok = PA_ParseStringToTags(context,
				out_str, XP_STRLEN(out_str),
				(void *)LO_ProcessTag);

			hooked_data_object = NULL;

			/*
			 * This tag has been replaced, and never happened.
			 */
			if (ok)
			{
				PA_FreeTag(tag);
				tag = NULL;
				XP_FREE(out_str);
				return(1);
			}
		}
		if (out_str != NULL)
		{
			XP_FREE(out_str);
		}
	}

	/*	
		Test to see if this is a new stream.
		If it is, kill the old one (NET_InterruptStream(url)),
		free its data, and continue.
	*/
	if (!state) { /* this is a new document being laid out */
		/* is there already a state for this context? */
		lo_TopState *top_state;
		lo_DocState *old_state;

		/* initialize PICS */
		PICS_Init(context);

		top_state = lo_FetchTopState(doc_id);
		if (top_state == NULL)
		{
			old_state = NULL;
		}
		else
		{
			old_state = top_state->doc_state;
		}

		if (old_state) {
			if (top_state->nurl) { /* is it still running? */
/* ALEKS				NET_InterruptStream (top_state->nurl); */
				/* We've done a complete "cancel" now. We shouldn't need to do any more
				work; we'll find this "old state" again and recycle it */
			}
		}
	}
		
	/*
	 * We be done.
	 */
	if (tag == NULL)
	{
        /* 
         * If this is an inline stream that is not the result of a
         * dynamic layer src change, then don't do anything. The
         * layer will be closed out by the closing </LAYER> tag
         */
        if (doc_data && doc_data->is_inline_stream && (!state || 
            (lo_InsideLayer(state) && !lo_IsCurrentLayerDynamic(state))))
            return 0;
        
		if (status == PA_ABORT)
		{
#ifdef OLD_MSGS
			FE_Progress (context, "Document Load Aborted");
#endif /* OLD_MSGS */
            lo_process_deferred_image_info(NULL);
			if (state != NULL)
			{
				state->top_state->layout_status = status;
			}
        /*
         * If this is an inline stream (the result of a dynamic
         * layer src change) close out the layer.
         */
        if (doc_data && doc_data->is_inline_stream)
            lo_FinishLayerLayout(context, state, EVENT_ABORT);
        else 
			lo_FinishLayout(context, state, EVENT_ABORT);
		}
		else if (status == PA_COMPLETE)
		{
			orig_state = state;
			state = lo_CurrentSubState(state);

            /*
             * If this is an inline stream (the result of a dynamic
             * layer src change) close out the layer.
             */
            if (doc_data && doc_data->is_inline_stream)
                lo_FinishLayerLayout(context, state, EVENT_LOAD);
            else 
			/*
			 * IF the document is complete, we need to flush
			 * out any remaining unfinished elements.
			 */
			if (state != NULL)
			{
				state->top_state->total_bytes = state->top_state->current_bytes;

				if (state->top_state->layout_blocking_element != NULL)
				{
#ifdef OLD_MSGS
				    FE_Progress(context, "Layout Phase Complete but Blocked");
#endif /* OLD_MSGS */
				}
				else
				{
				    /*
				     * makes sure we are at the bottom
				     * of everything in the document.
				     */
				    lo_CloseOutLayout(context, state);

#ifdef OLD_MSGS
				    FE_Progress(context, "Layout Phase Complete");
#endif /* OLD_MSGS */

				    lo_FinishLayout(context, state, EVENT_LOAD);
				}
				orig_state->top_state->layout_status = status;
			}
			else
			{
				LO_Element *recycle_list = NULL;
				int32 cnt;
				int32 width, height;
				int32 margin_width, margin_height;
#ifdef MEMORY_ARENAS
				lo_arena *old_arena = NULL;

				lo_GetRecycleList(context, doc_id, doc_data,
						  &recycle_list, &old_arena);
#else
				lo_GetRecycleList(context, doc_id, doc_data,
						  &recycle_list);
#endif /* MEMORY_ARENAS */
				margin_width = 0;
				margin_height = 0;
 
                /* Because Mocha is in a separate thread, the
                   complete destruction of contexts is deferred until
                   Mocha cleans up its data structures.  (See
                   ET_RemoveWindowContext()).  If this context was
                   previously a frameset context, it may still have grid
                   children that haven't been deleted yet.  Pull them
                   off the grid_children list so that we don't mistakenly
                   conclude that this context is a frameset context. */
                if (context->grid_children) {
                    XP_ListDestroy (context->grid_children);
                    context->grid_children = 0;
                }
				FE_LayoutNewDocument(context,
						     doc_data->url_struct,
						     &width, &height,
						     &margin_width,
						     &margin_height);

                if(context && doc_data && doc_data->url_struct) {
                    /*  Tell XP NavCenter API about the new document.
                     */
                    XP_SetNavCenterUrl(context, doc_data->url_struct->address);
                }

				/* see first lo_FreeRecycleList comment. safe */
				cnt = lo_FreeRecycleList(context, recycle_list);
#ifdef MEMORY_ARENAS
				cnt = lo_FreeMemoryArena(old_arena);
#endif /* MEMORY_ARENAS */

				FE_SetDocPosition(context, FE_VIEW, 0, 0);
				FE_SetDocDimension(context, FE_VIEW, 1, 1);

#ifdef OLD_MSGS
				FE_Progress(context, "Layout Phase Complete");
#endif /* OLD_MSGS */
				lo_FinishLayout(context, state, EVENT_LOAD);
			}
		}

		/*
		 * Since lo_FinishLayout make have caused state to be
		 * freed if it was a dangling sub_state, we need to
		 * reinitialize state here so it is safe to use it.
		 */
		state = lo_TopSubState(top_state);

		if (state != NULL)
		{
			/* done with netlib stream */
			state->top_state->nurl = NULL;
			state->top_state->doc_data = NULL;
		}


		/*
		 * Make sure we are done with all images now.
		 */
		lo_NoMoreImages(context);

		return(0);
	}

	/*
	 * Check if this is a new document being laid out
	 */
	if (top_state == NULL)
	{
		int32 width, height;
		int32 margin_width, margin_height;
		lo_SavedFormListData *form_list;
		lo_SavedEmbedListData *embed_list;
		lo_SavedGridData *grid_data;
		LO_Element *recycle_list = NULL;
		int32 start_element;

#ifdef MEMORY_ARENAS 
		lo_arena *old_arena = NULL;

		lo_GetRecycleList(context, doc_id, doc_data, &recycle_list, &old_arena);	/* whh */
#else
		lo_GetRecycleList(context, doc_id, doc_data, &recycle_list);	/* whh */
#endif /* MEMORY_ARENAS */

#ifdef LOCAL_DEBUG
XP_TRACE(("Initializing new doc %d\n", doc_id));
#endif /* DEBUG */
		width = 1;
		height = 1;
		margin_width = 0;
		margin_height = 0;
		/*
		 * Grid cells can set their own margin dimensions.
		 */
		if (context->is_grid_cell != FALSE)
		{
			lo_GetGridCellMargins(context,
				&margin_width, &margin_height);
		}

        /* Because Mocha is in a separate thread, the complete
           destruction of contexts is deferred until Mocha cleans up
           its data structures.  (See ET_RemoveWindowContext()).  If
           this context was previously a frameset context, it may
           still have grid children that haven't been deleted yet.
           Pull them off the grid_children list so that we don't
           mistakenly conclude that this context is a frameset
           context. */
        if (context->grid_children) {
            XP_ListDestroy (context->grid_children);
            context->grid_children = 0;
        }
		FE_LayoutNewDocument(context, doc_data->url_struct,
			&width, &height, &margin_width, &margin_height);

        if(context && doc_data && doc_data->url_struct) {
            /*  Tell XP NavCenter API about the new document.
             */
            XP_SetNavCenterUrl(context, doc_data->url_struct->address);
        }

        if (context->compositor) {
            lo_CreateDefaultLayers(context, &doc_layer, &body_layer);
            CL_ScrollCompositorWindow(context->compositor, 0, 0);
            CL_SetCompositorEnabled(context->compositor, PR_TRUE);
        }
		
		top_state = lo_NewTopState(context, doc_data->url);
		if ((context->compositor && (!doc_layer || !body_layer)) ||
                    top_state == NULL)
		{
#ifdef MEMORY_ARENAS
			if (old_arena != NULL)
			{
				int32 cnt;

				cnt = lo_FreeMemoryArena(old_arena);
			}
#endif /* MEMORY_ARENAS */
			return lo_ProcessTag_OutOfMemory(context, recycle_list, top_state);
		}
#ifdef MEMORY_ARENAS
        if ( ! EDT_IS_EDITOR(context) ) {
		    top_state->first_arena->next = old_arena;
        }
#endif /* MEMORY_ARENAS */

		/* take a special hacks from URL_Struct */
		top_state->auto_scroll =(intn)doc_data->url_struct->auto_scroll;
		top_state->is_binary = doc_data->url_struct->is_binary;
		/*
		 * Security dialogs only for FO_PRESENT docs.
		 */
		if (CLEAR_CACHE_BIT(doc_data->format_out) == FO_PRESENT)
		{
			top_state->security_level =
				doc_data->url_struct->security_on;
		}
		else
		{
			top_state->security_level = 0;
		}
        top_state->doc_layer = doc_layer;
        top_state->body_layer = body_layer;
		top_state->force_reload = doc_data->url_struct->force_reload;
		top_state->nurl = doc_data->url_struct;
		top_state->total_bytes = doc_data->url_struct->content_length;
		top_state->doc_data = doc_data;
		top_state->resize_reload = doc_data->url_struct->resize_reload;

		if (top_state->auto_scroll != 0)
		{
			top_state->scrolling_doc = TRUE;
			if (top_state->auto_scroll < 1)
			{
				top_state->auto_scroll = 1;
			}
			if (top_state->auto_scroll > 1000)
			{
				top_state->auto_scroll = 1000;
			}
		}

		/*
		 * give the parser a pointer to the layout data so we can
		 * get to it next time
		 */
		doc_data->layout_state = top_state;

		/*
		 * Add this new top state to the global state list.
		 */
		if (lo_StoreTopState(doc_id, top_state) == FALSE)
		{
			return lo_ProcessTag_OutOfMemory(context, recycle_list,
							 top_state);
		}

#ifdef EDITOR
		/* LTNOTE: maybe this should be passed into New_Layout.
		 * For now, it is not.
		 */
		top_state->edit_buffer = doc_data->edit_buffer;
#endif
		state = lo_NewLayout(context, width, height,
			margin_width, margin_height, NULL);
		if (state == NULL)
			return lo_ProcessTag_OutOfMemory(context, recycle_list, top_state);
		top_state->doc_state = state;

        /* The DOCUMENT layer's height is always the height of the
           window, at least for the purpose of computing percentage
           heights in child layers. */
        top_state->layers[LO_DOCUMENT_LAYER_ID]->height = 
            state->win_top + state->win_height + state->win_bottom;

        /* Reset the scoping for any JavaScript to be the top-level layer. */
        ET_SetActiveLayer(context, LO_DOCUMENT_LAYER_ID);

		/* RESTORE style stack 
		 */
		if(top_state->resize_reload)
		{
			History_entry *he = SHIST_GetCurrent(&context->hist);
			if (he)
			{
				if(he->savedData.style_stack)
				{
					if(top_state->style_stack)
						STYLESTACK_Delete(top_state->style_stack);
					top_state->style_stack = he->savedData.style_stack;
					he->savedData.style_stack = NULL;
				}
			}
		}
		
		/*
		 * Take care of forms.
		 * If we have been here before, we can pull the old form
		 * data out of the URL_Struct.  It may be invalid if the
		 * form was interrupted.
		 */
		RESTORE_SAVED_DATA(FormList, form_list, doc_data, recycle_list, top_state);
		if (!form_list->valid)
			lo_FreeDocumentFormListData(context, form_list);
		form_list->data_index = 0;

		/*
		 * Take care of embeds.
		 * If we have been here before, we can pull the old embed
		 * data out of the URL_Struct.
		 */
		RESTORE_SAVED_DATA(EmbedList, embed_list, doc_data, recycle_list, top_state);

		/*
		 * Take care of saved grid data.
		 * If we have been here before, we can pull the old
		 * grid data out of the URL_Struct.
		 */
		RESTORE_SAVED_DATA(Grid, grid_data, doc_data, recycle_list, top_state);

		/* XXX just in case a grid had onLoad or onUnload handlers */
		if (!doc_data->url_struct->resize_reload)
		{
			lo_RestoreContextEventHandlers(context, state, tag,
					    &doc_data->url_struct->savedData);
		}

		top_state->recycle_list = recycle_list;

		if (state->top_state->name_target != NULL)
		{
			state->display_blocking_element_id = -1;
		}

		start_element = doc_data->url_struct->position_tag;
		if (start_element == 1)
		{
			start_element = 0;
		}

		if (start_element > 0)
		{
			state->display_blocking_element_id = start_element;
		}

		if ((state->display_blocking_element_id > state->top_state->element_id)||
			(state->display_blocking_element_id  == -1))
		{
			state->display_blocked = TRUE;
		}
		else
		{
			FE_SetDocPosition(context, FE_VIEW, 0, state->base_y);
		}

		if ((Master_backdrop_url != NULL)&&(UserOverride != FALSE)
			&&(context->type != MWContextDialog))
		{
			lo_load_user_backdrop(context, state);
		}
	}

	if (state == NULL)
	{
		return(-1);
	}

    /* 
     * If we're processing an inline stream, we need to make a note of it
     * so that the tag goes into the correct place.
     */
    if (doc_data && doc_data->is_inline_stream && 
        (top_state->doc_data != doc_data)) 
    {
	    if (top_state->input_write_level >= MAX_INPUT_WRITE_LEVEL-1) {
		    top_state->out_of_memory = TRUE;
			return (MK_OUT_OF_MEMORY);
		}
        top_state->input_write_level++;
        top_state->tag_from_inline_stream = TRUE;
    }
    
	if (tag->data != NULL)
	{
		state->top_state->current_bytes += tag->data_len;
	}

	/*
	 * Divert all tags to the current sub-document if there is one.
	 */
	up_state = NULL;
	orig_state = state;
	while (state->sub_state != NULL)
	{
		up_state = state;
		state = state->sub_state;
	}

	orig_state->top_state->layout_status = status;

	/*
	 * The filter checks for all the NOFRAMES, NOEMBED, NOSCRIPT, and
	 * the APPLET ignore setup.
	 */
	if (top_state->layout_blocking_element != NULL)
	{
		lo_BlockTag(context, state, tag);
	}
	else if (lo_FilterTag(context, state, tag) == FALSE)
	{
		PA_FreeTag(tag);
	}
	else
	{
		lo_DocState *tmp_state;
		Bool may_save;
		int32 state_diff;

		if ((state->is_a_subdoc == SUBDOC_CELL)||
			(state->is_a_subdoc == SUBDOC_CAPTION))
		{
			may_save = TRUE;
		}
		else
		{
			may_save = FALSE;
		}

		/*
		 * Reset these so we can tell if anything happened
		 */
		top_state->diff_state = FALSE;
		top_state->state_pushes = 0;
		top_state->state_pops = 0;
		
		lo_LayoutTag(context, state, tag);

		if (top_state->wedged_on_mocha) {
			top_state->wedged_on_mocha = FALSE;
			return(1);
		}

		tmp_state = lo_CurrentSubState(orig_state);

		/* how has our state level changed? */
		state_diff = top_state->state_pushes - top_state->state_pops;
		
		if (may_save != FALSE)
		{
			/*
			 * Store the tag in the correct subdoc.
			 */
			if (state_diff == -1)
			{
				/*
				 * That tag popped us up one state level.  If this new
				 * state is still a subdoc, save the tag there.
				*/
				if ((tmp_state->is_a_subdoc == SUBDOC_CELL)||
				    (tmp_state->is_a_subdoc == SUBDOC_CAPTION))
				{
                    /* if we just popped a table we need to insert
                     * a dummy end tag to pop the dummy start tag
                     * we shove on the stack after createing a table
                     */
   					PA_Tag *new_tag = LO_CreateStyleSheetDummyTag(tag);
					if(new_tag)
					{
						lo_SaveSubdocTags(context, tmp_state, new_tag);
					}

				    lo_SaveSubdocTags(context, tmp_state, tag);
				}
				else
				{
				    PA_FreeTag(tag);
				}
			}
			else if (( up_state != NULL ) &&
				( top_state->diff_state != FALSE ) &&
				( state_diff == 0 ))
			{
				/*
				 * We have returned to the same state level but
				 * we have a different subdoc.
				 */
				if ((up_state->is_a_subdoc == SUBDOC_CELL)||
				     (up_state->is_a_subdoc == SUBDOC_CAPTION))
				{
				    lo_SaveSubdocTags(context, up_state, tag);
				}
				else
				{
				    PA_FreeTag(tag);
				}
			}
			else if (( top_state->diff_state == FALSE ) &&
				( state_diff == 0 ))
			{
				/*
				 * We are still in the same subdoc
				 */
				lo_SaveSubdocTags(context, state, tag);
			}
			else if (( state_diff == 1 ))
			{
				PA_Tag *new_tag;

				/*
				 * That tag started a new, nested subdoc.
				 * Add the starting tag to the parent.
				 */
				lo_SaveSubdocTags(context, state, tag);
				/*
				 * Since we have extended the parent chain,
				 * we need to reset the child to the new
				 * parent end-chain.
				 */
				if ((tmp_state->is_a_subdoc == SUBDOC_CELL)||
				    (tmp_state->is_a_subdoc == SUBDOC_CAPTION))
				{
					tmp_state->subdoc_tags =
						state->subdoc_tags_end;

					/* add an aditional dummy tag so that style sheets
					 * can use it to query styles from for this entry
					 * that created a table
					 */
					new_tag = LO_CreateStyleSheetDummyTag(tag);
					if(new_tag)
					{
						lo_SaveSubdocTags(context, tmp_state, new_tag);
					}
				}
			}
			/*
			 * This can never happen.
			 */
			else
			{
				PA_FreeTag(tag);
			}

			state = tmp_state;
		}
		else
		{
			if(state_diff == 1)
			{
				/* everytime a table is started we need to save the
				 * tag that created it so that style sheets can apply
				 * styles to it.  So we will save even in the "dont save"
				 * case
				 */
				PA_Tag *new_tag = LO_CreateStyleSheetDummyTag(tag);
				if(new_tag)
				{
					/* We just pushed another table into a previous table 
					 * we need to do some table magic to make the subdoc
					 * list work right
					 */
					lo_SaveSubdocTags(context, tmp_state, new_tag);
				}
			}                                            
            else if(state_diff == -1)
            {
                /* if we just popped a table we need to insert
                 * a dummy end tag to pop the dummy start tag
                 * we shove on the stack after createing a table
                 */
   				PA_Tag *new_tag = LO_CreateStyleSheetDummyTag(tag);
				if(new_tag)
				{
					lo_SaveSubdocTags(context, tmp_state, new_tag);
				}
            }

			PA_FreeTag(tag);
		}
	}

    if (doc_data && doc_data->is_inline_stream && 
        (top_state->doc_data != doc_data))
    {
        top_state->input_write_level--;
    }
    top_state->tag_from_inline_stream = FALSE;

	if (state->top_state->out_of_memory != FALSE)
	{
		lo_FinishLayout_OutOfMemory(context, state);
		return(MK_OUT_OF_MEMORY);
	}

	/*
	 * The parser (libparse/pa_parse.c) only calls us with these status
	 * codes when tag is null, and we handled that case far above (in fact,
	 * we will crash less far above on a null tag pointer, so we can't get
	 * here when aborting or completing).
	 */
	XP_ASSERT(status != PA_ABORT && status != PA_COMPLETE);

	return(1);
}

void
lo_RefreshDocumentArea(MWContext *context, lo_DocState *state, int32 x, int32 y,
	uint32 width, uint32 height)
{
	LO_Element *eptr;
	int32 i;
	int32 top, bottom;

	if (state->display_blocked != FALSE)
	{
		return;
	}
	
	/*	
	 * Find which lines intersect the drawable region.
	 */
	lo_RegionToLines (context, state, x, y, width, height, FALSE,
		&top, &bottom);

	/*
	 * Display all these lines, if there are any.
	 */
	if (bottom >= 0)
	{
		for (i=top; i <= bottom; i++)
		{
			lo_DisplayLine(context, state, i,
				x, y, width, height);
		}
	}

	/*
	 * Check the floating element list.
	 */
	eptr = state->float_list;
	while (eptr != NULL)
	{
		/* I'm not sure exactly why we need to do this, but this code
           has been here, in some form or another, since this file was
           first created -- fur. */
	    if (eptr->type == LO_SUBDOC)
	    {
			LO_SubDocStruct *subdoc;
			lo_DocState *sub_state;
		
			subdoc = (LO_SubDocStruct *)eptr;
			sub_state = (lo_DocState *)subdoc->state;

			if (sub_state == NULL)
				break;

			if (sub_state->base_y < 0)
				sub_state->display_blocked = FALSE;
	    }
		lo_DisplayElement(context, eptr,
						  state->base_x, state->base_y,
						  x, y, width, height);
	    eptr = eptr->lo_any.next;
	}
}


/*************************************
 * Function: LO_RefreshArea
 *
 * Description: This is a public function that allows the front end
 *	to ask that a particulat portion of the document be refreshed.
 *	The layout then finds the relevant elements, and calls
 *	the appropriate front end functions to display those
 *	elements.
 *
 * Params: Window context, x, y position of upper left corner,
 *	and width X height of the rectangle.
 *
 * Returns: Nothing
 *************************************/
void
LO_RefreshArea(MWContext *context, int32 x, int32 y,
	uint32 width, uint32 height)
{
	int32 doc_id;
	lo_TopState *top_state;
	lo_DocState *state;
	
	/*
	 * Get the unique document ID, and retreive this
	 * documents layout state.
	 */
	doc_id = XP_DOCID(context);
	top_state = lo_FetchTopState(doc_id);
	if ((top_state == NULL)||(top_state->doc_state == NULL))
	{
		return;
	}
	state = top_state->doc_state;

	lo_RefreshDocumentArea(context, state, x, y, width, height);
}


/*************************************
 * Function: lo_GetLineEnds
 *
 * Description: This is a private function that returns the line ends for
 * a particular line.
 *
 * Params: Window context, line number.
 *
 * Returns: A pointer to the first and last elements in the line at that location,
 *	or NULLs if there is no matching line.
 *************************************/
void
lo_GetLineEnds(MWContext *context, lo_DocState *state,
	int32 line, LO_Element** retBegin, LO_Element** retEnd)
{
	LO_Element **line_array;

	*retBegin = NULL;
	*retEnd = NULL;

	/*
	 * No document state yet.
	 */
	if (state == NULL)
	{
		return;
	}

	if (line >= 0)
#ifdef XP_WIN16
	{
		intn a_size;
		intn a_indx;
		intn a_line;
		XP_Block *larray_array;

		a_size = SIZE_LIMIT / sizeof(LO_Element *);
		a_indx = (intn)(line / a_size);
		a_line = (intn)(line - (a_indx * a_size));

		XP_LOCK_BLOCK(larray_array, XP_Block *, state->larray_array);
		state->line_array = larray_array[a_indx];
		XP_LOCK_BLOCK(line_array, LO_Element **, state->line_array);
		*retBegin = line_array[a_line];

		if (line >= (state->line_num - 2))
		{
			*retEnd = NULL;
		}
		else
		{
			if ((a_line + 1) == a_size)
			{
				XP_UNLOCK_BLOCK(state->line_array);
				state->line_array = larray_array[a_indx + 1];
				XP_LOCK_BLOCK(line_array, LO_Element **,
					state->line_array);
				*retEnd = line_array[0];
			}
			else
			{
				*retEnd = line_array[a_line + 1];
			}
		}
		XP_UNLOCK_BLOCK(state->line_array);
		XP_UNLOCK_BLOCK(state->larray_array);
	}
#else
	{
		XP_LOCK_BLOCK(line_array, LO_Element **, state->line_array);
		*retBegin = line_array[line];
		if (line == (state->line_num - 2))
		{
			*retEnd = NULL;
		}
		else
		{
			*retEnd = line_array[line + 1];
		}
		XP_UNLOCK_BLOCK(state->line_array);
	}
#endif /* XP_WIN16 */
}

Bool lo_IsDummyLayoutElement(LO_Element *ele)
{
	Bool isDummy = FALSE;
	switch(ele->lo_any.type)
	{
		case LO_PARAGRAPH:
		case LO_CENTER:
		case LO_MULTICOLUMN:
		case LO_FLOAT:
		case LO_TEXTBLOCK:
		case LO_LIST:
		case LO_DESCTITLE:
		case LO_DESCTEXT:
		case LO_BLOCKQUOTE:
		case LO_LAYER:
		case LO_HEADING:
		case LO_SPAN:
		case LO_DIV:
			isDummy = TRUE;
			break;
	}
	return isDummy;
}

/*************************************
 * Function: lo_XYToDocumentElement
 *
 * Description: This is a public function that allows the front end
 *	to ask for the particular element residing at the passed
 *	(x, y) position in the window.  This is very important
 *	for resolving button clicks on anchors.
 *
 * Params: Window context, x, y position of the window location.
 *
 * Returns: A pointer to the element record at that location,
 *	or NULL if there is no matching element.
 *************************************/
LO_Element *
lo_XYToDocumentElement2(MWContext *context, lo_DocState *state,
	int32 x, int32 y, Bool check_float, Bool into_cells, Bool into_ilayers,
    Bool editMode, int32 *ret_x, int32 *ret_y)
{
	Bool in_table, is_inflow_layer;
	int32 line;
	LO_Element *tptr, *eptr;
	LO_Element *end_ptr;
	LO_Element *tptrTable = NULL;
	LO_Element *tptrCell = NULL;

	/*
	 * No document state yet.
	 */
	if (state == NULL)
	{
		return(NULL);
	}

	tptr = NULL;

	line = lo_PointToLine(context, state, x, y);

#ifdef LOCAL_DEBUG
XP_TRACE(("lo_PointToLine says line %d\n", line));
#endif /* LOCAL_DEBUG */

    lo_GetLineEnds(context, state, line, &tptr, &end_ptr);

	in_table = FALSE;
	while (tptr != end_ptr)
	{
		int32 y2;
        int32 x1;
		int32 t_width, t_height;

		if (lo_IsDummyLayoutElement(tptr))
		{
			tptr = tptr->lo_any.next;
			continue;
		}

        x1 = tptr->lo_any.x + tptr->lo_any.x_offset;
 
		t_width = tptr->lo_any.width;
		/*
		 * Images need to account for border width
		 */
		if (tptr->type == LO_IMAGE)
		{
			t_width = t_width + (2 * tptr->lo_image.border_width);
            /*
             * If we're editing, images also account for their border space
             */
            if ( editMode )
            {
                t_width += 2 * tptr->lo_image.border_horiz_space;
                x1 -= tptr->lo_image.border_horiz_space;
            }
		}
		if (t_width <= 0)
		{
			t_width = 1;
		}

		t_height = tptr->lo_any.height;
		/*
		 * Images need to account for border width
		 */
		if (tptr->type == LO_IMAGE)
		{
			t_height = t_height + (2 * tptr->lo_image.border_width);

            /*
             * If we're editing, images also account for their border space
             */
            if ( editMode )
            {
                t_height += 2 * tptr->lo_image.border_vert_space;
            }
		}

        is_inflow_layer =
            (tptr->type == LO_CELL) && tptr->lo_cell.cell_inflow_layer;
                
		if (!in_table && !is_inflow_layer)
		{
			y2 = tptr->lo_any.y + tptr->lo_any.line_height;
			if (tptr->lo_any.line_height <= 0)
			{
				y2 = tptr->lo_any.y + tptr->lo_any.y_offset + t_height;
			}
		}
		else
		{
			y2 = tptr->lo_any.y + tptr->lo_any.y_offset +
				t_height;
		}
		if ((y >= tptr->lo_any.y)&&(y < y2)&&
			(x >= tptr->lo_any.x)&&
			(x < (x1 + t_width)))
		{
                        
			/*
			 * Tables are containers.  Don't stop on them,
			 * look inside them.
			 */
			if (tptr->type != LO_TABLE)
			{
                /* Inflow layers look just like table cells, but we
                   always look inside them, as if the layout elements
                   were part of the line's list of element's. */
                if (is_inflow_layer && into_ilayers) {
                    eptr = lo_XYToCellElement(context, state,
                                              (LO_CellStruct *)tptr, x, y,
                                              TRUE, into_cells, into_ilayers);
                    if (eptr) {
                        tptr = eptr;
                        break;
                    }

                    /* No matching element found inside cell, keep
                       looking on rest of line. */
                } else
                    break;
			}
			else
			{
				in_table = TRUE;
                /*
                 * Save the table element
                 *  to return if no other element is found
                */
                tptrTable = tptr;
			}
		}
		tptr = tptr->lo_any.next;
	}
	if (tptr == end_ptr)
	{
		tptr = NULL;
	}

	/*
	 * Check the floating element list.
	 */
	if ((tptr == NULL)&&(check_float != FALSE))
	{
		LO_Element *eptr;

		eptr = state->float_list;
		while (eptr != NULL)
		{
			int32 t_width, t_height;

			t_width = eptr->lo_any.width;
			/*
			 * Images need to account for border width
			 */
			if (eptr->type == LO_IMAGE)
			{
				t_width = t_width +
					(2 * eptr->lo_image.border_width);
			}
			if (t_width <= 0)
			{
				t_width = 1;
			}

			t_height = eptr->lo_any.height;
			/*
			 * Images need to account for border width
			 */
			if (eptr->type == LO_IMAGE)
			{
				t_height = t_height +
					(2 * eptr->lo_image.border_width);
			}

			if ((y >= eptr->lo_any.y)&&
				(y < (eptr->lo_any.y + eptr->lo_any.y_offset +
					t_height)))
			{
				if ((x >= eptr->lo_any.x)&&
					(x < (eptr->lo_any.x +
						eptr->lo_any.x_offset +
						t_width)))
				{
					/*
					 * Tables are containers.
					 * Don't stop on them,
					 * look inside them.
					 */
					if (eptr->type != LO_TABLE)
					{
						break;
					}
				}
			}
			eptr = eptr->lo_any.next;
		}
		tptr = eptr;
	}

	if ((tptr != NULL)&&(tptr->type == LO_SUBDOC))
	{
		LO_SubDocStruct *subdoc;
		int32 new_x, new_y;
		int32 ret_new_x, ret_new_y;

		subdoc = (LO_SubDocStruct *)tptr;

		new_x = x - (subdoc->x + subdoc->x_offset +
			subdoc->border_width);
		new_y = y - (subdoc->y + subdoc->y_offset +
			subdoc->border_width);
		tptr = lo_XYToDocumentElement(context,
			(lo_DocState *)subdoc->state, new_x, new_y, check_float,
			into_cells, into_ilayers, &ret_new_x, &ret_new_y);
		x = ret_new_x;
		y = ret_new_y;
	}
	else if ((tptr != NULL)&&(tptr->type == LO_CELL)&&(into_cells != FALSE))
	{
        /*
         * Save the cell element
         *  to return if no other element is found
        */
        tptrCell = tptr;
		tptr = lo_XYToCellElement(context, state,
			(LO_CellStruct *)tptr, x, y, TRUE, into_cells, into_ilayers);
	}

	*ret_x = x;
	*ret_y = y;

	return(tptr);
}

LO_Element *
lo_XYToDocumentElement(MWContext *context, lo_DocState *state,
    int32 x, int32 y, Bool check_float, Bool into_cells, Bool into_ilayers,
	int32 *ret_x, int32 *ret_y)
{
    return lo_XYToDocumentElement2(context, state, x, y, check_float, into_cells, into_ilayers, FALSE, ret_x, ret_y);
}

/*************************************
 * Function: LO_XYToElement
 *
 * Description: This is a public function that allows the front end
 *	to ask for the particular element residing at the passed
 *	(x, y) position in the window.  This is very important
 *	for resolving button clicks on anchors.
 *
 * Params: Window context, x, y position of the window location.
 *
 * Returns: A pointer to the element record at that location,
 *	or NULL if there is no matching element.
 *************************************/
LO_Element *
LO_XYToElement(MWContext *context, int32 x, int32 y, CL_Layer *layer)
{
	int32 doc_id;
	lo_TopState *top_state;
	lo_DocState *state;
	LO_Element *tptr = NULL;
	int32 ret_x, ret_y;
    LO_CellStruct *layer_cell;

	/*
	 * Get the unique document ID, and retreive this
	 * documents layout state.
	 */
	doc_id = XP_DOCID(context);
	top_state = lo_FetchTopState(doc_id);
	if ((top_state == NULL)||(top_state->doc_state == NULL))
	{
		return(NULL);
	}
	state = top_state->doc_state;

    layer_cell = lo_GetCellFromLayer(context, layer);
    
    if (layer_cell != NULL) {
        tptr = lo_XYToCellElement(context,
                                  state,
                                  layer_cell,
                                  x, y, TRUE, TRUE, TRUE);
    }
    else
        tptr = lo_XYToDocumentElement(context, state, x, y, TRUE, TRUE, TRUE,
                                      &ret_x, &ret_y);

	if ((tptr == NULL)&&(top_state->is_grid != FALSE))
	{
		tptr = lo_XYToGridEdge(context, state, x, y);
	}

	/*
	 * Make sure we do not return an invisible edge.
	 */
	if ((tptr != NULL)&&(tptr->type == LO_EDGE)&&
		(tptr->lo_edge.visible == FALSE))
	{
		tptr = NULL;
	}
	return(tptr);
}

static void
lo_set_image_info(MWContext *context, int32 ele_id, int32 width, int32 height)
{
	int32 doc_id;
	lo_TopState *top_state;
	lo_DocState *main_doc_state;
	lo_DocState *state;
	LO_ImageStruct *image;

#ifdef EDITOR
	if ( ele_id == ED_IMAGE_LOAD_HACK_ID ){
		EDT_SetImageInfo( context, ele_id, width, height );
		return;
	}
#endif


	/*
	 * Get the unique document ID, and retreive this
	 * documents layout state.
	 */
	doc_id = XP_DOCID(context);
	top_state = lo_FetchTopState(doc_id);
	if ((top_state == NULL)||(top_state->doc_state == NULL))
	{
		return;
	}
	main_doc_state = top_state->doc_state;
	state = lo_CurrentSubState(main_doc_state);

	/*
	 * If the font stack has already been freed, we are too far gone
	 * to deal with delayed images coming in.  Ignore them.
	 */
	if (state->font_stack == NULL)
	{
		top_state->layout_blocking_element = NULL;
		return;
	}

	if ((top_state->layout_blocking_element != NULL)&&
		(top_state->layout_blocking_element->lo_any.ele_id == ele_id))
	{
		image = (LO_ImageStruct *)top_state->layout_blocking_element;

		if (image->image_attr->attrmask & LO_ATTR_BACKDROP)
		{
			return;
		}

		image->width = width;
		image->height = height;
		lo_FinishImage(context, state, image);
		lo_FlushBlockage(context, state, main_doc_state);
	}
	else if (top_state->tags != NULL)
	{
		PA_Tag *tag_ptr;

		tag_ptr = top_state->tags;
		while (tag_ptr != NULL)
		{
			if (((tag_ptr->type == P_IMAGE)||
				(tag_ptr->type == P_NEW_IMAGE))&&
				(tag_ptr->lo_data != NULL)&&
				(tag_ptr->is_end == FALSE))
			{
				image = (LO_ImageStruct *)tag_ptr->lo_data;
				if (image->ele_id == ele_id)
				{
					image->width = width;
					image->height = height;
					break;
				}
			}
			tag_ptr = tag_ptr->next;
		}
	}
}

typedef struct setImageInfoClosure_struct
{
    MWContext      *context;
    int32           ele_id;
    int32           width;
    int32           height;
    struct setImageInfoClosure_struct
                   *next;
} setImageInfoClosure;

static int8 deferred_list_busy = 0;
static int8 destroy_deferred_list = 0;

/* List of images for which image size info is available,
   but for which the layout process is deferred */                             
static setImageInfoClosure *image_info_deferred_list;
static void *deferred_image_info_timeout = NULL;

static void
lo_discard_unprocessed_deferred_images(MWContext *context)
{                                    
    setImageInfoClosure *c, *prev_c;

	prev_c = NULL;
	c = image_info_deferred_list;
    if (deferred_list_busy)
    {
        /* set a flag to cause the list to get destroyed
         * as soon as we are not reentrant
         */
        destroy_deferred_list = TRUE;
        return;
    }
    else
    {
        destroy_deferred_list = FALSE;
    }

    while (c) {
		if(c->context == context) {
			setImageInfoClosure *free_tmp;

			if(prev_c)
				prev_c->next = c->next;
			else
				image_info_deferred_list = c->next;

			free_tmp = c;
			c = c->next;
            XP_FREE(free_tmp);
		}
		else
		{
			prev_c = c;
			c = c->next;
		}
    }
}

/* Handle image dimension information that has been reported to
   layout since the last time we were called. */
static void
lo_process_deferred_image_info(void *closure)
{
    setImageInfoClosure *c, *next_c;

	/* Don't allow reentrant calls. */
	if (deferred_list_busy)
		return;

    deferred_list_busy = 1;
    for (c = image_info_deferred_list; c; c = next_c) {
        next_c = c->next;
        lo_set_image_info(c->context, c->ele_id, c->width, c->height);

        if(destroy_deferred_list)
        {
            /* discared the images for the context that just tried to
             * call lo_discard_unprocessed_deferred_images.  After
             * that continue to pump this list until empty.
             */
            image_info_deferred_list = c;
            deferred_list_busy = FALSE;
            lo_discard_unprocessed_deferred_images(image_info_deferred_list->context);
            deferred_list_busy = TRUE;
            next_c = image_info_deferred_list;
        }
        else
        {
            XP_FREE(c);
        }
    }
    deferred_list_busy = 0;
    image_info_deferred_list = NULL;
    deferred_image_info_timeout = NULL;
}

/* Tell layout the dimensions of an image.  Actually, to avoid subtle
   bugs involving mutual recursion between the imagelib and the
   netlib, we don't process this information immediately, but rather
   thread it on a list to be processed later. */
void
LO_SetImageInfo(MWContext *context, int32 ele_id, int32 width, int32 height)
{
    setImageInfoClosure *c = XP_NEW(setImageInfoClosure);

    if (! c)
        return;
    c->context  = context;
    c->ele_id   = ele_id;
    c->width    = width;
    c->height   = height;
    c->next     = image_info_deferred_list;
    image_info_deferred_list = c;

    if (! deferred_image_info_timeout) {
        deferred_image_info_timeout = 
            FE_SetTimeout(lo_process_deferred_image_info, NULL, 0);
    }
}

void
lo_DeleteDocLists(MWContext *context, lo_DocLists *doc_lists)
{
	if ( doc_lists->url_list != NULL)
	{
		int i;
		LO_AnchorData **anchor_array;

#ifdef XP_WIN16
		{
		XP_Block *ulist_array;
		int32 j;
		intn a_size, a_indx, a_url;

		a_size = SIZE_LIMIT / sizeof(XP_Block *);
		a_indx = doc_lists->url_list_len / a_size;
		a_url = doc_lists->url_list_len - (a_indx * a_size);

		XP_LOCK_BLOCK(ulist_array, XP_Block *, doc_lists->ulist_array);
		for (j=0; j < (doc_lists->ulist_array_size - 1); j++)
		{
			doc_lists->url_list = ulist_array[j];
			XP_LOCK_BLOCK(anchor_array, LO_AnchorData **,
				doc_lists->url_list);
			for (i=0; i < a_size; i++)
			{
				if (anchor_array[i] != NULL)
				{
					lo_DestroyAnchor(anchor_array[i]);
				}
			}
			XP_UNLOCK_BLOCK(doc_lists->url_list);
			XP_FREE_BLOCK(doc_lists->url_list);
		}
		doc_lists->url_list = ulist_array[doc_lists->ulist_array_size - 1];
		XP_LOCK_BLOCK(anchor_array, LO_AnchorData **,
			doc_lists->url_list);
		for (i=0; i < a_url; i++)
		{
			if (anchor_array[i] != NULL)
			{
				lo_DestroyAnchor(anchor_array[i]);
			}
		}
		XP_UNLOCK_BLOCK(doc_lists->url_list);
		XP_FREE_BLOCK(doc_lists->url_list);

		XP_UNLOCK_BLOCK(doc_lists->ulist_array);
		XP_FREE_BLOCK(doc_lists->ulist_array);
		}
#else
		XP_LOCK_BLOCK(anchor_array, LO_AnchorData **,
			doc_lists->url_list);
		for (i=0; i < doc_lists->url_list_len; i++)
		{
			if (anchor_array[i] != NULL)
			{
				lo_DestroyAnchor(anchor_array[i]);
			}
		}
		XP_UNLOCK_BLOCK(doc_lists->url_list);
		XP_FREE_BLOCK(doc_lists->url_list);
#endif /* XP_WIN16 */
		doc_lists->url_list = NULL;
		doc_lists->url_list_len = 0;
	}

	if (doc_lists->name_list != NULL)
	{
		lo_NameList *nptr;

		nptr = doc_lists->name_list;
		while (nptr != NULL)
		{
			lo_NameList *name_rec;

			name_rec = nptr;
			nptr = nptr->next;
			if (name_rec->name != NULL)
			{
				PA_FREE(name_rec->name);
			}
			XP_DELETE(name_rec);
		}
		doc_lists->name_list = NULL;
        doc_lists->anchor_count = 0;
	}

	if ( doc_lists->form_list != NULL)
	{
		lo_FormData *fptr;

		fptr = doc_lists->form_list;
		while (fptr != NULL)
		{
			lo_FormData *form;

			form = fptr;
			fptr = fptr->next;
			if (form->name != NULL)
			{
				PA_FREE(form->name);
			}
			if (form->action != NULL)
			{
				PA_FREE(form->action);
			}
			if (form->form_elements != NULL)
			{
				PA_FREE(form->form_elements);
			}
			XP_DELETE(form);
		}
		doc_lists->form_list = NULL;
        doc_lists->current_form_num = 0;
	}

    doc_lists->embed_list = NULL;
    doc_lists->embed_list_count = 0;
    doc_lists->applet_list = NULL;
    doc_lists->applet_list_count = 0;
    doc_lists->image_list = doc_lists->last_image = NULL;
    doc_lists->image_list_count = 0;
}

/* XXX need to pass resize_reload through FE_FreeGridWindow */
static pa_DocData *lo_internal_doc_data;

LO_Element *
lo_InternalDiscardDocument(MWContext *context, lo_DocState *state,
						   pa_DocData *doc_data, Bool bWholeDoc)
{
	int32 doc_id;
	LO_Element *recycle_list;
	LO_Element **line_array;
	LO_TextAttr **text_attr_hash;

	/* Look in doc_data->url_struct for the *new* document's reload flag. */
	JSBool resize_reload = 
		(JSBool)(doc_data != NULL && doc_data->url_struct->resize_reload);

    /* XXX We're not getting rid of stuff on the deferred image list when
     * the editor does a relayout. The problem is that there might still be
     * entries on the list that corresponds to the old state of the document.
     */
    if (bWholeDoc)
	    lo_discard_unprocessed_deferred_images(context);

	/*
	 * Order is crucial here: run the onunload handlers in a frames tree
	 * from the top down, but destroy frames bottom-up.  Look for the
	 * LM_ReleaseDocument call below, almost at the end of this function.
	 * This way, onunload handlers get to use their kids' properties, and
	 * kids get to use their parent's properties and functions.
	 */
	if (bWholeDoc)
	{
		pa_DocData *old_doc_data;
		
		old_doc_data = state->top_state->doc_data;
		if (old_doc_data != NULL)
		{
			NET_StreamClass *parser_stream;
			parser_stream = old_doc_data->parser_stream;
			parser_stream->abort(parser_stream, 0);
			state->top_state->doc_data = NULL;
		}
	}

	if ( state->top_state->trash != NULL)
	{
		lo_RecycleElements(context, state,
			state->top_state->trash);
		state->top_state->trash = NULL;
	}

	if (state->float_list != NULL)
	{
		lo_RecycleElements(context, state, state->float_list);
		state->float_list = NULL;
	}

	if (state->line_array != NULL)
	{
		LO_Element *eptr;
		LO_Element **line_array_tmp;

#ifdef XP_WIN16
		{
			XP_Block *larray_array;
			intn cnt;

			XP_LOCK_BLOCK(larray_array, XP_Block *,
					state->larray_array);
			cnt = state->larray_array_size - 1;
			while (cnt > 0)
			{
				XP_FREE_BLOCK(larray_array[cnt]);
				cnt--;
			}
			state->line_array = larray_array[0];
			XP_UNLOCK_BLOCK(state->larray_array);
			XP_FREE_BLOCK(state->larray_array);
		}
#endif /* XP_WIN16 */

		eptr = NULL;
		XP_LOCK_BLOCK(line_array, LO_Element **, state->line_array);
		eptr = line_array[0];
		line_array_tmp = (LO_Element **)state->line_array;
		state->line_array = NULL;

        /* If this is a relayout delete, the elements are already owned by the document. So
         * don't recycle them.
         */
		if ( bWholeDoc && eptr != NULL)
		{
			lo_RecycleElements(context, state, eptr);
		}
		XP_UNLOCK_BLOCK(line_array_tmp);
		XP_FREE_BLOCK(line_array_tmp);
	}

	if ( bWholeDoc && state->top_state->text_attr_hash != NULL)
	{
		LO_TextAttr *attr;
		LO_TextAttr *attr_ptr;
		int i;

		XP_LOCK_BLOCK(text_attr_hash, LO_TextAttr **,
			state->top_state->text_attr_hash);
		for (i=0; i<FONT_HASH_SIZE; i++)
		{
			attr_ptr = text_attr_hash[i];
			while (attr_ptr != NULL)
			{
				attr = attr_ptr;
				if (attr->FE_Data != NULL)
				{
					FE_ReleaseTextAttrFeData(context, attr);
					attr->FE_Data = NULL;
				}
				attr_ptr = attr_ptr->next;
				XP_DELETE(attr);
			}
			text_attr_hash[i] = NULL;
		}
		XP_UNLOCK_BLOCK(state->top_state->text_attr_hash);
		XP_FREE_BLOCK(state->top_state->text_attr_hash);
		state->top_state->text_attr_hash = NULL;
	}

	if ( bWholeDoc && state->top_state->font_face_array != NULL)
	{
		int i;
		char **face_list;

		PA_LOCK(face_list, char **, state->top_state->font_face_array);
		for (i=0; i < state->top_state->font_face_array_len; i++)
		{
			XP_FREE(face_list[i]);
		}
		PA_UNLOCK(state->top_state->font_face_array);
		PA_FREE(state->top_state->font_face_array);
		state->top_state->font_face_array = NULL;
		state->top_state->font_face_array_len = 0;
		state->top_state->font_face_array_size = 0;
	}

	if ( bWholeDoc && state->top_state->map_list != NULL)
	{
		lo_MapRec *map_list;

		map_list = state->top_state->map_list;
		while (map_list != NULL)
		{
			map_list = lo_FreeMap(map_list);
		}
		state->top_state->map_list = NULL;
	}

    if (bWholeDoc)
        lo_DeleteDocLists(context, &state->top_state->doc_lists);
    else if (state->top_state->doc_lists.name_list != NULL)
	{
		lo_NameList *nptr;

		nptr = state->top_state->doc_lists.name_list;
		while (nptr != NULL)
		{
			lo_NameList *name_rec;

			name_rec = nptr;
			nptr = nptr->next;
			if (name_rec->name != NULL)
			{
				PA_FREE(name_rec->name);
			}
			XP_DELETE(name_rec);
		}
		state->top_state->doc_lists.name_list = NULL;
	}

	if ( bWholeDoc && state->top_state->the_grid != NULL)
	{
		pa_DocData *save;
		lo_GridRec *grid;
		lo_GridCellRec *cell_ptr;
		Bool incomplete;

		save = lo_internal_doc_data;
		lo_internal_doc_data = doc_data;
		grid = state->top_state->the_grid;
		cell_ptr = grid->cell_list;
		/*
		 * If this grid document finished loading, save
		 * its grid data to be restored when we return through
		 * history.
		 * We still have to free the FE grid windows.
		 */
		incomplete = (state->top_state->layout_status != PA_COMPLETE);
		if (incomplete == FALSE)
		{
			while (cell_ptr != NULL)
			{
				intn cell_status;
				char *old_url;

				cell_status =
					lo_GetCurrentGridCellStatus(cell_ptr);

				/*
				 * Only save cell history for COMPLETE cells.
				 */
				if (cell_status == PA_COMPLETE)
				{
				    /*
				     * free the original url for this cell.
				     */
				    old_url = cell_ptr->url;
				    if (cell_ptr->url != NULL)
				    {
						XP_FREE(cell_ptr->url);
				    }

				    /*
				     * Fetch the url for the current contents
				     * of the cell, and save that.
				     */
				    cell_ptr->url =
					lo_GetCurrentGridCellUrl(cell_ptr);

				    cell_ptr->hist_list = NULL;
				    if (cell_ptr->context != NULL)
				    {
					cell_ptr->hist_list = FE_FreeGridWindow(
						cell_ptr->context, TRUE);
				    }
				}
				else
				{
				    incomplete = TRUE;
				    cell_ptr->hist_list = NULL;
				    if (cell_ptr->context != NULL)
				    {
					(void)FE_FreeGridWindow(
						cell_ptr->context, FALSE);
				    }
				}
				cell_ptr->context = NULL;

				cell_ptr = cell_ptr->next;
			}
		}
		/*
		 * Check again in case one or more frames were incomplete.
		 * Also, some special windows have no history (such as
		 * the document info window).  We can't save grid
		 * data for those windows and should free it instead.
		 */
		if ((incomplete == FALSE)&&
			(state->top_state->savedData.Grid != NULL))
		{
			state->top_state->savedData.Grid->the_grid =
				state->top_state->the_grid;
			state->top_state->savedData.Grid->main_width =
				grid->main_width;
			state->top_state->savedData.Grid->main_height =
				grid->main_height;
		}
		/*
		 * Else free up everything now.
		 */
		else
		{
			cell_ptr = grid->cell_list;
			/*
			 * It is important to NULL out the cell_list
			 * now because FE_FreeGridWindow() could lead to
			 * somone trying to traverse it while we are in
			 * the middle of deleting it.
			 */
			grid->cell_list = NULL;
			while (cell_ptr != NULL)
			{
				lo_GridCellRec *tmp_cell;

				if (cell_ptr->context != NULL)
				{
					/*
					 * The context still owns its history
					 * list, so null the cell's pointer to
					 * be clear.  If this cell doesn't have
					 * a context, then it owns the history
					 * list and lo_FreeGridCellRec() will
					 * free it.
					 */
					cell_ptr->hist_list = NULL;
					(void)FE_FreeGridWindow(
						cell_ptr->context, FALSE);
				}
				tmp_cell = cell_ptr;
				cell_ptr = cell_ptr->next;
				lo_FreeGridCellRec(context, grid, tmp_cell);
			}
			lo_FreeGridRec(grid);
		}
		state->top_state->the_grid = NULL;
		lo_internal_doc_data = save;
	}

	if ( bWholeDoc && state->top_state->url != NULL)
	{
		XP_FREE(state->top_state->url);
		state->top_state->url = NULL;
	}

	if ( bWholeDoc )
	{
		XP_FREEIF(state->top_state->inline_stream_blocked_base_url);
        XP_FREEIF(state->top_state->main_stream_blocked_base_url);
        XP_FREEIF(state->top_state->base_url);
	}

	if ( bWholeDoc && state->top_state->base_target != NULL)
	{
		XP_FREE(state->top_state->base_target);
		state->top_state->base_target = NULL;
	}

	if ( bWholeDoc && state->top_state->name_target != NULL)
	{
		XP_FREE(state->top_state->name_target);
		state->top_state->name_target = NULL;
	}

#ifdef HTML_CERTIFICATE_SUPPORT
	if ( bWholeDoc && state->top_state->cert_list != NULL)
	{
		lo_Certificate *cptr;

		cptr = state->top_state->cert_list;
		while (cptr != NULL)
		{
			lo_Certificate *lo_cert;

			lo_cert = cptr;
			cptr = cptr->next;
			if (lo_cert->name != NULL)
			{
				PA_FREE(lo_cert->name);
			}
			if (lo_cert->cert != NULL)
			{
				CERT_DestroyCertificate(lo_cert->cert);
			}
			XP_DELETE(lo_cert);
		}
		state->top_state->cert_list = NULL;
	}
#endif /* HTML_CERTIFICATE_SUPPORT */

	if (bWholeDoc && state->top_state->unknown_head_tag != NULL)
	{
		XP_FREE(state->top_state->unknown_head_tag);
		state->top_state->unknown_head_tag = NULL;
	}

	if(resize_reload)
	{
		/* LJM: Save the JSS state in the hist so that we can get it
	 	 * once the resize is complete
		 */
		History_entry *he = SHIST_GetCurrent(&context->hist);
		if (he)
		{
			he->savedData.style_stack = state->top_state->style_stack;
			state->top_state->style_stack = 0;
		}
		else
		{
			if (state->top_state->style_stack != 0)
				STYLESTACK_Delete(state->top_state->style_stack);
			state->top_state->style_stack = 0;
		}
	}
	else
	{
		if (state->top_state->style_stack != 0)
			STYLESTACK_Delete(state->top_state->style_stack);
		state->top_state->style_stack = 0;
	}

	/* XXX all this because grid docs are never re-parsed after first load. */
	if (!resize_reload &&
		((state->top_state->savedData.OnLoad != NULL)||
		 (state->top_state->savedData.OnUnload != NULL)||
		 (state->top_state->savedData.OnFocus != NULL)||
		 (state->top_state->savedData.OnBlur != NULL)||
		 (state->top_state->savedData.OnHelp != NULL)||
		 (state->top_state->savedData.OnMouseOver != NULL)||
		 (state->top_state->savedData.OnMouseOut != NULL)||
		 (state->top_state->savedData.OnDragDrop != NULL)||
		 (state->top_state->savedData.OnMove != NULL)||
		 (state->top_state->savedData.OnResize != NULL)))
	{
		History_entry *he = SHIST_GetCurrent(&context->hist);
		if (he)
		{
			he->savedData.OnLoad = state->top_state->savedData.OnLoad;
			he->savedData.OnUnload = state->top_state->savedData.OnUnload;
			he->savedData.OnFocus = state->top_state->savedData.OnFocus;
			he->savedData.OnBlur = state->top_state->savedData.OnBlur;
 			he->savedData.OnHelp = state->top_state->savedData.OnHelp;
 			he->savedData.OnMouseOver = state->top_state->savedData.OnMouseOver;
 			he->savedData.OnMouseOut = state->top_state->savedData.OnMouseOut;
 			he->savedData.OnDragDrop = state->top_state->savedData.OnDragDrop;
 			he->savedData.OnMove = state->top_state->savedData.OnMove;
 			he->savedData.OnResize = state->top_state->savedData.OnResize;
		}
		else
		{
			if (state->top_state->savedData.OnLoad != NULL)
				PA_FREE(state->top_state->savedData.OnLoad);
			if (state->top_state->savedData.OnUnload != NULL)
				PA_FREE(state->top_state->savedData.OnUnload);
			if (state->top_state->savedData.OnFocus != NULL)
				PA_FREE(state->top_state->savedData.OnFocus);
			if (state->top_state->savedData.OnBlur != NULL)
				PA_FREE(state->top_state->savedData.OnBlur);
			if (state->top_state->savedData.OnHelp != NULL)
				PA_FREE(state->top_state->savedData.OnHelp);
			if (state->top_state->savedData.OnMouseOver != NULL)
				PA_FREE(state->top_state->savedData.OnMouseOver);
			if (state->top_state->savedData.OnMouseOut != NULL)
				PA_FREE(state->top_state->savedData.OnMouseOut);
			if (state->top_state->savedData.OnDragDrop != NULL)
				PA_FREE(state->top_state->savedData.OnDragDrop);
			if (state->top_state->savedData.OnMove != NULL)
				PA_FREE(state->top_state->savedData.OnMove);
			if (state->top_state->savedData.OnResize != NULL)
				PA_FREE(state->top_state->savedData.OnResize);
		}
		state->top_state->savedData.OnLoad = NULL;
		state->top_state->savedData.OnUnload = NULL;
		state->top_state->savedData.OnFocus = NULL;
		state->top_state->savedData.OnBlur = NULL;
		state->top_state->savedData.OnHelp = NULL;
		state->top_state->savedData.OnMouseOver = NULL;
		state->top_state->savedData.OnMouseOut = NULL;
		state->top_state->savedData.OnDragDrop = NULL;
		state->top_state->savedData.OnMove = NULL;
		state->top_state->savedData.OnResize = NULL;
	}


	if ( bWholeDoc )
	{
#ifdef WEBFONTS
	    /* Release webfonts loaded by this document */
	    if (WF_fbu && !resize_reload)
	    {
		    nffbu_ReleaseWebfonts(WF_fbu, context, NULL);
	    }
#endif /* WEBFONTS */

        recycle_list = state->top_state->recycle_list;

        /* Some lo_ImageStructs may have been reclaimed by the Memory Arenas
           code without actually calling IL_DestroyImage first.  This call
           frees up any remaining image lib requests. */
        IL_DestroyImageGroup(context->img_cx);

        lo_DestroyBlinkers(context);
        if (state->top_state->layers) 
        {
            int i;
            lo_TopState *top_state = state->top_state;

            /* If we're resizing, prevent the GC'ing of the JS object
             * associated with this layer when the layer destructor is
             * called, since we want to preserve it across a resize,
             * except for those layers created with JS's new operator,
             * which aren't preserved across resize.  */
            if (resize_reload)
            {
                for (i = 1; i <= state->top_state->max_layer_num; i++)
                {
                    lo_LayerDocState *layer_state = state->top_state->layers[i];
                    if (!layer_state || layer_state->is_constructed_layer)
                        continue;
                
                    layer_state->mocha_object = NULL;
                    state->top_state->layers[i] = NULL;
                }
            }

            /* Delete the main document layer_state */
            top_state->layers[0]->layer = NULL;
            lo_DeleteLayerState(context, state, top_state->layers[0]);
            
            top_state->num_layers_allocated = 0;
            top_state->max_layer_num = 0;

            lo_DeleteLayerStack(state);
        }

        if (context->compositor){
            lo_DestroyLayers(context);

            /* Reset the compositor to do on-screen compositing by default. */
            CL_SetCompositorOffscreenDrawing(context->compositor,
                                             CL_OFFSCREEN_AUTO);
            CL_SetCompositorEnabled(context->compositor, PR_FALSE);
        }
        XP_FREEIF(state->top_state->layers);
    }

    if ( bWholeDoc ) {
    	/* throw out mocha cruft */
    	ET_ReleaseDocument(context, resize_reload);

	    XP_DELETE(state->top_state);
	    XP_DELETE(state);
	    /*
	     * Get the unique document ID, and remove this
	     * documents layout state.
	     */
	    doc_id = XP_DOCID(context);
	    lo_RemoveTopState(doc_id);

    }
    else
    {
        XP_DELETE(state);
        recycle_list = NULL;
    }
	return(recycle_list);
}


void
LO_DiscardDocument(MWContext *context)
{
	int32 doc_id;
	lo_TopState *top_state;
	lo_DocState *state;
	LO_Element *recycle_list;
	int32 cnt;
#ifdef MEMORY_ARENAS
	lo_arena *old_arena;
#endif /* MEMORY_ARENAS */

	/*
	 * Get the unique document ID, and retreive this
	 * documents layout state.
	 */
	doc_id = XP_DOCID(context);
	top_state = lo_FetchTopState(doc_id);
	if ((top_state == NULL)||(top_state->doc_state == NULL))
	{
		return;
	}
	state = top_state->doc_state;
	
#ifdef MEMORY_ARENAS
    if ( ! EDT_IS_EDITOR(context) ) {
	    old_arena = top_state->first_arena;
    }
#endif /* MEMORY_ARENAS */

	/*
	 * Order is crucial here: run the onunload handlers in a frames tree
	 * from the top down, but destroy frames bottom-up.  Look for the
	 * LM_ReleaseDocument call below, almost at the end of this function.
	 * This way, onunload handlers get to use their kids' properties, and
	 * kids get to use their parent's properties and functions.
	 */
	ET_SendLoadEvent(context, EVENT_UNLOAD, NULL, NULL,
			 LO_DOCUMENT_LAYER_ID, FALSE);

	/* make sure no other threads are looking at our structures */
	LO_LockLayout();

	lo_SaveFormElementState(context, state, TRUE);
	/*
	 * If this document has no session history
	 * this data is not saved.
	 * It will be freed by history cleanup.
	 */
	if (SHIST_GetCurrent(&context->hist) == NULL)
	{
		top_state->savedData.FormList = NULL;
		top_state->savedData.EmbedList = NULL;
		top_state->savedData.Grid = NULL;
	}
	recycle_list =
		lo_InternalDiscardDocument(context, state, lo_internal_doc_data, TRUE);
	/* see first lo_FreeRecycleList comment. safe */
	cnt = lo_FreeRecycleList(context, recycle_list);
#ifdef MEMORY_ARENAS
    if ( ! EDT_IS_EDITOR(context) ) {
	    cnt = lo_FreeMemoryArena(old_arena);
    }
#endif /* MEMORY_ARENAS */

	/* we currently have no document loaded */
	context->doc_id = 0;

	/* the danger has passed.  let others look up our shorts again */
	LO_UnlockLayout();

}


lo_SavedFormListData *
lo_NewDocumentFormListData(void)
{
	lo_SavedFormListData *fptr;

	fptr = XP_NEW(lo_SavedFormListData);
	if (fptr == NULL)
	{
		return(NULL);
	}

	fptr->valid = FALSE;
	fptr->data_index = 0;
	fptr->data_count = 0;
	fptr->data_list = NULL;
	fptr->next = NULL;

	return(fptr);
}


void
LO_FreeDocumentFormListData(MWContext *context, void *data)
{
	lo_SavedFormListData *forms = (lo_SavedFormListData *)data;

	if (forms != NULL)
	{
		lo_FreeDocumentFormListData(context, forms);
		XP_DELETE(forms);
	}
}


lo_SavedGridData *
lo_NewDocumentGridData(void)
{
	lo_SavedGridData *sgptr;

	sgptr = XP_NEW(lo_SavedGridData);
	if (sgptr == NULL)
	{
		return(NULL);
	}

	sgptr->main_width = 0;
	sgptr->main_height = 0;
	sgptr->the_grid = NULL;

	return(sgptr);
}


void
LO_FreeDocumentGridData(MWContext *context, void *data)
{
	lo_SavedGridData *sgptr = (lo_SavedGridData *)data;

	if (sgptr != NULL)
	{
		lo_FreeDocumentGridData(context, sgptr);
		XP_DELETE(sgptr);
	}
}


lo_SavedEmbedListData *
lo_NewDocumentEmbedListData(void)
{
	lo_SavedEmbedListData *eptr;

	eptr = XP_NEW(lo_SavedEmbedListData);
	if (eptr == NULL)
	{
		return(NULL);
	}

	eptr->embed_count = 0;
	eptr->embed_data_list = NULL;

	return(eptr);
}


void
LO_FreeDocumentEmbedListData(MWContext *context, void *data)
{
	lo_SavedEmbedListData *eptr = (lo_SavedEmbedListData *)data;

	if (eptr != NULL)
	{
		lo_FreeDocumentEmbedListData(context, eptr);
		XP_DELETE(eptr);
	}
}

void
LO_HighlightAnchor(MWContext *context, LO_Element *element, Bool on)
{
	int32 doc_id;
	lo_TopState *top_state;
	lo_DocState *state;
	LO_Element *start, *end;
	LO_AnchorData *anchor;

	/*
	 * Get the unique document ID, and retrieve this
	 * documents layout state.
	 */
	doc_id = XP_DOCID(context);
	top_state = lo_FetchTopState(doc_id);
	if ((top_state == NULL)||(top_state->doc_state == NULL))
	{
		return;
	}
	state = top_state->doc_state;

	if (element == NULL)
		return;

	anchor = lo_GetElementAnchor(element);
	if (anchor == NULL)
		return;

	/* Find all the preceding layout elements that share the same anchor. */
	start = element;
	while (start->lo_any.prev)
	{
		LO_AnchorData *anchor2 = lo_GetElementAnchor(start->lo_any.prev);
		if (anchor != anchor2)
			break;
		start = start->lo_any.prev;
	}

	/* Find all the following layout elements that share the same anchor. */
	end = element;
	while (end->lo_any.next)
	{
		LO_AnchorData *anchor2 = lo_GetElementAnchor(end->lo_any.next);
		if (anchor != anchor2)
			break;
		end = end->lo_any.next;
	}

	end = end->lo_any.next;
	element = start;
	while (element != end)
	{
        Bool restoreUnselected;
		LO_Color save_fg;

		element->lo_any.x += state->base_x;
		element->lo_any.y += state->base_y;

        restoreUnselected = FALSE;
		switch (element->type)
		{
		case LO_IMAGE:
			/* Save image selection state */
			if (! (element->lo_image.ele_attrmask & LO_ELE_SELECTED))
				restoreUnselected = TRUE;
			
			/* Force display of selection rectangle. */
			element->lo_image.ele_attrmask = LO_ELE_SELECTED;

			/* Fall through ... */

		case LO_TEXT:
		case LO_SUBDOC:

			/* Temporarily set element color to selection color. */
			if (on) {
				lo_GetElementFGColor(element, &save_fg);
				lo_SetElementFGColor(element, &state->active_anchor_color);
			}

			/* Use the compositor to synchronously update the element's area. */
			lo_RefreshElement(element, anchor->layer, TRUE);

			/* Restore the element's color. */
			if (on)
				lo_SetElementFGColor(element, &save_fg);

			/* Restore image selection state */
			if (restoreUnselected)
				element->lo_image.ele_attrmask &= ~(LO_ELE_SELECTED);

		default:
			/* No highlighting for other types of elements */
			break;
		}

		element->lo_any.x -= state->base_x;
		element->lo_any.y -= state->base_y;

		element = element->lo_any.next;
	}
}

LO_Element *
LO_XYToNearestElement(MWContext *context, int32 x, int32 y, CL_Layer *layer)
{
	int32 doc_id;
	lo_TopState *top_state;
	lo_DocState *state;
	LO_Element *eptr;
	LO_Element **line_array;
	int32 ret_x, ret_y;
    LO_CellStruct *layer_cell;

	/*
	 * Get the unique document ID, and retreive this
	 * documents layout state.
	 */
	doc_id = XP_DOCID(context);
	top_state = lo_FetchTopState(doc_id);
	if ((top_state == NULL)||(top_state->doc_state == NULL))
	{
		return(NULL);
	}
	state = top_state->doc_state;

    layer_cell = lo_GetCellFromLayer(context, layer);
    if (layer_cell != NULL)
    {
        int32 new_x, new_y;

        new_y = y;
        if (new_y > layer_cell->height)
            new_y = layer_cell->height - 1;
        if (new_y < 0)
            new_y = 0;
        
        new_x = x;
        if (new_x > layer_cell->width)
            new_x = layer_cell->width - 1;
        if (new_x < 0)
            new_x = 0;
        eptr = lo_XYToNearestCellElement(context, 
                                         state,
                                         layer_cell, new_x, new_y);
        return eptr;
    }

	if (x <= state->win_left)
	{
		x = state->win_left + 1;
	}

	if (y < state->win_top)
	{
		y = state->win_top + 1;
	}

	eptr = lo_XYToDocumentElement(context, state, x, y, TRUE, TRUE, TRUE,
		&ret_x, &ret_y);

	if (eptr == NULL)
	{
		int32 top, bottom;

		/*	
		 * Find the nearest line.
		 */
		lo_RegionToLines (context, state, x, y, 1, 1, FALSE,
			&top, &bottom);

		if (top >= 0)
#ifdef XP_WIN16
		{
			intn a_size;
			intn a_indx;
			intn a_line;
			XP_Block *larray_array;

			a_size = SIZE_LIMIT / sizeof(LO_Element *);
			a_indx = (intn)(top / a_size);
			a_line = (intn)(top - (a_indx * a_size));
			XP_LOCK_BLOCK(larray_array, XP_Block *,
					state->larray_array);
			state->line_array = larray_array[a_indx];
			XP_LOCK_BLOCK(line_array, LO_Element **,
					state->line_array);
			eptr = line_array[a_line];
			XP_UNLOCK_BLOCK(state->line_array);
			XP_UNLOCK_BLOCK(state->larray_array);
		}
#else
		{
			XP_LOCK_BLOCK(line_array, LO_Element **,
					state->line_array);
			eptr = line_array[top];
			XP_UNLOCK_BLOCK(state->line_array);
		}
#endif /* XP_WIN16 */

		/*
		 * The nearest line may be a table with cells.  In which case
		 * we really need to move into that table to find the nearest
		 * cell, and possibly nearest element in that cell.
		 */
		if ((eptr != NULL)&&(eptr->type == LO_TABLE)&&
		    (eptr->lo_any.next->type == LO_CELL))
		{
		    LO_Element *new_eptr;

		    new_eptr = eptr->lo_any.next;
		    /*
		     * Find the cell that overlaps in Y.  Move to the lower
		     * cell if between cells.
		     */
		    while ((new_eptr != NULL)&&(new_eptr->type == LO_CELL))
		    {
			int32 y2;

			y2 = new_eptr->lo_any.y + new_eptr->lo_any.y_offset +
				new_eptr->lo_any.height +
				(2 * new_eptr->lo_cell.border_width);
			if (y <= y2)
			{
				break;
			}
			new_eptr = new_eptr->lo_any.next;
		    }
		    /*
		     * If we walked through the table to a non-cell element
		     * and still didn't match, match to the last cell in
		     * the table.
		     */
		    if ((new_eptr != NULL)&&(new_eptr->type != LO_CELL))
		    {
			new_eptr = new_eptr->lo_any.prev;
		    }

		    /*
		     * If new_eptr is not NULL it is the cell we matched.
		     * Move us just into that cell and see if we can match
		     * an element inside it.
		     */
		    if ((new_eptr != NULL)&&(new_eptr->type == LO_CELL))
		    {
			LO_Element *cell_eptr;
			int32 new_x, new_y;

			new_y = y;
			if (new_y >= (new_eptr->lo_any.y +
					new_eptr->lo_any.y_offset +
					new_eptr->lo_cell.border_width +
					new_eptr->lo_any.height))
			{
				new_y = new_eptr->lo_any.y +
					new_eptr->lo_any.y_offset +
					new_eptr->lo_cell.border_width +
					new_eptr->lo_any.height - 1;
			}
			if (new_y < (new_eptr->lo_any.y +
					new_eptr->lo_any.y_offset +
					new_eptr->lo_cell.border_width))
			{
				new_y = new_eptr->lo_any.y +
					new_eptr->lo_any.y_offset +
					new_eptr->lo_cell.border_width;
			}
			new_x = x;
			if (new_x >= (new_eptr->lo_any.x +
					new_eptr->lo_any.x_offset +
					new_eptr->lo_cell.border_width +
					new_eptr->lo_any.width))
			{
				new_x = new_eptr->lo_any.x +
					new_eptr->lo_any.x_offset +
					new_eptr->lo_cell.border_width +
					new_eptr->lo_any.width - 1;
			}
			if (new_x < (new_eptr->lo_any.x +
					new_eptr->lo_any.x_offset +
					new_eptr->lo_cell.border_width))
			{
				new_x = new_eptr->lo_any.x +
					new_eptr->lo_any.x_offset +
					new_eptr->lo_cell.border_width;
			}
			cell_eptr = lo_XYToNearestCellElement(context, state,
				(LO_CellStruct *)new_eptr, new_x, new_y);
			if (cell_eptr != NULL)
			{
				new_eptr = cell_eptr;
			}
		    }
		    if (new_eptr != NULL)
		    {
			eptr = new_eptr;
		    }
		}
	}

	return(eptr);
}

static void
lo_RefreshCellAnchors(lo_DocState *state, LO_CellStruct *cell);

/* return TRUE if the color of the current anchor is
 * one of the two default visted or unvisited colors
 * it will be FALSE in the case that style sheets sets
 * a specific color for the anchor
 */
static Bool
lo_is_default_anchor_color(lo_DocState *state, LO_TextAttr *tmp_attr)
{
	if(  (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))
          || (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)))
		return TRUE;

	return FALSE;
}

static void
lo_RefreshElementAnchor(lo_DocState *state, LO_Element *element)
{
	LO_TextAttr tmp_attr, *new_attr, *attr;
	LO_AnchorData *anchor_href;
	char *url;

	if (element->type == LO_CELL) {
		lo_RefreshCellAnchors(state, (LO_CellStruct *)element);
		return;
	}

	attr = lo_GetElementTextAttr(element);
	if (! attr)
		return;
	if (! (attr->attrmask & LO_ATTR_ANCHOR))
	    return;

	anchor_href = lo_GetElementAnchor(element);
	if (!anchor_href)
		return;

	if(!lo_is_default_anchor_color(state, attr))
		return;

	lo_CopyTextAttr(attr, &tmp_attr);

	url = (char*)anchor_href->anchor;
	if (GH_CheckGlobalHistory(url) != -1)
	{
		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);
	}
	else
	{
		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);
	}

	new_attr = lo_FetchTextAttr(state, &tmp_attr);
	
	if (new_attr != attr) {
		lo_SetElementTextAttr(element, new_attr);
		if (element->type == LO_TEXT || element->type == LO_IMAGE) {
			element->lo_any.x += state->base_x;
			element->lo_any.y += state->base_y;
			lo_RefreshElement(element, anchor_href->layer, TRUE);
			element->lo_any.x -= state->base_x;
			element->lo_any.y -= state->base_y;
		}
	}
}

static void
lo_RefreshCellAnchors(lo_DocState *state, LO_CellStruct *cell)
{
	LO_Element *eptr;

	eptr = cell->cell_list;
	while (eptr)
	{
		lo_RefreshElementAnchor(state, eptr);
		eptr = eptr->lo_any.next;
	}

	eptr = cell->cell_float_list;
	while (eptr)
	{
		lo_RefreshElementAnchor(state, eptr);
		eptr = eptr->lo_any.next;
	}
}

void
LO_RefreshAnchors(MWContext *context)
{
	int32 doc_id;
	lo_TopState *top_state;
	lo_DocState *state;
	LO_Element *eptr;
	LO_Element **line_array;

        if (!context->compositor)
            return;

	/*
	 * Get the unique document ID, and retrieve this
	 * document's layout state.
	 */
	doc_id = XP_DOCID(context);
	top_state = lo_FetchTopState(doc_id);
	if ((top_state == NULL)||(top_state->doc_state == NULL))
		return;
	state = top_state->doc_state;

	/*
	 * line_array could be NULL when discarding the document, so
	 * check it and exit early to avoid dereferencing it below.
	 */
	if (state->line_array == NULL)
		return;

#ifdef XP_WIN16
	{
		XP_Block *larray_array;

		XP_LOCK_BLOCK(larray_array, XP_Block *, state->larray_array);
		state->line_array = larray_array[0];
		XP_UNLOCK_BLOCK(state->larray_array);
	}
#endif /* XP_WIN16 */

	XP_LOCK_BLOCK(line_array, LO_Element **, state->line_array);
	eptr = line_array[0];
	XP_UNLOCK_BLOCK(state->line_array);

	for (; eptr; eptr = eptr->lo_any.next)
		lo_RefreshElementAnchor(state, eptr);
}

static void
lo_split_named_anchor(char *url, char **cmp_url, char **cmp_name)
{
	char tchar, *tptr, *tname;

	*cmp_url = NULL;
	
	if ( cmp_name )
		*cmp_name = NULL;

	if (url == NULL)
	{
		return;
	}

	tptr = url;
	while (((tchar = *tptr) != '\0')&&(tchar != '#'))
	{
		tptr++;
	}
	if (tchar == '\0')
	{
		*cmp_url = (char *)XP_STRDUP(url);
		return;
	}

	*tptr = '\0';
	*cmp_url = (char *)XP_STRDUP(url);
	*tptr = tchar;
	tname = ++tptr;
	while (((tchar = *tptr) != '\0')&&(tchar != '?'))
	{
		tptr++;
	}
	*tptr = '\0';
	if ( cmp_name )
		*cmp_name = (char *)XP_STRDUP(tname);
	*tptr = tchar;
	if (tchar == '?')
	{
		StrAllocCat(*cmp_url, tptr);
	}
}


/*
 * Is this anchor local to this document?
 */
Bool
lo_IsAnchorInDoc(lo_DocState *state, char *url)
{
	char *cmp_url;
	char *doc_url;
	Bool local;

	local = FALSE;
	/*
	 * Extract the base from the name part.
	 */
	lo_split_named_anchor(url, &cmp_url, NULL);

	/*
	 * Extract the base from the name part.
	 */
	doc_url = NULL;
	lo_split_named_anchor(state->top_state->url, &doc_url, NULL);

	/*
	 * If the document itself has no base URL (probably an error)
	 * then the only way we are local is if the passed in url has no
	 * base either.
	 */
	if (doc_url == NULL)
	{
		if (cmp_url == NULL)
		{
			local = TRUE;
		}
	}
	/*
	 * Else if the passed in url has no base, and we have a bas, you must
	 * be local because you are relative to our base.
	 */
	else if (cmp_url == NULL)
	{
		local = TRUE;
	}
	/*
	 * Else if the base of the two urls is equal, you are local.
	 */
	else if (XP_STRCMP(cmp_url, doc_url) == 0)
	{
		local = TRUE;
	}

	if (cmp_url != NULL)
	{
		XP_FREE(cmp_url);
	}
	if (doc_url != NULL)
	{
		XP_FREE(doc_url);
	}
	return(local);
}


static LO_Element *
lo_cell_id_to_element(lo_DocState *state, int32 ele_id, LO_CellStruct *cell)
{
	LO_Element *eptr;

	if (cell == NULL)
	{
		return(NULL);
	}

	eptr = cell->cell_list;
	while (eptr != NULL)
	{
		if (eptr->lo_any.ele_id == ele_id)
		{
			break;
		}
		if (eptr->type == LO_CELL)
		{
			LO_Element *cell_eptr;

			cell_eptr = lo_cell_id_to_element(state, ele_id,
						(LO_CellStruct *)eptr);
			if (cell_eptr != NULL)
			{
				eptr = cell_eptr;
				break;
			}
		}
		if (eptr->lo_any.ele_id > ele_id)
		{
			break;
		}
		eptr = eptr->lo_any.next;
	}
	return(eptr);
}


/*
 * Find the x, y, coords of the element id passed in, and return them.
 * If can't find the exact element, return the closest (next greater)
 * element id's position.
 * We need to walk into cells.
 * On severe error return 0, 0.
 */
static void
lo_element_id_to_xy(lo_DocState *state, int32 ele_id, int32 *xpos, int32 *ypos)
{
	LO_Element *eptr;
	LO_Element **line_array;

	*xpos = 0;
	*ypos = 0;

	/*
	 * On error return the top
	 */
	if (state == NULL)
	{
		return;
	}

#ifdef XP_WIN16
	{
		XP_Block *larray_array;

		XP_LOCK_BLOCK(larray_array, XP_Block *, state->larray_array);
		state->line_array = larray_array[0];
		XP_UNLOCK_BLOCK(state->larray_array);
	}
#endif /* XP_WIN16 */

	XP_LOCK_BLOCK(line_array, LO_Element **, state->line_array);
	eptr = line_array[0];
	XP_UNLOCK_BLOCK(state->line_array);

	while (eptr != NULL)
	{
		/*
		 * Exact match?
		 */
		if (eptr->lo_any.ele_id == ele_id)
		{
			*xpos = eptr->lo_any.x;
			*ypos = eptr->lo_any.y;
			break;
		}

		/*
		 * Look for a match in this cell
		 */
		if (eptr->type == LO_CELL)
		{
			LO_Element *cell_eptr;

			cell_eptr = lo_cell_id_to_element(state, ele_id,
						(LO_CellStruct *)eptr);
			if (cell_eptr != NULL)
			{
				*xpos = cell_eptr->lo_any.x;
				*ypos = cell_eptr->lo_any.y;
				break;
			}
		}

		/*
		 * If we passed it, fake a match.
		 */
		if (eptr->lo_any.ele_id > ele_id)
		{
			*xpos = eptr->lo_any.x;
			*ypos = eptr->lo_any.y;
			break;
		}
		eptr = eptr->lo_any.next;
	}
}


/*
 * Check the current URL to see if it is the URL for this doc, and if
 * so return the position of the named anchor referenced after the '#' in
 * the URL.  If there is no name or no match, return 0,0.  In comparing URLs
 * both NULL are considered equal
 */
Bool
LO_LocateNamedAnchor(MWContext *context, URL_Struct *url_struct,
			int32 *xpos, int32 *ypos)
{
    int i;
	int32 doc_id;
	lo_TopState *top_state;
	lo_DocState *state;
	lo_NameList *nptr;
	char *url;
	char *cmp_url;
	char *cmp_name;

	/*
	 * Get the unique document ID, and retreive this
	 * documents layout state.
	 */
	doc_id = XP_DOCID(context);
	top_state = lo_FetchTopState(doc_id);
	if ((top_state == NULL)||(top_state->doc_state == NULL))
	{
		return(FALSE);
	}
	state = top_state->doc_state;

	/*
	 * obvious error
	 */
	if ((url_struct == NULL)||(url_struct->address == NULL))
	{
		return(FALSE);
	}

	/*
	 * Extract the URL we are going to.
	 */
	url = url_struct->address;


	/*
	 * Split the passed url into the real url part
	 * and the name part.  The parts (if non-NULL) must
	 * be freed.
	 */
	cmp_url = NULL;
	cmp_name = NULL;
	lo_split_named_anchor(url, &cmp_url, &cmp_name);

	/*
	 * can't strcmp on NULL strings.
	 */
	if (state->top_state->url == NULL)
	{
		/*
		 * 2 NULLs are considered equal.
		 * If not equal fail here
		 */
		if (cmp_url != NULL)
		{
			if (cmp_name != NULL)
			{
				XP_FREE(cmp_name);
			}
			return(FALSE);
		}
	}
	/*
	 * Else lets compare the 2 URLs now
	 */
	else
	{
		/*
		 * The current URL might itself have a name part.  Lose it.
		 */
		char *this_url;
		int same_p;

		lo_split_named_anchor(state->top_state->url, &this_url, NULL);
		/*
		 * If the 2 URLs are not equal fail out here
		 */
		same_p = ((this_url)&&(XP_STRCMP(cmp_url, this_url) == 0));

		if (this_url != NULL)
		{
			XP_FREE (this_url);
		}

		if (!same_p)
		{
			XP_FREE(cmp_url);
			if (cmp_name != NULL)
			{
				XP_FREE(cmp_name);
			}
			return(FALSE);
		}
	}

	/*
	 * If we got here the URLs are considered equal.
	 * Free this one.
	 */
	if (cmp_url != NULL)
	{
		XP_FREE(cmp_url);
	}

	/*
	 * Do we have a saved element ID that is not the start
	 * of the document to return to because we are moving through history?
	 * If so, go there and return.
	 */
	if (url_struct->position_tag > 1)
	{
		lo_element_id_to_xy(state, url_struct->position_tag,
			xpos, ypos);
		return(TRUE);
	}

	/*
	 * Special case for going to a NULL or empty
	 * name to return the beginning of the
	 * document.
	 */
	if (cmp_name == NULL)
	{
		*xpos = 0;
		*ypos = 0;

		if (state->top_state->url != NULL)
		{
			XP_FREE(state->top_state->url);
		}
		state->top_state->url = XP_STRDUP(url);
		return(TRUE);
	}
	else if (*cmp_name == '\0')
	{
		*xpos = 0;
		*ypos = 0;
		XP_FREE(cmp_name);

		if (state->top_state->url != NULL)
		{
			XP_FREE(state->top_state->url);
		}
		state->top_state->url = XP_STRDUP(url);
		return(TRUE);
	}

	/*
	 * Actually search the name list for a matching name.
	 */
    for (i = 0; i <= state->top_state->max_layer_num; i++)
    {
        lo_LayerDocState *layer_state = state->top_state->layers[i];
        CL_Layer *layer;
        if (!layer_state)
            continue;
        layer = layer_state->layer;
        nptr = layer_state->doc_lists->name_list;
        
        while (nptr != NULL)
        {
            char *name;

            PA_LOCK(name, char *, nptr->name);
            /*
             * If this is a match, return success
             * here.
             */
            if (XP_STRCMP(cmp_name, name) == 0)
            {
                PA_UNLOCK(nptr->name);
                if (nptr->element == NULL)
                {
                    *xpos = 0;
                    *ypos = 0;
                }
                else
                {
                    /* Convert from element coordinates to document coordinates */
                    lo_GetLayerXYShift(layer, xpos, ypos);
                    *xpos = - *xpos;
                    *ypos = - *ypos;
                    *xpos += nptr->element->lo_any.x + CL_GetLayerXOrigin(layer);
                    *ypos += nptr->element->lo_any.y + CL_GetLayerYOrigin(layer);
                }
                XP_FREE(cmp_name);

                if (state->top_state->url != NULL)
                {
                    XP_FREE(state->top_state->url);
                }
                state->top_state->url = XP_STRDUP(url);
                LM_SendOnLocate(context, nptr);
                return(TRUE);
            }
            PA_UNLOCK(nptr->name);
            nptr = nptr->next;
        }
    }

	/*
	 * Failed to find the matchng name.
	 * If no match return the top of the doc.
	 */
	XP_FREE(cmp_name);
	*xpos = 0;
	*ypos = 0;

	if (state->top_state->url != NULL)
	{
		XP_FREE(state->top_state->url);
	}
	state->top_state->url = XP_STRDUP(url);
	return(TRUE);
}


Bool
LO_HasBGImage(MWContext *context)
{
	int32 doc_id;
	lo_TopState *top_state;

	/*
	 * Get the unique document ID, and retreive this
	 * documents layout state.
	 */
	doc_id = XP_DOCID(context);
	top_state = lo_FetchTopState(doc_id);
	if (top_state == NULL)
		return(FALSE);

#ifdef XP_WIN
	/*
	 * Evil hack alert.
	 * We don't want print context's under windows to use backdrops
	 *  currently as it causes bad display (black in the transparent
	 *  area due to drawing problems under windows).
	 */
	 if(context->type == MWContextPrint || context->type == MWContextMetaFile)
	 {
	 	return(FALSE);
	 }
#endif

     return (LO_GetLayerBackdropURL(top_state->doc_layer) != NULL);
}

void
LO_GetDocumentMargins(MWContext *context,
	int32 *margin_width, int32 *margin_height)
{
	int32 doc_id;
	lo_TopState *top_state;
	lo_DocState *state;

	/*
	 * Get the unique document ID, and retreive this
	 * documents layout state.
	 */
	doc_id = XP_DOCID(context);
	top_state = lo_FetchTopState(doc_id);
	if ((top_state == NULL)||(top_state->doc_state == NULL))
	{
		return;
	}
	state = top_state->doc_state;

	*margin_width = state->win_left;
	*margin_height = state->win_top;
}


/*
 * Provide a way for the parser to see if the currently loaded
 *   document who might need a call to mocha to unload
 */
Bool
LO_CheckForUnload(MWContext *context)
{
	lo_TopState *top_state;

	top_state = lo_FetchTopState(XP_DOCID(context));
	if (!top_state)
		return FALSE;

	return top_state->mocha_has_onunload;
}

/*
 * netlib is done sending data to the parser
 */
void 
LO_NetlibComplete(MWContext * context)
{
	lo_TopState *top_state;

	top_state = lo_FetchTopState(XP_DOCID(context));
	if (!top_state)
		return;
	top_state->nurl = NULL;
}

/*
 * Even though netlib is idle someone could be shoving data
 *   through layout/libparse.  Provide a way for people on
 *   the outside to tell if this is the case or not.
 */

Bool
LO_LayingOut(MWContext * context)
{
	lo_TopState *top_state;

	top_state = lo_FetchTopState(XP_DOCID(context));
	if (!top_state)
		return FALSE;
	return (top_state->doc_data != NULL);
}


void lo_UpdateStateWhileFlushingLine( MWContext *context, lo_DocState *state )
{
  int32 justification_remainder=0;

  if (state->top_state->nothing_displayed != FALSE) {
    /*
     * If we are displaying elements we are
     * no longer in the HEAD section of the HTML
     * we are in the BODY section.
     */
    state->top_state->in_head = FALSE;
    state->top_state->in_body = TRUE;

    lo_use_default_doc_background(context, state);
    state->top_state->nothing_displayed = FALSE;
  }

  /*
	 * There is a break at the end of this line.
	 * this may change min_width.
	 */
  {
    int32 new_break_holder;
    int32 min_width;
    int32 indent;

    new_break_holder = state->x;
    min_width = new_break_holder - state->break_holder;
    indent = state->list_stack->old_left_margin - state->win_left;
    min_width += indent;
    if (min_width > state->min_width)
      {
	state->min_width = min_width;
      }
    /* If we are not within <NOBR> content, allow break_holder
     * to be set to the new position where a line break can occur.
     * This fixes BUG #70782
     */
    if (state->breakable != FALSE) {
      state->break_holder = new_break_holder;
    }
  }

  /*
	 * If in a division centering or right aligning this line
	 */
  if ((state->align_stack != NULL)&&(state->delay_align == FALSE))
    {
      int32 push_right;
      LO_Element *tptr;

      if (state->align_stack->alignment == LO_ALIGN_CENTER)
	{
	  push_right = (state->right_margin - state->x) / 2;
	}
      else if (state->align_stack->alignment == LO_ALIGN_RIGHT)
	{
	  push_right = state->right_margin - state->x;
	}
      else if(state->align_stack->alignment == LO_ALIGN_JUSTIFY)
	{
	  push_right = lo_calc_push_right_for_justify(state, &justification_remainder);
	}
      else
	{
	  push_right = 0;
	}

      if ((push_right > 0 || justification_remainder)
	  &&(state->line_list != NULL))
	{
	  int32 count = 0;
	  int32 move_delta;
	  tptr = state->line_list;

	  if(state->align_stack->alignment == LO_ALIGN_JUSTIFY)
	    move_delta = 0;
	  else
	    move_delta = push_right;

	  while (tptr != NULL)
	    {
	      /* 
	       * We don't shift over inflow layers, since their contents
	       * have already been shifted over.
	       */
	      /*
	       * We also don't shift bullets of type BULLET_MQUOTE.
	       */
	      if (((tptr->type != LO_CELL)||
		   (!tptr->lo_cell.cell_inflow_layer))&&
		  ((tptr->type != LO_BULLET)||
		   ((tptr->type == LO_BULLET)&&
		    (tptr->lo_bullet.bullet_type != BULLET_MQUOTE))))
		{
		  tptr->lo_any.x += move_delta;
		}

	      {
		CL_Layer *layer = NULL;
		int32 x, y;
		int32 border_width;
		int32 x_offset, y_offset;

		border_width = 0;
		x = tptr->lo_any.x;
		y = tptr->lo_any.y;
		x_offset = tptr->lo_any.x_offset;
		y_offset = tptr->lo_any.y_offset;

		switch (tptr->type)
		  {
		  case LO_IMAGE:
		    layer = tptr->lo_image.layer;
		    border_width = tptr->lo_image.border_width;
		    break;
#ifdef JAVA
		  case LO_JAVA:
		    layer = tptr->lo_java.layer;
		    border_width = tptr->lo_java.border_width;
		    break;
#endif
		  case LO_EMBED:
		    layer = tptr->lo_embed.layer;
		    border_width = tptr->lo_embed.border_width;
		    break;
		  case LO_FORM_ELE:
		    layer = tptr->lo_form.layer;
		    break;
		  default:
		    layer = NULL;
		    break;
		  }

		if (layer)
		  CL_MoveLayer(layer,
			       x + x_offset + border_width,
			       y + y_offset + border_width);
	      }

				/* justification push_rights are additive */
	      if(state->align_stack->alignment == LO_ALIGN_JUSTIFY)
		{
		  move_delta += push_right;

		  /* if there is a justification remainder, add a pixel
		   * to the first n word breaks
		   */
		  if(count < justification_remainder)
		    move_delta++;
		}

	      /* 
	       * Note that if this is an inflow layer, we don't want
	       * to shift it since the alignment has already propogated
	       * to its contents.
	       */
	      if ((tptr->type == LO_CELL) &&
		  !tptr->lo_cell.cell_inflow_layer)
		{
		  lo_ShiftCell((LO_CellStruct *)tptr, move_delta, 0);
		}
	      tptr = tptr->lo_any.next;

	      count++;
	    }

	  if(state->align_stack->alignment == LO_ALIGN_JUSTIFY)
	    state->x = state->right_margin;
	  else
	    state->x += push_right;
	}
    }
}

/* Add line feed to line list, update doc state, and set line height of
   other elements on that line */
void lo_AppendLineFeed( MWContext *context, lo_DocState *state, LO_LinefeedStruct *linefeed, int32 breaking, Bool updateFE )
{
	LO_Element *tptr;

	if (breaking != FALSE)
	{
		linefeed->ele_attrmask |= LO_ELE_BREAKING;
	}

	/*
	 * Horrible bitflag overuse!!!  For multicolumn text
	 * we need to know if a line of text can be used for
	 * a column break, or if it cannot because it is wrapped
	 * around some object in the margin.  For lines that can be
	 * used for column breaks, we will set the BREAKABLE
	 * flag in their element mask.
	 */
	if ((state->left_margin_stack == NULL)&&
		(state->right_margin_stack == NULL))
	{
		linefeed->ele_attrmask |= LO_ELE_BREAKABLE;
	}

	if ((state->align_stack != NULL)&&
		(state->delay_align != FALSE)&&
		(state->align_stack->alignment != LO_ALIGN_LEFT))
	{
		if (state->align_stack->alignment == LO_ALIGN_CENTER)
		{
			linefeed->ele_attrmask |= LO_ELE_DELAY_CENTER;
		}
		else if (state->align_stack->alignment == LO_ALIGN_RIGHT)
		{
			linefeed->ele_attrmask |= LO_ELE_DELAY_RIGHT;
		}
	}

	tptr = state->line_list;

	if (tptr == NULL)
	{
		state->line_list = (LO_Element *)linefeed;
		linefeed->prev = NULL;
	}
	else
	{
		LO_Element *next_tptr = tptr;
		do
		{
			tptr = next_tptr;
			if (updateFE) 
			{
				/*
				 * If the display is blocked for an element
				 * we havn't reached yet, check to see if
				 * it is in this line, and if so, save its
				 * y position.
				 */
				if ((state->display_blocked != FALSE)&&
	#ifdef EDITOR
					(!state->edit_relayout_display_blocked)&&
	#endif
					(state->is_a_subdoc == SUBDOC_NOT)&&
					(state->display_blocking_element_y == 0)&&
					(state->display_blocking_element_id != -1)&&
					(tptr->lo_any.ele_id >=
					state->display_blocking_element_id))
				{
					state->display_blocking_element_y =
						state->y;
					/*
					 * Special case, if the blocking element
					 * is on the first line, no blocking
					 * at all needs to happen.
					 */
					if (state->y == state->win_top)
					{
						state->display_blocked = FALSE;
						FE_SetDocPosition(context,
							FE_VIEW, 0, state->base_y);
						if (context->compositor)
						{
						  XP_Rect rect;
						  
						  rect.left = 0;
						  rect.top = 0;
						  rect.right = state->win_width;
						  rect.bottom = state->win_height;
						  CL_UpdateDocumentRect(context->compositor,
												&rect, (PRBool)FALSE);
						}
					}
				}
			}

			tptr->lo_any.line_height = state->line_height;
			/*
			 * Special for bullets of type BULLET_MQUOTE
			 * They should always be as tall as the line.
			 */
			if ((tptr->type == LO_BULLET)&&
				(tptr->lo_bullet.bullet_type ==
					BULLET_MQUOTE))
			{
				tptr->lo_any.y_offset = 0;
				tptr->lo_any.height =
					tptr->lo_any.line_height;
			}
			
			if ( (next_tptr = tptr->lo_any.next) == NULL )
            {
				tptr->lo_any.next = (LO_Element*)linefeed;
				linefeed->prev = tptr;
            }
		} while( next_tptr != NULL );
	}
	state->x += linefeed->width;
}

void lo_AppendLineListToLineArray( MWContext *context, lo_DocState *state, LO_Element *lastElementOnLineList)
{
	LO_Element **line_array;
#ifdef XP_WIN16
	int32 a_size;
	int32 a_indx;
	int32 a_line;
	XP_Block *larray_array;
#endif /* XP_WIN16 */

	/*
	 * If necessary, grow the line array to hold more lines.
	 */
#ifdef XP_WIN16
	a_size = SIZE_LIMIT / sizeof(LO_Element *);
	a_indx = (state->line_num - 1) / a_size;
	a_line = state->line_num - (a_indx * a_size);

	XP_LOCK_BLOCK(larray_array, XP_Block *, state->larray_array);
	state->line_array = larray_array[a_indx];

	if (a_line == a_size)
	{
		state->line_array = XP_ALLOC_BLOCK(LINE_INC *
					sizeof(LO_Element *));
		if (state->line_array == NULL)
		{
			XP_UNLOCK_BLOCK(state->larray_array);
			state->top_state->out_of_memory = TRUE;
			return;
		}
		XP_LOCK_BLOCK(line_array, LO_Element **, state->line_array);
		line_array[0] = NULL;
		XP_UNLOCK_BLOCK(state->line_array);
		state->line_array_size = LINE_INC;

		state->larray_array_size++;
		XP_UNLOCK_BLOCK(state->larray_array);
		state->larray_array = XP_REALLOC_BLOCK(
			state->larray_array, (state->larray_array_size
			* sizeof(XP_Block)));
		if (state->larray_array == NULL)
		{
			state->top_state->out_of_memory = TRUE;
			return;
		}
		XP_LOCK_BLOCK(larray_array, XP_Block *, state->larray_array);
		larray_array[state->larray_array_size - 1] = state->line_array;
		state->line_array = larray_array[a_indx];
	}
	else if (a_line >= state->line_array_size)
	{
		state->line_array_size += LINE_INC;
		if (state->line_array_size > a_size)
		{
			state->line_array_size = (intn)a_size;
		}
		state->line_array = XP_REALLOC_BLOCK(state->line_array,
		    (state->line_array_size * sizeof(LO_Element *)));
		if (state->line_array == NULL)
		{
			XP_UNLOCK_BLOCK(state->larray_array);
			state->top_state->out_of_memory = TRUE;
			return;
		}
		larray_array[a_indx] = state->line_array;
	}

	/*
	 * Place this line of elements into the line array.
	 */
	XP_LOCK_BLOCK(line_array, LO_Element **, state->line_array);
	line_array[a_line - 1] = state->line_list;
	XP_UNLOCK_BLOCK(state->line_array);

	XP_UNLOCK_BLOCK(state->larray_array);
#else
	if (state->line_num > state->line_array_size)
	{
		int32 line_inc;

		if (state->line_array_size > (LINE_INC * 10))
		{
			line_inc = state->line_array_size / 10;
		}
		else
		{
			line_inc = LINE_INC;
		}

		state->line_array = XP_REALLOC_BLOCK(state->line_array,
			((state->line_array_size + line_inc) *
			sizeof(LO_Element *)));
		if (state->line_array == NULL)
		{
			state->top_state->out_of_memory = TRUE;
			return;
		}
		state->line_array_size += line_inc;
	}

	/*
	 * Place this line of elements into the line array.
	 */
	XP_LOCK_BLOCK(line_array, LO_Element **, state->line_array);
	line_array[state->line_num - 1] = state->line_list;
	XP_UNLOCK_BLOCK(state->line_array);
#endif /* XP_WIN16 */

	/*
	 * connect the complete doubly linked list between this line
	 * and the last one.
	 */
	if (state->end_last_line != NULL)
	{
		state->end_last_line->lo_any.next = state->line_list;
		state->line_list->lo_any.prev = state->end_last_line;
	}
	state->end_last_line = lastElementOnLineList;

	state->line_list = NULL;
	state->line_num++;
}

void lo_UpdateStateAfterFlushingLine( MWContext *context, lo_DocState *state, LO_LinefeedStruct *linefeed, Bool inRelayout )
{
	/* LO_LinefeedStruct *linefeed; */
	
	lo_AppendLineListToLineArray( context, state, (LO_Element *) linefeed );

	/* 
	 *Don't draw if we're doing layers...we'll just refresh when the 
	 * the layer size increases.
	 */
	/*
	 * Have the front end display this line.
	 */
    /* For layers, only draw if a compositor doesn't exist */
    if (!context->compositor && !inRelayout)
        /* We need to supply a display rectangle that is guaranteed to
           encompass all elements on the line.  The special 0x3fffffff
           value is approximately half the range of a 32-bit int, so
           it leaves room for overflow if arithmetic is done on these
           values. */
        lo_DisplayLine(context, state, (state->line_num - 2),
                       0, 0, 0x3fffffffL, 0x3fffffffL);

	/*
	 * We are starting a new line.  Clear any old break
	 * positions left over, clear the line_list, and increase
	 * the line number.
	 */
	state->old_break = NULL;
	state->old_break_block = NULL;
	state->old_break_pos = -1;
	state->old_break_width = 0;
	state->baseline = 0;
}

#ifdef TEST_16BIT
#undef XP_WIN16
#endif /* TEST_16BIT */

#ifdef PROFILE
#pragma profile off
#endif
