/*
    GNU LESSER GENERAL PUBLIC LICENSE
    Copyright (C) 2006 The Lobo Project

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    Contact info: lobochief@users.sourceforge.net
*/
/*
 * Created on Apr 16, 2005
 */
package org.lobobrowser.html.renderer;

import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.*;

import org.lobobrowser.html.*;
import org.lobobrowser.html.style.AbstractCSS2Properties;
import org.lobobrowser.html.style.HtmlValues;
import org.lobobrowser.html.style.RenderState;
import org.lobobrowser.html.domimpl.*;
import org.w3c.dom.*;
import java.util.logging.*;

/**
 * A substantial portion of the HTML rendering logic of the package can
 * be found in this class.
 * This class is in charge of laying out the DOM subtree of a node,
 * usually on behalf of an RBlock.
 * It creates a renderer subtree consisting of RLine's or RBlock's. RLine's in
 * turn contain RWord's and so on.
 * This class also happens to be used as an RBlock scrollable viewport.
 * @author J. H. S.
 */
public class RBlockViewport extends BaseRCollection {
	// NOTES:
	// - seqRenderables should be "lines" in positional order
	// to allow binary searches by position.
	
	public static final Insets ZERO_INSETS = new Insets(0, 0, 0, 0);
	private static final Logger logger = Logger.getLogger(RBlockViewport.class.getName());
	
	//private final ArrayList awtComponents = new ArrayList();
	private final RenderableContainer container;
	private final int listNesting;
	private final UserAgentContext userAgentContext;
	private final HtmlRendererContext rendererContext;
	private final FrameContext frameContext;
	
	private SortedSet positionedRenderables;
	private ArrayList seqRenderables = null;
//	private Collection exportedRenderables;
	private RLine currentLine;
	private int maxX;
	private int maxY;
	//private int availHeight;
	private int desiredWidth; // includes insets
	private int desiredHeight; // includes insets
	private int availContentHeight; // does not include insets
	private int availContentWidth; // does not include insets
	private int yLimit; 
	private int positionedOrdinal;
	private Insets paddingInsets;
	private boolean overrideNoWrap;
	private FloatingBounds floatBounds = null;
	
	//private ModelNode currentRenderableContext;
	private boolean skipParagraphBreakBefore;
	//private boolean skipLineBreakBefore;
	private static final Map elementLayout = new HashMap(70);
	private static final MarkupLayout miscLayout = new MiscLayout();
	
	static {
		Map el = elementLayout;
		EmLayout em = new EmLayout();
		el.put("I", em);
		el.put("EM", em);
		el.put("CITE", em);
		el.put("H1", new HLayout(24));
		el.put("H2", new HLayout(18));
		el.put("H3", new HLayout(15));
		el.put("H4", new HLayout(12));
		el.put("H5", new HLayout(10));
		el.put("H6", new HLayout(8));
		StrongLayout strong = new StrongLayout();
		el.put("B", strong);
		el.put("STRONG", strong);
		el.put("TH", strong);
		el.put("U", new ULayout());
		el.put("STRIKE", new StrikeLayout());
		el.put("BR", new BrLayout());
		el.put("P", new PLayout());
		el.put("NOSCRIPT", new NoScriptLayout());
		NopLayout nop = new NopLayout();
		el.put("SCRIPT", nop);
		el.put("HEAD", nop);
		el.put("TITLE", nop);
		el.put("META", nop);
		el.put("STYLE", nop);
		el.put("LINK", nop);
		el.put("IMG", new ImgLayout());
		el.put("TABLE", new TableLayout());
		ChildrenLayout children = new ChildrenLayout();
		el.put("HTML", children);
		AnchorLayout anchor = new AnchorLayout();
		el.put("A", anchor);
		el.put("ANCHOR", anchor);
		el.put("INPUT", new InputLayout2());
		el.put("TEXTAREA", new TextAreaLayout2());
		el.put("SELECT", new SelectLayout());
		ListItemLayout list = new ListItemLayout();
		el.put("UL", list);
		el.put("OL", list);
		el.put("LI", list);
		CommonBlockLayout cbl = new CommonBlockLayout();		
		el.put("PRE", cbl);
		el.put("CENTER", cbl);
		el.put("CAPTION", cbl);
		DivLayout div = new DivLayout();
		el.put("DIV", div);
		el.put("BODY", div);
		el.put("DL", div);
		el.put("DT", div);
		BlockQuoteLayout bq = new BlockQuoteLayout();
		el.put("BLOCKQUOTE", bq);
		el.put("DD", bq);
		el.put("HR", new HrLayout());
		el.put("SPAN", new SpanLayout());
		ObjectLayout ol = new ObjectLayout(false, true);
		el.put("OBJECT", new ObjectLayout(true, true));
		el.put("APPLET", ol);
		el.put("EMBED", ol);
		el.put("IFRAME", new IFrameLayout());
	}
	
	/**
	 * Constructs an HtmlBlockLayout.
	 * @param container This is usually going to be an RBlock.
	 * @param listNesting The nesting level for lists. This is zero except inside a list.
	 * @param pcontext The HTMLParserContext instance.
	 * @param frameContext This is usually going to be HtmlBlock, an object where text selections are contained.
	 * @param parent This is usually going to be the parent of <code>container</code>.
	 */
	public RBlockViewport(ModelNode modelNode, RenderableContainer container, int listNesting, UserAgentContext pcontext, HtmlRendererContext rcontext, FrameContext frameContext, RCollection parent) {
		super(container, modelNode);
		this.parent = parent;
		this.userAgentContext = pcontext;
		this.rendererContext = rcontext;
		this.frameContext = frameContext;
		this.container = container;
		this.listNesting = listNesting;
		// Layout here can always be "invalidated"
		this.layoutUpTreeCanBeInvalidated = true;		
	}
	
	public void invalidateLayoutLocal() {	
		// Workaround for fact that RBlockViewport does not 
		// get validated or invalidated.
		this.layoutUpTreeCanBeInvalidated = true;
	}
	
	public int getAvailContentWidth() {
		return this.availContentWidth;
	}
	
	/**
	 * Builds the layout/renderer tree from scratch.
	 * Note: Returned dimension needs to be actual size needed for rendered content,
	 * not the available container size. This is relied upon by table layout.
	 * @param yLimit If other than -1, <code>layout</code> will throw <code>SizeExceededException</code>
	 * in the event that the layout goes beyond this y-coordinate point.
	 */
	public void layout(int desiredWidth, int desiredHeight, Insets paddingInsets, int yLimit, FloatingBounds floatBounds) {
		// Expected in GUI thread. It's possible it may be invoked during pack()
		// outside of the GUI thread.
		if(!EventQueue.isDispatchThread() && logger.isLoggable(Level.INFO)) {
			logger.warning("layout(): Invoked outside GUI dispatch thread.");
		}
		RenderableContainer container = this.container;	
		this.paddingInsets = paddingInsets;
		this.yLimit = yLimit;
		this.desiredHeight = desiredHeight;
		this.desiredWidth = desiredWidth;
		this.floatBounds = floatBounds;
		this.isFloatLimit = null;
		this.pendingFloats = null;

		// maxX and maxY should not be reset by layoutPass.
		this.maxX = paddingInsets.left;
		this.maxY = paddingInsets.top;

		int availw = desiredWidth - paddingInsets.left - paddingInsets.right;
		if(availw < 0) {
			availw = 0;
		}
		int availh = desiredHeight - paddingInsets.top - paddingInsets.bottom;
		if(availh == 0) {
			availh = 0;
		}
		this.availContentHeight = availh;
		this.availContentWidth = availw;

		// New floating algorithm.
		this.layoutPass((NodeImpl) this.modelNode);
		
		Collection delayedPairs = container.getDelayedPairs();
		if(delayedPairs != null && delayedPairs.size() > 0) {
			// Add positioned renderables that belong here
			Iterator i = delayedPairs.iterator();
			while(i.hasNext()) {
				DelayedPair pair = (DelayedPair) i.next();
				if(pair.targetParent == container) {
					this.importDelayedPair(pair);
				}				
			}
		}		

		// Compute bounds...		
		RLine lastLine = this.currentLine;
		Rectangle lastBounds = lastLine.getBounds();
		int lastTopX = lastBounds.x + lastBounds.width;
		if(lastTopX > this.maxX) {
			this.maxX = lastTopX;
		}
		int lastTopY = lastBounds.y + lastBounds.height;
		int maxY = this.maxY;
		if(lastTopY > maxY) {
			this.maxY = maxY = lastTopY;
		}
		this.width = paddingInsets.right + this.maxX;
		this.height = paddingInsets.bottom + maxY;
	}

	private void layoutPass(NodeImpl rootNode) {
		RenderableContainer container = this.container;	
		container.clearDelayedPairs();
		this.positionedOrdinal = 0;

		// Remove sequential renderables...
		this.seqRenderables = null;

		// Remove other renderables... 
		this.positionedRenderables = null;
		
		// Call addLine after setting margins
		this.currentLine = this.addLine(rootNode, null, this.paddingInsets.top);
		this.skipParagraphBreakBefore = true;
		
		// Start laying out...
		// The parent is expected to have set the RenderState already.
		this.layoutChildren(rootNode);		
		
		// This adds last-line floats.
		this.lineDone(this.currentLine);
	}
	
	/**
	 * Applies any horizonal aLignment. It may adjust height if necessary.
	 * @param canvasWidth The new width of the viewport. It could be
	 *                    different to the previously calculated width.
	 * @param paddingInsets
	 */
	public void alignX(int alignXPercent, int canvasWidth, Insets paddingInsets) {
		int prevMaxY = this.maxY;
		// Horizontal alignment
		if(alignXPercent > 0) {
			ArrayList renderables = this.seqRenderables;
			if(renderables != null) {
				Insets insets = this.paddingInsets;
				FloatingBounds floatBounds = this.floatBounds;
				int numRenderables = renderables.size();
				int yoffset = 0; // This may get adjusted due to blocks and floats.
				for(int i = 0; i < numRenderables; i++) {
					Object r = renderables.get(i);
					if(r instanceof BoundableRenderable) {
						BoundableRenderable seqRenderable = (BoundableRenderable) r;
						int y = seqRenderable.getY();
						int newY;
						if(yoffset > 0) {
							newY = y + yoffset;
							seqRenderable.setY(newY);
							if(newY + seqRenderable.getHeight() > this.maxY) {
								this.maxY = newY + seqRenderable.getHeight();
							}
						}
						else {
							newY = y;
						}
						boolean isVisibleBlock = seqRenderable instanceof RBlock && ((RBlock) seqRenderable).isOverflowVisible();
						int leftOffset = isVisibleBlock ? insets.left : this.fetchLeftOffset(y);
						int rightOffset = isVisibleBlock ? insets.right : this.fetchRightOffset(y);
						int actualAvailWidth = canvasWidth - leftOffset - rightOffset;
						int difference = actualAvailWidth - seqRenderable.getWidth();
						if(difference > 0) {
							int shift = (difference * alignXPercent) / 100;
							int newX = leftOffset + shift;
							seqRenderable.setX(newX);
							if(floatBounds != null && isVisibleBlock) {
								RBlock block = (RBlock) seqRenderable;
								// Only if expandable
								yoffset += this.readjustBlock(block, newX, newY, floatBounds);
							}
						}
					}
				}
			}
		}		
		if(prevMaxY != this.maxY) {
			this.height += (this.maxY - prevMaxY);
		}
	}

	/**
	 * Applies vertical alignment.
	 * @param canvasHeight
	 * @param paddingInsets
	 */
	public void alignY(int alignYPercent, int canvasHeight, Insets paddingInsets) {
		int prevMaxY = this.maxY;
		if(alignYPercent > 0) {
			int availContentHeight = canvasHeight - paddingInsets.top - paddingInsets.bottom;
			int usedHeight = this.maxY - paddingInsets.top;
			int difference = availContentHeight - usedHeight;
			if(difference > 0) {
				int shift = (difference * alignYPercent) / 100;
				ArrayList rlist = this.seqRenderables;
				if(rlist != null) {
					// Try sequential renderables first.
					Iterator renderables = rlist.iterator();
					while(renderables.hasNext()) {
						Object r = renderables.next();
						if(r instanceof BoundableRenderable) {
							BoundableRenderable line = (BoundableRenderable) r;
							int newY = line.getY() + shift;
							line.setY(newY);
							if(newY + line.getHeight() > this.maxY) {
								this.maxY = newY + line.getHeight();
							}
						}
					}
				}
				
				// Now other renderables, but only those that can be
				// vertically aligned
				Set others = this.positionedRenderables;
				if(others != null) {
					Iterator i2 = others.iterator();
					while(i2.hasNext()) {
						PositionedRenderable pr = (PositionedRenderable) i2.next();
						if(pr.verticalAlignable) {
							BoundableRenderable br = pr.renderable;
							int newY = br.getY() + shift;
							br.setY(newY);
							if(newY + br.getHeight() > this.maxY) {
								this.maxY = newY + br.getHeight();
							}
						}
					}
				}
			}
		}
		if(prevMaxY != this.maxY) {
			this.height += (this.maxY - prevMaxY);
		}
	}
	
	/**
	 * 
	 * @param block A block needing readjustment due to horizontal alignment.
	 * @return 
	 */
	private int readjustBlock(RBlock block, final int newX, final int newY, final FloatingBounds floatBounds) {
		final int rightInsets = this.paddingInsets.right;
		final int expectedWidth = this.desiredWidth - rightInsets - newX;
		final int blockShiftRight = rightInsets;
		final int prevHeight = block.height;
		FloatingBoundsSource floatBoundsSource = new FloatingBoundsSource() {
			public FloatingBounds getChildBlockFloatingBounds(int apparentBlockWidth) {
				int actualRightShift = blockShiftRight + (expectedWidth - apparentBlockWidth);		
				return new ShiftedFloatingBounds(floatBounds, -newX, -actualRightShift, -newY);
			}
		};
		block.adjust(expectedWidth, this.availContentHeight, true, false, floatBoundsSource);
		return block.height - prevHeight;
	}

	private RLine addLine(ModelNode startNode, RLine prevLine, int newLineY) {
		// lineDone must be called before we try to
		// get float bounds.
		this.lineDone(prevLine);
		this.checkY(newLineY);
		int leftOffset = this.fetchLeftOffset(newLineY);
		int newX = leftOffset;
		int newMaxWidth = this.desiredWidth - this.fetchRightOffset(newLineY) - leftOffset;
		RLine rline;
		if(prevLine == null) {
			// Note: Assumes that prevLine == null means it's the first line.
			RenderState rs = this.modelNode.getRenderState();
			// Text indentation only applies to the first line in the block.
			int textIndent = rs == null ? 0 : rs.getTextIndent(this.availContentWidth);
			if(textIndent != 0) {
				newX += textIndent;
				// Line width also changes!
				newMaxWidth += (leftOffset - newX);
			}
		}
		else {
			if(prevLine.x + prevLine.width > this.maxX) {
				this.maxX = prevLine.x + prevLine.width;
			}
		}
		rline = new RLine(startNode, this.container, newX, newLineY, newMaxWidth, 0);
		rline.setParent(this);
		ArrayList sr = this.seqRenderables;
		if(sr == null) {
			sr = new ArrayList(1);
			this.seqRenderables = sr;
		}
		sr.add(rline);
		this.currentLine = rline;
		return rline;
	}
	
	private void layoutMarkup(NodeImpl node) {
		// This is the "inline" layout of an element.
		// The difference with layoutChildren is that this
		// method checks for padding and margin insets.
		RenderState rs = node.getRenderState();
		Insets marginInsets = null;
		Insets paddingInsets = null;
		if(rs != null) {
			marginInsets = rs.getMarginInsets();
			paddingInsets = rs.getPaddingInsets();
		}
		int leftSpacing = 0;
		int rightSpacing = 0;
		if(marginInsets != null) {
			leftSpacing += marginInsets.left;
			rightSpacing += marginInsets.right;
		}
		if(paddingInsets != null) {
			leftSpacing += paddingInsets.left;
			rightSpacing += paddingInsets.right;
		}
		if(leftSpacing > 0) {
			RLine line = this.currentLine;
			line.addSpacing(new RSpacing(node, this.container, leftSpacing, line.height));
		}
		this.layoutChildren( node);
		if(rightSpacing > 0) {
			RLine line = this.currentLine;
			line.addSpacing(new RSpacing(node, this.container, rightSpacing, line.height));
		}
	}

	private void layoutChildren(NodeImpl node) {
		NodeImpl[] childrenArray = node.getChildrenArray();
		if(childrenArray != null) {
			int length = childrenArray.length;
			for(int i = 0; i < length; i++) {
				NodeImpl child = childrenArray[i];
				short nodeType = child.getNodeType();
				if(nodeType == Node.TEXT_NODE) {
					this.layoutText( child);					
				}
				else if(nodeType == Node.ELEMENT_NODE) {
					// Note that scanning for node bounds (anchor location)
					// depends on there being a style changer for inline elements.
					this.currentLine.addStyleChanger(new RStyleChanger(child));
					String nodeName = child.getNodeName().toUpperCase();
					MarkupLayout ml = (MarkupLayout) elementLayout.get(nodeName);
					if(ml == null) {
						ml = miscLayout;
					}
					ml.layoutMarkup(this, (HTMLElementImpl) child);
					this.currentLine.addStyleChanger(new RStyleChanger(node));
				}
				else if(nodeType == Node.COMMENT_NODE || nodeType == Node.PROCESSING_INSTRUCTION_NODE) {
					// ignore
				}
				else {
					throw new IllegalStateException("Unknown node: " + child);
				}
			}
		}
	}

	private final void positionRBlock(HTMLElementImpl markupElement, RBlock renderable) {
		this.positionRElement(markupElement, renderable, false, renderable.isOverflowVisible());
	}
	
	private final void positionRElement(HTMLElementImpl markupElement, RElement renderable, boolean usesAlignAttribute, boolean obeysFloats) {
		if(!this.addElsewhereIfPositioned( renderable, markupElement, usesAlignAttribute, true, false)) {
			int availContentWidth = this.availContentWidth;
			int availContentHeight = this.availContentHeight;
			RLine line = this.currentLine;
			// Inform line done before layout so floats are considered.
			this.lineDone(line);
			if(obeysFloats) {
				int newLineY = line == null ? this.paddingInsets.top : line.y + line.height;
				int leftOffset = this.fetchLeftOffset(newLineY);
				int rightOffset = this.fetchRightOffset(newLineY);
				availContentWidth = this.desiredWidth - leftOffset - rightOffset;
			}
			renderable.layout(availContentWidth, availContentHeight);
			this.addAsSeqBlock(renderable, obeysFloats, false);
		}
	}

	private final void layoutRBlock(HTMLElementImpl markupElement, Insets defaultMarginInsets) {
		RBlock renderable = (RBlock) markupElement.getUINode();
		if(renderable == null) {
			renderable = new RBlock(markupElement, this.listNesting, this.userAgentContext, this.rendererContext, this.frameContext, this.container, RBlock.OVERFLOW_NONE);
			markupElement.setUINode(renderable);
		}
		renderable.setOriginalParent(this);
		renderable.setDefaultMarginInsets(defaultMarginInsets);
		this.positionRBlock( markupElement, renderable);
	}

	private final void layoutRTable(HTMLElementImpl markupElement) {
		RElement renderable = (RElement) markupElement.getUINode();
		if(renderable == null) {
			renderable = new RTable(markupElement, this.userAgentContext, this.rendererContext, this.frameContext, container);
			markupElement.setUINode((UINode) renderable);
		}
		renderable.setOriginalParent(this);
		this.positionRElement(markupElement, renderable, markupElement instanceof HTMLTableElementImpl, true);
	}

	private final void layoutListItem(HTMLElementImpl markupElement) {
		RListItem renderable = (RListItem) markupElement.getUINode();
		if(renderable == null) {
			renderable = new RListItem(markupElement, this.listNesting, this.userAgentContext, this.rendererContext, this.frameContext, this.container, null);
			markupElement.setUINode(renderable);
		}
		renderable.setOriginalParent(this);
		this.positionRBlock( markupElement, renderable);		
	}

	private final void layoutList(HTMLElementImpl markupElement) {
		RList renderable = (RList) markupElement.getUINode();
		if(renderable == null) {
			renderable = new RList(markupElement, this.listNesting, this.userAgentContext, this.rendererContext, this.frameContext, this.container, null);
			markupElement.setUINode(renderable);
		}
		renderable.setOriginalParent(this);
		this.positionRBlock( markupElement, renderable);		
	}

	private void addParagraphBreak(ModelNode startNode) {
		// This needs to get replaced with paragraph collapsing
		this.addLineBreak(startNode, LineBreak.NONE);
		this.addLineBreak(startNode, LineBreak.NONE);
	}
	
	private void addLineBreak(ModelNode startNode, int breakType) {
		RLine line = this.currentLine;
		if(line == null) {
			Insets insets = this.paddingInsets;
			this.addLine(startNode, null, insets.top);
			line = this.currentLine;
		}
		if(line.getHeight() == 0) {
			RenderState rs = startNode.getRenderState();
			int fontHeight = rs.getFontMetrics().getHeight();
			line.setHeight(fontHeight);
		}
		line.setLineBreak(new LineBreak(breakType, startNode));
		int newLineY;
		FloatingBounds fb = this.floatBounds;
		if(breakType == LineBreak.NONE || fb == null) {
			newLineY = line == null ? this.paddingInsets.top : line.y + line.height;
		}
		else {
			int prevY = line == null ? this.paddingInsets.top : line.y + line.height;
			switch(breakType) {
			case LineBreak.LEFT:
				newLineY = fb.getLeftClearY(prevY);
				break;
			case LineBreak.RIGHT:
				newLineY = fb.getRightClearY(prevY);
				break;
			default:
				newLineY = fb.getClearY(prevY);
				break;
			}			
		}
		this.currentLine = this.addLine(startNode, line, newLineY);
	}
	
	private boolean addElsewhereIfFloat(BoundableRenderable renderable, HTMLElementImpl element, boolean usesAlignAttribute, AbstractCSS2Properties style, boolean layout, boolean adjust) {
		// "static" handled here
		String align = null;
		if(style != null) {
			align = style.getFloat();
			if(align != null && align.length() == 0) {
				align = null;
			}
		}
		if(align == null && usesAlignAttribute) {
			align = element.getAttribute("align");
		}
		if(align != null) {
			if("left".equalsIgnoreCase(align)) {
				this.layoutFloat(renderable, layout, adjust, true);
				return true;
			}
			else if("right".equalsIgnoreCase(align)) {
				this.layoutFloat(renderable, layout, adjust, false);
				return true;
			}
			else {
				// fall through
			}
		}	
		return false;
	}
	
	final RBlockViewport getParentViewport(ExportedRenderable er) {
		if(er.alignment == 0) {
			return this.getParentViewport();
		}
		else {
			return this.getParentViewportForAlign();
		}
	}
	
//	final boolean isImportable(ExportedRenderable er) {
//		if(er.alignment == 0) {
//			return this.positionsAbsolutes();
//		}
//		else {
//			return this.getParentViewportForAlign() == null;
//		}
//	}
	
	final RBlockViewport getParentViewport() {
		// Use originalParent, which for one, is not going to be null during layout.
		RCollection parent = this.getOriginalOrCurrentParent();
		while(parent != null && !(parent instanceof RBlockViewport)) {
			parent = parent.getOriginalOrCurrentParent();
		}
		return (RBlockViewport) parent;
	}

	final RBlockViewport getParentViewportForAlign() {
		// Use originalParent, which for one, is not going to be null during layout.
		Object parent = this.getOriginalOrCurrentParent();
		if(parent instanceof RBlock) {
			RBlock block = (RBlock) parent;
			if(!block.couldBeScrollable()) {
				parent = ((BaseElementRenderable) parent).getOriginalOrCurrentParent();
				if(parent instanceof RBlockViewport) {
					return (RBlockViewport) parent;
				}
			}	
		}
		return null;
	}

	private static int getPosition(HTMLElementImpl element) {
		RenderState rs = element.getRenderState();
		return rs == null ? RenderState.POSITION_STATIC : rs.getPosition();
	}
	
	/**
	 * Checks for position and float attributes.
	 * @param container
	 * @param containerSize
	 * @param insets
	 * @param renderable
	 * @param element
	 * @param usesAlignAttribute
	 * @return True if it was added elsewhere.
	 */
	private boolean addElsewhereIfPositioned(RElement renderable, HTMLElementImpl element, boolean usesAlignAttribute, boolean layout, boolean obeysFloats) {
		// At this point block already has bounds.
		AbstractCSS2Properties style = element.getCurrentStyle();
		int position = getPosition(element);
		boolean absolute = position == RenderState.POSITION_ABSOLUTE;
		boolean relative = position == RenderState.POSITION_RELATIVE;
		if(absolute || relative) {
			if(layout) {
				// Presumes the method will return true.
				renderable.layout(this.availContentWidth, this.availContentHeight);
				if(absolute && renderable instanceof RBlock) {
					((RBlock) renderable).adjust();
				}
			}
			RenderState rs = element.getRenderState();
			String leftText = style.getLeft();
			RLine line = this.currentLine;
			int lineBottomY = line == null ? 0 : line.getY() + line.getHeight();
			int newLeft;
			if(leftText != null) {
				newLeft = HtmlValues.getPixelSize(leftText, rs, 0, this.availContentWidth);
			}
			else {
				String rightText = style.getRight();
				if(rightText != null) {
					int right = HtmlValues.getPixelSize(rightText, rs, 0, this.availContentWidth);
					newLeft = this.desiredWidth - right - renderable.getWidth();
					// If right==0 and renderable.width is larger than the parent's width, 
					// the expected behavior is for newLeft to be negative.
				}
				else {
					newLeft = 0;
				}
			}
			int newTop = relative ? 0 : lineBottomY;
			String topText = style.getTop();
			if(topText != null) {
				newTop = HtmlValues.getPixelSize(topText, rs, newTop, this.availContentHeight);
			}
			else {
				String bottomText = style.getBottom();
				if(bottomText != null) {
					int bottom = HtmlValues.getPixelSize(bottomText, rs, 0, this.availContentHeight);
					newTop = this.desiredHeight - bottom - renderable.getHeight();
					if(!relative && newTop < 0) {
						newTop = 0;
					}
				}
			}
			if(relative) {
				// First, try to add normally.
				RRelative rrel = new RRelative(this.container, element, renderable, newLeft, newTop);
				rrel.adjustSize();
				if(!this.addElsewhereIfFloat(rrel, element, usesAlignAttribute, style, false, true)) {
					this.addAsSeqBlock(rrel, obeysFloats, true);
				}
				else {
					// Adjust size of RRelative again - float might have been adjusted.
					rrel.adjustSize();
				}
			}
			else {
				// Schedule as delayed pair. Will be positioned after
				// everything else.
				this.scheduleAbsDelayedPair(renderable, newLeft, newTop);
				// Does not affect bounds of this viewport yet.
				return true;
			}
			int newBottomY = renderable.getY() + renderable.getHeight();
			this.checkY(newBottomY);
			if(newBottomY > this.maxY) {
				this.maxY = newBottomY;
			}
			return true;
		}
		else {
			if(this.addElsewhereIfFloat( renderable, element, usesAlignAttribute, style, layout, layout)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Checks property 'float' and in some cases attribute 'align'.
	 */
	private void addRenderableToLineCheckStyle(RElement renderable, HTMLElementImpl element, boolean usesAlignAttribute) {
		if(this.addElsewhereIfPositioned( renderable, element, usesAlignAttribute, false, true)) {
			return;
		}
		this.addRenderableToLine(renderable);
	}
	
	private void addRenderableToLine(Renderable renderable) {
		//this.skipLineBreakBefore = false;
		RenderState rs = renderable.getModelNode().getRenderState();
		int whiteSpace = this.overrideNoWrap ? RenderState.WS_NOWRAP : rs.getWhiteSpace();
		boolean allowOverflow = whiteSpace == RenderState.WS_NOWRAP || whiteSpace == RenderState.WS_PRE;

		RLine line = this.currentLine;
		int liney = line.y;
		boolean emptyLine = line.isEmpty();
		FloatingBounds floatBounds = this.floatBounds;
		int cleary;
		if(floatBounds != null) {
			cleary = floatBounds.getFirstClearY(liney);
		}
		else {
			cleary = liney + line.height;
		}
		int overflowType = floatBounds != null && cleary != liney ? RLine.OVERFLOW_ALLOW_NONE : (allowOverflow ? RLine.OVERFLOW_ALLOW_ALL : RLine.OVERFLOW_ALLOW_FIRST);
		this.skipParagraphBreakBefore = false;
		try {
			line.add(renderable, overflowType);
		} catch(OverflowException oe) {
			int nextY = emptyLine ? cleary : liney + line.height;
			this.addLine(renderable.getModelNode(), line, nextY);
			Collection renderables = oe.getRenderables();
			Iterator i = renderables.iterator();
			while(i.hasNext()) {
				Renderable r = (Renderable) i.next();
				this.addRenderableToLine(r);
			}
		}
		if(renderable instanceof RUIControl) {
			this.container.addComponent(((RUIControl) renderable).widget.getComponent());
		}
	}

	private void addWordToLine(RWord renderable, boolean allowOverflow) {
		//this.skipLineBreakBefore = false;
		this.skipParagraphBreakBefore = false;
		RLine line = this.currentLine;
		int liney = line.y;
		boolean emptyLine = line.isEmpty();
		FloatingBounds floatBounds = this.floatBounds;
		int cleary;
		if(floatBounds != null) {
			cleary = floatBounds.getFirstClearY(liney);
		}
		else {
			cleary = liney + line.height;
		}
		int overflowType = floatBounds != null && cleary != liney ? RLine.OVERFLOW_ALLOW_NONE : (allowOverflow ? RLine.OVERFLOW_ALLOW_ALL : RLine.OVERFLOW_ALLOW_FIRST);
		try {
			line.addWord(renderable, overflowType);
		} catch(OverflowException oe) {
			int nextY = emptyLine ? cleary : liney + line.height;
			this.addLine(renderable.getModelNode(), line, nextY);
			Collection renderables = oe.getRenderables();
			Iterator i = renderables.iterator();
			while(i.hasNext()) {
				Renderable r = (Renderable) i.next();
				this.addRenderableToLine(r);
			}
		}
	}

	private void addAsSeqBlockCheckStyle(RElement block, HTMLElementImpl element, boolean usesAlignAttribute) {
		if(this.addElsewhereIfPositioned(block, element, usesAlignAttribute, false, true)) {
			return;
		}
		this.addAsSeqBlock(block);		
	}
	
	private void addAsSeqBlock(BoundableRenderable block) {
		this.addAsSeqBlock(block, true, true);
	}
	
	private void addAsSeqBlock(BoundableRenderable block, boolean obeysFloats, boolean informLineDone) {
		Insets insets = this.paddingInsets;
		int insetsl = insets.left;
		ArrayList sr = this.seqRenderables;
		if(sr == null) {
			sr = new ArrayList(1);
			this.seqRenderables = sr;
		}
		RLine prevLine = this.currentLine;
		if(prevLine != null) {
			if(informLineDone) {
				this.lineDone(prevLine);	
			}
			if(prevLine.x + prevLine.width > this.maxX) {
				this.maxX = prevLine.x + prevLine.width;
			}
			// Check height only with floats.
		}
		int newLineY = prevLine == null ? insets.top : prevLine.y + prevLine.height;
		int blockX;
		int blockY = newLineY;
		int blockWidth = block.getWidth();
		if(obeysFloats) {
			//TODO: execution of fetchLeftOffset done twice with positionRElement.
			FloatingBounds floatBounds = this.floatBounds;
			if(floatBounds != null) {
				int blockOffset = this.fetchLeftOffset(newLineY);
				blockX = blockOffset;
				int rightOffset = this.fetchRightOffset(newLineY);
				int actualAvailWidth = this.desiredWidth - rightOffset - blockOffset;
				if(blockWidth > actualAvailWidth) {
					blockY = floatBounds.getClearY(newLineY);
				}	
			}
			else {
				blockX = insetsl;
			}
		}
		else {
			//Block does not obey alignment margins
			blockX = insetsl;
		}
		block.setOrigin(blockX, blockY);
		sr.add(block);
		block.setParent(this);
		if(blockX + blockWidth > this.maxX) {
			this.maxX = blockX + blockWidth;
		}
		newLineY = blockY + block.getHeight();
		this.checkY(newLineY);
		int leftOffset = this.fetchLeftOffset(newLineY);
		int newX = leftOffset;
		int newMaxWidth = this.desiredWidth - this.fetchRightOffset(newLineY) - leftOffset;
		ModelNode lineNode = block.getModelNode().getParentModelNode();
		RLine newLine = new RLine(lineNode, this.container, newX, newLineY, newMaxWidth, 0);
		newLine.setParent(this);
		sr.add(newLine);
		this.currentLine = newLine;
		this.skipParagraphBreakBefore = false;
	}
			
	private void layoutText(NodeImpl textNode) {
		RenderState renderState = textNode.getRenderState();
		if(renderState == null) {
			throw new IllegalStateException("RenderState is null for node " + textNode + " with parent " + textNode.getParentNode());
		}
		FontMetrics fm = renderState.getFontMetrics();
		int descent = fm.getDescent();
		int ascentPlusLeading = fm.getAscent() + fm.getLeading();
		int wordHeight = fm.getHeight();
		int blankWidth = fm.charWidth(' ');
		int whiteSpace = this.overrideNoWrap ? RenderState.WS_NOWRAP : renderState.getWhiteSpace();
		String text = textNode.getNodeValue(); 
		if(whiteSpace != RenderState.WS_PRE) {
			boolean allowOverflow = whiteSpace == RenderState.WS_NOWRAP;
			int length = text.length();
			StringBuffer word = new StringBuffer(12);
			for(int i = 0; i < length; i++) {
				char ch = text.charAt(i);
				if(Character.isWhitespace(ch)) {
					int wlen = word.length();
					if(wlen > 0) {
						RWord rword = new RWord(textNode, word.toString(), container, fm, descent, ascentPlusLeading, wordHeight);
						this.addWordToLine(rword, allowOverflow);
						word.delete(0, wlen);
					}
					RLine line = this.currentLine;
					if(line.width > 0) {
						RBlank rblank = new RBlank(textNode, fm, container, ascentPlusLeading, blankWidth, wordHeight);
						line.addBlank(rblank);					
					}
					for(i++; i < length; i++) {
						ch = text.charAt(i);
						if(!Character.isWhitespace(ch)) {
							word.append(ch);
							break;
						}
					}
				}
				else {
					word.append(ch);
				}
			}
			if(word.length() > 0) {
				RWord rword = new RWord(textNode, word.toString(), container, fm, descent, ascentPlusLeading, wordHeight);
				this.addWordToLine(rword, allowOverflow);
			}
		}
		else {
			int length = text.length();
			boolean lastCharSlashR = false;
			StringBuffer line = new StringBuffer();
			for(int i = 0; i < length; i++) {
				char ch = text.charAt(i);
				switch(ch) {
				case '\r':
					lastCharSlashR = true;
					break;
				case '\n':
					int llen = line.length();
					if(llen > 0) {
						RWord rword = new RWord(textNode, line.toString(), container, fm, descent, ascentPlusLeading, wordHeight);
						this.addWordToLine(rword, true);
						line.delete(0, line.length());
					}
					RLine prevLine = this.currentLine;
					prevLine.setLineBreak(new LineBreak(LineBreak.NONE, textNode));
					this.addLine(textNode, prevLine, prevLine.y + prevLine.height);
					break;
				default:
					if(lastCharSlashR) {
						line.append('\r');
						lastCharSlashR = false;
					}
					line.append(ch);
					break;
				}
			}
			if(line.length() > 0) {
				RWord rword = new RWord(textNode, line.toString(), container, fm, descent, ascentPlusLeading, wordHeight);
				this.addWordToLine(rword, true);
			}
		}
	}

	/**
	 * 
	 * @param others An ordered collection.
	 * @param seqRenderables
	 * @param destination
	 */
	private void populateZIndexGroups(Collection others, Collection seqRenderables, ArrayList destination) {
		this.populateZIndexGroups(others, seqRenderables == null ? null : seqRenderables.iterator(), destination);
	}

	/**
	 * 
	 * @param others An ordered collection.
	 * @param seqRenderablesIterator
	 * @param destination
	 */
	private void populateZIndexGroups(Collection others, Iterator seqRenderablesIterator, ArrayList destination) {
		// First, others with z-index < 0
		Iterator i1 = others.iterator();
		Renderable pending = null;
		while(i1.hasNext()) {
			PositionedRenderable pr = (PositionedRenderable) i1.next();
			BoundableRenderable r = pr.renderable;
			if(r.getZIndex() >= 0) {
				pending = r;
				break;
			}
			destination.add(r);
		}
		
		// Second, sequential renderables
		Iterator i2 = seqRenderablesIterator;
		if(i2 != null) {
			while(i2.hasNext()) {
				destination.add(i2.next());
			}
		}
		
		// Third, other renderables with z-index >= 0.
		if(pending != null) {
			destination.add(pending);
			while(i1.hasNext()) {
				PositionedRenderable pr = (PositionedRenderable) i1.next();
				Renderable r = pr.renderable;
				destination.add(r);			
			}
		}
	}
	
	public Renderable[] getRenderablesArray() {
		SortedSet others = this.positionedRenderables;
		int othersSize = others == null ? 0 : others.size();
		if(othersSize == 0) {
			ArrayList sr = this.seqRenderables;
			return sr == null ? Renderable.EMPTY_ARRAY : (Renderable[]) sr.toArray(Renderable.EMPTY_ARRAY);
		}
		else {
			ArrayList allRenderables = new ArrayList();
			this.populateZIndexGroups(others, this.seqRenderables, allRenderables);
			return (Renderable[]) allRenderables.toArray(Renderable.EMPTY_ARRAY);
		}
	}
	
	public Iterator getRenderables() {
		SortedSet others = this.positionedRenderables;
		if(others == null || others.size() == 0) {
			ArrayList sr = this.seqRenderables;
			return sr == null ? null : sr.iterator();
		}
		else {
			ArrayList allRenderables = new ArrayList();
			this.populateZIndexGroups(others, this.seqRenderables, allRenderables);
			return allRenderables.iterator();
		}
	}

	public Iterator getRenderables(Rectangle clipBounds) {
		if(!EventQueue.isDispatchThread() && logger.isLoggable(Level.INFO)) {
			logger.warning("getRenderables(): Invoked outside GUI dispatch thread.");
		}
		ArrayList sr = this.seqRenderables;
		Iterator baseIterator = null;
		if(sr != null) {
			Renderable[] array = (Renderable[]) sr.toArray(Renderable.EMPTY_ARRAY);
			Range range = MarkupUtilities.findRenderables(array, clipBounds, true);
			baseIterator = org.lobobrowser.util.ArrayUtilities.iterator(array, range.offset, range.length);
		}
		SortedSet others = this.positionedRenderables;
		if(others == null || others.size() == 0) {
			return baseIterator;
		}
		else {
			ArrayList matches = new ArrayList();
			// ArrayList "matches" keeps the order from "others".
			Iterator i = others.iterator();
			while(i.hasNext()) {
				PositionedRenderable pr = (PositionedRenderable) i.next();
				Object r = pr.renderable;
				if(r instanceof BoundableRenderable) {
					BoundableRenderable br = (BoundableRenderable) r;
					Rectangle rbounds = br.getBounds();
					if(clipBounds.intersects(rbounds)) {
						matches.add(pr);
					}
				}
			}
			if(matches.size() == 0) {
				return baseIterator;
			}
			else {
				ArrayList destination = new ArrayList();
				this.populateZIndexGroups(matches, baseIterator, destination);
				return destination.iterator();
			}
		}
	}

	
	public BoundableRenderable getRenderable(int x, int y) {
		Iterator i = this.getRenderables(x, y);
		return i == null ? null : (i.hasNext() ? (BoundableRenderable) i.next() : null);
	}

	public BoundableRenderable getRenderable(java.awt.Point point) {
		return this.getRenderable(point.x, point.y);
	}

	public Iterator getRenderables(java.awt.Point point) {
		return this.getRenderables(point.x, point.y);
	}
	
	public Iterator getRenderables(int pointx, int pointy) {
		if(!EventQueue.isDispatchThread() && logger.isLoggable(Level.INFO)) {
			logger.warning("getRenderable(): Invoked outside GUI dispatch thread.");
		}
		Collection result = null;
		SortedSet others = this.positionedRenderables;
		int size = others == null ? 0 : others.size();
		PositionedRenderable[] otherArray = size == 0 ? null : (PositionedRenderable[]) others.toArray(PositionedRenderable.EMPTY_ARRAY);
		// Try to find in other renderables with z-index >= 0 first.
		int index = 0;
		if(size != 0) {
			int px = pointx;
			int py = pointy;	
			// Must go in reverse order
			for(index = size; --index >= 0;) {
				PositionedRenderable pr = otherArray[index];
				BoundableRenderable r = pr.renderable;
				if(r.getZIndex() < 0) {
					break;
				}
				if(r instanceof BoundableRenderable) {
					BoundableRenderable br = (BoundableRenderable) r;
					Rectangle rbounds = br.getBounds();
					if(rbounds.contains(px, py)) {
						if(result == null) {
							result = new LinkedList();
						}
						result.add(br);
					}
				}
			}
		}
		
		// Now do a "binary" search on sequential renderables.
		ArrayList sr = this.seqRenderables;
		if(sr != null) {
			Renderable[] array = (Renderable[]) sr.toArray(Renderable.EMPTY_ARRAY);
			BoundableRenderable found = MarkupUtilities.findRenderable(array, pointx, pointy, true);
			if(found != null) {
				if(result == null) {
					result = new LinkedList();
				}
				result.add(found);
			}
		}
		
		// Finally, try to find it in renderables with z-index < 0.
		if(size != 0) {
			int px = pointx;
			int py = pointy;	
			// Must go in reverse order
			for(; index >= 0; index--) {
				PositionedRenderable pr = otherArray[index];
				Renderable r = pr.renderable;
				if(r instanceof BoundableRenderable) {
					BoundableRenderable br = (BoundableRenderable) r;
					Rectangle rbounds = br.getBounds();
					if(rbounds.contains(px, py)) {
						if(result == null) {
							result = new LinkedList();
						}
						result.add(br);
					}
				}
			}
		}	
		return result == null ? null : result.iterator();
	}

	private RElement setupNewUIControl(RenderableContainer container, HTMLElementImpl element, UIControl control) {
		RElement renderable = new RUIControl(element, control, container, this.frameContext, this.userAgentContext);
		element.setUINode((UINode) renderable);
		return renderable;
	}
	
	private final void addAlignableAsBlock(HTMLElementImpl markupElement, RElement renderable) {
		//TODO: Get rid of this method?
		// At this point block already has bounds.
		boolean regularAdd = false;
		String align = markupElement.getAttribute("align");
		if(align != null) {
			if("left".equalsIgnoreCase(align)) {
				this.layoutFloat(renderable, false, false, true); 
			}
			else if("right".equalsIgnoreCase(align)) {
				this.layoutFloat(renderable, false, false, true);
			}
			else {
				regularAdd = true;
			}	
		}
		else {
			regularAdd = true;
		}		
		if(regularAdd) {
			this.addAsSeqBlock(renderable);
		}
	}
	
	private final void layoutHr(HTMLElementImpl markupElement) {
		RElement renderable = (RElement) markupElement.getUINode();
		if(renderable == null) {
			renderable = this.setupNewUIControl(container, markupElement, new HrControl(markupElement));
		}
		renderable.layout(this.availContentWidth, this.availContentHeight);
		this.addAlignableAsBlock( markupElement, renderable);
	}

	private final BaseInputControl createInputControl(HTMLBaseInputElement markupElement) {
		String type = markupElement.getAttribute("type");
		if(type == null) {
			return new InputTextControl(markupElement);
		}
		type = type.toLowerCase();
		if("text".equals(type) || type.length() == 0) {
			return new InputTextControl(markupElement);
		}
		else if("hidden".equals(type)) {
			return null;
		}
		else if("submit".equals(type)) {
			return new InputButtonControl(markupElement);
		}
		else if("password".equals(type)) {
			return new InputPasswordControl(markupElement);
		}
		else if("radio".equals(type)) {
			return new InputRadioControl(markupElement);
		}
		else if("checkbox".equals(type)) {
			return new InputCheckboxControl(markupElement);
		}
		else if("image".equals(type)) {
			return new InputImageControl(markupElement);
		}
		else if("reset".equals(type)) {
			return new InputButtonControl(markupElement);
		}
		else if("button".equals(type)) {
			return new InputButtonControl(markupElement);
		}
		else if("file".equals(type)) {
			return new InputFileControl(markupElement);
		}
		else {
			return null;
		}
	}
	
	/**
	 * Gets offset from the left due to floats. It includes padding.
	 */
	private final int fetchLeftOffset(int newLineY) {
		Insets paddingInsets = this.paddingInsets;
		FloatingBounds floatBounds = this.floatBounds;
		if(floatBounds == null) {
			return paddingInsets.left;
		}
		int left = floatBounds.getLeft(newLineY);
		if(left < paddingInsets.left) {
			return paddingInsets.left;
		}
		return left;
	}
	
	/**
	 * Gets offset from the right due to floats. It includes padding.
	 */
	private final int fetchRightOffset(int newLineY) {
		Insets paddingInsets = this.paddingInsets;
		FloatingBounds floatBounds = this.floatBounds;
		if(floatBounds == null) {
			return paddingInsets.right;
		}
		int right = floatBounds.getRight(newLineY);
		if(right < paddingInsets.right) {
			return paddingInsets.right;
		}
		return right;
	}
	
	private static final SizeExceededException SEE = new SizeExceededException();
	
	private final void checkY(int y) {
		if(this.yLimit != -1 && y > this.yLimit) {
			throw SEE;
		}
	}
	
	private final void layoutFloat(BoundableRenderable renderable, boolean layout, boolean adjust, boolean leftFloat) {
		renderable.setOriginalParent(this);
		if(layout) {
			int availWidth = this.availContentWidth;
			int availHeight = this.availContentHeight;
			if(renderable instanceof RElement) {
				RElement e = (RElement) renderable;
				e.layout(availWidth, availHeight);
			}
		}
		if(adjust && renderable instanceof RBlock) {
			((RBlock) renderable).adjust();
		}
		RFloatInfo floatInfo = new RFloatInfo(renderable.getModelNode(), renderable, leftFloat, false);
		this.currentLine.simplyAdd(floatInfo);		
		this.scheduleFloat(floatInfo);
	}	

	
//	private final void positionFloat(RElement brenderable) {		
//		//OPTIMIZE: Point translation done twice.
//		Point localPoint = brenderable == this ? new Point(pair.x, pair.y) : this.translateDescendentPoint(brenderable.getOriginalOrCurrentParent(), pair.x, pair.y);
//		int width = brenderable.getWidth();
//		int height = brenderable.getHeight();
//		int y = localPoint.y;
//		int newY = y + height;
//		this.checkY(newY);
//		if(newY > this.maxY) {
//			this.maxY = newY;
//		}
//		int alignment = pair.alignment;
//		FloatingBounds prevFloatBounds = this.floatBounds;
//		int x;
//		Insets paddingInsets = this.paddingInsets;
//		int left = paddingInsets == null ? 0 : paddingInsets.left;
//		if(prevFloatBounds != null) {
//			x = alignment > 0 ? left + this.availContentWidth - prevFloatBounds.getRight(y) - width : left + prevFloatBounds.getLeft(y);
//		}
//		else {
//			x = alignment > 0 ? left + this.availContentWidth - width : left;
//		}
//		if(x < left) {
//			x = left;
//		}
//		int effectiveWidth = width;
//		if(alignment > 0) {
//			if(localPoint.x < x) {
//				effectiveWidth += (x - localPoint.x);
//				x = localPoint.x;
//			}
//		}
//		else {
//			if(localPoint.x > x) {
//				effectiveWidth += (localPoint.x - x);
//				x = localPoint.x;
//			}
//		}
//		FloatingViewportBounds newFloatBounds = new FloatingViewportBounds(prevFloatBounds, alignment, y, effectiveWidth, height);
//		this.floatBounds = newFloatBounds;
//		brenderable.setOrigin(x, y);
//		if(x + width > this.maxX) {
//			this.maxX = x + width;
//		}
//	}
//
//	private final void addFloat(DelayedPair pair) {		
//		BoundableRenderable brenderable = pair.child;
//		this.addPositionedRenderable(brenderable, true);
//	}
	
	
//	private boolean scheduleAddInContainingViewport(RBlockViewport originalTarget, BoundableRenderable renderable, int x, int y, int alignment) {
//		// Always succeeds. It gets reimported in the local
//		// viewport if it turns out it can't be exported up.
//		this.addExportedRenderable(originalTarget, renderable, x, y, alignment);
//		return true;
//	}

	private void scheduleAbsDelayedPair(BoundableRenderable renderable, int x, int y) {
		// It gets reimported in the local
		// viewport if it turns out it can't be exported up.
		RenderableContainer container = this.container;
		for(;;) {
			if(container instanceof Renderable) {
				Object node = ((Renderable) container).getModelNode();
				if(node instanceof HTMLElementImpl) {
					HTMLElementImpl element = (HTMLElementImpl) node;
					int position = getPosition(element);
					if(position != RenderState.POSITION_STATIC) {
						break;
					}
					RenderableContainer newContainer = container.getParentContainer();
					if(newContainer == null) {
						break;
					}
					container = newContainer;
				}
				else {
					break;
				}
			}
			else {
				break;
			}
		}
		DelayedPair pair = new DelayedPair(container, renderable, x, y);
		this.container.addDelayedPair(pair);
	}

//	private void scheduleFloatDelayedPair(BoundableRenderable renderable, int x, int y, int alignment) {
//		// It gets reimported in the local
//		// viewport if it turns out it can't be exported up.
//		RenderableContainer targetContainer = this.container;
//		RBlockViewport targetViewport = this;
//		for(;;) {
//			if(targetViewport.isPositionedElsewhere()) {
//				break;
//			}
//			if(!(targetContainer instanceof RBlock)) {
//				break;
//			}
//			RBlock rblock = (RBlock) targetContainer;
//			Object parent = rblock.getOriginalOrCurrentParent();
//			if(!(parent instanceof RBlockViewport)) {
//				break;
//			}
//			targetViewport = (RBlockViewport) parent;
//			targetContainer = targetViewport.container;
//		}
//		DelayedPair pair = new DelayedPair(alignment, targetContainer, renderable, x, y);
//		this.container.addDelayedPair(pair);
//	}

	void importDelayedPair(DelayedPair pair) {
		BoundableRenderable r = pair.child;
		r.setOrigin(pair.x, pair.y);
		this.addPositionedRenderable(r, false);
		// Size of block does not change - it's
		// set in stone?
	}

//	boolean addToExportedRenderables(ExportedRenderable er) {
//		// Always succeeds. It gets reimported in the local
//		// viewport if it turns out it can't be exported up.
//		this.addExportedRenderable(er);
//		return true;
//	}

//	private final void addExportedRenderable(RBlockViewport originalTarget, BoundableRenderable renderable, int x, int y, int align) {
//		// Note that exported renderables currently require
//		// the parent to be a block contained in a viewport.
//		Collection er = this.exportedRenderables;
//		if(er == null) {
//			er = new LinkedList();
//			this.exportedRenderables = er;
//		}
//		er.add(new ExportedRenderable(originalTarget, renderable, x, y, align));
//	}

//	private final void addExportedRenderable(ExportedRenderable er) {
//		// Note that exported renderables currently require
//		// the parent to be a block contained in a viewport.
//		Collection ers = this.exportedRenderables;
//		if(ers == null) {
//			ers = new LinkedList();
//			this.exportedRenderables = ers;
//		}
//		ers.add(er);
//	}

//	final Iterator getExportedRenderables() {
//		Collection er = this.exportedRenderables;
//		return er == null ? null : er.iterator();
//	}
	
	private final void addPositionedRenderable(BoundableRenderable renderable, boolean verticalAlignable) {
		// Expected to be called only in GUI thread.
		SortedSet others = this.positionedRenderables;
		if(others == null) {
			others = new TreeSet(new ZIndexComparator());
			this.positionedRenderables = others;
		}
		others.add(new PositionedRenderable(renderable, verticalAlignable, this.positionedOrdinal++));
		renderable.setParent(this);
		if(renderable instanceof RUIControl) {
			this.container.addComponent(((RUIControl) renderable).widget.getComponent());
		}
	}
	
	public int getFirstLineHeight() {
		ArrayList renderables = this.seqRenderables;
		if(renderables != null) {
			int size = renderables.size();
			if(size == 0) {
				return 0;
			}
			for(int i = 0; i < size; i++) {
				BoundableRenderable br = (BoundableRenderable) renderables.get(0);
				int height = br.getHeight();
				if(height != 0) {
					return height;
				}
			}
		}
		// Not found!!
		return 1;
	}

	public int getFirstBaselineOffset() {
		ArrayList renderables = this.seqRenderables;
		if(renderables != null) {
			Iterator i = renderables.iterator();
			while(i.hasNext()) {
				Object r = i.next();
				if(r instanceof RLine) {
					int blo =((RLine) r).getBaselineOffset();
					if(blo != 0) {
						return blo;
					}
				}
				else if(r instanceof RBlock) {
					RBlock block = (RBlock) r;
					if(block.getHeight() > 0) {
						Insets insets = block.getInsets(false, false);
						Insets paddingInsets = block.getPaddingInsets(block.getModelNode().getRenderState());
						return block.getFirstBaselineOffset() + insets.top + (paddingInsets == null ? 0 : paddingInsets.top);
					}					
				}
			}
		}
		return 0;
	}
	
    //----------------------------------------------------------------

	public RenderableSpot getLowestRenderableSpot(int x, int y) {
	    BoundableRenderable br = this.getRenderable(new Point(x, y));
	    if(br != null) {
	    	return br.getLowestRenderableSpot(x - br.getX(), y - br.getY());
	    }
	    else {
	    	return new RenderableSpot(this, x, y);
	    }
	}

	/* (non-Javadoc)
	 * @see org.xamjwg.html.renderer.BoundableRenderable#onMouseClick(java.awt.event.MouseEvent, int, int)
	 */
	public boolean onMouseClick(MouseEvent event, int x, int y) {
		Iterator i = this.getRenderables(new Point(x, y));
		if(i != null) {
			while(i.hasNext()) {
			    BoundableRenderable br = (BoundableRenderable) i.next();
			    if(br != null) {
			    	Rectangle bounds = br.getBounds();
			    	if(!br.onMouseClick(event, x - bounds.x, y - bounds.y)) {
			    		return false;
			    	}
			    }
			}
		}
		return true;
	}

	public boolean onDoubleClick(MouseEvent event, int x, int y) {
		Iterator i = this.getRenderables(new Point(x, y));
		if(i != null) {
			while(i.hasNext()) {
			    BoundableRenderable br = (BoundableRenderable) i.next();
			    if(br != null) {
			    	Rectangle bounds = br.getBounds();
			    	if(!br.onDoubleClick(event, x - bounds.x, y - bounds.y)) {
			    		return false;
			    	}
			    }
			}
		}
		return true;
	}

	/* (non-Javadoc)
	 * @see org.xamjwg.html.renderer.BoundableRenderable#onMouseDisarmed(java.awt.event.MouseEvent)
	 */
	public boolean onMouseDisarmed(MouseEvent event) {
		BoundableRenderable br = this.armedRenderable;
		if(br != null) {
			try {
				return br.onMouseDisarmed(event);
			} finally {
				this.armedRenderable = null;
			}
		}
		else {
			return true;
		}
	}

	private BoundableRenderable armedRenderable;
	
	/* (non-Javadoc)
	 * @see org.xamjwg.html.renderer.BoundableRenderable#onMousePressed(java.awt.event.MouseEvent, int, int)
	 */
	public boolean onMousePressed(MouseEvent event, int x, int y) {
		Iterator i = this.getRenderables(new Point(x, y));
		if(i != null) {
			while(i.hasNext()) {
			    BoundableRenderable br = (BoundableRenderable) i.next();
			    if(br != null) {
			    	Rectangle bounds = br.getBounds();
			    	if(!br.onMousePressed(event, x - bounds.x, y - bounds.y)) {
				    	this.armedRenderable = br;
			    		return false;
			    	}
			    }
			}
		}
		return true;
	}

	/* (non-Javadoc)
	 * @see org.xamjwg.html.renderer.BoundableRenderable#onMouseReleased(java.awt.event.MouseEvent, int, int)
	 */
	public boolean onMouseReleased(MouseEvent event, int x, int y) {
		Iterator i = this.getRenderables(new Point(x, y));
		if(i != null) {
			while(i.hasNext()) {
			    BoundableRenderable br = (BoundableRenderable) i.next();
			    if(br != null) {
			    	Rectangle bounds = br.getBounds();
			    	if(!br.onMouseReleased(event, x - bounds.x, y - bounds.y)) {
				    	BoundableRenderable oldArmedRenderable = this.armedRenderable;
				    	if(oldArmedRenderable != null && br != oldArmedRenderable) {
				    		oldArmedRenderable.onMouseDisarmed(event);
				    		this.armedRenderable = null;
				    	}
			    		return false;
			    	}
			    }
			}
		}
    	BoundableRenderable oldArmedRenderable = this.armedRenderable;
    	if(oldArmedRenderable != null) {
    		oldArmedRenderable.onMouseDisarmed(event);
    		this.armedRenderable = null;
    	}	    	
    	return true;
	}

	public void paint(Graphics g) {
		Rectangle clipBounds = g.getClipBounds();
		Iterator i = this.getRenderables(clipBounds);
		if(i != null) {
			int renderableCount = 0;
			while(i.hasNext()) {
				renderableCount++;
				Object robj = i.next();			
				// The expected behavior in HTML is for boxes
				// not to be clipped unless overflow=hidden.
				if(robj instanceof BoundableRenderable) {
					BoundableRenderable renderable = (BoundableRenderable) robj;
					renderable.paintTranslated(g);
					//numRenderables++;
				}
				else {
					((Renderable) robj).paint(g);
				}
			}
		}
	}
	
	//----------------------------------------------------------------

	private static class NopLayout implements MarkupLayout {
		public void layoutMarkup(RBlockViewport bodyLayout, HTMLElementImpl markupElement) {
		}
	}

	private static class NoScriptLayout implements MarkupLayout {
		public void layoutMarkup(RBlockViewport bodyLayout, HTMLElementImpl markupElement) {
			UserAgentContext ucontext = bodyLayout.userAgentContext;
			if(!ucontext.isScriptingEnabled()) {
				bodyLayout.layoutMarkup( markupElement);
			}
			else {
				//NOP
			}
		}
	}

	private static class ChildrenLayout implements MarkupLayout {
		/* (non-Javadoc)
		 * @see org.xamjwg.html.renderer.MarkupLayout#layoutMarkup(java.awt.Container, java.awt.Insets, org.xamjwg.html.domimpl.HTMLElementImpl)
		 */
		public void layoutMarkup(RBlockViewport bodyLayout, HTMLElementImpl markupElement) {
			bodyLayout.layoutChildren(markupElement);
		}
	}

	private static class MiscLayout extends CommonLayout {
		public MiscLayout() {
			super(DISPLAY_INLINE);
		}
	}

	private static class HLayout extends CommonLayout {		
		public HLayout(int fontSize) {
			super(DISPLAY_BLOCK);
		}
		
		/* (non-Javadoc)
		 * @see org.xamjwg.html.renderer.MarkupLayout#layoutMarkup(java.awt.Container, java.awt.Insets, org.xamjwg.html.domimpl.HTMLElementImpl)
		 */
		public void layoutMarkup(RBlockViewport bodyLayout, HTMLElementImpl markupElement) {
			if(!bodyLayout.skipParagraphBreakBefore) {
				RLine currentLine = bodyLayout.currentLine;
				if(currentLine.width == 0) {
					bodyLayout.addLineBreak(markupElement, LineBreak.NONE);
				}
				else {
					bodyLayout.addParagraphBreak(markupElement);
				}
				bodyLayout.skipParagraphBreakBefore = true;
			}
			super.layoutMarkup(bodyLayout, markupElement);
			if(markupElement.hasChildNodes()) {
				bodyLayout.addLineBreak(markupElement, LineBreak.NONE);
				bodyLayout.skipParagraphBreakBefore = true;				
			}
		}
	}

	private static class PLayout extends CommonLayout {
		public PLayout() {
			super(DISPLAY_BLOCK);
		}
		
		/* (non-Javadoc)
		 * @see org.xamjwg.html.renderer.MarkupLayout#layoutMarkup(java.awt.Container, java.awt.Insets, org.xamjwg.html.domimpl.HTMLElementImpl)
		 */
		public void layoutMarkup(RBlockViewport bodyLayout, HTMLElementImpl markupElement) {
			if(!bodyLayout.skipParagraphBreakBefore) {
				RLine currentLine = bodyLayout.currentLine;
				if(currentLine.width == 0) {
					bodyLayout.addLineBreak(markupElement, LineBreak.NONE);
				}
				else {
					bodyLayout.addParagraphBreak( markupElement);
				}
				bodyLayout.skipParagraphBreakBefore = true;
			}
			super.layoutMarkup(bodyLayout, markupElement);
			if(markupElement.hasChildNodes()) {
				bodyLayout.addLineBreak(markupElement, LineBreak.NONE);
				bodyLayout.skipParagraphBreakBefore = true;				
			}
		}
	}

	private static class ListItemLayout extends CommonLayout {
		public ListItemLayout() {
			super(DISPLAY_LIST_ITEM);
		}
	}

	private static class BrLayout implements MarkupLayout {
		/* (non-Javadoc)
		 * @see org.xamjwg.html.renderer.MarkupLayout#layoutMarkup(java.awt.Container, java.awt.Insets, org.xamjwg.html.domimpl.HTMLElementImpl)
		 */
		public void layoutMarkup(RBlockViewport bodyLayout, HTMLElementImpl markupElement) {
			String clear = markupElement.getAttribute("clear");
			bodyLayout.addLineBreak(markupElement, LineBreak.getBreakType(clear));
		}
	}

	private static class HrLayout implements MarkupLayout {
		/* (non-Javadoc)
		 * @see org.xamjwg.html.renderer.MarkupLayout#layoutMarkup(java.awt.Container, java.awt.Insets, org.xamjwg.html.domimpl.HTMLElementImpl)
		 */
		public void layoutMarkup(RBlockViewport bodyLayout, HTMLElementImpl markupElement) {
			bodyLayout.layoutHr( markupElement);
		}
	}

	private static class TableLayout extends CommonLayout {
		public TableLayout() {
			super(DISPLAY_TABLE);
		}
	}

	private static class CommonBlockLayout extends CommonLayout {
		public CommonBlockLayout() {
			super(DISPLAY_BLOCK);
		}		
	}

	//---------------------------------------------------------------------------
	
	private static class DivLayout extends CommonLayout {
		public DivLayout() {
			super(DISPLAY_BLOCK);
		}
	}
	
	private static class BlockQuoteLayout extends CommonLayout {
		public java.awt.Insets getDefaultMarginInsets() {
			return new Insets(0, 36, 0, 0);
		}

		public BlockQuoteLayout() {
			super(DISPLAY_BLOCK);
		}
	}

	private static class SpanLayout extends CommonLayout {
		public SpanLayout() {
			super(DISPLAY_INLINE);
		}
	}

	private static class EmLayout extends CommonLayout {
		public EmLayout() {
			super(DISPLAY_INLINE);
		}
		
		/* (non-Javadoc)
		 * @see org.xamjwg.html.renderer.MarkupLayout#layoutMarkup(java.awt.Container, java.awt.Insets, org.xamjwg.html.domimpl.HTMLElementImpl)
		 */
		public void layoutMarkup(RBlockViewport bodyLayout, HTMLElementImpl markupElement) {
			super.layoutMarkup(bodyLayout, markupElement);
		}
	}
	
	private static class ULayout extends CommonLayout {
		public ULayout() {
			super(DISPLAY_INLINE);
		}
		
		/* (non-Javadoc)
		 * @see org.xamjwg.html.renderer.MarkupLayout#layoutMarkup(java.awt.Container, java.awt.Insets, org.xamjwg.html.domimpl.HTMLElementImpl)
		 */
		public void layoutMarkup(RBlockViewport bodyLayout, HTMLElementImpl markupElement) {
			super.layoutMarkup(bodyLayout, markupElement);
		}
	}

	private static class StrikeLayout extends CommonLayout {
		public StrikeLayout() {
			super(DISPLAY_INLINE);
		}
	}

	private static class StrongLayout extends CommonLayout {
		public StrongLayout() {
			super(DISPLAY_INLINE);
		}
	}
	
	private static class AnchorLayout extends CommonLayout {
		public AnchorLayout() {
			super(DISPLAY_INLINE);
		}
	}
	
	private static class ObjectLayout extends CommonWidgetLayout {
		private boolean tryToRenderContent;
		
		/**
		 * @param tryToRenderContent If the object is unknown, content is rendered as HTML.
		 * @param usesAlignAttribute
		 */
		public ObjectLayout(boolean tryToRenderContent, boolean usesAlignAttribute) {
			super(ADD_INLINE, usesAlignAttribute);
			this.tryToRenderContent = tryToRenderContent;
		}
		
		/**
		 * Must use this ThreadLocal because
		 * an ObjectLayout instance is shared
		 * across renderers.
		 */
		private final ThreadLocal htmlObject = new ThreadLocal();

		public void layoutMarkup(RBlockViewport bodyLayout, HTMLElementImpl markupElement) {
			HtmlObject ho = bodyLayout.rendererContext.getHtmlObject(markupElement);
			if(ho == null && this.tryToRenderContent) {
				// Don't know what to do with it - render contents.
				bodyLayout.layoutMarkup( markupElement);
			}
			else if (ho != null) {
				this.htmlObject.set(ho);
				super.layoutMarkup(bodyLayout, markupElement);
			}
		}

		protected RElement createRenderable(RBlockViewport bodyLayout, HTMLElementImpl markupElement) {
			HtmlObject ho = (HtmlObject) this.htmlObject.get();
			UIControl uiControl = new UIControlWrapper(ho);
			RUIControl ruiControl = new RUIControl(markupElement, uiControl, bodyLayout.container, bodyLayout.frameContext, bodyLayout.userAgentContext);
			return ruiControl;
		}
	}
	
	private static class ImgLayout extends CommonWidgetLayout {
		public ImgLayout() {
			super(ADD_INLINE, true);
		}

		protected RElement createRenderable(RBlockViewport bodyLayout, HTMLElementImpl markupElement) {
			UIControl control = new ImgControl((HTMLImageElementImpl) markupElement);
			return new RImgControl(markupElement, control, bodyLayout.container, bodyLayout.frameContext, bodyLayout.userAgentContext);
		}
	}
	
	private static class InputLayout2 extends CommonWidgetLayout {
		public InputLayout2() {
			super(ADD_INLINE, true);			
		}

		protected RElement createRenderable(RBlockViewport bodyLayout, HTMLElementImpl markupElement) {			
			HTMLBaseInputElement bie = (HTMLBaseInputElement) markupElement;
			BaseInputControl uiControl = bodyLayout.createInputControl(bie);
			if(uiControl == null) {
				return null;
			}
			bie.setInputContext(uiControl);
			return new RUIControl(markupElement, uiControl, bodyLayout.container, bodyLayout.frameContext, bodyLayout.userAgentContext);
		}
	}

	private static class SelectLayout extends CommonWidgetLayout {
		public SelectLayout() {
			super(ADD_INLINE, true);			
		}
		
		protected RElement createRenderable(RBlockViewport bodyLayout, HTMLElementImpl markupElement) {			
			HTMLBaseInputElement bie = (HTMLBaseInputElement) markupElement;
			BaseInputControl uiControl = new InputSelectControl(bie);
			bie.setInputContext(uiControl);
			return new RUIControl(markupElement, uiControl, bodyLayout.container, bodyLayout.frameContext, bodyLayout.userAgentContext);
		}
	}

	private static class TextAreaLayout2 extends CommonWidgetLayout {
		public TextAreaLayout2() {
			super(ADD_INLINE, true);			
		}
		
		protected RElement createRenderable(RBlockViewport bodyLayout, HTMLElementImpl markupElement) {			
			HTMLBaseInputElement bie = (HTMLBaseInputElement) markupElement;
			BaseInputControl control = new InputTextAreaControl(bie);
			bie.setInputContext(control);
			return new RUIControl(markupElement, control, bodyLayout.container, bodyLayout.frameContext, bodyLayout.userAgentContext);
		}
	}

	private static class IFrameLayout extends CommonWidgetLayout {
		public IFrameLayout() {
			super(ADD_INLINE, true);			
		}
		
		protected RElement createRenderable(RBlockViewport bodyLayout, HTMLElementImpl markupElement) {			
			BrowserFrame frame = bodyLayout.rendererContext.createBrowserFrame();	
			((HTMLIFrameElementImpl) markupElement).setBrowserFrame(frame);
			UIControl control = new BrowserFrameUIControl(markupElement, frame);
			return new RUIControl(markupElement, control, bodyLayout.container, bodyLayout.frameContext, bodyLayout.userAgentContext);
		}
	}

	//------------------------------------------------------------------------
	
	/**
	 * This is layout common to elements that render themselves,
	 * except RBlock, RTable and RList.
	 */
	private static abstract class CommonWidgetLayout implements MarkupLayout {
		protected static final int ADD_INLINE = 0;
		protected static final int ADD_AS_BLOCK = 1;
		private final int method;
		private final boolean useAlignAttribute;
		
		public CommonWidgetLayout(int method, boolean usesAlignAttribute) {
			this.method = method;
			this.useAlignAttribute = usesAlignAttribute;
		}
		
		public void layoutMarkup(RBlockViewport bodyLayout, HTMLElementImpl markupElement) {
			AbstractCSS2Properties style = markupElement.getCurrentStyle();
			if(style != null) {
				String display = style.getDisplay();
				if(display != null && "none".equalsIgnoreCase(display)) {
					return;
				}
			}
			UINode node = markupElement.getUINode();
			RElement renderable = null;
			if(node == null) {
				renderable = this.createRenderable(bodyLayout, markupElement);
				if(renderable == null) {
					if(logger.isLoggable(Level.INFO)) {
						logger.info("layoutMarkup(): Don't know how to render " + markupElement + ".");
					}
					return;
				}
				markupElement.setUINode((UINode) renderable);
			}
			else {
				renderable = (RElement) node;
			}
			int availWidth = bodyLayout.availContentWidth;
			int heightAvailToRenderable = bodyLayout.availContentHeight;
			renderable.layout(availWidth, heightAvailToRenderable);
			switch(this.method) {
			case ADD_INLINE:
				bodyLayout.addRenderableToLineCheckStyle( renderable, markupElement, this.useAlignAttribute);
				break;
			case ADD_AS_BLOCK:
				bodyLayout.addAsSeqBlockCheckStyle(renderable, markupElement, this.useAlignAttribute);
				break;
			}
		}

		protected abstract RElement createRenderable(RBlockViewport bodyLayout, HTMLElementImpl markupElement);
	}
	
	private static abstract class CommonLayout implements MarkupLayout {
		protected static final int DISPLAY_NONE = 0;
		protected static final int DISPLAY_INLINE = 1;
		protected static final int DISPLAY_BLOCK = 2;
		protected static final int DISPLAY_LIST_ITEM = 3;
		protected static final int DISPLAY_TABLE_ROW = 4;
		protected static final int DISPLAY_TABLE_CELL = 5;		
		protected static final int DISPLAY_TABLE = 6;				
		
		private final int display;
		
		public CommonLayout(int defaultDisplay) {
			this.display = defaultDisplay;
		}

		/**
		 * The default margin insets for a block. Only used
		 * with DISPLAY_BLOCK.
		 */
		public java.awt.Insets getDefaultMarginInsets() {
			return null;
		}
		
		public void layoutMarkup(RBlockViewport bodyLayout, HTMLElementImpl markupElement) {
			RenderState rs = markupElement.getRenderState();
			int display = rs == null ? this.display : rs.getDisplay();
			if(display == RenderState.DISPLAY_INLINE) {
				// Inline elements with absolute or fixed positions need
				// to be treated as blocks.
				int position = rs == null ? RenderState.POSITION_STATIC : rs.getPosition();
				if(position == RenderState.POSITION_ABSOLUTE || position == RenderState.POSITION_FIXED) {
					display = RenderState.DISPLAY_BLOCK;
				}				
				else {
					int boxFloat = rs == null ? RenderState.FLOAT_NONE : rs.getFloat();
					if(boxFloat != RenderState.FLOAT_NONE) {
						display = RenderState.DISPLAY_BLOCK;
					}
				}
			}
			switch(display) {
				case DISPLAY_NONE:
					// skip it completely.
					UINode node = markupElement.getUINode();
					if(node instanceof BaseBoundableRenderable) {
						// This is necessary so that if the element is made
						// visible again, it can be invalidated.
						((BaseBoundableRenderable) node).markLayoutValid();
					}
					break;
				case DISPLAY_BLOCK:
					bodyLayout.layoutRBlock( markupElement, this.getDefaultMarginInsets());
					break;
				case DISPLAY_LIST_ITEM:
					String tagName = markupElement.getTagName();
					if("UL".equalsIgnoreCase(tagName) || "OL".equalsIgnoreCase(tagName)) {
						bodyLayout.layoutList( markupElement);
					}
					else {
						bodyLayout.layoutListItem( markupElement);
					}
					break;
				case DISPLAY_TABLE:
					bodyLayout.layoutRTable( markupElement);
					break;
				default:
					// Assume INLINE
					bodyLayout.layoutMarkup( markupElement);
					break;
			}
		}		
	}
	
	public boolean isContainedByNode() {
		return false;
	}

	public String toString() {
		return "RBlockViewport[node=" + this.modelNode + "]";
	}
	
	/**
	 * Performs layout adjustment step. 
	 * @param desiredWidth The desired viewport width, including padding.
	 * @param desiredHeight The desired viewport height, including padding.
	 * @param paddingInsets The padding insets.
	 * @param floatBounds The starting float bounds, including floats 
	 *                    in ancestors.
	 */
	public void adjust(int desiredWidth, int desiredHeight, Insets paddingInsets, FloatingBounds floatBounds) {
		// Initializations
		this.paddingInsets = paddingInsets;
		this.desiredHeight = desiredHeight;
		this.desiredWidth = desiredWidth;
		this.floatBounds = floatBounds;

		int availw = desiredWidth - paddingInsets.left - paddingInsets.right;
		if(availw < 0) {
			availw = 0;
		}
		int availh = desiredHeight - paddingInsets.top - paddingInsets.bottom;
		if(availh < 0) {
			availh = 0;
		}
		this.availContentWidth = availw;
		this.availContentHeight = availh;

		// maxX and maxY should not be reset by layoutPass.
		this.maxX = paddingInsets.left;
		this.maxY = paddingInsets.top;

		// Keep copy of old sequential renderables,
		// and clear the list.
		ArrayList oldSeqRenderables = this.seqRenderables;
		this.seqRenderables = null;
		
		// Clear current line
		this.currentLine = null;
		
		// Reprocess all sequential renderables
		if(oldSeqRenderables != null) {
			Iterator i = oldSeqRenderables.iterator();
			while(i.hasNext()) {
				Renderable r = (Renderable) i.next();
				this.reprocessSeqRenderable(r);
			}
		}

		RLine lastLine = this.currentLine;

		// This adds any pending floats
		this.lineDone(this.currentLine);

		// Calculate maxX and maxY.
		if(lastLine != null) {
			Rectangle lastBounds = lastLine.getBounds();
			int lastTopX = lastBounds.x + lastBounds.width;
			if(lastTopX > this.maxX) {
				this.maxX = lastTopX;
			}
			int lastTopY = lastBounds.y + lastBounds.height;
			int maxY = this.maxY;
			if(lastTopY > maxY) {
				this.maxY = maxY = lastTopY;
			}
		}
		
		// Check positioned renderables for maxX and maxY
		SortedSet posRenderables = this.positionedRenderables;
		if(posRenderables != null) {
			Iterator i = posRenderables.iterator();
			while(i.hasNext()) {
				PositionedRenderable pr = (PositionedRenderable) i.next();
				BoundableRenderable br = pr.renderable;
				if(br.getX() + br.getWidth() > this.maxX) {
					this.maxX = br.getX() + br.getWidth();
				}
				if(br.getY() + br.getHeight() > this.maxY) {
					this.maxY = br.getY() + br.getHeight();
				}
			}
		}
		
		this.width = paddingInsets.right + this.maxX;
		this.height = paddingInsets.bottom + maxY;		
	}
	
	private void reprocessSeqRenderable(Renderable r) {
		if(r instanceof RLine) {
			this.reprocessLine((RLine) r);
		}
		else if(r instanceof RElement) {
			this.reprocessElement((RElement) r);
		}
		else if(r instanceof RRelative) {
			this.reprocessRelative((RRelative) r);
		}
		else {
			throw new IllegalStateException("Unexpected Renderable: " + r);
		}
	}
	
	private void reprocessLine(RLine line) {
		Iterator renderables = line.getRenderables();
		if(renderables != null) {
			while(renderables.hasNext()) {
				Renderable r = (Renderable) renderables.next();
				if(r instanceof RWord) {
					// TODO: allowOverflow?
					RWord word = (RWord) r;
					RenderState rs = word.getModelNode().getRenderState();
					int whiteSpace = rs.getWhiteSpace();
					boolean allowOverflow = whiteSpace == RenderState.WS_NOWRAP || whiteSpace == RenderState.WS_PRE;
					if(this.currentLine == null) {
						// Must add at this point in case there was a float.
						this.currentLine = this.addLine(word.getModelNode(), null, this.paddingInsets.top);
					}
					this.addWordToLine(word, allowOverflow);
				}
				else if (r instanceof RFloatInfo) { 
					RFloatInfo oldr = (RFloatInfo) r;
					// Switch to a float info with registerElement=true.
					this.scheduleFloat(new RFloatInfo(oldr.getModelNode(), oldr.getRenderable(), oldr.isLeftFloat(), true));
				}
				else {
					if(this.currentLine == null) {
						// Must add at this point in case there was a float.
						this.currentLine = this.addLine(r.getModelNode(), null, this.paddingInsets.top);
					}
					this.addRenderableToLine(r);
				}
			}
		}
		LineBreak br = line.getLineBreak();
		if(br != null) {
			this.addLineBreak(br.getModelNode(), br.getBreakType());
		}
	} 

	private void reprocessElement(RElement element) {
		RLine line = this.currentLine;
		this.lineDone(line);
		boolean isRBlock = element instanceof RBlock;
		boolean obeysFloats = !isRBlock || !((RBlock) element).isOverflowVisible();
		if(obeysFloats) {
			if(isRBlock) {
				RBlock block = (RBlock) element;
				int newLineY = line == null ? this.paddingInsets.top : line.y + line.height;
				int leftOffset = this.fetchLeftOffset(newLineY);
				int rightOffset = this.fetchRightOffset(newLineY);
				int availContentWidth = this.desiredWidth - leftOffset - rightOffset;				
				block.adjust(availContentWidth, this.availContentHeight, true, false, null);
				// Because a block that obeys margins is also a float limit,
				// we don't expect exported float bounds.
			}
		}
		else {
			RBlock block = (RBlock) element;
			final FloatingBounds currentFloatBounds = this.floatBounds;
			FloatingBoundsSource blockFloatBoundsSource = null;
			if(currentFloatBounds != null) {
				Insets paddingInsets = this.paddingInsets;
				final int blockShiftX = paddingInsets.left;
				final int blockShiftRight = paddingInsets.right;
				final int blockShiftY = line == null ? paddingInsets.top : line.y + line.height;
				final int expectedBlockWidth = this.availContentWidth;
				blockFloatBoundsSource = new FloatingBoundsSource() {
					public FloatingBounds getChildBlockFloatingBounds(int apparentBlockWidth) {
						int actualRightShift = blockShiftRight + (expectedBlockWidth - apparentBlockWidth);
						return new ShiftedFloatingBounds(currentFloatBounds, -blockShiftX, -actualRightShift, -blockShiftY);
					}
				};
			}
			block.adjust(this.availContentWidth, this.availContentHeight, true, false, blockFloatBoundsSource);
			FloatingBounds blockBounds = block.getExportableFloatingBounds();
			if(blockBounds != null) {
				FloatingBounds prevBounds = this.floatBounds;
				FloatingBounds newBounds;
				if(prevBounds == null) {
					newBounds = blockBounds;
				}
				else {
					newBounds = new CombinedFloatingBounds(prevBounds, blockBounds);
				}
				if(newBounds.getMaxY() > this.maxY && this.isFloatLimit()) {
					this.maxY = newBounds.getMaxY();
				}
			}
		}
		this.addAsSeqBlock(element, obeysFloats, false);
	}

	private void reprocessRelative(RRelative relative) {
		RLine line = this.currentLine;
		this.lineDone(line);
		boolean obeysFloats = false;
		RElement element = relative.getElement();
		if(element instanceof RBlock) {
			obeysFloats = false;
			RBlock block = (RBlock) element;
			final FloatingBounds currentFloatBounds = this.floatBounds;
			FloatingBoundsSource blockFloatBoundsSource = null;
			if(currentFloatBounds != null) {
				Insets paddingInsets = this.paddingInsets;
				final int blockShiftX = paddingInsets.left + relative.getXOffset();
				final int blockShiftRight = paddingInsets.right - relative.getXOffset();
				final int blockShiftY = (line == null ? paddingInsets.top : line.y + line.height) + relative.getYOffset();
				final int expectedBlockWidth = this.availContentWidth;
				blockFloatBoundsSource = new FloatingBoundsSource() {
					public FloatingBounds getChildBlockFloatingBounds(int apparentBlockWidth) {
						int actualRightShift = blockShiftRight + (expectedBlockWidth - apparentBlockWidth);
						return new ShiftedFloatingBounds(currentFloatBounds, -blockShiftX, -actualRightShift, -blockShiftY);
					}
				};
			}
			block.adjust(this.availContentWidth, this.availContentHeight, true, false, blockFloatBoundsSource);
			relative.adjustSize();
			FloatingBounds blockBounds = relative.getExportableFloatingBounds();
			if(blockBounds != null) {
				FloatingBounds prevBounds = this.floatBounds;
				FloatingBounds newBounds;
				if(prevBounds == null) {
					newBounds = blockBounds;
				}
				else {
					newBounds = new CombinedFloatingBounds(prevBounds, blockBounds);
				}
				if(newBounds.getMaxY() > this.maxY && this.isFloatLimit()) {
					this.maxY = newBounds.getMaxY();
				}
			}
		}
		else {
			obeysFloats = true;
		}
		this.addAsSeqBlock(relative, obeysFloats, false);
	}

	private void scheduleFloat(RFloatInfo floatInfo) {
		RLine line = this.currentLine;
		if(line == null) {
			int y = line == null ? this.paddingInsets.top : line.getY();
			this.placeFloat(floatInfo.getRenderable(), y, floatInfo.isLeftFloat(), floatInfo.isRegisterElement());
		}
		else if(line.getWidth() == 0) {
			int y = line.getY();
			this.placeFloat(floatInfo.getRenderable(), y, floatInfo.isLeftFloat(), floatInfo.isRegisterElement());			
			int leftOffset = this.fetchLeftOffset(y);
			int rightOffset = this.fetchRightOffset(y);
			line.changeLimits(leftOffset, this.desiredWidth - leftOffset - rightOffset);
		}
		else {
			Collection c = this.pendingFloats;
			if(c == null) {
				c = new LinkedList();
				this.pendingFloats = c;
			}
			c.add(floatInfo);
		}
	}

	private Collection pendingFloats = null;
	
	private void lineDone(RLine line) {
		int yAfterLine = line == null ? this.paddingInsets.top : line.y + line.height;
		Collection pfs = this.pendingFloats;
		if(pfs != null) {
			this.pendingFloats = null;
			Iterator i = pfs.iterator();
			while(i.hasNext()) {
				RFloatInfo pf = (RFloatInfo) i.next();
				this.placeFloat(pf.getRenderable(), yAfterLine, pf.isLeftFloat(), pf.isRegisterElement());
			}
		}
	}
	
	/**
	 * 
	 * @param element
	 * @param y The desired top position of the float element.
	 * @param floatType -1 (left) or +1 (right)
	 */
	private void placeFloat(BoundableRenderable element, int y, boolean leftFloat, boolean registerElement) {
		Insets insets = this.paddingInsets;
		int boxY = y;
		int boxWidth = element.getWidth();
		int boxHeight = element.getHeight();
		int desiredWidth = this.desiredWidth;
		int boxX;
		for(;;) {
			int leftOffset = this.fetchLeftOffset(boxY);
			int rightOffset = this.fetchRightOffset(boxY);
			boxX = leftFloat ? leftOffset : desiredWidth - rightOffset - boxWidth; 
			if(leftOffset == insets.left && rightOffset == insets.right) {
				// Probably typical scenario and not much we can
				// do if it overflows.
				break;
			}
			if(leftFloat && boxX + boxWidth <= desiredWidth - rightOffset) {
				// We're fine
				break;
			}
			else if(!leftFloat && boxX >= leftOffset) {
				// We're fine
				break;
			}
			// At this point the float doesn't fit at the current Y position.
			FloatingBounds fb = this.floatBounds;
			int newY = fb == null ? boxY + boxHeight : fb.getFirstClearY(boxY);
			if(newY == boxY) {
				// Possible if prior box has height zero?
				break;
			}
			boxY = newY;
		}
		// Position element
		element.setOrigin(boxX, boxY);
		// Update float bounds accordingly
		int offsetFromBorder = leftFloat ? boxX + boxWidth : desiredWidth - boxX;
		this.floatBounds = new FloatingViewportBounds(this.floatBounds, leftFloat, boxY, offsetFromBorder, boxHeight);
		// Add element to collection
		if(registerElement) {
			this.addPositionedRenderable(element, true);
		}
		// Adjust maxX based on float.
		if(boxX + boxWidth > this.maxX) {
			this.maxX = boxX + boxWidth;
		}
		// Adjust maxY based on float, but only if this viewport is the float limit.
		if(this.isFloatLimit()) {
			if(boxY + boxHeight > this.maxY) {
				this.maxY = boxY + boxHeight;
			}
		}
	}

	private Boolean isFloatLimit = null;
	
	private boolean isFloatLimit() {
		Boolean fl = this.isFloatLimit;
		if(fl == null) {
			fl = this.isFloatLimitImpl();
			this.isFloatLimit = fl;
		}
		return fl.booleanValue();
	}

	private Boolean isFloatLimitImpl() {
		Object parent = this.getOriginalOrCurrentParent();
		if(!(parent instanceof RBlock)) {
			return Boolean.TRUE;
		}
		RBlock blockParent = (RBlock) parent;
		Object grandParent = blockParent.getOriginalOrCurrentParent();
		if(!(grandParent instanceof RBlockViewport)) {
			// Could be contained in a table, or it could
			// be a list item, for example.
			return Boolean.TRUE;
		}
		ModelNode node = this.modelNode;
		if(!(node instanceof HTMLElementImpl)) {
			// Can only be a document here.
			return Boolean.TRUE;
		}
		HTMLElementImpl element = (HTMLElementImpl) node;
		int position = getPosition(element);
		if(position == RenderState.POSITION_ABSOLUTE || position == RenderState.POSITION_FIXED) {
			return Boolean.TRUE;
		}
		AbstractCSS2Properties props = element.getCurrentStyle();
		RenderState rs = element.getRenderState();
		int floatValue = rs == null ? RenderState.FLOAT_NONE : rs.getFloat();
		if(floatValue != RenderState.FLOAT_NONE) {
			return Boolean.TRUE;
		}
		String overflow = props.getOverflow();
		if(overflow != null && !"visible".equalsIgnoreCase(overflow)) {
			return Boolean.TRUE;
		}
		return Boolean.FALSE;
	}
	
	/**
	 * Gets FloatingBounds from this viewport that should
	 * be considered by an ancestor block.
	 */
	public FloatingBounds getExportableFloatingBounds() {
		FloatingBounds floatBounds = this.floatBounds;
		if(floatBounds == null) {
			return null;
		}
		if(this.isFloatLimit()) {
			return null;
		}
		int maxY = floatBounds.getMaxY();
		if(maxY > this.height) {
			return floatBounds;
		}
		return null;
	}	
}
